diff options
Diffstat (limited to 'src/main/java')
6 files changed, 837 insertions, 132 deletions
diff --git a/src/main/java/com/amazon/carbonado/Storable.java b/src/main/java/com/amazon/carbonado/Storable.java index c68f477..d6f0c1c 100644 --- a/src/main/java/com/amazon/carbonado/Storable.java +++ b/src/main/java/com/amazon/carbonado/Storable.java @@ -18,6 +18,10 @@ package com.amazon.carbonado;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
import java.util.Map;
/**
@@ -430,6 +434,33 @@ public interface Storable<S extends Storable<S>> { */
S copy();
+ /**
+ * Serializes property values and states for temporary storage or for
+ * network transfer. Call {@link #readFrom} to restore. Derived and join
+ * properties are not serialized.
+ *
+ * <p>The encoding used by this method is much simpler than what is
+ * provided by standard object serialization. It does not encode class info
+ * or property names, which is why it is not suitable for long term
+ * storage.
+ *
+ * @throws IOException if exception from stream
+ * @throws SupportException if Storable cannot be serialized
+ * @since 1.2
+ */
+ void writeTo(OutputStream out) throws IOException, SupportException;
+
+ /**
+ * Restores property values and states as encoded by {@link #writeTo}.
+ * Derived properties are not directly modified, but all other properties
+ * not restored are reset to their initial state.
+ *
+ * @throws IOException if exception from stream
+ * @throws SupportException if Storable cannot be serialized
+ * @since 1.2
+ */
+ void readFrom(InputStream in) throws IOException, SupportException;
+
int hashCode();
/**
diff --git a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java index 522edc6..ba20e31 100644 --- a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java +++ b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java @@ -55,6 +55,8 @@ public class CommonMethodNames { GET_PROPERTY_VALUE = "getPropertyValue",
SET_PROPERTY_VALUE = "setPropertyValue",
PROPERTY_MAP = "propertyMap",
+ WRITE_TO = "writeTo",
+ READ_FROM = "readFrom",
TO_STRING_KEY_ONLY_METHOD_NAME = "toStringKeyOnly",
TO_STRING_METHOD_NAME = "toString",
HASHCODE_METHOD_NAME = "hashCode",
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java index 711ee58..cb60349 100644 --- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java @@ -18,6 +18,8 @@ package com.amazon.carbonado.gen;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
@@ -51,6 +53,7 @@ import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.UniqueConstraintException;
@@ -67,6 +70,10 @@ import com.amazon.carbonado.info.StorablePropertyAdapter; import com.amazon.carbonado.info.StorablePropertyAnnotation;
import com.amazon.carbonado.info.StorablePropertyConstraint;
+import com.amazon.carbonado.raw.DataDecoder;
+import com.amazon.carbonado.raw.DataEncoder;
+import com.amazon.carbonado.raw.GenericEncodingStrategy;
+
import static com.amazon.carbonado.gen.CommonMethodNames.*;
/**
@@ -1624,6 +1631,10 @@ public final class StorableGenerator<S extends Storable> { addSetPropertyValueMethod();
addPropertyMapMethod();
+ // Define serialization methods.
+ addWriteToMethod();
+ addReadFromMethod();
+
// Define standard object methods.
addHashCodeMethod();
addEqualsMethod(EQUAL_FULL);
@@ -2715,6 +2726,79 @@ public final class StorableGenerator<S extends Storable> { b.returnValue(mapType);
}
+ private void addWriteToMethod() {
+ TypeDesc streamType = TypeDesc.forClass(OutputStream.class);
+
+ MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), WRITE_TO, null,
+ new TypeDesc[] {streamType});
+
+ if (mi == null) {
+ return;
+ }
+
+ GenericEncodingStrategy<S> encoder = new GenericEncodingStrategy<S>(mStorableType, null);
+
+ CodeBuilder b = new CodeBuilder(mi);
+
+ LocalVariable encodedVar;
+ try {
+ encodedVar = encoder.buildSerialEncoding(b, null);
+ } catch (SupportException e) {
+ CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
+ return;
+ }
+
+ b.loadLocal(encodedVar);
+ b.arrayLength();
+ b.loadLocal(b.getParameter(0));
+ b.invokeStatic(TypeDesc.forClass(DataEncoder.class), "writeLength", TypeDesc.INT,
+ new TypeDesc[] {TypeDesc.INT, streamType});
+ b.pop();
+
+ b.loadLocal(b.getParameter(0));
+ b.loadLocal(encodedVar);
+ b.invokeVirtual(streamType, "write", null, new TypeDesc[] {encodedVar.getType()});
+ b.returnVoid();
+ }
+
+ private void addReadFromMethod() {
+ TypeDesc streamType = TypeDesc.forClass(InputStream.class);
+
+ MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), READ_FROM, null,
+ new TypeDesc[] {streamType});
+
+ if (mi == null) {
+ return;
+ }
+
+ CodeBuilder b = new CodeBuilder(mi);
+
+ TypeDesc dataDecoderType = TypeDesc.forClass(DataDecoder.class);
+
+ b.loadLocal(b.getParameter(0));
+ b.invokeStatic(dataDecoderType, "readLength", TypeDesc.INT, new TypeDesc[] {streamType});
+
+ LocalVariable encodedVar = b.createLocalVariable(null, TypeDesc.forClass(byte[].class));
+ b.newObject(encodedVar.getType());
+ b.storeLocal(encodedVar);
+
+ b.loadLocal(b.getParameter(0));
+ b.loadLocal(encodedVar);
+ b.invokeStatic(dataDecoderType, "readFully", null,
+ new TypeDesc[] {streamType, encodedVar.getType()});
+
+ GenericEncodingStrategy<S> encoder = new GenericEncodingStrategy<S>(mStorableType, null);
+
+ try {
+ encoder.buildSerialDecoding(b, null, encodedVar);
+ } catch (SupportException e) {
+ CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
+ return;
+ }
+
+ b.returnVoid();
+ }
+
/**
* Defines a hashCode method.
*/
diff --git a/src/main/java/com/amazon/carbonado/raw/DataDecoder.java b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java index 485ce82..bffa7c2 100644 --- a/src/main/java/com/amazon/carbonado/raw/DataDecoder.java +++ b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java @@ -18,6 +18,10 @@ package com.amazon.carbonado.raw;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.IOException;
+
import com.amazon.carbonado.CorruptEncodingException;
import static com.amazon.carbonado.raw.DataEncoder.*;
@@ -399,7 +403,8 @@ public class DataDecoder { valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) |
((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
} else {
- valueLength = ((b & 0x07) << 24) | ((src[srcOffset++] & 0xff) << 16) |
+ valueLength = ((src[srcOffset++] & 0xff) << 24) |
+ ((src[srcOffset++] & 0xff) << 16) |
((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
}
@@ -505,6 +510,66 @@ public class DataDecoder { }
/**
+ * Decodes a length value which was encoded by {@link DataDecoder#encodeLength}.
+ *
+ * @return length value
+ * @since 1.2
+ */
+ public static int readLength(InputStream in) throws IOException, EOFException {
+ int b0 = in.read();
+ if (b0 < 0) {
+ throw new EOFException();
+ }
+ if (b0 <= 0x7f) {
+ return b0;
+ }
+ int b1 = in.read();
+ if (b1 < 0) {
+ throw new EOFException();
+ }
+ if (b0 <= 0xbf) {
+ return ((b0 & 0x3f) << 8) | b1;
+ }
+ int b2 = in.read();
+ if (b2 < 0) {
+ throw new EOFException();
+ }
+ if (b0 <= 0xdf) {
+ return ((b0 & 0x1f) << 16) | (b1 << 8) | b2;
+ }
+ int b3 = in.read();
+ if (b3 < 0) {
+ throw new EOFException();
+ }
+ if (b0 <= 0xef) {
+ return ((b0 & 0x0f) << 24) | (b1 << 16) | (b2 << 8) | b3;
+ }
+ int b4 = in.read();
+ if (b4 < 0) {
+ throw new EOFException();
+ }
+ return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
+ }
+
+ /**
+ * Reads as many bytes from the stream as is necessary to fill the given
+ * byte array. An EOFException is thrown if the stream end is encountered.
+ *
+ * @since 1.2
+ */
+ public static void readFully(InputStream in, byte[] b) throws IOException, EOFException {
+ final int length = b.length;
+ int total = 0;
+ while (total < length) {
+ int amt = in.read(b, total, length - total);
+ if (amt < 0) {
+ throw new EOFException();
+ }
+ total += amt;
+ }
+ }
+
+ /**
* Decodes the given byte array which was encoded by {@link
* DataEncoder#encodeSingle}.
*
diff --git a/src/main/java/com/amazon/carbonado/raw/DataEncoder.java b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java index 54846ce..1049644 100644 --- a/src/main/java/com/amazon/carbonado/raw/DataEncoder.java +++ b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java @@ -18,6 +18,9 @@ package com.amazon.carbonado.raw;
+import java.io.IOException;
+import java.io.OutputStream;
+
/**
* A very low-level class that supports encoding of primitive data. For
* encoding data into keys, see {@link KeyEncoder}.
@@ -356,7 +359,7 @@ public class DataEncoder { }
// Write the value length first, in a variable amount of bytes.
- int amt = writeLength(valueLength, dst, dstOffset);
+ int amt = encodeLength(valueLength, dst, dstOffset);
// Now write the value.
System.arraycopy(value, valueOffset, dst, dstOffset + amt, valueLength);
@@ -422,7 +425,7 @@ public class DataEncoder { int valueLength = value.length();
// Write the value length first, in a variable amount of bytes.
- dstOffset += writeLength(valueLength, dst, dstOffset);
+ dstOffset += encodeLength(valueLength, dst, dstOffset);
for (int i = 0; i < valueLength; i++) {
int c = value.charAt(i);
@@ -501,7 +504,7 @@ public class DataEncoder { return encodedLen;
}
- private static int writeLength(int valueLength, byte[] dst, int dstOffset) {
+ private static int encodeLength(int valueLength, byte[] dst, int dstOffset) {
if (valueLength < 128) {
dst[dstOffset] = (byte)valueLength;
return 1;
@@ -531,6 +534,41 @@ public class DataEncoder { }
/**
+ * Writes a positive length value in up to five bytes.
+ *
+ * @return number of bytes written
+ * @since 1.2
+ */
+ public static int writeLength(int valueLength, OutputStream out) throws IOException {
+ if (valueLength < 128) {
+ out.write(valueLength);
+ return 1;
+ } else if (valueLength < 16384) {
+ out.write((valueLength >> 8) | 0x80);
+ out.write(valueLength);
+ return 2;
+ } else if (valueLength < 2097152) {
+ out.write((valueLength >> 16) | 0xc0);
+ out.write(valueLength >> 8);
+ out.write(valueLength);
+ return 3;
+ } else if (valueLength < 268435456) {
+ out.write((valueLength >> 24) | 0xe0);
+ out.write(valueLength >> 16);
+ out.write(valueLength >> 8);
+ out.write(valueLength);
+ return 4;
+ } else {
+ out.write(0xf0);
+ out.write(valueLength >> 24);
+ out.write(valueLength >> 16);
+ out.write(valueLength >> 8);
+ out.write(valueLength);
+ return 5;
+ }
+ }
+
+ /**
* Encodes the given byte array for use when there is only a single
* property, whose type is a byte array. The original byte array is
* returned if the padding lengths are zero.
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java index 4c295dd..7cf3468 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java @@ -29,6 +29,7 @@ import org.cojen.classfile.Label; import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.Opcode;
import org.cojen.classfile.TypeDesc;
+import org.cojen.util.BeanComparator;
import org.cojen.util.BeanIntrospector;
import org.cojen.util.BeanProperty;
@@ -40,7 +41,7 @@ import com.amazon.carbonado.lob.Blob; import com.amazon.carbonado.lob.Clob;
import com.amazon.carbonado.lob.Lob;
-import com.amazon.carbonado.gen.StorableGenerator;
+import static com.amazon.carbonado.gen.StorableGenerator.*;
import com.amazon.carbonado.gen.TriggerSupport;
import com.amazon.carbonado.info.ChainedProperty;
@@ -62,6 +63,8 @@ import com.amazon.carbonado.info.StorablePropertyAdapter; * @author Brian S O'Neill
*/
public class GenericEncodingStrategy<S extends Storable> {
+ private static enum Mode { KEY, DATA, SERIAL }
+
private final Class<S> mType;
private final StorableIndex<S> mPkIndex;
@@ -165,7 +168,7 @@ public class GenericEncodingStrategy<S extends Storable> { throws SupportException
{
properties = ensureKeyProperties(properties);
- return buildEncoding(true, assembler,
+ return buildEncoding(Mode.KEY, assembler,
extractProperties(properties), extractDirections(properties),
instanceVar, adapterInstanceClass,
useReadMethods,
@@ -205,7 +208,7 @@ public class GenericEncodingStrategy<S extends Storable> { throws SupportException
{
properties = ensureKeyProperties(properties);
- buildDecoding(true, assembler,
+ buildDecoding(Mode.KEY, assembler,
extractProperties(properties), extractDirections(properties),
instanceVar, adapterInstanceClass, useWriteMethods,
-1, null, // no generation support
@@ -248,7 +251,7 @@ public class GenericEncodingStrategy<S extends Storable> { throws SupportException
{
properties = ensureDataProperties(properties);
- return buildEncoding(false, assembler,
+ return buildEncoding(Mode.DATA, assembler,
properties, null,
instanceVar, adapterInstanceClass,
useReadMethods, generation, null, null);
@@ -296,12 +299,55 @@ public class GenericEncodingStrategy<S extends Storable> { throws SupportException
{
properties = ensureDataProperties(properties);
- buildDecoding(false, assembler, properties, null,
+ buildDecoding(Mode.DATA, assembler, properties, null,
instanceVar, adapterInstanceClass, useWriteMethods,
generation, altGenerationHandler, encodedVar);
}
/**
+ * Generates bytecode instructions to encode properties and their
+ * states. This encoding is suitable for short-term serialization only.
+ *
+ * @param assembler code assembler to receive bytecode instructions
+ * @param properties specific properties to decode, defaults to all
+ * properties if null
+ * @return local variable referencing a byte array with encoded data
+ * @throws SupportException if any property type is not supported
+ * @since 1.2
+ */
+ public LocalVariable buildSerialEncoding(CodeAssembler assembler,
+ StorableProperty<S>[] properties)
+ throws SupportException
+ {
+ properties = ensureAllProperties(properties);
+ return buildEncoding
+ (Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, null);
+ }
+
+ /**
+ * Generates bytecode instructions to decode properties and their states. A
+ * CorruptEncodingException may be thrown from generated code.
+ *
+ * @param assembler code assembler to receive bytecode instructions
+ * @param properties specific properties to decode, defaults to all
+ * properties if null
+ * @param encodedVar required variable, which must be a byte array. At
+ * runtime, it references encoded data.
+ * @throws SupportException if any property type is not supported
+ * @throws IllegalArgumentException if encodedVar is not a byte array
+ * @since 1.2
+ */
+ public void buildSerialDecoding(CodeAssembler assembler,
+ StorableProperty<S>[] properties,
+ LocalVariable encodedVar)
+ throws SupportException
+ {
+ properties = ensureAllProperties(properties);
+ buildDecoding
+ (Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, encodedVar);
+ }
+
+ /**
* Returns the type of Storable that code is generated for.
*/
public final Class<S> getType() {
@@ -392,7 +438,7 @@ public class GenericEncodingStrategy<S extends Storable> { }
/**
- * Returns all data properties for storable.
+ * Returns all non-derived data properties for storable.
*/
@SuppressWarnings("unchecked")
protected StorableProperty<S>[] gatherAllDataProperties() {
@@ -410,6 +456,25 @@ public class GenericEncodingStrategy<S extends Storable> { return list.toArray(new StorableProperty[list.size()]);
}
+ /**
+ * Returns all non-join, non-derived properties for storable.
+ */
+ @SuppressWarnings("unchecked")
+ protected StorableProperty<S>[] gatherAllProperties() {
+ Map<String, ? extends StorableProperty<S>> map =
+ StorableIntrospector.examine(mType).getAllProperties();
+
+ List<StorableProperty<S>> list = new ArrayList<StorableProperty<S>>(map.size());
+
+ for (StorableProperty<S> property : map.values()) {
+ if (!property.isJoin() && !property.isDerived()) {
+ list.add(property);
+ }
+ }
+
+ return list.toArray(new StorableProperty[list.size()]);
+ }
+
protected StorablePropertyInfo checkSupport(StorableProperty<S> property)
throws SupportException
{
@@ -514,11 +579,28 @@ public class GenericEncodingStrategy<S extends Storable> { return properties;
}
+ private StorableProperty<S>[] ensureAllProperties(StorableProperty<S>[] properties) {
+ if (properties == null) {
+ properties = gatherAllProperties();
+ } else {
+ for (Object prop : properties) {
+ if (prop == null) {
+ throw new IllegalArgumentException();
+ }
+ }
+ // Sort to generate more efficient code.
+ properties = properties.clone();
+ Arrays.sort(properties,
+ BeanComparator.forClass(StorableProperty.class).orderBy("number"));
+ }
+ return properties;
+ }
+
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
- private LocalVariable buildEncoding(boolean forKey,
+ private LocalVariable buildEncoding(Mode mode,
CodeAssembler a,
StorableProperty<S>[] properties,
Direction[] directions,
@@ -543,11 +625,23 @@ public class GenericEncodingStrategy<S extends Storable> { // Encoding order is:
//
// 1. Prefix
- // 2. Generation prefix
- // 3. Properties
- // 4. Suffix
-
- final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
+ // 2. Generation
+ // 3. Property states (if Mode.SERIAL)
+ // 4. Properties
+ // 5. Suffix
+
+ final int prefix;
+ switch (mode) {
+ default:
+ prefix = 0;
+ break;
+ case KEY:
+ prefix = mKeyPrefixPadding;
+ break;
+ case DATA:
+ prefix = mDataPrefixPadding;
+ break;
+ }
final int generationPrefix;
if (generation < 0) {
@@ -558,7 +652,18 @@ public class GenericEncodingStrategy<S extends Storable> { generationPrefix = 4;
}
- final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
+ final int suffix;
+ switch (mode) {
+ default:
+ suffix = 0;
+ break;
+ case KEY:
+ suffix = mKeySuffixPadding;
+ break;
+ case DATA:
+ suffix = mDataSuffixPadding;
+ break;
+ }
final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
final LocalVariable encodedVar = a.createLocalVariable(null, byteArrayType);
@@ -574,7 +679,7 @@ public class GenericEncodingStrategy<S extends Storable> { StorableProperty<S> property = properties[0];
StorablePropertyInfo info = infos[0];
- if (info.getStorageType().toClass() == byte[].class) {
+ if (mode != Mode.SERIAL && info.getStorageType().toClass() == byte[].class) {
// Since there is only one property, and it is just a byte
// array, optimize by not doing any fancy encoding. If the
// property is optional, then a byte prefix is needed to
@@ -583,8 +688,8 @@ public class GenericEncodingStrategy<S extends Storable> { loadPropertyValue(a, info, 0, useReadMethods,
instanceVar, adapterInstanceClass, partialStartVar);
- boolean descending =
- forKey && directions != null && directions[0] == Direction.DESCENDING;
+ boolean descending = mode == Mode.KEY
+ && directions != null && directions[0] == Direction.DESCENDING;
TypeDesc[] params;
if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
@@ -621,24 +726,31 @@ public class GenericEncodingStrategy<S extends Storable> { }
}
- boolean doPartial = forKey && (partialStartVar != null || partialEndVar != null);
+ boolean doPartial = mode == Mode.SERIAL
+ || mode == Mode.KEY && (partialStartVar != null || partialEndVar != null);
// Calculate exactly how many bytes are needed to encode. The length
// is composed of a static and a variable amount. The variable amount
// is determined at runtime.
int staticLength = 0;
- if (!forKey || partialStartVar == null) {
+ if (mode != Mode.KEY || partialStartVar == null) {
// Only include prefix as static if no runtime check is needed
// against runtime partial start value.
staticLength += prefix + generationPrefix;
}
- if (!forKey || partialEndVar == null) {
+ if (mode != Mode.KEY || partialEndVar == null) {
// Only include suffix as static if no runtime check is needed
// against runtime partial end value.
staticLength += suffix;
}
+ if (mode == Mode.SERIAL) {
+ // Need room to encode property states. Two bits per property, so
+ // one byte can encode the state of four properties.
+ staticLength += (properties.length + 3) / 4;
+ }
+
boolean hasVariableLength;
if (doPartial) {
hasVariableLength = true;
@@ -685,41 +797,27 @@ public class GenericEncodingStrategy<S extends Storable> { if (doPartial) {
// Initialize the stashed propery to null or zero to make
// the verifier happy.
- switch (propVar.getType().getTypeCode()) {
- case TypeDesc.OBJECT_CODE:
- a.loadNull();
- break;
- case TypeDesc.LONG_CODE:
- a.loadConstant(0L);
- break;
- case TypeDesc.FLOAT_CODE:
- a.loadConstant(0.0f);
- break;
- case TypeDesc.DOUBLE_CODE:
- a.loadConstant(0.0d);
- break;
- case TypeDesc.INT_CODE: default:
- a.loadConstant(0);
- break;
- }
-
+ loadBlankValue(a, propVar.getType());
a.storeLocal(propVar);
}
}
Label[] entryPoints = null;
- if (partialStartVar != null) {
+ if (mode == Mode.SERIAL || partialStartVar != null) {
// Will jump into an arbitrary location, so always have a stack
// variable available.
a.loadConstant(0);
hasStackVar = true;
+ }
+ if (partialStartVar != null) {
entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
}
Label exitPoint = a.createLabel();
+ LocalVariable stateFieldVar = null;
for (int i=0; i<properties.length; i++) {
StorableProperty<S> property = properties[i];
StorablePropertyInfo info = infos[i];
@@ -738,6 +836,36 @@ public class GenericEncodingStrategy<S extends Storable> { continue;
}
+ Label nextProperty = null;
+ if (mode == Mode.SERIAL) {
+ // Skip uninitialized properties.
+ nextProperty = a.createLabel();
+
+ if (stateFieldVar != null) {
+ a.loadLocal(stateFieldVar);
+ } else {
+ int fieldOrdinal = property.getNumber() >> 4;
+
+ a.loadThis();
+ a.loadField(PROPERTY_STATE_FIELD_NAME + fieldOrdinal, TypeDesc.INT);
+
+ if (i + 1 < properties.length
+ && properties[i + 1].getNumber() >> 4 == fieldOrdinal)
+ {
+ // Save for use by next property.
+ stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
+ a.storeLocal(stateFieldVar);
+ a.loadLocal(stateFieldVar);
+ } else {
+ stateFieldVar = null;
+ }
+ }
+
+ a.loadConstant(PROPERTY_STATE_MASK << ((property.getNumber() & 0xf) * 2));
+ a.math(Opcode.IAND);
+ a.ifZeroComparisonBranch(nextProperty, "==");
+ }
+
TypeDesc propType = info.getStorageType();
if (propType.isPrimitive()) {
@@ -831,7 +959,7 @@ public class GenericEncodingStrategy<S extends Storable> { instanceVar, adapterInstanceClass, partialStartVar);
String className =
- (forKey ? KeyEncoder.class : DataEncoder.class).getName();
+ (mode == Mode.KEY ? KeyEncoder.class : DataEncoder.class).getName();
a.invokeStatic(className, "calculateEncodedStringLength",
TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING});
if (hasStackVar) {
@@ -845,7 +973,7 @@ public class GenericEncodingStrategy<S extends Storable> { instanceVar, adapterInstanceClass, partialStartVar);
String className =
- (forKey ? KeyEncoder.class : DataEncoder.class).getName();
+ (mode == Mode.KEY ? KeyEncoder.class : DataEncoder.class).getName();
a.invokeStatic(className, "calculateEncodedLength",
TypeDesc.INT, new TypeDesc[] {byteArrayType});
if (hasStackVar) {
@@ -864,11 +992,17 @@ public class GenericEncodingStrategy<S extends Storable> { } else {
throw notSupported(property);
}
+
+ if (nextProperty != null) {
+ nextProperty.setLocation();
+ }
}
exitPoint.setLocation();
- if (forKey && partialStartVar != null && (prefix > 0 || generationPrefix > 0)) {
+ if (mode == Mode.KEY
+ && partialStartVar != null && (prefix > 0 || generationPrefix > 0))
+ {
// Prefix must be allocated only if runtime value of
// partialStartVar is zero.
a.loadLocal(partialStartVar);
@@ -883,7 +1017,7 @@ public class GenericEncodingStrategy<S extends Storable> { noPrefix.setLocation();
}
- if (forKey && partialEndVar != null && suffix > 0) {
+ if (mode == Mode.KEY && partialEndVar != null && suffix > 0) {
// Suffix must be allocated only if runtime value of
// partialEndVar is equal to property count.
a.loadLocal(partialEndVar);
@@ -915,23 +1049,34 @@ public class GenericEncodingStrategy<S extends Storable> { // Now encode into the byte array.
int constantOffset = 0;
- LocalVariable offset = null;
+ LocalVariable offsetVar = null;
- if (!forKey || partialStartVar == null) {
+ if (mode != Mode.KEY || partialStartVar == null) {
// Only include prefix as constant offset if no runtime check is
// needed against runtime partial start value.
- constantOffset += prefix + generationPrefix;
- encodeGeneration(a, encodedVar, prefix, generation);
+ constantOffset += prefix;
+ encodeGeneration(a, encodedVar, constantOffset, generation);
+ constantOffset += generationPrefix;
+ }
+
+ if (mode == Mode.SERIAL) {
+ encodePropertyStates(a, encodedVar, constantOffset, properties);
+ constantOffset += (properties.length + 3) / 4;
+
+ // Some properties are skipped at runtime, so offset variable is required.
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
+ a.loadConstant(constantOffset);
+ a.storeLocal(offsetVar);
}
Label[] entryPoints = null;
- if (forKey && partialStartVar != null) {
+ if (mode == Mode.KEY && partialStartVar != null) {
// Will jump into an arbitrary location, so put an initial value
// into offset variable.
- offset = a.createLocalVariable(null, TypeDesc.INT);
- a.loadConstant(0);
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
+ a.loadConstant(constantOffset);
if (prefix > 0) {
// Prefix is allocated only if partial start is zero. Check if
// offset should be adjusted to skip over it.
@@ -943,17 +1088,27 @@ public class GenericEncodingStrategy<S extends Storable> { encodeGeneration(a, encodedVar, prefix, generation);
noPrefix.setLocation();
}
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
}
Label exitPoint = a.createLabel();
+ LocalVariable stateFieldVar;
+ if (mode != Mode.SERIAL) {
+ stateFieldVar = null;
+ } else {
+ stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
+ }
+ int lastFieldOrdinal = -1;
+
for (int i=0; i<properties.length; i++) {
StorableProperty<S> property = properties[i];
StorablePropertyInfo info = infos[i];
+ Label nextProperty = a.createLabel();
+
if (doPartial) {
if (entryPoints != null) {
entryPoints[i].setLocation();
@@ -966,6 +1121,26 @@ public class GenericEncodingStrategy<S extends Storable> { }
}
+ if (mode == Mode.SERIAL) {
+ // Skip uninitialized properties.
+
+ int fieldOrdinal = property.getNumber() >> 4;
+
+ if (fieldOrdinal == lastFieldOrdinal) {
+ a.loadLocal(stateFieldVar);
+ } else {
+ a.loadThis();
+ a.loadField(PROPERTY_STATE_FIELD_NAME + fieldOrdinal, TypeDesc.INT);
+ a.storeLocal(stateFieldVar);
+ a.loadLocal(stateFieldVar);
+ lastFieldOrdinal = fieldOrdinal;
+ }
+
+ a.loadConstant(PROPERTY_STATE_MASK << ((property.getNumber() & 0xf) * 2));
+ a.math(Opcode.IAND);
+ a.ifZeroComparisonBranch(nextProperty, "==");
+ }
+
if (info.isLob()) {
// Need RawSupport instance for getting locator from Lob.
pushRawSupport(a, instanceVar);
@@ -1015,28 +1190,28 @@ public class GenericEncodingStrategy<S extends Storable> { // Fill out remaining parameters before calling specific method
// to encode property value.
a.loadLocal(encodedVar);
- if (offset == null) {
+ if (offsetVar == null) {
a.loadConstant(constantOffset);
} else {
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
}
- boolean descending =
- forKey && directions != null && directions[i] == Direction.DESCENDING;
+ boolean descending = mode == Mode.KEY
+ && directions != null && directions[i] == Direction.DESCENDING;
- int amt = encodeProperty(a, propType, forKey, descending);
+ int amt = encodeProperty(a, propType, mode, descending);
if (amt > 0) {
if (i + 1 < properties.length) {
// Only adjust offset if there are more properties.
- if (offset == null) {
+ if (offsetVar == null) {
constantOffset += amt;
} else {
a.loadConstant(amt);
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
a.math(Opcode.IADD);
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
}
}
} else {
@@ -1045,19 +1220,21 @@ public class GenericEncodingStrategy<S extends Storable> { a.pop();
} else {
// Only adjust offset if there are more properties.
- if (offset == null) {
+ if (offsetVar == null) {
if (constantOffset > 0) {
a.loadConstant(constantOffset);
a.math(Opcode.IADD);
}
- offset = a.createLocalVariable(null, TypeDesc.INT);
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
} else {
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
a.math(Opcode.IADD);
}
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
}
}
+
+ nextProperty.setLocation();
}
exitPoint.setLocation();
@@ -1156,8 +1333,7 @@ public class GenericEncodingStrategy<S extends Storable> { if (useAdapterInstance) {
// Push adapter instance to stack to be used later.
- String fieldName =
- info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
+ String fieldName = info.getPropertyName() + ADAPTER_FIELD_ELEMENT + 0;
TypeDesc adapterType = TypeDesc.forClass
(info.getToStorageAdapter().getDeclaringClass());
a.loadStaticField
@@ -1219,6 +1395,29 @@ public class GenericEncodingStrategy<S extends Storable> { }
/**
+ * Generates code that loads zero, false, or null to the stack.
+ */
+ private void loadBlankValue(CodeAssembler a, TypeDesc type) {
+ switch (type.getTypeCode()) {
+ case TypeDesc.OBJECT_CODE:
+ a.loadNull();
+ break;
+ case TypeDesc.LONG_CODE:
+ a.loadConstant(0L);
+ break;
+ case TypeDesc.FLOAT_CODE:
+ a.loadConstant(0.0f);
+ break;
+ case TypeDesc.DOUBLE_CODE:
+ a.loadConstant(0.0d);
+ break;
+ case TypeDesc.INT_CODE: default:
+ a.loadConstant(0);
+ break;
+ }
+ }
+
+ /**
* Returns a negative value if encoding is variable. The minimum static
* amount is computed from the one's compliment. Of the types with variable
* encoding lengths, only for primitives is the minimum static amount
@@ -1312,14 +1511,13 @@ public class GenericEncodingStrategy<S extends Storable> { * @return 0 if an int amount is pushed onto the stack, or a positive value
* if offset adjust amount is constant
*/
- private int encodeProperty(CodeAssembler a, TypeDesc type,
- boolean forKey, boolean descending) {
+ private int encodeProperty(CodeAssembler a, TypeDesc type, Mode mode, boolean descending) {
TypeDesc[] params = new TypeDesc[] {
type, TypeDesc.forClass(byte[].class), TypeDesc.INT
};
if (type.isPrimitive()) {
- if (forKey && descending) {
+ if (mode == Mode.KEY && descending) {
a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", null, params);
} else {
a.invokeStatic(DataEncoder.class.getName(), "encode", null, params);
@@ -1364,7 +1562,7 @@ public class GenericEncodingStrategy<S extends Storable> { retType = TypeDesc.INT;
}
- if (forKey && descending) {
+ if (mode == Mode.KEY && descending) {
a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", retType, params);
} else {
a.invokeStatic(DataEncoder.class.getName(), "encode", retType, params);
@@ -1373,7 +1571,7 @@ public class GenericEncodingStrategy<S extends Storable> { return adjust;
} else {
// Type is a String or byte array.
- if (forKey) {
+ if (mode == Mode.KEY) {
if (descending) {
a.invokeStatic
(KeyEncoder.class.getName(), "encodeDesc", TypeDesc.INT, params);
@@ -1391,6 +1589,8 @@ public class GenericEncodingStrategy<S extends Storable> { * Generates code that stores a one or four byte generation value into a
* byte array referenced by the local variable.
*
+ * @param encodedVar references a byte array
+ * @param offset offset into byte array
* @param generation if less than zero, no code is generated
*/
private void encodeGeneration(CodeAssembler a, LocalVariable encodedVar,
@@ -1419,6 +1619,100 @@ public class GenericEncodingStrategy<S extends Storable> { }
/**
+ * Generates code that encodes property states using
+ * ((properties.length + 3) / 4) bytes.
+ *
+ * @param encodedVar references a byte array
+ * @param offset offset into byte array
+ */
+ private void encodePropertyStates(CodeAssembler a, LocalVariable encodedVar,
+ int offset, StorableProperty<S>[] properties)
+ {
+ LocalVariable stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
+ int lastFieldOrdinal = -1;
+
+ LocalVariable accumVar = a.createLocalVariable(null, TypeDesc.INT);
+ int accumShift = 0;
+
+ for (int i=0; i<properties.length; i++) {
+ StorableProperty<S> property = properties[i];
+
+ int fieldOrdinal = property.getNumber() >> 4;
+
+ if (fieldOrdinal == lastFieldOrdinal) {
+ a.loadLocal(stateFieldVar);
+ } else {
+ a.loadThis();
+ a.loadField(PROPERTY_STATE_FIELD_NAME + fieldOrdinal, TypeDesc.INT);
+ a.storeLocal(stateFieldVar);
+ a.loadLocal(stateFieldVar);
+ lastFieldOrdinal = fieldOrdinal;
+ }
+
+ int stateShift = (property.getNumber() & 0xf) * 2;
+
+ int accumPack = 2;
+ int mask = PROPERTY_STATE_MASK << stateShift;
+
+ // Try to pack more state properties into one operation.
+ while ((accumShift + accumPack) < 8) {
+ if (i + 1 >= properties.length) {
+ // No more properties to encode.
+ break;
+ }
+ StorableProperty<S> nextProperty = properties[i + 1];
+ if (property.getNumber() + 1 != nextProperty.getNumber()) {
+ // Properties are not consecutive.
+ break;
+ }
+ if (fieldOrdinal != (nextProperty.getNumber() >> 4)) {
+ // Property states are stored in different fields.
+ break;
+ }
+ accumPack += 2;
+ mask |= PROPERTY_STATE_MASK << ((nextProperty.getNumber() & 0xf) * 2);
+ property = nextProperty;
+ i++;
+ }
+
+ a.loadConstant(mask);
+ a.math(Opcode.IAND);
+
+ if (stateShift < accumShift) {
+ a.loadConstant(accumShift - stateShift);
+ a.math(Opcode.ISHL);
+ } else if (stateShift > accumShift) {
+ a.loadConstant(stateShift - accumShift);
+ a.math(Opcode.IUSHR);
+ }
+
+ if (accumShift != 0) {
+ a.loadLocal(accumVar);
+ a.math(Opcode.IOR);
+ }
+
+ a.storeLocal(accumVar);
+
+ if ((accumShift += accumPack) >= 8) {
+ // Accumulator is full, so copy it to byte array.
+ a.loadLocal(encodedVar);
+ a.loadConstant(offset++);
+ a.loadLocal(accumVar);
+ a.storeToArray(TypeDesc.BYTE);
+ accumShift = 0;
+ }
+ }
+
+ if (accumShift > 0) {
+ // Copy remaining states.
+ a.loadLocal(encodedVar);
+ a.loadConstant(offset++);
+ a.loadLocal(accumVar);
+ a.storeToArray(TypeDesc.BYTE);
+ }
+ }
+
+ /**
* Generates code to push RawSupport instance to the stack. RawSupport is
* available only in Storable instances. If instanceVar is an Object[], a
* SupportException is thrown.
@@ -1442,8 +1736,7 @@ public class GenericEncodingStrategy<S extends Storable> { a.loadLocal(instanceVar);
}
- a.loadField(StorableGenerator.SUPPORT_FIELD_NAME,
- TypeDesc.forClass(TriggerSupport.class));
+ a.loadField(SUPPORT_FIELD_NAME, TypeDesc.forClass(TriggerSupport.class));
a.checkCast(TypeDesc.forClass(RawSupport.class));
}
@@ -1489,7 +1782,7 @@ public class GenericEncodingStrategy<S extends Storable> { /////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
- private void buildDecoding(boolean forKey,
+ private void buildDecoding(Mode mode,
CodeAssembler a,
StorableProperty<S>[] properties,
Direction[] directions,
@@ -1512,10 +1805,22 @@ public class GenericEncodingStrategy<S extends Storable> { //
// 1. Prefix
// 2. Generation prefix
- // 3. Properties
- // 4. Suffix
-
- final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
+ // 3. Property states (if Mode.SERIAL)
+ // 4. Properties
+ // 5. Suffix
+
+ final int prefix;
+ switch (mode) {
+ default:
+ prefix = 0;
+ break;
+ case KEY:
+ prefix = mKeyPrefixPadding;
+ break;
+ case DATA:
+ prefix = mDataPrefixPadding;
+ break;
+ }
final int generationPrefix;
if (generation < 0) {
@@ -1526,7 +1831,18 @@ public class GenericEncodingStrategy<S extends Storable> { generationPrefix = 4;
}
- final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
+ final int suffix;
+ switch (mode) {
+ default:
+ suffix = 0;
+ break;
+ case KEY:
+ suffix = mKeySuffixPadding;
+ break;
+ case DATA:
+ suffix = mDataSuffixPadding;
+ break;
+ }
final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
@@ -1538,7 +1854,7 @@ public class GenericEncodingStrategy<S extends Storable> { StorableProperty<S> property = properties[0];
StorablePropertyInfo info = infos[0];
- if (info.getStorageType().toClass() == byte[].class) {
+ if (mode != Mode.SERIAL && info.getStorageType().toClass() == byte[].class) {
// Since there is only one property, and it is just a byte
// array, it doesn't have any fancy encoding.
@@ -1547,8 +1863,8 @@ public class GenericEncodingStrategy<S extends Storable> { a.loadLocal(encodedVar);
- boolean descending =
- forKey && directions != null && directions[0] == Direction.DESCENDING;
+ boolean descending = mode == Mode.KEY
+ && directions != null && directions[0] == Direction.DESCENDING;
TypeDesc[] params;
if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
@@ -1582,21 +1898,102 @@ public class GenericEncodingStrategy<S extends Storable> { }
}
- // Now decode from the byte array.
+ // Now decode properties from the byte array.
int constantOffset = prefix + generationPrefix;
- LocalVariable offset = null;
+ LocalVariable offsetVar = null;
+
// References to local variables which will hold references.
- LocalVariable[] stringRef = new LocalVariable[1];
- LocalVariable[] byteArrayRef = new LocalVariable[1];
+ LocalVariable[] stringRefRef = new LocalVariable[1];
+ LocalVariable[] byteArrayRefRef = new LocalVariable[1];
LocalVariable[] valueRefRef = new LocalVariable[1];
- for (int i=0; i<infos.length; i++) {
+ if (mode == Mode.SERIAL) {
+ decodePropertyStates(a, encodedVar, constantOffset, properties);
+ constantOffset += (properties.length + 3) / 4;
+
+ // Some properties are skipped at runtime, so offset variable is required.
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
+ a.loadConstant(constantOffset);
+ a.storeLocal(offsetVar);
+
+ // References need to be initialized early because some properties
+ // are skipped at runtime.
+
+ for (int i=0; i<properties.length; i++) {
+ StorableProperty<S> property = properties[i];
+ if (String.class.isAssignableFrom(property.getType())) {
+ if (stringRefRef[0] == null) {
+ TypeDesc refType = TypeDesc.forClass(String[].class);
+ stringRefRef[0] = a.createLocalVariable(null, refType);
+ a.loadConstant(1);
+ a.newObject(refType);
+ a.storeLocal(stringRefRef[0]);
+ }
+ } else if (byte[].class.isAssignableFrom(property.getType())) {
+ if (byteArrayRefRef[0] == null) {
+ TypeDesc refType = TypeDesc.forClass(byte[][].class);
+ byteArrayRefRef[0] = a.createLocalVariable(null, refType);
+ a.loadConstant(1);
+ a.newObject(refType);
+ a.storeLocal(byteArrayRefRef[0]);
+ }
+ }
+ }
+ }
+
+ LocalVariable stateFieldVar;
+ if (mode != Mode.SERIAL) {
+ stateFieldVar = null;
+ } else {
+ stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
+ }
+ int lastFieldOrdinal = -1;
+
+ for (int i=0; i<properties.length; i++) {
+ StorableProperty<S> property = properties[i];
StorablePropertyInfo info = infos[i];
+ Label storePropertyLocation = a.createLabel();
+ Label nextPropertyLocation = a.createLabel();
+
// Push to stack in preparation for storing a property.
pushDecodingInstanceVar(a, i, instanceVar);
+ if (mode == Mode.SERIAL) {
+ // Load property if initialized, else reset it.
+
+ int fieldOrdinal = property.getNumber() >> 4;
+
+ if (fieldOrdinal == lastFieldOrdinal) {
+ a.loadLocal(stateFieldVar);
+ } else {
+ a.loadThis();
+ a.loadField(PROPERTY_STATE_FIELD_NAME + fieldOrdinal, TypeDesc.INT);
+ a.storeLocal(stateFieldVar);
+ a.loadLocal(stateFieldVar);
+ lastFieldOrdinal = fieldOrdinal;
+ }
+
+ a.loadConstant(PROPERTY_STATE_MASK << ((property.getNumber() & 0xf) * 2));
+ a.math(Opcode.IAND);
+ Label isInitialized = a.createLabel();
+ a.ifZeroComparisonBranch(isInitialized, "!=");
+
+ // Reset property value to zero, false, or null.
+ loadBlankValue(a, TypeDesc.forClass(property.getType()));
+
+ if (info.getToStorageAdapter() != null) {
+ // Bypass adapter.
+ a.storeField(info.getPropertyName(), info.getPropertyType());
+ a.branch(nextPropertyLocation);
+ } else {
+ a.branch(storePropertyLocation);
+ }
+
+ isInitialized.setLocation();
+ }
+
TypeDesc storageType = info.getStorageType();
if (info.isLob()) {
@@ -1612,17 +2009,17 @@ public class GenericEncodingStrategy<S extends Storable> { }
a.loadLocal(encodedVar);
- if (offset == null) {
+ if (offsetVar == null) {
a.loadConstant(constantOffset);
} else {
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
}
- boolean descending =
- forKey && directions != null && directions[i] == Direction.DESCENDING;
+ boolean descending = mode == Mode.KEY
+ && directions != null && directions[i] == Direction.DESCENDING;
- int amt = decodeProperty(a, info, storageType, forKey, descending,
- stringRef, byteArrayRef, valueRefRef);
+ int amt = decodeProperty(a, info, storageType, mode, descending,
+ stringRefRef, byteArrayRefRef, valueRefRef);
if (info.isLob()) {
getLobFromLocator(a, info);
@@ -1633,33 +2030,33 @@ public class GenericEncodingStrategy<S extends Storable> { // Only adjust offset if there are more properties.
if (amt > 0) {
- if (offset == null) {
+ if (offsetVar == null) {
constantOffset += amt;
} else {
a.loadConstant(amt);
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
a.math(Opcode.IADD);
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
}
} else {
// Offset adjust is one if returned object is null.
a.dup();
Label notNull = a.createLabel();
a.ifNullBranch(notNull, false);
- a.loadConstant(1 + (offset == null ? constantOffset : 0));
+ a.loadConstant(1 + (offsetVar == null ? constantOffset : 0));
Label cont = a.createLabel();
a.branch(cont);
notNull.setLocation();
- a.loadConstant(~amt + (offset == null ? constantOffset : 0));
+ a.loadConstant(~amt + (offsetVar == null ? constantOffset : 0));
cont.setLocation();
- if (offset == null) {
- offset = a.createLocalVariable(null, TypeDesc.INT);
+ if (offsetVar == null) {
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
} else {
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
a.math(Opcode.IADD);
}
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
}
}
} else {
@@ -1668,17 +2065,17 @@ public class GenericEncodingStrategy<S extends Storable> { a.pop();
} else {
// Only adjust offset if there are more properties.
- if (offset == null) {
+ if (offsetVar == null) {
if (constantOffset > 0) {
a.loadConstant(constantOffset);
a.math(Opcode.IADD);
}
- offset = a.createLocalVariable(null, TypeDesc.INT);
+ offsetVar = a.createLocalVariable(null, TypeDesc.INT);
} else {
- a.loadLocal(offset);
+ a.loadLocal(offsetVar);
a.math(Opcode.IADD);
}
- a.storeLocal(offset);
+ a.storeLocal(offsetVar);
}
// Get the value out of the ref array so that it can be stored.
@@ -1687,7 +2084,11 @@ public class GenericEncodingStrategy<S extends Storable> { a.loadFromArray(valueRefRef[0].getType());
}
+ storePropertyLocation.setLocation();
+
storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
+
+ nextPropertyLocation.setLocation();
}
}
@@ -1701,7 +2102,7 @@ public class GenericEncodingStrategy<S extends Storable> { */
private int decodeProperty(CodeAssembler a,
GenericPropertyInfo info, TypeDesc storageType,
- boolean forKey, boolean descending,
+ Mode mode, boolean descending,
LocalVariable[] stringRefRef, LocalVariable[] byteArrayRefRef,
LocalVariable[] valueRefRef)
throws SupportException
@@ -1794,7 +2195,7 @@ public class GenericEncodingStrategy<S extends Storable> { }
TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT};
- if (forKey && descending) {
+ if (mode == Mode.KEY && descending) {
a.invokeStatic
(KeyDecoder.class.getName(), methodName + "Desc", returnType, params);
} else {
@@ -1811,12 +2212,13 @@ public class GenericEncodingStrategy<S extends Storable> { return adjust;
} else {
- String className = (forKey ? KeyDecoder.class : DataDecoder.class).getName();
+ String className = (mode == Mode.KEY ? KeyDecoder.class : DataDecoder.class).getName();
String methodName;
TypeDesc refType;
if (storageType == TypeDesc.STRING) {
- methodName = (forKey && descending) ? "decodeStringDesc" : "decodeString";
+ methodName = (mode == Mode.KEY && descending)
+ ? "decodeStringDesc" : "decodeString";
refType = TypeDesc.forClass(String[].class);
if (stringRefRef[0] == null) {
stringRefRef[0] = a.createLocalVariable(null, refType);
@@ -1827,7 +2229,7 @@ public class GenericEncodingStrategy<S extends Storable> { a.loadLocal(stringRefRef[0]);
valueRefRef[0] = stringRefRef[0];
} else if (storageType.toClass() == byte[].class) {
- methodName = (forKey && descending) ? "decodeDesc" : "decode";
+ methodName = (mode == Mode.KEY && descending) ? "decodeDesc" : "decode";
refType = TypeDesc.forClass(byte[][].class);
if (byteArrayRefRef[0] == null) {
byteArrayRefRef[0] = a.createLocalVariable(null, refType);
@@ -1913,8 +2315,7 @@ public class GenericEncodingStrategy<S extends Storable> { LocalVariable temp = a.createLocalVariable(null, storageType);
a.storeLocal(temp);
- String fieldName =
- info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
+ String fieldName = info.getPropertyName() + ADAPTER_FIELD_ELEMENT + 0;
TypeDesc adapterType = TypeDesc.forClass
(info.getToStorageAdapter().getDeclaringClass());
a.loadStaticField
@@ -1969,26 +2370,10 @@ public class GenericEncodingStrategy<S extends Storable> { // Might attempt to unbox null, which
// throws NullPointerException. Instead,
// convert null to zero or false.
-
a.dup();
a.ifNullBranch(convertLabel, false);
a.pop(); // pop null off stack
-
- switch (propType.getTypeCode()) {
- default:
- a.loadConstant(0);
- break;
- case TypeDesc.LONG_CODE:
- a.loadConstant(0L);
- break;
- case TypeDesc.FLOAT_CODE:
- a.loadConstant(0.0f);
- break;
- case TypeDesc.DOUBLE_CODE:
- a.loadConstant(0.0d);
- break;
- }
-
+ loadBlankValue(a, propType);
a.branch(convertedLabel);
}
@@ -2109,4 +2494,104 @@ public class GenericEncodingStrategy<S extends Storable> { generationMatches.setLocation();
}
+
+ /**
+ * Generates code that decodes property states from
+ * ((properties.length + 3) / 4) bytes.
+ *
+ * @param encodedVar references a byte array
+ * @param offset offset into byte array
+ */
+ private void decodePropertyStates(CodeAssembler a, LocalVariable encodedVar,
+ int offset, StorableProperty<S>[] properties)
+ {
+ LocalVariable stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
+ int lastFieldOrdinal = -1;
+
+ LocalVariable accumVar = a.createLocalVariable(null, TypeDesc.INT);
+ int accumShift = 8;
+
+ for (int i=0; i<properties.length; i++) {
+ StorableProperty<S> property = properties[i];
+
+ int fieldOrdinal = property.getNumber() >> 4;
+
+ if (fieldOrdinal != lastFieldOrdinal) {
+ if (lastFieldOrdinal >= 0) {
+ // Copy states to field.
+ a.loadThis();
+ a.loadLocal(stateFieldVar);
+ a.storeField(PROPERTY_STATE_FIELD_NAME + lastFieldOrdinal, TypeDesc.INT);
+ }
+ a.loadThis();
+ a.loadField(PROPERTY_STATE_FIELD_NAME + fieldOrdinal, TypeDesc.INT);
+ a.storeLocal(stateFieldVar);
+ lastFieldOrdinal = fieldOrdinal;
+ }
+
+ if (accumShift >= 8) {
+ // Load accumulator byte.
+ a.loadLocal(encodedVar);
+ a.loadConstant(offset++);
+ a.loadFromArray(TypeDesc.BYTE);
+ a.loadConstant(0xff);
+ a.math(Opcode.IAND);
+ a.storeLocal(accumVar);
+ accumShift = 0;
+ }
+
+ int stateShift = (property.getNumber() & 0xf) * 2;
+
+ int accumPack = 2;
+ int mask = PROPERTY_STATE_MASK << stateShift;
+
+ // Try to pack more state properties into one operation.
+ while ((accumShift + accumPack) < 8) {
+ if (i + 1 >= properties.length) {
+ // No more properties to encode.
+ break;
+ }
+ StorableProperty<S> nextProperty = properties[i + 1];
+ if (property.getNumber() + 1 != nextProperty.getNumber()) {
+ // Properties are not consecutive.
+ break;
+ }
+ if (fieldOrdinal != (nextProperty.getNumber() >> 4)) {
+ // Property states are stored in different fields.
+ break;
+ }
+ accumPack += 2;
+ mask |= PROPERTY_STATE_MASK << ((nextProperty.getNumber() & 0xf) * 2);
+ property = nextProperty;
+ i++;
+ }
+
+ a.loadLocal(accumVar);
+
+ if (stateShift < accumShift) {
+ a.loadConstant(accumShift - stateShift);
+ a.math(Opcode.IUSHR);
+ } else if (stateShift > accumShift) {
+ a.loadConstant(stateShift - accumShift);
+ a.math(Opcode.ISHL);
+ }
+
+ a.loadConstant(mask);
+ a.math(Opcode.IAND);
+ a.loadLocal(stateFieldVar);
+ a.loadConstant(~mask);
+ a.math(Opcode.IAND);
+ a.math(Opcode.IOR);
+ a.storeLocal(stateFieldVar);
+
+ accumShift += accumPack;
+ }
+
+ if (lastFieldOrdinal >= 0) {
+ // Copy remaining states to last state field.
+ a.loadThis();
+ a.loadLocal(stateFieldVar);
+ a.storeField(PROPERTY_STATE_FIELD_NAME + lastFieldOrdinal, TypeDesc.INT);
+ }
+ }
}
|