From 4226a7812c0bab11703bb5bb4c514e6618d0d8db Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Sat, 16 Feb 2008 00:13:18 +0000
Subject: Merge serialization support.

---
 src/main/java/com/amazon/carbonado/Storable.java   |  31 +
 .../amazon/carbonado/gen/CommonMethodNames.java    |   2 +
 .../amazon/carbonado/gen/StorableGenerator.java    |  84 +++
 .../java/com/amazon/carbonado/raw/DataDecoder.java |  67 +-
 .../java/com/amazon/carbonado/raw/DataEncoder.java |  44 +-
 .../carbonado/raw/GenericEncodingStrategy.java     | 741 +++++++++++++++++----
 6 files changed, 837 insertions(+), 132 deletions(-)

(limited to 'src')

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);
             }
 
@@ -504,6 +509,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;
@@ -530,6 +533,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
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,11 +299,54 @@ 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.
      */
@@ -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
@@ -1218,6 +1394,29 @@ public class GenericEncodingStrategy<S extends Storable> {
         return !isObjectArrayInstanceVar;
     }
 
+    /**
+     * 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
@@ -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,
@@ -1418,6 +1618,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
@@ -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);
+        }
+    }
 }
-- 
cgit v1.2.3