Bytecode pattern #553 (#896)

* Added bytecode pattern

* Diagram changed and added licence information

* Added bytecode module to main pom.

* Fixed missing dependency error
This commit is contained in:
Pawel Zawitowski
2019-09-03 20:50:48 +02:00
committed by Ilkka Seppälä
parent 7f6067f19f
commit 318f811fea
13 changed files with 819 additions and 0 deletions

View File

@ -0,0 +1,79 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.bytecode;
import com.iluwatar.bytecode.util.InstructionConverterUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The intention of Bytecode pattern is to give behavior the flexibility of data by encoding it as instructions
* for a virtual machine.
* An instruction set defines the low-level operations that can be performed. A series of instructions is encoded as
* a sequence of bytes. A virtual machine executes these instructions one at a time,
* using a stack for intermediate values. By combining instructions, complex high-level behavior can be defined.
*
* This pattern should be used when there is a need to define high number of behaviours and implementation engine
* is not a good choice because
* It is too lowe level
* Iterating on it takes too long due to slow compile times or other tooling issues.
* It has too much trust. If you want to ensure the behavior being defined cant break the game,
* you need to sandbox it from the rest of the codebase.
*
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Main app method
* @param args command line args
*/
public static void main(String[] args) {
VirtualMachine vm = new VirtualMachine();
Wizard wizard = new Wizard();
wizard.setHealth(45);
wizard.setAgility(7);
wizard.setWisdom(11);
vm.getWizards()[0] = wizard;
interpretInstruction("LITERAL 0", vm);
interpretInstruction( "LITERAL 0", vm);
interpretInstruction( "GET_HEALTH", vm);
interpretInstruction( "LITERAL 0", vm);
interpretInstruction( "GET_AGILITY", vm);
interpretInstruction( "LITERAL 0", vm);
interpretInstruction( "GET_WISDOM ", vm);
interpretInstruction( "ADD", vm);
interpretInstruction( "LITERAL 2", vm);
interpretInstruction( "DIVIDE", vm);
interpretInstruction( "ADD", vm);
interpretInstruction( "SET_HEALTH", vm);
}
private static void interpretInstruction(String instruction, VirtualMachine vm) {
InstructionConverterUtil converter = new InstructionConverterUtil();
vm.execute(converter.convertToByteCode(instruction));
LOGGER.info(instruction + String.format("%" + (12 - instruction.length()) + "s", "" ) + vm.getStack());
}
}

View File

@ -0,0 +1,65 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.bytecode;
/**
* Representation of instructions understandable by virtual machine
*/
public enum Instruction {
LITERAL(1),
SET_HEALTH(2),
SET_WISDOM (3),
SET_AGILITY(4),
PLAY_SOUND(5),
SPAWN_PARTICLES(6),
GET_HEALTH(7),
GET_AGILITY(8),
GET_WISDOM(9),
ADD(10),
DIVIDE (11);
private int value;
Instruction(int value) {
this.value = value;
}
public int getIntValue() {
return value;
}
/**
* Converts integer value to Instruction
* @param value value of instruction
* @return representation of the instruction
*/
public static Instruction getInstruction(int value) {
for (int i = 0; i < Instruction.values().length; i++) {
if (Instruction.values()[i].getIntValue() == value) {
return Instruction.values()[i];
}
}
throw new IllegalArgumentException("Invalid instruction value");
}
}

View File

@ -0,0 +1,142 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.bytecode;
import java.util.Stack;
/**
* Implementation of virtual machine
*/
public class VirtualMachine {
private Stack<Integer> stack = new Stack();
private Wizard[] wizards = new Wizard[2];
/**
* Constructor
*/
public VirtualMachine() {
for (int i = 0; i < wizards.length; i++) {
wizards[i] = new Wizard();
}
}
/**
* Executes provided bytecode
* @param bytecode to execute
*/
public void execute(int[] bytecode) {
for (int i = 0; i < bytecode.length; i++) {
Instruction instruction = Instruction.getInstruction(bytecode[i]);
int wizard;
int amount;
switch (instruction) {
case LITERAL:
// Read the next byte from the bytecode.
int value = bytecode[++i];
stack.push(value);
break;
case SET_AGILITY:
amount = stack.pop();
wizard = stack.pop();
setAgility(wizard, amount);
break;
case SET_WISDOM:
amount = stack.pop();
wizard = stack.pop();
setWisdom(wizard, amount);
break;
case SET_HEALTH:
amount = stack.pop();
wizard = stack.pop();
setHealth(wizard, amount);
break;
case GET_HEALTH:
wizard = stack.pop();
stack.push(getHealth(wizard));
break;
case GET_AGILITY:
wizard = stack.pop();
stack.push(getAgility(wizard));
break;
case GET_WISDOM:
wizard = stack.pop();
stack.push(getWisdom(wizard));
break;
case ADD:
int a = stack.pop();
int b = stack.pop();
stack.push(a + b);
break;
case DIVIDE:
a = stack.pop();
b = stack.pop();
stack.push(b / a);
break;
case PLAY_SOUND:
wizard = stack.pop();
getWizards()[wizard].playSound();
break;
case SPAWN_PARTICLES:
wizard = stack.pop();
getWizards()[wizard].spawnParticles();
break;
default:
throw new IllegalArgumentException("Invalid instruction value");
}
}
}
public Stack<Integer> getStack() {
return stack;
}
public void setHealth(int wizard, int amount) {
wizards[wizard].setHealth(amount);
}
public void setWisdom(int wizard, int amount) {
wizards[wizard].setWisdom(amount);
}
public void setAgility(int wizard, int amount) {
wizards[wizard].setAgility(amount);
}
public int getHealth(int wizard) {
return wizards[wizard].getHealth();
}
public int getWisdom(int wizard) {
return wizards[wizard].getWisdom();
}
public int getAgility(int wizard) {
return wizards[wizard].getAgility();
}
public Wizard[] getWizards() {
return wizards;
}
}

View File

@ -0,0 +1,83 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.bytecode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class represent game objects which properties can be changed by instructions interpreted by virtual machine
*/
public class Wizard {
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
private int health;
private int agility;
private int wisdom;
private int numberOfPlayedSounds;
private int numberOfSpawnedParticles;
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getAgility() {
return agility;
}
public void setAgility(int agility) {
this.agility = agility;
}
public int getWisdom() {
return wisdom;
}
public void setWisdom(int wisdom) {
this.wisdom = wisdom;
}
public void playSound() {
LOGGER.info("Playing sound");
numberOfPlayedSounds++;
}
public void spawnParticles() {
LOGGER.info("Spawning particles");
numberOfSpawnedParticles++;
}
public int getNumberOfPlayedSounds() {
return numberOfPlayedSounds;
}
public int getNumberOfSpawnedParticles() {
return numberOfSpawnedParticles;
}
}

View File

@ -0,0 +1,76 @@
/**
* The MIT License
* Copyright (c) 2014 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.bytecode.util;
import com.iluwatar.bytecode.Instruction;
/**
* Utility class used for instruction validation and conversion
*/
public class InstructionConverterUtil {
/**
* Converts instructions represented as String
*
* @param instructions to convert
* @return array of int representing bytecode
*/
public static int[] convertToByteCode(String instructions) {
if (instructions == null || instructions.trim().length() == 0) {
return new int[0];
}
String[] splitedInstructions = instructions.trim().split(" ");
int[] bytecode = new int[splitedInstructions.length];
for (int i = 0; i < splitedInstructions.length; i++) {
if (isValidInstruction(splitedInstructions[i])) {
bytecode[i] = Instruction.valueOf(splitedInstructions[i]).getIntValue();
} else if (isValidInt(splitedInstructions[i])) {
bytecode[i] = Integer.valueOf(splitedInstructions[i]);
} else {
throw new IllegalArgumentException("Invalid instruction or number: " + splitedInstructions[i]);
}
}
return bytecode;
}
private static boolean isValidInstruction(String instruction) {
try {
Instruction.valueOf(instruction);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
private static boolean isValidInt(String value) {
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}