diff --git a/Java/maven-import.txt b/Java/maven-import.txt
index 555b856..9e03813 100644
--- a/Java/maven-import.txt
+++ b/Java/maven-import.txt
@@ -5,7 +5,7 @@
# your project has local repository in MY-PROJECT/lib/
# CIYAM AT JAR pathname is in ${CIYAM_AT_JAR}
-CIYAM_AT_VERSION=1.3.7
+CIYAM_AT_VERSION=1.3.8
CIYAM_AT_JAR=../CIYAM-AT/Java/target/AT-${CIYAM_AT_VERSION}.jar
cd MY-PROJECT
mvn install:install-file -DlocalRepositoryPath=lib/ -Dfile=${CIYAM_AT_JAR} -DgroupId=org.ciyam -DartifactId=AT -Dpackaging=jar -Dversion=${CIYAM_AT_VERSION}
diff --git a/Java/pom.xml b/Java/pom.xml
index b772e6e..f668173 100644
--- a/Java/pom.xml
+++ b/Java/pom.xml
@@ -4,7 +4,7 @@
4.0.0
org.ciyam
AT
- 1.3.7
+ 1.3.8
jar
true
diff --git a/Java/src/main/java/org/ciyam/at/FunctionCode.java b/Java/src/main/java/org/ciyam/at/FunctionCode.java
index 33a17e1..3c6c17a 100644
--- a/Java/src/main/java/org/ciyam/at/FunctionCode.java
+++ b/Java/src/main/java/org/ciyam/at/FunctionCode.java
@@ -1091,7 +1091,7 @@ public enum FunctionCode {
byte[] message = new byte[dataLength];
- ByteBuffer messageByteBuffer = state.dataByteBuffer.slice();
+ ByteBuffer messageByteBuffer = state.dataByteBuffer.asReadOnlyBuffer();
messageByteBuffer.position(dataStart * MachineState.VALUE_SIZE);
messageByteBuffer.limit(dataStart * MachineState.VALUE_SIZE + dataLength);
diff --git a/Java/src/main/java/org/ciyam/at/MachineState.java b/Java/src/main/java/org/ciyam/at/MachineState.java
index 2d65d15..dc1fa03 100644
--- a/Java/src/main/java/org/ciyam/at/MachineState.java
+++ b/Java/src/main/java/org/ciyam/at/MachineState.java
@@ -172,18 +172,6 @@ public class MachineState {
// Header OK
}
- private void setupSegmentsAndStacks() {
- this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE);
- this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE);
-
- // Set up stacks
- this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE);
- this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
-
- this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE);
- this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
- }
-
/** For creating a new machine state */
public MachineState(API api, AtLoggerFactory loggerFactory, byte[] creationBytes) {
this(ByteBuffer.wrap(creationBytes));
@@ -192,13 +180,17 @@ public class MachineState {
if (creationBytes.length != expectedLength)
throw new IllegalArgumentException("Creation bytes length does not match header values");
- setupSegmentsAndStacks();
+ int codeBytesLength = this.numCodePages * this.constants.CODE_PAGE_SIZE;
+ this.codeByteBuffer = ByteBuffer.allocate(codeBytesLength);
+ System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, codeBytesLength);
- System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE);
-
- System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0,
+ // Copy initial data segment from creation bytes so that we don't modify creationBytes on execution
+ this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE);
+ System.arraycopy(creationBytes, HEADER_LENGTH + codeBytesLength, this.dataByteBuffer.array(), 0,
this.numDataPages * this.constants.DATA_PAGE_SIZE);
+ constructStacks();
+
commonFinalConstruction(api, loggerFactory);
}
@@ -212,15 +204,26 @@ public class MachineState {
if (dataBytes.length > this.numDataPages * this.constants.DATA_PAGE_SIZE)
throw new IllegalArgumentException("Number of data pages too small to hold data bytes");
- setupSegmentsAndStacks();
-
- System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length);
+ this.codeByteBuffer = ByteBuffer.wrap(codeBytes).asReadOnlyBuffer();
+ // Copy dataBytes so that we don't modify original during execution
+ this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE);
System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length);
+ constructStacks();
+
commonFinalConstruction(api, loggerFactory);
}
+ private void constructStacks() {
+ // Set up stacks
+ this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE);
+ this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
+
+ this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE);
+ this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
+ }
+
private void commonFinalConstruction(API api, AtLoggerFactory loggerFactory) {
this.api = api;
this.loggerFactory = loggerFactory;
@@ -442,7 +445,10 @@ public class MachineState {
/** Returns code bytes only as these are read-only so no need to be duplicated in every serialized state */
public byte[] getCodeBytes() {
- return this.codeByteBuffer.array();
+ // We create a copy because codeByteBuffer is a read-only sub-slice of another ByteBuffer
+ byte[] codeBytes = new byte[this.codeByteBuffer.limit()];
+ this.codeByteBuffer.position(0).get(codeBytes);
+ return codeBytes;
}
private static class NumericByteArrayOutputStream extends ByteArrayOutputStream {
@@ -588,7 +594,6 @@ public class MachineState {
if (codeBytes.length != state.numCodePages * state.constants.CODE_PAGE_SIZE)
throw new IllegalStateException("Passed codeBytes does not match length in header");
- state.api = api;
state.loggerFactory = loggerFactory;
state.logger = loggerFactory.create(MachineState.class);
@@ -597,15 +602,31 @@ public class MachineState {
state.previousBalance = 0;
state.steps = 0;
- state.setupSegmentsAndStacks();
+ // Ring-fence code bytes
+ state.codeByteBuffer = ByteBuffer.wrap(codeBytes).asReadOnlyBuffer();
- // Pull in code bytes
- System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length);
+ reuse(state, api, byteBuffer);
+
+ return state;
+ }
+
+ /** For restoring a previously serialized machine state, reusing existing instance as much as possible. */
+ public void reuseFromBytes(API api, byte[] stateBytes) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes);
+ reuse(this, api, byteBuffer);
+ }
+
+ private static void reuse(MachineState state, API api, ByteBuffer byteBuffer) {
+ byte[] stateBytes = byteBuffer.array();
+ state.api = api;
// Pull in data bytes
- int dataBytesLength = state.dataByteBuffer.capacity();
- System.arraycopy(stateBytes, byteBuffer.position(), state.dataByteBuffer.array(), 0, dataBytesLength);
- byteBuffer.position(byteBuffer.position() + dataBytesLength);
+ int dataBytesLength = state.numDataPages * state.constants.DATA_PAGE_SIZE;
+ state.dataByteBuffer = ByteBuffer.allocate(dataBytesLength);
+ System.arraycopy(stateBytes, HEADER_LENGTH, state.dataByteBuffer.array(), 0, dataBytesLength);
+ byteBuffer.position(HEADER_LENGTH + dataBytesLength);
+
+ state.constructStacks();
// Pull in call stack
int callStackLength = byteBuffer.getInt();
@@ -624,8 +645,6 @@ public class MachineState {
byteBuffer.position(byteBuffer.position() + userStackLength);
extractMisc(byteBuffer, state);
-
- return state;
}
/** For restoring only flags from a previously serialized machine state */
@@ -635,7 +654,7 @@ public class MachineState {
MachineState state = new MachineState(byteBuffer);
// Skip data segment
- byteBuffer.position(byteBuffer.position() + state.numDataPages * state.constants.DATA_PAGE_SIZE);
+ byteBuffer.position(HEADER_LENGTH + state.numDataPages * state.constants.DATA_PAGE_SIZE);
// Skip call stack
int callStackLength = byteBuffer.getInt();
diff --git a/Java/src/test/java/org/ciyam/at/SerializationTests.java b/Java/src/test/java/org/ciyam/at/SerializationTests.java
index 49f60e6..7aef33c 100644
--- a/Java/src/test/java/org/ciyam/at/SerializationTests.java
+++ b/Java/src/test/java/org/ciyam/at/SerializationTests.java
@@ -148,6 +148,14 @@ public class SerializationTests extends ExecutableTest {
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
+ // Check reuse works too
+ byte[] dupStateBytes = new byte[stateBytes.length];
+ System.arraycopy(stateBytes, 0, dupStateBytes, 0, stateBytes.length);
+
+ restoredState.reuseFromBytes(api, dupStateBytes);
+ byte[] restoredReusedStateBytes = restoredState.toBytes();
+ assertTrue("Serialization->Deserialization->Reserialization reuse error", Arrays.equals(stateBytes, restoredReusedStateBytes));
+
return stateBytes;
}