From 79cebfadf8703afe9bf28786bc4df1af348c876e Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Thu, 5 Oct 2006 03:54:20 +0000
Subject: Moved raw package out of spi package.

---
 .../amazon/carbonado/raw/CustomStorableCodec.java  |  332 ++++
 .../carbonado/raw/CustomStorableCodecFactory.java  |   70 +
 .../java/com/amazon/carbonado/raw/DataDecoder.java |  567 ++++++
 .../java/com/amazon/carbonado/raw/DataEncoder.java |  595 ++++++
 .../carbonado/raw/GenericEncodingStrategy.java     | 1963 ++++++++++++++++++++
 .../carbonado/raw/GenericInstanceFactory.java      |   36 +
 .../amazon/carbonado/raw/GenericPropertyInfo.java  |   60 +
 .../amazon/carbonado/raw/GenericStorableCodec.java |  813 ++++++++
 .../carbonado/raw/GenericStorableCodecFactory.java |   76 +
 .../java/com/amazon/carbonado/raw/KeyDecoder.java  |  646 +++++++
 .../java/com/amazon/carbonado/raw/KeyEncoder.java  |  741 ++++++++
 .../amazon/carbonado/raw/LayoutPropertyInfo.java   |   86 +
 .../java/com/amazon/carbonado/raw/RawCursor.java   |  743 ++++++++
 .../amazon/carbonado/raw/RawStorableGenerator.java |  355 ++++
 .../java/com/amazon/carbonado/raw/RawSupport.java  |   97 +
 .../java/com/amazon/carbonado/raw/RawUtil.java     |   66 +
 .../com/amazon/carbonado/raw/StorableCodec.java    |  118 ++
 .../amazon/carbonado/raw/StorableCodecFactory.java |   54 +
 .../amazon/carbonado/raw/StorablePropertyInfo.java |  132 ++
 .../com/amazon/carbonado/raw/package-info.java     |   23 +
 .../amazon/carbonado/spi/StorableSerializer.java   |    2 +-
 .../carbonado/spi/raw/CustomStorableCodec.java     |  332 ----
 .../spi/raw/CustomStorableCodecFactory.java        |   70 -
 .../com/amazon/carbonado/spi/raw/DataDecoder.java  |  567 ------
 .../com/amazon/carbonado/spi/raw/DataEncoder.java  |  595 ------
 .../carbonado/spi/raw/GenericEncodingStrategy.java | 1963 --------------------
 .../carbonado/spi/raw/GenericInstanceFactory.java  |   36 -
 .../carbonado/spi/raw/GenericPropertyInfo.java     |   60 -
 .../carbonado/spi/raw/GenericStorableCodec.java    |  813 --------
 .../spi/raw/GenericStorableCodecFactory.java       |   76 -
 .../com/amazon/carbonado/spi/raw/KeyDecoder.java   |  646 -------
 .../com/amazon/carbonado/spi/raw/KeyEncoder.java   |  741 --------
 .../carbonado/spi/raw/LayoutPropertyInfo.java      |   86 -
 .../com/amazon/carbonado/spi/raw/RawCursor.java    |  743 --------
 .../carbonado/spi/raw/RawStorableGenerator.java    |  355 ----
 .../com/amazon/carbonado/spi/raw/RawSupport.java   |   97 -
 .../java/com/amazon/carbonado/spi/raw/RawUtil.java |   66 -
 .../amazon/carbonado/spi/raw/StorableCodec.java    |  118 --
 .../carbonado/spi/raw/StorableCodecFactory.java    |   54 -
 .../carbonado/spi/raw/StorablePropertyInfo.java    |  132 --
 .../com/amazon/carbonado/spi/raw/package-info.java |   23 -
 41 files changed, 7574 insertions(+), 7574 deletions(-)
 create mode 100644 src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/DataDecoder.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/DataEncoder.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericPropertyInfo.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/LayoutPropertyInfo.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/RawCursor.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/RawSupport.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/RawUtil.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/StorableCodec.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java
 create mode 100644 src/main/java/com/amazon/carbonado/raw/package-info.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java
 delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/package-info.java

(limited to 'src/main/java/com/amazon')

diff --git a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java
new file mode 100644
index 0000000..e319e7c
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.ClassInjector;
+import org.cojen.util.WeakIdentityMap;
+
+import com.amazon.carbonado.CorruptEncodingException;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.info.Direction;
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+
+import com.amazon.carbonado.util.QuickConstructorGenerator;
+
+/**
+ * Allows codecs to be defined for storables that have a custom encoding.
+ *
+ * @author Brian S O'Neill
+ * @see CustomStorableCodecFactory
+ */
+public abstract class CustomStorableCodec<S extends Storable> implements StorableCodec<S> {
+    // Generated storable instances maintain a reference to user-defined
+    // concrete subclass of this class.
+    private static final String CUSTOM_STORABLE_CODEC_FIELD_NAME = "customStorableCodec$";
+
+    @SuppressWarnings("unchecked")
+    private static Map<Class, RawStorableGenerator.Flavors<? extends Storable>> cCache =
+        new WeakIdentityMap();
+
+    /**
+     * Returns a storable implementation that calls into CustomStorableCodec
+     * implementation for encoding and decoding.
+     */
+    @SuppressWarnings("unchecked")
+    static <S extends Storable> Class<? extends S>
+        getStorableClass(Class<S> type, boolean isMaster)
+        throws SupportException
+    {
+        synchronized (cCache) {
+            Class<? extends S> storableClass;
+
+            RawStorableGenerator.Flavors<S> flavors =
+                (RawStorableGenerator.Flavors<S>) cCache.get(type);
+
+            if (flavors == null) {
+                flavors = new RawStorableGenerator.Flavors<S>();
+                cCache.put(type, flavors);
+            } else if ((storableClass = flavors.getClass(isMaster)) != null) {
+                return storableClass;
+            }
+
+            storableClass = generateStorableClass(type, isMaster);
+            flavors.setClass(storableClass, isMaster);
+
+            return storableClass;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <S extends Storable> Class<? extends S>
+        generateStorableClass(Class<S> type, boolean isMaster)
+        throws SupportException
+    {
+        final Class<? extends S> abstractClass =
+            RawStorableGenerator.getAbstractClass(type, isMaster);
+
+        ClassInjector ci = ClassInjector.create
+            (type.getName(), abstractClass.getClassLoader());
+
+        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
+        cf.markSynthetic();
+        cf.setSourceFile(CustomStorableCodec.class.getName());
+        cf.setTarget("1.5");
+
+        // Declare some types.
+        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+        final TypeDesc[] byteArrayParam = {byteArrayType};
+        final TypeDesc customStorableCodecType = TypeDesc.forClass(CustomStorableCodec.class);
+
+        // Add field for saving reference to concrete CustomStorableCodec.
+        cf.addField(Modifiers.PRIVATE.toFinal(true),
+                    CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+
+        // Add constructor that accepts a RawSupport and a CustomStorableCodec.
+        {
+            TypeDesc[] params = {rawSupportType, customStorableCodecType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // Call super class constructor.
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            params = new TypeDesc[] {rawSupportType};
+            b.invokeSuperConstructor(params);
+
+            // Set private reference to customStorableCodec.
+            b.loadThis();
+            b.loadLocal(b.getParameter(1));
+            b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+
+            b.returnVoid();
+        }
+
+        // Add constructor that accepts a RawSupport, an encoded key, an
+        // encoded data, and a CustomStorableCodec.
+        {
+            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType,
+                                 customStorableCodecType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // Set private reference to customStorableCodec before calling
+            // super constructor. This is necessary because super class
+            // constructor will call our decode methods, which need the
+            // customStorableCodec. This trick is not allowed in Java, but the
+            // virtual machine verifier allows it.
+            b.loadThis();
+            b.loadLocal(b.getParameter(3));
+            b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+
+            // Now call super class constructor.
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.loadLocal(b.getParameter(1));
+            b.loadLocal(b.getParameter(2));
+            params = new TypeDesc[] {rawSupportType, byteArrayType, byteArrayType};
+            b.invokeSuperConstructor(params);
+
+            b.returnVoid();
+        }
+
+        // Implement protected abstract methods inherited from parent class.
+
+        // byte[] encodeKey()
+        {
+            // Encode the primary key into a byte array that supports correct
+            // ordering. No special key comparator is needed.
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.ENCODE_KEY_METHOD_NAME,
+                                         byteArrayType, null);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            b.loadThis();
+            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+            TypeDesc[] params = {TypeDesc.forClass(Storable.class)};
+            b.loadThis();
+            b.invokeVirtual(customStorableCodecType, "encodePrimaryKey", byteArrayType, params);
+            b.returnValue(byteArrayType);
+        }
+
+        // byte[] encodeData()
+        {
+            // Encoding non-primary key data properties.
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.ENCODE_DATA_METHOD_NAME,
+                                         byteArrayType, null);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            b.loadThis();
+            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+            TypeDesc[] params = {TypeDesc.forClass(Storable.class)};
+            b.loadThis();
+            b.invokeVirtual(customStorableCodecType, "encodeData", byteArrayType, params);
+            b.returnValue(byteArrayType);
+        }
+
+        // void decodeKey(byte[])
+        {
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.DECODE_KEY_METHOD_NAME,
+                                         null, byteArrayParam);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            b.loadThis();
+            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+            TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType};
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeVirtual(customStorableCodecType, "decodePrimaryKey", null, params);
+            b.returnVoid();
+        }
+
+        // void decodeData(byte[])
+        {
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.DECODE_DATA_METHOD_NAME,
+                                         null, byteArrayParam);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            b.loadThis();
+            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
+            TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType};
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeVirtual(customStorableCodecType, "decodeData", null, params);
+            b.returnVoid();
+        }
+
+        return ci.defineClass(cf);
+    }
+
+    private final Class<S> mType;
+    private final int mPkPropertyCount;
+    private final InstanceFactory mInstanceFactory;
+
+    public interface InstanceFactory {
+        Storable instantiate(RawSupport support, CustomStorableCodec codec);
+
+        Storable instantiate(RawSupport support, byte[] key, byte[] value,
+                             CustomStorableCodec codec)
+            throws FetchException;
+    }
+
+    /**
+     * @param isMaster when true, version properties and sequences are managed
+     * @throws SupportException if Storable is not supported
+     */
+    public CustomStorableCodec(Class<S> type, boolean isMaster) throws SupportException {
+        mType = type;
+        mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount();
+        Class<? extends S> storableClass = getStorableClass(type, isMaster);
+        mInstanceFactory = QuickConstructorGenerator
+            .getInstance(storableClass, InstanceFactory.class);
+    }
+
+    public Class<S> getStorableType() {
+        return mType;
+    }
+
+    @SuppressWarnings("unchecked")
+    public S instantiate(RawSupport<S> support) {
+        return (S) mInstanceFactory.instantiate(support, this);
+    }
+
+    @SuppressWarnings("unchecked")
+    public S instantiate(RawSupport<S> support, byte[] key, byte[] value)
+        throws FetchException
+    {
+        return (S) mInstanceFactory.instantiate(support, key, value, this);
+    }
+
+    public byte[] encodePrimaryKey(S storable) {
+        return encodePrimaryKey(storable, 0, mPkPropertyCount);
+    }
+
+    public byte[] encodePrimaryKey(Object[] values) {
+        return encodePrimaryKey(values, 0, mPkPropertyCount);
+    }
+
+    /**
+     * Convenient access to all the storable properties.
+     */
+    public Map<String, ? extends StorableProperty<S>> getAllProperties() {
+        return StorableIntrospector.examine(getStorableType()).getAllProperties();
+    }
+
+    /**
+     * Convenient way to define the clustered primary key index
+     * descriptor. Direction can be specified by prefixing the property name
+     * with a '+' or '-'. If unspecified, direction is assumed to be ascending.
+     */
+    @SuppressWarnings("unchecked")
+    public StorableIndex<S> buildPkIndex(String... propertyNames) {
+        Map<String, ? extends StorableProperty<S>> map = getAllProperties();
+        int length = propertyNames.length;
+        StorableProperty<S>[] properties = new StorableProperty[length];
+        Direction[] directions = new Direction[length];
+        for (int i=0; i<length; i++) {
+            String name = propertyNames[i];
+            char c = name.charAt(0);
+            Direction dir = Direction.fromCharacter(c);
+            if (dir != Direction.UNSPECIFIED || c == Direction.UNSPECIFIED.toCharacter()) {
+                name = name.substring(1);
+            } else {
+                // Default to ascending if not specified.
+                dir = Direction.ASCENDING;
+            }
+            if ((properties[i] = map.get(name)) == null) {
+                throw new IllegalArgumentException("Unknown property: " + name);
+            }
+            directions[i] = dir;
+        }
+        return new StorableIndex<S>(properties, directions, true, true);
+    }
+
+    /**
+     * Decode the primary key into properties of the storable.
+     */
+    public abstract void decodePrimaryKey(S storable, byte[] bytes)
+        throws CorruptEncodingException;
+
+    /**
+     * Encode all properties of the storable excluding the primary key.
+     */
+    public abstract byte[] encodeData(S storable);
+
+    /**
+     * Decode the data into properties of the storable.
+     */
+    public abstract void decodeData(S storable, byte[] bytes)
+        throws CorruptEncodingException;
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java
new file mode 100644
index 0000000..bfba733
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.layout.Layout;
+
+/**
+ * Factory for custom storable codecs.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class CustomStorableCodecFactory implements StorableCodecFactory {
+    public CustomStorableCodecFactory() {
+    }
+
+    /**
+     * Returns null to let repository decide what the name should be.
+     */
+    public String getStorageName(Class<? extends Storable> type) throws SupportException {
+        return null;
+    }
+
+    /**
+     * @param type type of storable to create codec for
+     * @param pkIndex ignored
+     * @param isMaster when true, version properties and sequences are managed
+     * @param layout when non-null, attempt to encode a storable layout
+     * generation value in each storable
+     * @throws SupportException if type is not supported
+     */
+    public <S extends Storable> CustomStorableCodec<S> createCodec(Class<S> type,
+                                                                   StorableIndex pkIndex,
+                                                                   boolean isMaster,
+                                                                   Layout layout)
+        throws SupportException
+    {
+        return createCodec(type, isMaster, layout);
+    }
+
+    /**
+     * @param type type of storable to create codec for
+     * @param isMaster when true, version properties and sequences are managed
+     * @param layout when non-null, attempt to encode a storable layout
+     * generation value in each storable
+     * @throws SupportException if type is not supported
+     */
+    protected abstract <S extends Storable> CustomStorableCodec<S>
+        createCodec(Class<S> type, boolean isMaster, Layout layout)
+        throws SupportException;
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/DataDecoder.java b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java
new file mode 100644
index 0000000..485ce82
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/DataDecoder.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.CorruptEncodingException;
+
+import static com.amazon.carbonado.raw.DataEncoder.*;
+
+/**
+ * A very low-level class that decodes key components encoded by methods of
+ * {@link DataEncoder}.
+ *
+ * @author Brian S O'Neill
+ */
+public class DataDecoder {
+    static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    /**
+     * Decodes a signed integer from exactly 4 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed integer value
+     */
+    public static int decodeInt(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int value = (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) |
+                ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff);
+            return value ^ 0x80000000;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Integer object from exactly 1 or 5 bytes. If null is
+     * returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Integer object or null
+     */
+    public static Integer decodeIntegerObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeInt(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed long from exactly 8 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed long value
+     */
+    public static long decodeLong(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return
+                (((long)(((src[srcOffset    ]       ) << 24) |
+                         ((src[srcOffset + 1] & 0xff) << 16) |
+                         ((src[srcOffset + 2] & 0xff) << 8 ) |
+                         ((src[srcOffset + 3] & 0xff)      )) ^ 0x80000000 ) << 32) |
+                (((long)(((src[srcOffset + 4]       ) << 24) |
+                         ((src[srcOffset + 5] & 0xff) << 16) |
+                         ((src[srcOffset + 6] & 0xff) << 8 ) |
+                         ((src[srcOffset + 7] & 0xff)      )) & 0xffffffffL)      );
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Long object from exactly 1 or 9 bytes. If null is
+     * returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Long object or null
+     */
+    public static Long decodeLongObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeLong(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed byte from exactly 1 byte.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed byte value
+     */
+    public static byte decodeByte(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (byte)(src[srcOffset] ^ 0x80);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Byte object from exactly 1 or 2 bytes. If null is
+     * returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Byte object or null
+     */
+    public static Byte decodeByteObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeByte(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed short from exactly 2 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed short value
+     */
+    public static short decodeShort(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x8000);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Short object from exactly 1 or 3 bytes. If null is
+     * returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Short object or null
+     */
+    public static Short decodeShortObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeShort(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a char from exactly 2 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return char value
+     */
+    public static char decodeChar(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (char)((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff));
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a Character object from exactly 1 or 3 bytes. If null is
+     * returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Character object or null
+     */
+    public static Character decodeCharacterObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeChar(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a boolean from exactly 1 byte.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return boolean value
+     */
+    public static boolean decodeBoolean(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return src[srcOffset] == (byte)128;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a Boolean object from exactly 1 byte.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Boolean object or null
+     */
+    public static Boolean decodeBooleanObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            switch (src[srcOffset]) {
+            case NULL_BYTE_LOW: case NULL_BYTE_HIGH:
+                return null;
+            case (byte)128:
+                return Boolean.TRUE;
+            default:
+                return Boolean.FALSE;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a float from exactly 4 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return float value
+     */
+    public static float decodeFloat(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        int bits = decodeFloatBits(src, srcOffset);
+        bits ^= (bits < 0) ? 0x80000000 : 0xffffffff;
+        return Float.intBitsToFloat(bits);
+    }
+
+    /**
+     * Decodes a Float object from exactly 4 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Float object or null
+     */
+    public static Float decodeFloatObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        int bits = decodeFloatBits(src, srcOffset);
+        bits ^= (bits < 0) ? 0x80000000 : 0xffffffff;
+        return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits);
+    }
+
+    protected static int decodeFloatBits(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) |
+                ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a double from exactly 8 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return double value
+     */
+    public static double decodeDouble(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        long bits = decodeDoubleBits(src, srcOffset);
+        bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL;
+        return Double.longBitsToDouble(bits);
+    }
+
+    /**
+     * Decodes a Double object from exactly 8 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Double object or null
+     */
+    public static Double decodeDoubleObj(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        long bits = decodeDoubleBits(src, srcOffset);
+        bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL;
+        return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits);
+    }
+
+    protected static long decodeDoubleBits(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return
+                (((long)(((src[srcOffset    ]       ) << 24) |
+                         ((src[srcOffset + 1] & 0xff) << 16) |
+                         ((src[srcOffset + 2] & 0xff) << 8 ) |
+                         ((src[srcOffset + 3] & 0xff)      )) ) << 32) |
+                (((long)(((src[srcOffset + 4]       ) << 24) |
+                         ((src[srcOffset + 5] & 0xff) << 16) |
+                         ((src[srcOffset + 6] & 0xff) << 8 ) |
+                         ((src[srcOffset + 7] & 0xff)      )) & 0xffffffffL));
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes the given byte array.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded byte array is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decode(byte[] src, int srcOffset, byte[][] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            final int originalOffset = srcOffset;
+
+            int b = src[srcOffset++] & 0xff;
+            if (b >= 0xf8) {
+                valueRef[0] = null;
+                return 1;
+            }
+
+            int valueLength;
+            if (b <= 0x7f) {
+                valueLength = b;
+            } else if (b <= 0xbf) {
+                valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff);
+            } else if (b <= 0xdf) {
+                valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) |
+                    (src[srcOffset++] & 0xff);
+            } else if (b <= 0xef) {
+                valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) |
+                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
+            } else {
+                valueLength = ((b & 0x07) << 24) | ((src[srcOffset++] & 0xff) << 16) |
+                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
+            }
+
+            if (valueLength == 0) {
+                valueRef[0] = EMPTY_BYTE_ARRAY;
+            } else {
+                byte[] value = new byte[valueLength];
+                System.arraycopy(src, srcOffset, value, 0, valueLength);
+                valueRef[0]= value;
+            }
+
+            return srcOffset - originalOffset + valueLength;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes an encoded string from the given byte array.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded string is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decodeString(byte[] src, int srcOffset, String[] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            final int originalOffset = srcOffset;
+
+            int b = src[srcOffset++] & 0xff;
+            if (b >= 0xf8) {
+                valueRef[0] = null;
+                return 1;
+            }
+
+            int valueLength;
+            if (b <= 0x7f) {
+                valueLength = b;
+            } else if (b <= 0xbf) {
+                valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff);
+            } else if (b <= 0xdf) {
+                valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) |
+                    (src[srcOffset++] & 0xff);
+            } else if (b <= 0xef) {
+                valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) |
+                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
+            } else {
+                valueLength = ((src[srcOffset++] & 0xff) << 24) |
+                    ((src[srcOffset++] & 0xff) << 16) |
+                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
+            }
+
+            if (valueLength == 0) {
+                valueRef[0] = "";
+                return srcOffset - originalOffset;
+            }
+
+            char[] value = new char[valueLength];
+            int valueOffset = 0;
+
+            while (valueOffset < valueLength) {
+                int c = src[srcOffset++] & 0xff;
+                switch (c >> 5) {
+                case 0: case 1: case 2: case 3:
+                    // 0xxxxxxx
+                    value[valueOffset++] = (char)c;
+                    break;
+                case 4: case 5:
+                    // 10xxxxxx xxxxxxxx
+                    value[valueOffset++] = (char)(((c & 0x3f) << 8) | (src[srcOffset++] & 0xff));
+                    break;
+                case 6:
+                    // 110xxxxx xxxxxxxx xxxxxxxx
+                    c = ((c & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8)
+                        | (src[srcOffset++] & 0xff);
+                    if (c >= 0x10000) {
+                        // Split into surrogate pair.
+                        c -= 0x10000;
+                        value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff));
+                        value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff));
+                    } else {
+                        value[valueOffset++] = (char)c;
+                    }
+                    break;
+                default:
+                    // 111xxxxx
+                    // Illegal.
+                    throw new CorruptEncodingException
+                        ("Corrupt encoded string data (source offset = "
+                         + (srcOffset - 1) + ')');
+                }
+            }
+
+            valueRef[0] = new String(value);
+
+            return srcOffset - originalOffset;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * DataEncoder#encodeSingle}.
+     *
+     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
+     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
+     */
+    public static byte[] decodeSingle(byte[] src, int prefixPadding, int suffixPadding)
+        throws CorruptEncodingException
+    {
+        try {
+            int length = src.length - suffixPadding - prefixPadding;
+            if (length == 0) {
+                return EMPTY_BYTE_ARRAY;
+            }
+            if (prefixPadding <= 0 && suffixPadding <= 0) {
+                return src;
+            }
+            byte[] dst = new byte[length];
+            System.arraycopy(src, prefixPadding, dst, 0, length);
+            return dst;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * DataEncoder#encodeSingleNullable}.
+     */
+    public static byte[] decodeSingleNullable(byte[] src) throws CorruptEncodingException {
+        return decodeSingleNullable(src, 0, 0);
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * DataEncoder#encodeSingleNullable}.
+     *
+     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
+     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
+     */
+    public static byte[] decodeSingleNullable(byte[] src, int prefixPadding, int suffixPadding)
+        throws CorruptEncodingException
+    {
+        try {
+            byte b = src[prefixPadding];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            int length = src.length - suffixPadding - 1 - prefixPadding;
+            if (length == 0) {
+                return EMPTY_BYTE_ARRAY;
+            }
+            byte[] value = new byte[length];
+            System.arraycopy(src, 1 + prefixPadding, value, 0, length);
+            return value;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/DataEncoder.java b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java
new file mode 100644
index 0000000..54846ce
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/DataEncoder.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+/**
+ * A very low-level class that supports encoding of primitive data. For
+ * encoding data into keys, see {@link KeyEncoder}.
+ *
+ * @author Brian S O'Neill
+ * @see DataDecoder
+ */
+public class DataEncoder {
+    // Note: Most of these methods are inherited by KeyEncoder, which is why
+    // they are encoded for supporting proper ordering.
+
+    /** Byte to use for null, low ordering */
+    static final byte NULL_BYTE_LOW = 0;
+
+    /** Byte to use for null, high ordering */
+    static final byte NULL_BYTE_HIGH = (byte)~NULL_BYTE_LOW;
+
+    /** Byte to use for not-null, low ordering */
+    static final byte NOT_NULL_BYTE_HIGH = (byte)128;
+
+    /** Byte to use for not-null, high ordering */
+    static final byte NOT_NULL_BYTE_LOW = (byte)~NOT_NULL_BYTE_HIGH;
+
+    static final byte[] NULL_BYTE_ARRAY_HIGH = {NULL_BYTE_HIGH};
+    static final byte[] NULL_BYTE_ARRAY_LOW = {NULL_BYTE_LOW};
+    static final byte[] NOT_NULL_BYTE_ARRAY_HIGH = {NOT_NULL_BYTE_HIGH};
+    static final byte[] NOT_NULL_BYTE_ARRAY_LOW = {NOT_NULL_BYTE_LOW};
+
+    /**
+     * Encodes the given signed integer into exactly 4 bytes.
+     *
+     * @param value signed integer value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(int value, byte[] dst, int dstOffset) {
+        value ^= 0x80000000;
+        dst[dstOffset    ] = (byte)(value >> 24);
+        dst[dstOffset + 1] = (byte)(value >> 16);
+        dst[dstOffset + 2] = (byte)(value >> 8);
+        dst[dstOffset + 3] = (byte)value;
+    }
+
+    /**
+     * Encodes the given signed Integer object into exactly 1 or 5 bytes. If
+     * the Integer object is never expected to be null, consider encoding as an
+     * int primitive.
+     *
+     * @param value optional signed Integer value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(Integer value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
+            encode(value.intValue(), dst, dstOffset + 1);
+            return 5;
+        }
+    }
+
+    /**
+     * Encodes the given signed long into exactly 8 bytes.
+     *
+     * @param value signed long value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(long value, byte[] dst, int dstOffset) {
+        int w = ((int)(value >> 32)) ^ 0x80000000;
+        dst[dstOffset    ] = (byte)(w >> 24);
+        dst[dstOffset + 1] = (byte)(w >> 16);
+        dst[dstOffset + 2] = (byte)(w >> 8);
+        dst[dstOffset + 3] = (byte)w;
+        w = (int)value;
+        dst[dstOffset + 4] = (byte)(w >> 24);
+        dst[dstOffset + 5] = (byte)(w >> 16);
+        dst[dstOffset + 6] = (byte)(w >> 8);
+        dst[dstOffset + 7] = (byte)w;
+    }
+
+    /**
+     * Encodes the given signed Long object into exactly 1 or 9 bytes. If the
+     * Long object is never expected to be null, consider encoding as a long
+     * primitive.
+     *
+     * @param value optional signed Long value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(Long value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
+            encode(value.longValue(), dst, dstOffset + 1);
+            return 9;
+        }
+    }
+
+    /**
+     * Encodes the given signed byte into exactly 1 byte.
+     *
+     * @param value signed byte value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(byte value, byte[] dst, int dstOffset) {
+        dst[dstOffset] = (byte)(value ^ 0x80);
+    }
+
+    /**
+     * Encodes the given signed Byte object into exactly 1 or 2 bytes. If the
+     * Byte object is never expected to be null, consider encoding as a byte
+     * primitive.
+     *
+     * @param value optional signed Byte value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(Byte value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
+            dst[dstOffset + 1] = (byte)(value ^ 0x80);
+            return 2;
+        }
+    }
+
+    /**
+     * Encodes the given signed short into exactly 2 bytes.
+     *
+     * @param value signed short value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(short value, byte[] dst, int dstOffset) {
+        value ^= 0x8000;
+        dst[dstOffset    ] = (byte)(value >> 8);
+        dst[dstOffset + 1] = (byte)value;
+    }
+
+    /**
+     * Encodes the given signed Short object into exactly 1 or 3 bytes. If the
+     * Short object is never expected to be null, consider encoding as a short
+     * primitive.
+     *
+     * @param value optional signed Short value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(Short value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
+            encode(value.shortValue(), dst, dstOffset + 1);
+            return 3;
+        }
+    }
+
+    /**
+     * Encodes the given character into exactly 2 bytes.
+     *
+     * @param value character value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(char value, byte[] dst, int dstOffset) {
+        dst[dstOffset    ] = (byte)(value >> 8);
+        dst[dstOffset + 1] = (byte)value;
+    }
+
+    /**
+     * Encodes the given Character object into exactly 1 or 3 bytes. If the
+     * Character object is never expected to be null, consider encoding as a
+     * char primitive.
+     *
+     * @param value optional Character value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(Character value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
+            encode(value.charValue(), dst, dstOffset + 1);
+            return 3;
+        }
+    }
+
+    /**
+     * Encodes the given boolean into exactly 1 byte.
+     *
+     * @param value boolean value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(boolean value, byte[] dst, int dstOffset) {
+        dst[dstOffset] = value ? (byte)128 : (byte)127;
+    }
+
+    /**
+     * Encodes the given Boolean object into exactly 1 byte.
+     *
+     * @param value optional Boolean value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(Boolean value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+        } else {
+            dst[dstOffset] = value.booleanValue() ? (byte)128 : (byte)127;
+        }
+    }
+
+    /**
+     * Encodes the given float into exactly 4 bytes.
+     *
+     * @param value float value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(float value, byte[] dst, int dstOffset) {
+        int bits = Float.floatToIntBits(value);
+        bits ^= (bits < 0) ? 0xffffffff : 0x80000000;
+        dst[dstOffset    ] = (byte)(bits >> 24);
+        dst[dstOffset + 1] = (byte)(bits >> 16);
+        dst[dstOffset + 2] = (byte)(bits >> 8);
+        dst[dstOffset + 3] = (byte)bits;
+    }
+
+    /**
+     * Encodes the given Float object into exactly 4 bytes. A non-canonical NaN
+     * value is used to represent null.
+     *
+     * @param value optional Float value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(Float value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            encode(0x7fffffff, dst, dstOffset);
+        } else {
+            encode(value.floatValue(), dst, dstOffset);
+        }
+    }
+
+    /**
+     * Encodes the given double into exactly 8 bytes.
+     *
+     * @param value double value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(double value, byte[] dst, int dstOffset) {
+        long bits = Double.doubleToLongBits(value);
+        bits ^= (bits < 0) ? 0xffffffffffffffffL : 0x8000000000000000L;
+        int w = (int)(bits >> 32);
+        dst[dstOffset    ] = (byte)(w >> 24);
+        dst[dstOffset + 1] = (byte)(w >> 16);
+        dst[dstOffset + 2] = (byte)(w >> 8);
+        dst[dstOffset + 3] = (byte)w;
+        w = (int)bits;
+        dst[dstOffset + 4] = (byte)(w >> 24);
+        dst[dstOffset + 5] = (byte)(w >> 16);
+        dst[dstOffset + 6] = (byte)(w >> 8);
+        dst[dstOffset + 7] = (byte)w;
+    }
+
+    /**
+     * Encodes the given Double object into exactly 8 bytes. A non-canonical
+     * NaN value is used to represent null.
+     *
+     * @param value optional Double value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encode(Double value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            encode(0x7fffffffffffffffL, dst, dstOffset);
+        } else {
+            encode(value.doubleValue(), dst, dstOffset);
+        }
+    }
+
+    /**
+     * Encodes the given optional byte array into a variable amount of
+     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
+     * the amount written can be determined by calling calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(byte[] value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        }
+        return encode(value, 0, value.length, dst, dstOffset);
+    }
+
+    /**
+     * Encodes the given optional byte array into a variable amount of
+     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
+     * the amount written can be determined by calling calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param valueOffset offset into byte array
+     * @param valueLength length of data in byte array
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(byte[] value, int valueOffset, int valueLength,
+                             byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        }
+
+        // Write the value length first, in a variable amount of bytes.
+        int amt = writeLength(valueLength, dst, dstOffset);
+
+        // Now write the value.
+        System.arraycopy(value, valueOffset, dst, dstOffset + amt, valueLength);
+
+        return amt + valueLength;
+    }
+
+    /**
+     * Returns the amount of bytes required to encode the given byte array.
+     *
+     * @param value byte array value to encode, may be null
+     * @return amount of bytes needed to encode
+     */
+    public static int calculateEncodedLength(byte[] value) {
+        return value == null ? 1 : calculateEncodedLength(value, 0, value.length);
+    }
+
+    /**
+     * Returns the amount of bytes required to encode the given byte array.
+     *
+     * @param value byte array value to encode, may be null
+     * @param valueOffset offset into byte array
+     * @param valueLength length of data in byte array
+     * @return amount of bytes needed to encode
+     */
+    public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) {
+        if (value == null) {
+            return 1;
+        } else if (valueLength < 128) {
+            return 1 + valueLength;
+        } else if (valueLength < 16384) {
+            return 2 + valueLength;
+        } else if (valueLength < 2097152) {
+            return 3 + valueLength;
+        } else if (valueLength < 268435456) {
+            return 4 + valueLength;
+        } else {
+            return 5 + valueLength;
+        }
+    }
+
+    /**
+     * Encodes the given optional String into a variable amount of bytes. The
+     * amount written can be determined by calling
+     * calculateEncodedStringLength.
+     * <p>
+     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
+     * characters are written in one byte. This encoding is more efficient than
+     * UTF-8, but it isn't compatible with UTF-8.
+     *
+     * @param value String value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(String value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        }
+        final int originalOffset = dstOffset;
+
+        int valueLength = value.length();
+
+        // Write the value length first, in a variable amount of bytes.
+        dstOffset += writeLength(valueLength, dst, dstOffset);
+
+        for (int i = 0; i < valueLength; i++) {
+            int c = value.charAt(i);
+            if (c <= 0x7f) {
+                dst[dstOffset++] = (byte)c;
+            } else if (c <= 0x3fff) {
+                dst[dstOffset++] = (byte)(0x80 | (c >> 8));
+                dst[dstOffset++] = (byte)(c & 0xff);
+            } else {
+                if (c >= 0xd800 && c <= 0xdbff) {
+                    // Found a high surrogate. Verify that surrogate pair is
+                    // well-formed. Low surrogate must follow high surrogate.
+                    if (i + 1 < valueLength) {
+                        int c2 = value.charAt(i + 1);
+                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
+                            c = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff));
+                            i++;
+                        }
+                    }
+                }
+                dst[dstOffset++] = (byte)(0xc0 | (c >> 16));
+                dst[dstOffset++] = (byte)((c >> 8) & 0xff);
+                dst[dstOffset++] = (byte)(c & 0xff);
+            }
+        }
+
+        return dstOffset - originalOffset;
+    }
+
+    /**
+     * Returns the amount of bytes required to encode the given String.
+     *
+     * @param value String to encode, may be null
+     */
+    public static int calculateEncodedStringLength(String value) {
+        if (value == null) {
+            return 1;
+        }
+
+        int valueLength = value.length();
+        int encodedLen;
+
+        if (valueLength < 128) {
+            encodedLen = 1;
+        } else if (valueLength < 16384) {
+            encodedLen = 2;
+        } else if (valueLength < 2097152) {
+            encodedLen = 3;
+        } else if (valueLength < 268435456) {
+            encodedLen = 4;
+        } else {
+            encodedLen = 5;
+        }
+
+        for (int i = 0; i < valueLength; i++) {
+            int c = value.charAt(i);
+            if (c <= 0x7f) {
+                encodedLen++;
+            } else if (c <= 0x3fff) {
+                encodedLen += 2;
+            } else {
+                if (c >= 0xd800 && c <= 0xdbff) {
+                    // Found a high surrogate. Verify that surrogate pair is
+                    // well-formed. Low surrogate must follow high surrogate.
+                    if (i + 1 < valueLength) {
+                        int c2 = value.charAt(i + 1);
+                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
+                            i++;
+                        }
+                    }
+                }
+                encodedLen += 3;
+            }
+        }
+
+        return encodedLen;
+    }
+
+    private static int writeLength(int valueLength, byte[] dst, int dstOffset) {
+        if (valueLength < 128) {
+            dst[dstOffset] = (byte)valueLength;
+            return 1;
+        } else if (valueLength < 16384) {
+            dst[dstOffset++] = (byte)((valueLength >> 8) | 0x80);
+            dst[dstOffset] = (byte)valueLength;
+            return 2;
+        } else if (valueLength < 2097152) {
+            dst[dstOffset++] = (byte)((valueLength >> 16) | 0xc0);
+            dst[dstOffset++] = (byte)(valueLength >> 8);
+            dst[dstOffset] = (byte)valueLength;
+            return 3;
+        } else if (valueLength < 268435456) {
+            dst[dstOffset++] = (byte)((valueLength >> 24) | 0xe0);
+            dst[dstOffset++] = (byte)(valueLength >> 16);
+            dst[dstOffset++] = (byte)(valueLength >> 8);
+            dst[dstOffset] = (byte)valueLength;
+            return 4;
+        } else {
+            dst[dstOffset++] = (byte)0xf0;
+            dst[dstOffset++] = (byte)(valueLength >> 24);
+            dst[dstOffset++] = (byte)(valueLength >> 16);
+            dst[dstOffset++] = (byte)(valueLength >> 8);
+            dst[dstOffset] = (byte)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.
+     *
+     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
+     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
+     */
+    public static byte[] encodeSingle(byte[] value, int prefixPadding, int suffixPadding) {
+        if (prefixPadding <= 0 && suffixPadding <= 0) {
+            return value;
+        }
+        int length = value.length;
+        byte[] dst = new byte[prefixPadding + length + suffixPadding];
+        System.arraycopy(value, 0, dst, prefixPadding, length);
+        return dst;
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * nullable property, whose type is a byte array.
+     */
+    public static byte[] encodeSingleNullable(byte[] value) {
+        return encodeSingleNullable(value, 0, 0);
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * nullable property, whose type is a byte array.
+     *
+     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
+     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
+     */
+    public static byte[] encodeSingleNullable(byte[] value, int prefixPadding, int suffixPadding) {
+        if (prefixPadding <= 0 && suffixPadding <= 0) {
+            if (value == null) {
+                return NULL_BYTE_ARRAY_HIGH;
+            }
+
+            int length = value.length;
+            if (length == 0) {
+                return NOT_NULL_BYTE_ARRAY_HIGH;
+            }
+
+            byte[] dst = new byte[1 + length];
+            dst[0] = NOT_NULL_BYTE_HIGH;
+            System.arraycopy(value, 0, dst, 1, length);
+            return dst;
+        }
+
+        if (value == null) {
+            byte[] dst = new byte[prefixPadding + 1 + suffixPadding];
+            dst[prefixPadding] = NULL_BYTE_HIGH;
+            return dst;
+        }
+
+        int length = value.length;
+        byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding];
+        dst[prefixPadding] = NOT_NULL_BYTE_HIGH;
+        System.arraycopy(value, 0, dst, prefixPadding + 1, length);
+        return dst;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
new file mode 100644
index 0000000..2cd0807
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
@@ -0,0 +1,1963 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.cojen.classfile.CodeAssembler;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.LocalVariable;
+import org.cojen.classfile.Opcode;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.BeanIntrospector;
+import org.cojen.util.BeanProperty;
+
+import com.amazon.carbonado.CorruptEncodingException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.lob.Blob;
+import com.amazon.carbonado.lob.Clob;
+import com.amazon.carbonado.lob.Lob;
+
+import com.amazon.carbonado.spi.StorableGenerator;
+import com.amazon.carbonado.spi.TriggerSupport;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.Direction;
+import com.amazon.carbonado.info.OrderedProperty;
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.info.StorablePropertyAdapter;
+
+/**
+ * Generates bytecode instructions for encoding/decoding Storable properties
+ * to/from raw bytes.
+ *
+ * <p>Note: subclasses must override and specialize the hashCode and equals
+ * methods. Failure to do so interferes with {@link StorableCodecFactory}'s
+ * generated code cache.
+ *
+ * @author Brian S O'Neill
+ */
+public class GenericEncodingStrategy<S extends Storable> {
+    private final Class<S> mType;
+    private final StorableIndex<S> mPkIndex;
+
+    private final int mKeyPrefixPadding;
+    private final int mKeySuffixPadding;
+    private final int mDataPrefixPadding;
+    private final int mDataSuffixPadding;
+
+    /**
+     * @param type type of Storable to generate code for
+     * @param pkIndex specifies sequence and ordering of key properties (optional)
+     */
+    public GenericEncodingStrategy(Class<S> type, StorableIndex<S> pkIndex) {
+        this(type, pkIndex, 0, 0, 0, 0);
+    }
+
+    /**
+     * @param type type of Storable to generate code for
+     * @param pkIndex specifies sequence and ordering of key properties (optional)
+     * @param keyPrefixPadding amount of padding bytes at start of keys
+     * @param keySuffixPadding amount of padding bytes at end of keys
+     * @param dataPrefixPadding amount of padding bytes at start of data values
+     * @param dataSuffixPadding amount of padding bytes at end of data values
+     */
+    @SuppressWarnings("unchecked")
+    public GenericEncodingStrategy(Class<S> type, StorableIndex<S> pkIndex,
+                                   int keyPrefixPadding, int keySuffixPadding,
+                                   int dataPrefixPadding, int dataSuffixPadding) {
+        mType = type;
+
+        if (keyPrefixPadding < 0 || keySuffixPadding < 0 ||
+            dataPrefixPadding < 0 || dataSuffixPadding < 0) {
+            throw new IllegalArgumentException();
+        }
+        mKeyPrefixPadding = keyPrefixPadding;
+        mKeySuffixPadding = keySuffixPadding;
+        mDataPrefixPadding = dataPrefixPadding;
+        mDataSuffixPadding = dataSuffixPadding;
+
+        if (pkIndex == null) {
+            Map<String, ? extends StorableProperty<S>> map =
+                StorableIntrospector.examine(mType).getPrimaryKeyProperties();
+
+            StorableProperty<S>[] properties = new StorableProperty[map.size()];
+            map.values().toArray(properties);
+
+            Direction[] directions = new Direction[map.size()];
+            Arrays.fill(directions, Direction.UNSPECIFIED);
+
+            pkIndex = new StorableIndex<S>(properties, directions, true);
+        }
+
+        mPkIndex = pkIndex;
+    }
+
+    /**
+     * Generates bytecode instructions to encode properties. The encoding is
+     * suitable for "key" encoding, which means it is correctly comparable.
+     *
+     * <p>Note: if a partialStartVar is provided and this strategy has a key
+     * prefix, the prefix is allocated only if the runtime value of
+     * partialStartVar is zero. Likewise, if a partialEndVar is provided and
+     * this strategy has a key suffix, the suffix is allocated only of the
+     * runtime value of partialEndVar is one less than the property count.
+     *
+     * @param assembler code assembler to receive bytecode instructions
+     * @param properties specific properties to encode, defaults to all key
+     * properties if null
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are read from the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @param useReadMethods when true, access properties by public read
+     * methods instead of protected fields - should be used if class being
+     * generated doesn't have access to these fields
+     * @param partialStartVar optional variable for supporting partial key
+     * generation. It must be an int, whose runtime value must be less than the
+     * properties array length. It marks the range start of the partial
+     * property range.
+     * @param partialEndVar optional variable for supporting partial key
+     * generation. It must be an int, whose runtime value must be less than or
+     * equal to the properties array length. It marks the range end (exclusive)
+     * of the partial property range.
+     *
+     * @return local variable referencing a byte array with encoded key
+     *
+     * @throws SupportException if any property type is not supported
+     * @throws IllegalArgumentException if assembler is null, or if instanceVar
+     * is not the correct instance type, or if partial variable types are not
+     * ints
+     */
+    public LocalVariable buildKeyEncoding(CodeAssembler assembler,
+                                          OrderedProperty<S>[] properties,
+                                          LocalVariable instanceVar,
+                                          Class<?> adapterInstanceClass,
+                                          boolean useReadMethods,
+                                          LocalVariable partialStartVar,
+                                          LocalVariable partialEndVar)
+        throws SupportException
+    {
+        properties = ensureKeyProperties(properties);
+        return buildEncoding(true, assembler,
+                             extractProperties(properties), extractDirections(properties),
+                             instanceVar, adapterInstanceClass,
+                             useReadMethods,
+                             -1, // no generation support
+                             partialStartVar, partialEndVar);
+    }
+
+    /**
+     * Generates bytecode instructions to decode properties. 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 key
+     * properties if null
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are placed into the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @param useWriteMethods when true, set properties by public write
+     * methods instead of protected fields - should be used if class being
+     * generated doesn't have access to these fields
+     * @param encodedVar required variable, which must be a byte array. At
+     * runtime, it references an encoded key.
+     *
+     * @throws SupportException if any property type is not supported
+     * @throws IllegalArgumentException if assembler is null, or if instanceVar
+     * is not the correct instance type, or if encodedVar is not a byte array
+     */
+    public void buildKeyDecoding(CodeAssembler assembler,
+                                 OrderedProperty<S>[] properties,
+                                 LocalVariable instanceVar,
+                                 Class<?> adapterInstanceClass,
+                                 boolean useWriteMethods,
+                                 LocalVariable encodedVar)
+        throws SupportException
+    {
+        properties = ensureKeyProperties(properties);
+        buildDecoding(true, assembler,
+                      extractProperties(properties), extractDirections(properties),
+                      instanceVar, adapterInstanceClass, useWriteMethods,
+                      -1, null, // no generation support
+                      encodedVar);
+    }
+
+    /**
+     * Generates bytecode instructions to encode properties. The encoding is
+     * suitable for "data" encoding, which means it is not correctly
+     * comparable, but it is more efficient than key encoding. Partial encoding
+     * is not supported.
+     *
+     * @param assembler code assembler to receive bytecode instructions
+     * @param properties specific properties to encode, defaults to all non-key
+     * properties if null
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are read from the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @param useReadMethods when true, access properties by public read
+     * methods instead of protected fields
+     * @param generation when non-negative, write a storable layout generation
+     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
+     * 128..max is encoded in four bytes, with the most significant bit set.
+     *
+     * @return local variable referencing a byte array with encoded data
+     *
+     * @throws SupportException if any property type is not supported
+     * @throws IllegalArgumentException if assembler is null, or if instanceVar
+     * is not the correct instance type
+     */
+    public LocalVariable buildDataEncoding(CodeAssembler assembler,
+                                           StorableProperty<S>[] properties,
+                                           LocalVariable instanceVar,
+                                           Class<?> adapterInstanceClass,
+                                           boolean useReadMethods,
+                                           int generation)
+        throws SupportException
+    {
+        properties = ensureDataProperties(properties);
+        return buildEncoding(false, assembler,
+                             properties, null,
+                             instanceVar, adapterInstanceClass,
+                             useReadMethods, generation, null, null);
+    }
+
+    /**
+     * Generates bytecode instructions to decode properties. 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 non-key
+     * properties if null
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are placed into the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @param useWriteMethods when true, set properties by public write
+     * methods instead of protected fields - should be used if class being
+     * generated doesn't have access to these fields
+     * @param generation when non-negative, decoder expects a storable layout
+     * generation value to match this value. Otherwise, it throws a
+     * CorruptEncodingException.
+     * @param altGenerationHandler if non-null and a generation is provided,
+     * this label defines an alternate generation handler. It is executed
+     * instead of throwing a CorruptEncodingException if the generation doesn't
+     * match. The actual generation is available on the top of the stack for
+     * the handler to consume.
+     * @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 assembler is null, or if instanceVar
+     * is not the correct instance type, or if encodedVar is not a byte array
+     */
+    public void buildDataDecoding(CodeAssembler assembler,
+                                  StorableProperty<S>[] properties,
+                                  LocalVariable instanceVar,
+                                  Class<?> adapterInstanceClass,
+                                  boolean useWriteMethods,
+                                  int generation,
+                                  Label altGenerationHandler,
+                                  LocalVariable encodedVar)
+        throws SupportException
+    {
+        properties = ensureDataProperties(properties);
+        buildDecoding(false, assembler, properties, null,
+                      instanceVar, adapterInstanceClass, useWriteMethods,
+                      generation, altGenerationHandler, encodedVar);
+    }
+
+    /**
+     * Returns the type of Storable that code is generated for.
+     */
+    public final Class<S> getType() {
+        return mType;
+    }
+
+    /**
+     * Returns true if the type of the given property type is supported. The
+     * types currently supported are primitives, primitive wrapper objects,
+     * Strings, and byte arrays.
+     */
+    public boolean isSupported(Class<?> propertyType) {
+        return isSupported(TypeDesc.forClass(propertyType));
+    }
+
+    /**
+     * Returns true if the type of the given property type is supported. The
+     * types currently supported are primitives, primitive wrapper objects,
+     * Strings, byte arrays and Lobs.
+     */
+    public boolean isSupported(TypeDesc propertyType) {
+        if (propertyType.toPrimitiveType() != null) {
+            return true;
+        }
+        return propertyType == TypeDesc.STRING ||
+            propertyType == TypeDesc.forClass(byte[].class) ||
+            propertyType.toClass() != null && Lob.class.isAssignableFrom(propertyType.toClass());
+    }
+
+    public int getKeyPrefixPadding() {
+        return mKeyPrefixPadding;
+    }
+
+    public int getKeySuffixPadding() {
+        return mKeySuffixPadding;
+    }
+
+    public int getDataPrefixPadding() {
+        return mDataPrefixPadding;
+    }
+
+    public int getDataSuffixPadding() {
+        return mDataSuffixPadding;
+    }
+
+    /**
+     * Returns amount of prefix key bytes that encoding strategy instance
+     * produces which are always the same. Default implementation returns 0.
+     */
+    public int getConstantKeyPrefixLength() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return mType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof GenericEncodingStrategy) {
+            GenericEncodingStrategy other = (GenericEncodingStrategy) obj;
+            return mType == other.mType
+                && mKeyPrefixPadding == other.mKeyPrefixPadding
+                && mKeySuffixPadding == other.mKeySuffixPadding
+                && mDataPrefixPadding == other.mDataPrefixPadding
+                && mDataSuffixPadding == other.mDataSuffixPadding;
+        }
+        return false;
+    }
+
+    /**
+     * Returns all key properties in the form of an index.
+     */
+    protected StorableIndex<S> getPrimaryKeyIndex() {
+        return mPkIndex;
+    }
+
+    /**
+     * Returns all key properties as ordered properties, possibly with
+     * unspecified directions.
+     */
+    protected OrderedProperty<S>[] gatherAllKeyProperties() {
+        return mPkIndex.getOrderedProperties();
+    }
+
+    /**
+     * Returns all data properties for storable.
+     */
+    @SuppressWarnings("unchecked")
+    protected StorableProperty<S>[] gatherAllDataProperties() {
+        Map<String, ? extends StorableProperty<S>> map =
+            StorableIntrospector.examine(mType).getDataProperties();
+
+        StorableProperty<S>[] properties = new StorableProperty[map.size()];
+
+        int ordinal = 0;
+        for (StorableProperty<S> property : map.values()) {
+            properties[ordinal++] = property;
+        }
+
+        return properties;
+    }
+
+    protected StorablePropertyInfo checkSupport(StorableProperty<S> property)
+        throws SupportException
+    {
+        if (isSupported(property.getType())) {
+            return new StorablePropertyInfo(property);
+        }
+
+        // Look for an adapter that will allow this property to be supported.
+        if (property.getAdapter() != null) {
+            StorablePropertyAdapter adapter = property.getAdapter();
+            for (Class<?> storageType : adapter.getStorageTypePreferences()) {
+                if (!isSupported(storageType)) {
+                    continue;
+                }
+
+                if (property.isNullable() && storageType.isPrimitive()) {
+                    continue;
+                }
+
+                Method fromStorage, toStorage;
+                fromStorage = adapter.findAdaptMethod(storageType, property.getType());
+                if (fromStorage == null) {
+                    continue;
+                }
+                toStorage = adapter.findAdaptMethod(property.getType(), storageType);
+                if (toStorage != null) {
+                    return new StorablePropertyInfo(property, storageType, fromStorage, toStorage);
+                }
+            }
+        }
+
+        throw notSupported(property);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected StorablePropertyInfo[] checkSupport(StorableProperty<S>[] properties)
+        throws SupportException
+    {
+        int length = properties.length;
+        StorablePropertyInfo[] infos = new StorablePropertyInfo[length];
+        for (int i=0; i<length; i++) {
+            infos[i] = checkSupport(properties[i]);
+        }
+        return infos;
+    }
+
+    private SupportException notSupported(StorableProperty<S> property) {
+        return notSupported(property.getName(), property.getType().getName());
+    }
+
+    private SupportException notSupported(String propertyName, String typeName) {
+        return new SupportException
+            ("Type \"" + typeName +
+             "\" not supported for property \"" + propertyName + '"');
+    }
+
+    private OrderedProperty<S>[] ensureKeyProperties(OrderedProperty<S>[] properties) {
+        if (properties == null) {
+            properties = gatherAllKeyProperties();
+        } else {
+            for (Object prop : properties) {
+                if (prop == null) {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+        return properties;
+    }
+
+    @SuppressWarnings("unchecked")
+    private StorableProperty<S>[] extractProperties(OrderedProperty<S>[] ordered) {
+        StorableProperty<S>[] properties = new StorableProperty[ordered.length];
+        for (int i=0; i<ordered.length; i++) {
+            ChainedProperty chained = ordered[i].getChainedProperty();
+            if (chained.getChainCount() > 0) {
+                throw new IllegalArgumentException();
+            }
+            properties[i] = chained.getPrimeProperty();
+        }
+        return properties;
+    }
+
+    private Direction[] extractDirections(OrderedProperty<S>[] ordered) {
+        Direction[] directions = new Direction[ordered.length];
+        for (int i=0; i<ordered.length; i++) {
+            directions[i] = ordered[i].getDirection();
+        }
+        return directions;
+    }
+
+    private StorableProperty<S>[] ensureDataProperties(StorableProperty<S>[] properties) {
+        if (properties == null) {
+            properties = gatherAllDataProperties();
+        } else {
+            for (Object prop : properties) {
+                if (prop == null) {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+        return properties;
+    }
+
+    /////////////////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////////
+
+    private LocalVariable buildEncoding(boolean forKey,
+                                        CodeAssembler a,
+                                        StorableProperty<S>[] properties,
+                                        Direction[] directions,
+                                        LocalVariable instanceVar,
+                                        Class<?> adapterInstanceClass,
+                                        boolean useReadMethods,
+                                        int generation,
+                                        LocalVariable partialStartVar,
+                                        LocalVariable partialEndVar)
+        throws SupportException
+    {
+        if (a == null) {
+            throw new IllegalArgumentException();
+        }
+        if (partialStartVar != null && partialStartVar.getType() != TypeDesc.INT) {
+            throw new IllegalArgumentException();
+        }
+        if (partialEndVar != null && partialEndVar.getType() != TypeDesc.INT) {
+            throw new IllegalArgumentException();
+        }
+
+        // Encoding order is:
+        //
+        // 1. Prefix
+        // 2. Generation prefix
+        // 3. Properties
+        // 4. Suffix
+
+        final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
+
+        final int generationPrefix;
+        if (generation < 0) {
+            generationPrefix = 0;
+        } else if (generation < 128) {
+            generationPrefix = 1;
+        } else {
+            generationPrefix = 4;
+        }
+
+        final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
+
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+        final LocalVariable encodedVar = a.createLocalVariable(null, byteArrayType);
+
+        StorablePropertyInfo[] infos = checkSupport(properties);
+
+        if (properties.length == 1) {
+            // Ignore partial key encoding variables, since there can't be a
+            // partial of one property.
+            partialStartVar = null;
+            partialEndVar = null;
+
+            StorableProperty<S> property = properties[0];
+            StorablePropertyInfo info = infos[0];
+
+            if (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
+                // identify a null reference.
+
+                loadPropertyValue(a, info, 0, useReadMethods,
+                                  instanceVar, adapterInstanceClass, partialStartVar);
+
+                boolean descending =
+                    forKey && directions != null && directions[0] == Direction.DESCENDING;
+
+                TypeDesc[] params;
+                if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
+                    a.loadConstant(prefix + generationPrefix);
+                    a.loadConstant(suffix);
+                    params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT};
+                } else {
+                    params = new TypeDesc[] {byteArrayType};
+                }
+
+                if (property.isNullable()) {
+                    if (descending) {
+                        a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleNullableDesc",
+                                       byteArrayType, params);
+                    } else {
+                        a.invokeStatic(DataEncoder.class.getName(), "encodeSingleNullable",
+                                       byteArrayType, params);
+                    }
+                } else if (descending) {
+                    a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleDesc",
+                                   byteArrayType, params);
+                } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
+                    a.invokeStatic(DataEncoder.class.getName(), "encodeSingle",
+                                   byteArrayType, params);
+                } else {
+                    // Just return raw property value - no need to cache it either.
+                }
+
+                a.storeLocal(encodedVar);
+
+                encodeGeneration(a, encodedVar, prefix, generation);
+
+                return encodedVar;
+            }
+        }
+
+        boolean doPartial = forKey && (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) {
+            // Only include prefix as static if no runtime check is needed
+            // against runtime partial start value.
+            staticLength += prefix + generationPrefix;
+        }
+        if (!forKey || partialEndVar == null) {
+            // Only include suffix as static if no runtime check is needed
+            // against runtime partial end value.
+            staticLength += suffix;
+        }
+
+        boolean hasVariableLength;
+        if (doPartial) {
+            hasVariableLength = true;
+        } else {
+            hasVariableLength = false;
+            for (GenericPropertyInfo info : infos) {
+                int len = staticEncodingLength(info);
+                if (len >= 0) {
+                    staticLength += len;
+                } else {
+                    staticLength += ~len;
+                    hasVariableLength = true;
+                }
+            }
+        }
+
+        // Generate code that loops over all the properties that have a
+        // variable length. Load each property and perform the necessary
+        // tests to determine the exact encoding length.
+
+        boolean hasStackVar = false;
+        if (hasVariableLength) {
+            Label[] entryPoints = null;
+
+            if (partialStartVar != null) {
+                // Will jump into an arbitrary location, so always have a stack
+                // variable available.
+                a.loadConstant(0);
+                hasStackVar = true;
+
+                entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
+            }
+
+            Label exitPoint = a.createLabel();
+
+            for (int i=0; i<properties.length; i++) {
+                StorableProperty<S> property = properties[i];
+                StorablePropertyInfo info = infos[i];
+
+                if (doPartial) {
+                    if (entryPoints != null) {
+                        entryPoints[i].setLocation();
+                    }
+                    if (partialEndVar != null) {
+                        // Add code to jump out of partial.
+                        a.loadConstant(i);
+                        a.loadLocal(partialEndVar);
+                        a.ifComparisonBranch(exitPoint, ">=");
+                    }
+                } else if (staticEncodingLength(info) >= 0) {
+                    continue;
+                }
+
+                TypeDesc propType = info.getStorageType();
+
+                if (propType.isPrimitive()) {
+                    // This should only ever get executed if implementing
+                    // partial support. Otherwise, the static encoding length
+                    // would have been already calculated.
+                    a.loadConstant(staticEncodingLength(info));
+                    if (hasStackVar) {
+                        a.math(Opcode.IADD);
+                    } else {
+                        hasStackVar = true;
+                    }
+                } else if (propType.toPrimitiveType() != null) {
+                    int amt = 0;
+                    switch (propType.toPrimitiveType().getTypeCode()) {
+                    case TypeDesc.BYTE_CODE:
+                    case TypeDesc.BOOLEAN_CODE:
+                        amt = 1;
+                        break;
+                    case TypeDesc.SHORT_CODE:
+                    case TypeDesc.CHAR_CODE:
+                        amt = 2;
+                        break;
+                    case TypeDesc.INT_CODE:
+                    case TypeDesc.FLOAT_CODE:
+                        amt = 4;
+                        break;
+                    case TypeDesc.LONG_CODE:
+                    case TypeDesc.DOUBLE_CODE:
+                        amt = 8;
+                        break;
+                    }
+
+                    int extra = 0;
+                    if (doPartial) {
+                        // If value is null, then there may be a one byte size
+                        // adjust for the null value. Otherwise it is the extra
+                        // amount plus the size to encode the raw primitive
+                        // value. If doPartial is false, then this extra amount
+                        // was already accounted for in the static encoding
+                        // length.
+
+                        switch (propType.toPrimitiveType().getTypeCode()) {
+                        case TypeDesc.BYTE_CODE:
+                        case TypeDesc.SHORT_CODE:
+                        case TypeDesc.CHAR_CODE:
+                        case TypeDesc.INT_CODE:
+                        case TypeDesc.LONG_CODE:
+                            extra = 1;
+                        }
+                    }
+
+                    if (!property.isNullable() || (doPartial && extra == 0)) {
+                        a.loadConstant(amt);
+                        if (hasStackVar) {
+                            a.math(Opcode.IADD);
+                        }
+                        hasStackVar = true;
+                    } else {
+                        // Load property to test for null.
+                        loadPropertyValue(a, info, i, useReadMethods,
+                                          instanceVar, adapterInstanceClass, partialStartVar);
+
+                        Label isNull = a.createLabel();
+                        a.ifNullBranch(isNull, true);
+
+                        a.loadConstant(amt);
+
+                        if (hasStackVar) {
+                            a.math(Opcode.IADD);
+                            isNull.setLocation();
+                            if (extra > 0) {
+                                a.loadConstant(extra);
+                                a.math(Opcode.IADD);
+                            }
+                        } else {
+                            hasStackVar = true;
+                            // Make sure that there is a zero (or extra) value on
+                            // the stack if the isNull branch is taken.
+                            Label notNull = a.createLabel();
+                            a.branch(notNull);
+                            isNull.setLocation();
+                            a.loadConstant(extra);
+                            notNull.setLocation();
+                        }
+                    }
+                } else if (propType == TypeDesc.STRING) {
+                    // Load property to test for null.
+                    loadPropertyValue(a, info, i, useReadMethods,
+                                      instanceVar, adapterInstanceClass, partialStartVar);
+
+                    String className =
+                        (forKey ? KeyEncoder.class : DataEncoder.class).getName();
+                    a.invokeStatic(className, "calculateEncodedStringLength",
+                                   TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING});
+                    if (hasStackVar) {
+                        a.math(Opcode.IADD);
+                    } else {
+                        hasStackVar = true;
+                    }
+                } else if (propType.toClass() == byte[].class) {
+                    // Load property to test for null.
+                    loadPropertyValue(a, info, i, useReadMethods,
+                                      instanceVar, adapterInstanceClass, partialStartVar);
+
+                    String className =
+                        (forKey ? KeyEncoder.class : DataEncoder.class).getName();
+                    a.invokeStatic(className, "calculateEncodedLength",
+                                   TypeDesc.INT, new TypeDesc[] {byteArrayType});
+                    if (hasStackVar) {
+                        a.math(Opcode.IADD);
+                    } else {
+                        hasStackVar = true;
+                    }
+                } else if (info.isLob()) {
+                    // Lob locator is a long, or 8 bytes.
+                    a.loadConstant(8);
+                    if (hasStackVar) {
+                        a.math(Opcode.IADD);
+                    } else {
+                        hasStackVar = true;
+                    }
+                } else {
+                    throw notSupported(property);
+                }
+            }
+
+            exitPoint.setLocation();
+
+            if (forKey && partialStartVar != null && (prefix > 0 || generationPrefix > 0)) {
+                // Prefix must be allocated only if runtime value of
+                // partialStartVar is zero.
+                a.loadLocal(partialStartVar);
+                Label noPrefix = a.createLabel();
+                a.ifZeroComparisonBranch(noPrefix, "!=");
+                a.loadConstant(prefix + generationPrefix);
+                if (hasStackVar) {
+                    a.math(Opcode.IADD);
+                } else {
+                    hasStackVar = true;
+                }
+                noPrefix.setLocation();
+            }
+
+            if (forKey && partialEndVar != null && suffix > 0) {
+                // Suffix must be allocated only if runtime value of
+                // partialEndVar is equal to property count.
+                a.loadLocal(partialEndVar);
+                Label noSuffix = a.createLabel();
+                a.loadConstant(properties.length);
+                a.ifComparisonBranch(noSuffix, "!=");
+                a.loadConstant(suffix);
+                if (hasStackVar) {
+                    a.math(Opcode.IADD);
+                } else {
+                    hasStackVar = true;
+                }
+                noSuffix.setLocation();
+            }
+        }
+
+        // Allocate a byte array of the exact size.
+        if (hasStackVar) {
+            if (staticLength > 0) {
+                a.loadConstant(staticLength);
+                a.math(Opcode.IADD);
+            }
+        } else {
+            a.loadConstant(staticLength);
+        }
+        a.newObject(byteArrayType);
+        a.storeLocal(encodedVar);
+
+        // Now encode into the byte array.
+
+        int constantOffset = 0;
+        LocalVariable offset = null;
+
+        if (!forKey || 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);
+        }
+
+        Label[] entryPoints = null;
+
+        if (forKey && 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);
+            if (prefix > 0) {
+                // Prefix is allocated only if partial start is zero. Check if
+                // offset should be adjusted to skip over it.
+                a.loadLocal(partialStartVar);
+                Label noPrefix = a.createLabel();
+                a.ifZeroComparisonBranch(noPrefix, "!=");
+                a.loadConstant(prefix + generationPrefix);
+                a.math(Opcode.IADD);
+                encodeGeneration(a, encodedVar, prefix, generation);
+                noPrefix.setLocation();
+            }
+            a.storeLocal(offset);
+
+            entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
+        }
+
+        Label exitPoint = a.createLabel();
+
+        for (int i=0; i<properties.length; i++) {
+            StorableProperty<S> property = properties[i];
+            StorablePropertyInfo info = infos[i];
+
+            if (doPartial) {
+                if (entryPoints != null) {
+                    entryPoints[i].setLocation();
+                }
+                if (partialEndVar != null) {
+                    // Add code to jump out of partial.
+                    a.loadConstant(i);
+                    a.loadLocal(partialEndVar);
+                    a.ifComparisonBranch(exitPoint, ">=");
+                }
+            }
+
+            if (info.isLob()) {
+                // Need RawSupport instance for getting locator from Lob.
+                pushRawSupport(a, instanceVar);
+            }
+
+            boolean fromInstance = loadPropertyValue
+                (a, info, i, useReadMethods, instanceVar, adapterInstanceClass, partialStartVar);
+
+            TypeDesc propType = info.getStorageType();
+            if (!property.isNullable() && propType.toPrimitiveType() != null) {
+                // Since property type is a required primitive wrapper, convert
+                // to a primitive rather than encoding using the form that
+                // distinguishes null.
+
+                // Property value that was passed in may be null, which is not
+                // allowed.
+                if (!fromInstance && !propType.isPrimitive()) {
+                    a.dup();
+                    Label notNull = a.createLabel();
+                    a.ifNullBranch(notNull, false);
+
+                    TypeDesc errorType = TypeDesc.forClass(IllegalArgumentException.class);
+                    a.newObject(errorType);
+                    a.dup();
+                    a.loadConstant("Value for property \"" + property.getName() +
+                                   "\" cannot be null");
+                    a.invokeConstructor(errorType, new TypeDesc[] {TypeDesc.STRING});
+                    a.throwObject();
+
+                    notNull.setLocation();
+                }
+
+                a.convert(propType, propType.toPrimitiveType());
+                propType = propType.toPrimitiveType();
+            }
+
+            if (info.isLob()) {
+                // Extract locator from RawSupport.
+                getLobLocator(a, info);
+
+                // Locator is a long, so switch the type to be encoded properly.
+                propType = TypeDesc.LONG;
+            }
+
+            // Fill out remaining parameters before calling specific method
+            // to encode property value.
+            a.loadLocal(encodedVar);
+            if (offset == null) {
+                a.loadConstant(constantOffset);
+            } else {
+                a.loadLocal(offset);
+            }
+
+            boolean descending =
+                forKey && directions != null && directions[i] == Direction.DESCENDING;
+
+            int amt = encodeProperty(a, propType, forKey, descending);
+
+            if (amt > 0) {
+                if (i + 1 < properties.length) {
+                    // Only adjust offset if there are more properties.
+
+                    if (offset == null) {
+                        constantOffset += amt;
+                    } else {
+                        a.loadConstant(amt);
+                        a.loadLocal(offset);
+                        a.math(Opcode.IADD);
+                        a.storeLocal(offset);
+                    }
+                }
+            } else {
+                if (i + 1 >= properties.length) {
+                    // Don't need to keep track of offset anymore.
+                    a.pop();
+                } else {
+                    // Only adjust offset if there are more properties.
+                    if (offset == null) {
+                        if (constantOffset > 0) {
+                            a.loadConstant(constantOffset);
+                            a.math(Opcode.IADD);
+                        }
+                        offset = a.createLocalVariable(null, TypeDesc.INT);
+                    } else {
+                        a.loadLocal(offset);
+                        a.math(Opcode.IADD);
+                    }
+                    a.storeLocal(offset);
+                }
+            }
+        }
+
+        exitPoint.setLocation();
+
+        return encodedVar;
+    }
+
+    /**
+     * Generates code to load a property value onto the operand stack.
+     *
+     * @param info info for property to load
+     * @param ordinal zero-based property ordinal, used only if instanceVar
+     * refers to an object array.
+     * @param useReadMethod when true, access property by public read method
+     * instead of protected field
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are read from the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @param partialStartVar optional variable for supporting partial key
+     * generation. It must be an int, whose runtime value must be less than the
+     * properties array length. It marks the range start of the partial
+     * property range.
+     * @return true if property was loaded from instance, false if loaded from
+     * value array
+     */
+    protected boolean loadPropertyValue(CodeAssembler a,
+                                        StorablePropertyInfo info, int ordinal,
+                                        boolean useReadMethod,
+                                        LocalVariable instanceVar,
+                                        Class<?> adapterInstanceClass,
+                                        LocalVariable partialStartVar)
+    {
+        TypeDesc type = info.getPropertyType();
+        TypeDesc storageType = info.getStorageType();
+
+        boolean isObjectArrayInstanceVar = instanceVar != null
+            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
+
+        boolean useAdapterInstance = adapterInstanceClass != null
+            && info.getToStorageAdapter() != null
+            && (useReadMethod || isObjectArrayInstanceVar);
+
+        if (useAdapterInstance) {
+            // Push adapter instance to stack to be used later.
+            String fieldName =
+                info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
+            TypeDesc adapterType = TypeDesc.forClass
+                (info.getToStorageAdapter().getDeclaringClass());
+            a.loadStaticField
+                (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType);
+        }
+
+        if (instanceVar == null) {
+            a.loadThis();
+            if (useReadMethod) {
+                info.addInvokeReadMethod(a);
+            } else {
+                // Access property value directly from protected field of "this".
+                if (info.getToStorageAdapter() == null) {
+                    a.loadField(info.getPropertyName(), type);
+                } else {
+                    // Invoke adapter method.
+                    a.invokeVirtual(info.getReadMethodName() + '$', storageType, null);
+                }
+            }
+        } else if (!isObjectArrayInstanceVar) {
+            a.loadLocal(instanceVar);
+            if (useReadMethod) {
+                info.addInvokeReadMethod(a, instanceVar.getType());
+            } else {
+                // Access property value directly from protected field of
+                // referenced instance. Assumes code is being defined in the
+                // same package or a subclass.
+                if (info.getToStorageAdapter() == null) {
+                    a.loadField(instanceVar.getType(), info.getPropertyName(), type);
+                } else {
+                    // Invoke adapter method.
+                    a.invokeVirtual(instanceVar.getType(),
+                                    info.getReadMethodName() + '$', storageType, null);
+                }
+            }
+        } else {
+            // Access property value from object array.
+
+            a.loadLocal(instanceVar);
+            a.loadConstant(ordinal);
+            if (ordinal > 0 && partialStartVar != null) {
+                a.loadLocal(partialStartVar);
+                a.math(Opcode.ISUB);
+            }
+
+            a.loadFromArray(TypeDesc.OBJECT);
+            a.checkCast(type.toObjectType());
+            if (type.isPrimitive()) {
+                a.convert(type.toObjectType(), type);
+            }
+        }
+
+        if (useAdapterInstance) {
+            // Invoke adapter method on instance pushed earlier.
+            a.invoke(info.getToStorageAdapter());
+        }
+
+        return !isObjectArrayInstanceVar;
+    }
+
+    /**
+     * 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
+     * returned more than zero.
+     */
+    private int staticEncodingLength(GenericPropertyInfo info) {
+        TypeDesc type = info.getStorageType();
+        TypeDesc primType = type.toPrimitiveType();
+
+        if (primType == null) {
+            if (info.isLob()) {
+                // Lob locator is stored as a long.
+                return 8;
+            }
+        } else {
+            if (info.isNullable()) {
+                // Type is a primitive wrapper.
+                switch (primType.getTypeCode()) {
+                case TypeDesc.BYTE_CODE:
+                    return ~1;
+                case TypeDesc.BOOLEAN_CODE:
+                    return 1;
+                case TypeDesc.SHORT_CODE:
+                case TypeDesc.CHAR_CODE:
+                    return ~1;
+                case TypeDesc.INT_CODE:
+                    return ~1;
+                case TypeDesc.FLOAT_CODE:
+                    return 4;
+                case TypeDesc.LONG_CODE:
+                    return ~1;
+                case TypeDesc.DOUBLE_CODE:
+                    return 8;
+                }
+            } else {
+                // Type is primitive or a required primitive wrapper.
+                switch (type.getTypeCode()) {
+                case TypeDesc.BYTE_CODE:
+                case TypeDesc.BOOLEAN_CODE:
+                    return 1;
+                case TypeDesc.SHORT_CODE:
+                case TypeDesc.CHAR_CODE:
+                    return 2;
+                case TypeDesc.INT_CODE:
+                case TypeDesc.FLOAT_CODE:
+                    return 4;
+                case TypeDesc.LONG_CODE:
+                case TypeDesc.DOUBLE_CODE:
+                    return 8;
+                }
+            }
+        }
+
+        return ~0;
+    }
+
+    /**
+     * @param partialStartVar must not be null
+     */
+    private Label[] jumpToPartialEntryPoints(CodeAssembler a, LocalVariable partialStartVar,
+                                             int propertyCount) {
+        // Create all the entry points for offset var, whose locations will be
+        // set later.
+        int[] cases = new int[propertyCount];
+        Label[] entryPoints = new Label[propertyCount];
+        for (int i=0; i<propertyCount; i++) {
+            cases[i] = i;
+            entryPoints[i] = a.createLabel();
+        }
+
+        // Now jump in!
+        Label errorLoc = a.createLabel();
+        a.loadLocal(partialStartVar);
+        a.switchBranch(cases, entryPoints, errorLoc);
+
+        errorLoc.setLocation();
+        TypeDesc errorType = TypeDesc.forClass(IllegalArgumentException.class);
+        a.newObject(errorType);
+        a.dup();
+        a.loadConstant("Illegal partial start offset");
+        a.invokeConstructor(errorType, new TypeDesc[] {TypeDesc.STRING});
+        a.throwObject();
+
+        return entryPoints;
+    }
+
+    /**
+     * Generates code that calls an encoding method in DataEncoder or
+     * KeyEncoder. Parameters must already be on the stack.
+     *
+     * @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) {
+        TypeDesc[] params = new TypeDesc[] {
+            type, TypeDesc.forClass(byte[].class), TypeDesc.INT
+        };
+
+        if (type.isPrimitive()) {
+            if (forKey && descending) {
+                a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", null, params);
+            } else {
+                a.invokeStatic(DataEncoder.class.getName(), "encode", null, params);
+            }
+
+            switch (type.getTypeCode()) {
+            case TypeDesc.BYTE_CODE:
+            case TypeDesc.BOOLEAN_CODE:
+                return 1;
+            case TypeDesc.SHORT_CODE:
+            case TypeDesc.CHAR_CODE:
+                return 2;
+            default:
+            case TypeDesc.INT_CODE:
+            case TypeDesc.FLOAT_CODE:
+                return 4;
+            case TypeDesc.LONG_CODE:
+            case TypeDesc.DOUBLE_CODE:
+                return 8;
+            }
+        } else if (type.toPrimitiveType() != null) {
+            // Type is a primitive wrapper.
+
+            int adjust;
+            TypeDesc retType;
+
+            switch (type.toPrimitiveType().getTypeCode()) {
+            case TypeDesc.BOOLEAN_CODE:
+                adjust = 1;
+                retType = null;
+                break;
+            case TypeDesc.FLOAT_CODE:
+                adjust = 4;
+                retType = null;
+                break;
+            case TypeDesc.DOUBLE_CODE:
+                adjust = 8;
+                retType = null;
+                break;
+            default:
+                adjust = 0;
+                retType = TypeDesc.INT;
+            }
+
+            if (forKey && descending) {
+                a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", retType, params);
+            } else {
+                a.invokeStatic(DataEncoder.class.getName(), "encode", retType, params);
+            }
+
+            return adjust;
+        } else {
+            // Type is a String or byte array.
+            if (forKey) {
+                if (descending) {
+                    a.invokeStatic
+                        (KeyEncoder.class.getName(), "encodeDesc", TypeDesc.INT, params);
+                } else {
+                    a.invokeStatic(KeyEncoder.class.getName(), "encode", TypeDesc.INT, params);
+                }
+            } else {
+                a.invokeStatic(DataEncoder.class.getName(), "encode", TypeDesc.INT, params);
+            }
+            return 0;
+        }
+    }
+
+    /**
+     * Generates code that stores a one or four byte generation value into a
+     * byte array referenced by the local variable.
+     *
+     * @param generation if less than zero, no code is generated
+     */
+    private void encodeGeneration(CodeAssembler a, LocalVariable encodedVar,
+                                  int offset, int generation)
+    {
+        if (offset < 0) {
+            throw new IllegalArgumentException();
+        }
+        if (generation < 0) {
+            return;
+        }
+        if (generation < 128) {
+            a.loadLocal(encodedVar);
+            a.loadConstant(offset);
+            a.loadConstant((byte) generation);
+            a.storeToArray(TypeDesc.BYTE);
+        } else {
+            generation |= 0x80000000;
+            for (int i=0; i<4; i++) {
+                a.loadLocal(encodedVar);
+                a.loadConstant(offset + i);
+                a.loadConstant((byte) (generation >> (8 * (3 - i))));
+                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.
+     */
+    private void pushRawSupport(CodeAssembler a, LocalVariable instanceVar)
+        throws SupportException
+    {
+        boolean isObjectArrayInstanceVar = instanceVar != null
+            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
+
+        if (isObjectArrayInstanceVar) {
+            throw new SupportException("Lob properties not supported");
+        }
+
+        if (instanceVar == null) {
+            a.loadThis();
+        } else {
+            a.loadLocal(instanceVar);
+        }
+
+        a.loadField(StorableGenerator.SUPPORT_FIELD_NAME,
+                    TypeDesc.forClass(TriggerSupport.class));
+        a.checkCast(TypeDesc.forClass(RawSupport.class));
+    }
+
+    /**
+     * Generates code to get a Lob locator value from RawSupport. RawSupport
+     * instance and Lob instance must be on the stack. Result is a long locator
+     * value on the stack.
+     */
+    private void getLobLocator(CodeAssembler a, StorablePropertyInfo info) {
+        if (!info.isLob()) {
+            throw new IllegalArgumentException();
+        }
+        a.invokeInterface(TypeDesc.forClass(RawSupport.class), "getLocator",
+                          TypeDesc.LONG, new TypeDesc[] {info.getStorageType()});
+    }
+
+    /**
+     * Generates code to get a Lob from a locator from RawSupport. RawSupport
+     * instance and long locator must be on the stack. Result is a Lob on the
+     * stack, which may be null.
+     */
+    private void getLobFromLocator(CodeAssembler a, StorablePropertyInfo info) {
+        if (!info.isLob()) {
+            throw new IllegalArgumentException();
+        }
+
+        TypeDesc type = info.getStorageType();
+        String name;
+        if (Blob.class.isAssignableFrom(type.toClass())) {
+            name = "getBlob";
+        } else if (Clob.class.isAssignableFrom(type.toClass())) {
+            name = "getClob";
+        } else {
+            throw new IllegalArgumentException();
+        }
+
+        a.invokeInterface(TypeDesc.forClass(RawSupport.class), name,
+                          type, new TypeDesc[] {TypeDesc.LONG});
+    }
+
+    /////////////////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////////
+
+    private void buildDecoding(boolean forKey,
+                               CodeAssembler a,
+                               StorableProperty<S>[] properties,
+                               Direction[] directions,
+                               LocalVariable instanceVar,
+                               Class<?> adapterInstanceClass,
+                               boolean useWriteMethods,
+                               int generation,
+                               Label altGenerationHandler,
+                               LocalVariable encodedVar)
+        throws SupportException
+    {
+        if (a == null) {
+            throw new IllegalArgumentException();
+        }
+        if (encodedVar == null || encodedVar.getType() != TypeDesc.forClass(byte[].class)) {
+            throw new IllegalArgumentException();
+        }
+
+        // Decoding order is:
+        //
+        // 1. Prefix
+        // 2. Generation prefix
+        // 3. Properties
+        // 4. Suffix
+
+        final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
+
+        final int generationPrefix;
+        if (generation < 0) {
+            generationPrefix = 0;
+        } else if (generation < 128) {
+            generationPrefix = 1;
+        } else {
+            generationPrefix = 4;
+        }
+
+        final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
+
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+
+        StorablePropertyInfo[] infos = checkSupport(properties);
+
+        decodeGeneration(a, encodedVar, prefix, generation, altGenerationHandler);
+
+        if (properties.length == 1) {
+            StorableProperty<S> property = properties[0];
+            StorablePropertyInfo info = infos[0];
+
+            if (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.
+
+                // Push to stack in preparation for storing a property.
+                pushDecodingInstanceVar(a, 0, instanceVar);
+
+                a.loadLocal(encodedVar);
+
+                boolean descending =
+                    forKey && directions != null && directions[0] == Direction.DESCENDING;
+
+                TypeDesc[] params;
+                if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
+                    a.loadConstant(prefix + generationPrefix);
+                    a.loadConstant(suffix);
+                    params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT};
+                } else {
+                    params = new TypeDesc[] {byteArrayType};
+                }
+
+                if (property.isNullable()) {
+                    if (descending) {
+                        a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleNullableDesc",
+                                       byteArrayType, params);
+                    } else {
+                        a.invokeStatic(DataDecoder.class.getName(), "decodeSingleNullable",
+                                       byteArrayType, params);
+                    }
+                } else if (descending) {
+                    a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleDesc",
+                                   byteArrayType, params);
+                } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
+                    a.invokeStatic(DataDecoder.class.getName(), "decodeSingle",
+                                   byteArrayType, params);
+                } else {
+                    // Just store raw property value.
+                }
+
+                storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
+                return;
+            }
+        }
+
+        // Now decode from the byte array.
+
+        int constantOffset = prefix + generationPrefix;
+        LocalVariable offset = null;
+        // References to local variables which will hold references.
+        LocalVariable[] stringRef = new LocalVariable[1];
+        LocalVariable[] byteArrayRef = new LocalVariable[1];
+        LocalVariable[] valueRefRef = new LocalVariable[1];
+
+        for (int i=0; i<infos.length; i++) {
+            StorablePropertyInfo info = infos[i];
+
+            // Push to stack in preparation for storing a property.
+            pushDecodingInstanceVar(a, i, instanceVar);
+
+            TypeDesc storageType = info.getStorageType();
+
+            if (info.isLob()) {
+                // Need RawSupport instance for getting Lob from locator.
+                pushRawSupport(a, instanceVar);
+                // Locator is encoded as a long.
+                storageType = TypeDesc.LONG;
+            }
+
+            a.loadLocal(encodedVar);
+            if (offset == null) {
+                a.loadConstant(constantOffset);
+            } else {
+                a.loadLocal(offset);
+            }
+
+            boolean descending =
+                forKey && directions != null && directions[i] == Direction.DESCENDING;
+
+            int amt = decodeProperty(a, info, storageType, forKey, descending,
+                                     stringRef, byteArrayRef, valueRefRef);
+
+            if (info.isLob()) {
+                getLobFromLocator(a, info);
+            }
+
+            if (amt != 0) {
+                if (i + 1 < properties.length) {
+                    // Only adjust offset if there are more properties.
+
+                    if (amt > 0) {
+                        if (offset == null) {
+                            constantOffset += amt;
+                        } else {
+                            a.loadConstant(amt);
+                            a.loadLocal(offset);
+                            a.math(Opcode.IADD);
+                            a.storeLocal(offset);
+                        }
+                    } 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));
+                        Label cont = a.createLabel();
+                        a.branch(cont);
+                        notNull.setLocation();
+                        a.loadConstant(~amt + (offset == null ? constantOffset : 0));
+                        cont.setLocation();
+
+                        if (offset == null) {
+                            offset = a.createLocalVariable(null, TypeDesc.INT);
+                        } else {
+                            a.loadLocal(offset);
+                            a.math(Opcode.IADD);
+                        }
+                        a.storeLocal(offset);
+                    }
+                }
+            } else {
+                if (i + 1 >= properties.length) {
+                    // Don't need to keep track of offset anymore.
+                    a.pop();
+                } else {
+                    // Only adjust offset if there are more properties.
+                    if (offset == null) {
+                        if (constantOffset > 0) {
+                            a.loadConstant(constantOffset);
+                            a.math(Opcode.IADD);
+                        }
+                        offset = a.createLocalVariable(null, TypeDesc.INT);
+                    } else {
+                        a.loadLocal(offset);
+                        a.math(Opcode.IADD);
+                    }
+                    a.storeLocal(offset);
+                }
+
+                // Get the value out of the ref array so that it can be stored.
+                a.loadLocal(valueRefRef[0]);
+                a.loadConstant(0);
+                a.loadFromArray(valueRefRef[0].getType());
+            }
+
+            storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
+        }
+    }
+
+    /**
+     * Generates code that calls a decoding method in DataDecoder or
+     * KeyDecoder. Parameters must already be on the stack.
+     *
+     * @return 0 if an int amount is pushed onto the stack, or a positive value
+     * if offset adjust amount is constant, or a negative value if offset
+     * adjust is constant or one more
+     */
+    private int decodeProperty(CodeAssembler a,
+                               GenericPropertyInfo info, TypeDesc storageType,
+                               boolean forKey, boolean descending,
+                               LocalVariable[] stringRefRef, LocalVariable[] byteArrayRefRef,
+                               LocalVariable[] valueRefRef)
+        throws SupportException
+    {
+        TypeDesc primType = storageType.toPrimitiveType();
+
+        if (primType != null) {
+            String methodName;
+            TypeDesc returnType;
+            int adjust;
+
+            if (primType != storageType && info.isNullable()) {
+                // Property type is a nullable boxed primitive.
+                returnType = storageType;
+
+                switch (primType.getTypeCode()) {
+                case TypeDesc.BYTE_CODE:
+                    methodName = "decodeByteObj";
+                    adjust = ~2;
+                    break;
+                case TypeDesc.BOOLEAN_CODE:
+                    methodName = "decodeBooleanObj";
+                    adjust = 1;
+                    break;
+                case TypeDesc.SHORT_CODE:
+                    methodName = "decodeShortObj";
+                    adjust = ~3;
+                    break;
+                case TypeDesc.CHAR_CODE:
+                    methodName = "decodeCharacterObj";
+                    adjust = ~3;
+                    break;
+                default:
+                case TypeDesc.INT_CODE:
+                    methodName = "decodeIntegerObj";
+                    adjust = ~5;
+                    break;
+                case TypeDesc.FLOAT_CODE:
+                    methodName = "decodeFloatObj";
+                    adjust = 4;
+                    break;
+                case TypeDesc.LONG_CODE:
+                    methodName = "decodeLongObj";
+                    adjust = ~9;
+                    break;
+                case TypeDesc.DOUBLE_CODE:
+                    methodName = "decodeDoubleObj";
+                    adjust = 8;
+                    break;
+                }
+            } else {
+                // Property type is a primitive or a boxed primitive.
+                returnType = primType;
+
+                switch (primType.getTypeCode()) {
+                case TypeDesc.BYTE_CODE:
+                    methodName = "decodeByte";
+                    adjust = 1;
+                    break;
+                case TypeDesc.BOOLEAN_CODE:
+                    methodName = "decodeBoolean";
+                    adjust = 1;
+                    break;
+                case TypeDesc.SHORT_CODE:
+                    methodName = "decodeShort";
+                    adjust = 2;
+                    break;
+                case TypeDesc.CHAR_CODE:
+                    methodName = "decodeChar";
+                    adjust = 2;
+                    break;
+                default:
+                case TypeDesc.INT_CODE:
+                    methodName = "decodeInt";
+                    adjust = 4;
+                    break;
+                case TypeDesc.FLOAT_CODE:
+                    methodName = "decodeFloat";
+                    adjust = 4;
+                    break;
+                case TypeDesc.LONG_CODE:
+                    methodName = "decodeLong";
+                    adjust = 8;
+                    break;
+                case TypeDesc.DOUBLE_CODE:
+                    methodName = "decodeDouble";
+                    adjust = 8;
+                    break;
+                }
+            }
+
+            TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT};
+            if (forKey && descending) {
+                a.invokeStatic
+                    (KeyDecoder.class.getName(), methodName + "Desc", returnType, params);
+            } else {
+                a.invokeStatic
+                    (DataDecoder.class.getName(), methodName, returnType, params);
+            }
+
+            if (returnType.isPrimitive()) {
+                if (!storageType.isPrimitive()) {
+                    // Wrap it.
+                    a.convert(returnType, storageType);
+                }
+            }
+
+            return adjust;
+        } else {
+            String className = (forKey ? KeyDecoder.class : DataDecoder.class).getName();
+            String methodName;
+            TypeDesc refType;
+
+            if (storageType == TypeDesc.STRING) {
+                methodName = (forKey && descending) ? "decodeStringDesc" : "decodeString";
+                refType = TypeDesc.forClass(String[].class);
+                if (stringRefRef[0] == null) {
+                    stringRefRef[0] = a.createLocalVariable(null, refType);
+                    a.loadConstant(1);
+                    a.newObject(refType);
+                    a.storeLocal(stringRefRef[0]);
+                }
+                a.loadLocal(stringRefRef[0]);
+                valueRefRef[0] = stringRefRef[0];
+            } else if (storageType.toClass() == byte[].class) {
+                methodName = (forKey && descending) ? "decodeDesc" : "decode";
+                refType = TypeDesc.forClass(byte[][].class);
+                if (byteArrayRefRef[0] == null) {
+                    byteArrayRefRef[0] = a.createLocalVariable(null, refType);
+                    a.loadConstant(1);
+                    a.newObject(refType);
+                    a.storeLocal(byteArrayRefRef[0]);
+                }
+                a.loadLocal(byteArrayRefRef[0]);
+                valueRefRef[0] = byteArrayRefRef[0];
+            } else {
+                throw notSupported(info.getPropertyName(), storageType.getFullName());
+            }
+
+            TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT, refType};
+            a.invokeStatic(className, methodName, TypeDesc.INT, params);
+
+            return 0;
+        }
+    }
+
+    /**
+     * Push decoding instanceVar to stack in preparation to calling
+     * storePropertyValue.
+     *
+     * @param ordinal zero-based property ordinal, used only if instanceVar
+     * refers to an object array.
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are written to the runtime value of this array instead
+     * of a Storable instance.
+     * @see #storePropertyValue storePropertyValue
+     */
+    protected void pushDecodingInstanceVar(CodeAssembler a, int ordinal,
+                                           LocalVariable instanceVar) {
+        if (instanceVar == null) {
+            // Push this to stack in preparation for storing a property.
+            a.loadThis();
+        } else if (instanceVar.getType() != TypeDesc.forClass(Object[].class)) {
+            // Push reference to stack in preparation for storing a property.
+            a.loadLocal(instanceVar);
+        } else {
+            // Push array and index to stack in preparation for storing a property.
+            a.loadLocal(instanceVar);
+            a.loadConstant(ordinal);
+        }
+    }
+
+    /**
+     * Generates code to store a property value into an instance which is
+     * already on the operand stack. If instance is an Object array, index into
+     * array must also be on the operand stack.
+     *
+     * @param info info for property to store to
+     * @param useWriteMethod when true, set property by public write method
+     * instead of protected field
+     * @param instanceVar local variable referencing Storable instance,
+     * defaults to "this" if null. If variable type is an Object array, then
+     * property values are written to the runtime value of this array instead
+     * of a Storable instance.
+     * @param adapterInstanceClass class containing static references to
+     * adapter instances - defaults to instanceVar
+     * @see #pushDecodingInstanceVar pushDecodingInstanceVar
+     */
+    protected void storePropertyValue(CodeAssembler a, StorablePropertyInfo info,
+                                      boolean useWriteMethod,
+                                      LocalVariable instanceVar,
+                                      Class<?> adapterInstanceClass) {
+        TypeDesc type = info.getPropertyType();
+        TypeDesc storageType = info.getStorageType();
+
+        boolean isObjectArrayInstanceVar = instanceVar != null
+            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
+
+        boolean useAdapterInstance = adapterInstanceClass != null
+            && info.getToStorageAdapter() != null
+            && (useWriteMethod || isObjectArrayInstanceVar);
+
+        if (useAdapterInstance) {
+            // Push adapter instance to adapt property value. It must be on the
+            // stack before the property value, so swap.
+
+            // Store unadapted property to temp var in order to be swapped.
+            LocalVariable temp = a.createLocalVariable(null, storageType);
+            a.storeLocal(temp);
+
+            String fieldName =
+                info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
+            TypeDesc adapterType = TypeDesc.forClass
+                (info.getToStorageAdapter().getDeclaringClass());
+            a.loadStaticField
+                (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType);
+
+            a.loadLocal(temp);
+            a.invoke(info.getFromStorageAdapter());
+
+            // Stack now contains property adapted to its publicly declared type.
+        }
+
+        if (instanceVar == null) {
+            if (useWriteMethod) {
+                info.addInvokeWriteMethod(a);
+            } else {
+                // Set property value directly to protected field of instance.
+                if (info.getToStorageAdapter() == null) {
+                    a.storeField(info.getPropertyName(), type);
+                } else {
+                    // Invoke adapter method.
+                    a.invokeVirtual(info.getWriteMethodName() + '$',
+                                    null, new TypeDesc[] {storageType});
+                }
+            }
+        } else if (!isObjectArrayInstanceVar) {
+            TypeDesc instanceVarType = instanceVar.getType();
+
+            // Drop properties that are missing or whose types are incompatible.
+            doDrop: {
+                Class instanceVarClass = instanceVarType.toClass();
+                if (instanceVarClass != null) {
+                    Map<String, BeanProperty> props =
+                        BeanIntrospector.getAllProperties(instanceVarClass);
+                    BeanProperty prop = props.get(info.getPropertyName());
+                    if (prop != null) {
+                        if (prop.getType() == type.toClass()) {
+                            break doDrop;
+                        }
+                        // Types differ, but if primitive types, perform conversion.
+                        TypeDesc primType = type.toPrimitiveType();
+                        if (primType != null) {
+                            TypeDesc propType = TypeDesc.forClass(prop.getType());
+                            TypeDesc primPropType = propType.toPrimitiveType();
+                            if (primPropType != null) {
+                                // Apply conversion and store property.
+                                a.convert(type, propType);
+                                type = propType;
+                                break doDrop;
+                            }
+                        }
+                    }
+                }
+
+                // Drop missing or incompatible property.
+                if (storageType.isDoubleWord()) {
+                    a.pop2();
+                } else {
+                    a.pop();
+                }
+                return;
+            }
+
+            if (useWriteMethod) {
+                info.addInvokeWriteMethod(a, instanceVarType);
+            } else {
+                // Set property value directly to protected field of referenced
+                // instance. Assumes code is being defined in the same package
+                // or a subclass.
+                if (info.getToStorageAdapter() == null) {
+                    a.storeField(instanceVarType, info.getPropertyName(), type);
+                } else {
+                    // Invoke adapter method.
+                    a.invokeVirtual(instanceVarType, info.getWriteMethodName() + '$',
+                                    null, new TypeDesc[] {storageType});
+                }
+            }
+        } else {
+            // Set property value to object array. No need to check if we
+            // should call a write method because arrays don't have write
+            // methods.
+            if (type.isPrimitive()) {
+                a.convert(type, type.toObjectType());
+            }
+            a.storeToArray(TypeDesc.OBJECT);
+        }
+    }
+
+    /**
+     * Generates code that ensures a matching generation value exists in the
+     * byte array referenced by the local variable, throwing a
+     * CorruptEncodingException otherwise.
+     *
+     * @param generation if less than zero, no code is generated
+     */
+    private void decodeGeneration(CodeAssembler a, LocalVariable encodedVar,
+                                  int offset, int generation, Label altGenerationHandler)
+    {
+        if (offset < 0) {
+            throw new IllegalArgumentException();
+        }
+        if (generation < 0) {
+            return;
+        }
+
+        LocalVariable actualGeneration = a.createLocalVariable(null, TypeDesc.INT);
+        a.loadLocal(encodedVar);
+        a.loadConstant(offset);
+        a.loadFromArray(TypeDesc.BYTE);
+        a.storeLocal(actualGeneration);
+        a.loadLocal(actualGeneration);
+        Label compareGeneration = a.createLabel();
+        a.ifZeroComparisonBranch(compareGeneration, ">=");
+
+        // Decode four byte generation format.
+        a.loadLocal(actualGeneration);
+        a.loadConstant(24);
+        a.math(Opcode.ISHL);
+        a.loadConstant(0x7fffffff);
+        a.math(Opcode.IAND);
+        for (int i=1; i<4; i++) {
+            a.loadLocal(encodedVar);
+            a.loadConstant(offset + i);
+            a.loadFromArray(TypeDesc.BYTE);
+            a.loadConstant(0xff);
+            a.math(Opcode.IAND);
+            int shift = 8 * (3 - i);
+            if (shift > 0) {
+                a.loadConstant(shift);
+                a.math(Opcode.ISHL);
+            }
+            a.math(Opcode.IOR);
+        }
+        a.storeLocal(actualGeneration);
+
+        compareGeneration.setLocation();
+
+        a.loadConstant(generation);
+        a.loadLocal(actualGeneration);
+        Label generationMatches = a.createLabel();
+        a.ifComparisonBranch(generationMatches, "==");
+
+        if (altGenerationHandler != null) {
+            a.loadLocal(actualGeneration);
+            a.branch(altGenerationHandler);
+        } else {
+            // Throw CorruptEncodingException.
+
+            TypeDesc corruptEncodingEx = TypeDesc.forClass(CorruptEncodingException.class);
+            a.newObject(corruptEncodingEx);
+            a.dup();
+            a.loadConstant(generation);    // expected generation
+            a.loadLocal(actualGeneration); // actual generation
+            a.invokeConstructor(corruptEncodingEx, new TypeDesc[] {TypeDesc.INT, TypeDesc.INT});
+            a.throwObject();
+        }
+
+        generationMatches.setLocation();
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java b/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java
new file mode 100644
index 0000000..a9c535d
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+
+/**
+ * Can be used with {@link com.amazon.carbonado.util.QuickConstructorGenerator}
+ * for instantiating generic storable instances.
+ *
+ * @author Brian S O'Neill
+ */
+public interface GenericInstanceFactory {
+    Storable instantiate(RawSupport support);
+
+    Storable instantiate(RawSupport support, byte[] key, byte[] value)
+        throws FetchException;
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericPropertyInfo.java b/src/main/java/com/amazon/carbonado/raw/GenericPropertyInfo.java
new file mode 100644
index 0000000..8aa92ee
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/GenericPropertyInfo.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.reflect.Method;
+
+import org.cojen.classfile.TypeDesc;
+
+/**
+ * Minimal information required by {@link GenericEncodingStrategy} to encode
+ * and decode a storable property or layout property.
+ *
+ * @author Brian S O'Neill
+ */
+public interface GenericPropertyInfo {
+    String getPropertyName();
+
+    /**
+     * Returns the user specified property type.
+     */
+    TypeDesc getPropertyType();
+
+    /**
+     * Returns the storage supported type. If it differs from the property
+     * type, then adapter methods must also exist.
+     */
+    TypeDesc getStorageType();
+
+    boolean isNullable();
+
+    boolean isLob();
+
+    /**
+     * Returns the optional method used to adapt the property from the
+     * storage supported type to the user visible type.
+     */
+    Method getFromStorageAdapter();
+
+    /**
+     * Returns the optional method used to adapt the property from the user
+     * visible type to the storage supported type.
+     */
+    Method getToStorageAdapter();
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
new file mode 100644
index 0000000..f572dd8
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.LocalVariable;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.ClassInjector;
+import org.cojen.util.IntHashMap;
+import org.cojen.util.KeyFactory;
+import org.cojen.util.SoftValuedHashMap;
+
+import com.amazon.carbonado.CorruptEncodingException;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.FetchNoneException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.info.Direction;
+import com.amazon.carbonado.info.OrderedProperty;
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.layout.Layout;
+import com.amazon.carbonado.spi.CodeBuilderUtil;
+import com.amazon.carbonado.util.ThrowUnchecked;
+import com.amazon.carbonado.util.QuickConstructorGenerator;
+
+/**
+ * Generic codec that supports any kind of storable by auto-generating and
+ * caching storable implementations.
+ *
+ * @author Brian S O'Neill
+ * @see GenericStorableCodecFactory
+ */
+public class GenericStorableCodec<S extends Storable> implements StorableCodec<S> {
+    private static final String BLANK_KEY_FIELD_NAME = "blankKey$";
+    private static final String CODEC_FIELD_NAME = "codec$";
+    private static final String ASSIGN_CODEC_METHOD_NAME = "assignCodec$";
+
+    // Maps GenericEncodingStrategy instances to GenericStorableCodec instances.
+    private static final Map cCache = new SoftValuedHashMap();
+
+    /**
+     * Returns an instance of the codec. The Storable type itself may be an
+     * interface or a class. If it is a class, then it must not be final, and
+     * it must have a public, no-arg constructor.
+     *
+     * @param isMaster when true, version properties and sequences are managed
+     * @param layout when non-null, encode a storable layout generation
+     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
+     * 128..max is encoded in four bytes, with the most significant bit set.
+     * @throws SupportException if Storable is not supported
+     * @throws amazon.carbonado.MalformedTypeException if Storable type is not well-formed
+     * @throws IllegalArgumentException if type is null
+     */
+    @SuppressWarnings("unchecked")
+    static synchronized <S extends Storable> GenericStorableCodec<S> getInstance
+        (GenericStorableCodecFactory factory,
+         GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
+        throws SupportException
+    {
+        Object key;
+        if (layout == null) {
+            key = KeyFactory.createKey(new Object[] {encodingStrategy, isMaster});
+        } else {
+            key = KeyFactory.createKey
+                (new Object[] {encodingStrategy, isMaster, factory, layout.getGeneration()});
+        }
+
+        GenericStorableCodec<S> codec = (GenericStorableCodec<S>) cCache.get(key);
+        if (codec == null) {
+            codec = new GenericStorableCodec<S>
+                (factory,
+                 encodingStrategy.getType(),
+                 generateStorable(encodingStrategy, isMaster, layout),
+                 encodingStrategy,
+                 layout);
+            cCache.put(key, codec);
+        }
+
+        return codec;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <S extends Storable> Class<? extends S> generateStorable
+        (GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
+        throws SupportException
+    {
+        final Class<S> storableClass = encodingStrategy.getType();
+        final Class<? extends S> abstractClass =
+            RawStorableGenerator.getAbstractClass(storableClass, isMaster);
+        final int generation = layout == null ? -1 : layout.getGeneration();
+
+        ClassInjector ci = ClassInjector.create
+            (storableClass.getName(), abstractClass.getClassLoader());
+
+        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
+        cf.markSynthetic();
+        cf.setSourceFile(GenericStorableCodec.class.getName());
+        cf.setTarget("1.5");
+
+        // Declare some types.
+        final TypeDesc storageType = TypeDesc.forClass(Storage.class);
+        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+        final TypeDesc[] byteArrayParam = {byteArrayType};
+        final TypeDesc codecType = TypeDesc.forClass(GenericStorableCodec.class);
+        final TypeDesc decoderType = TypeDesc.forClass(Decoder.class);
+        final TypeDesc weakRefType = TypeDesc.forClass(WeakReference.class);
+
+        // If Layout provided, then keep a (weak) static reference to this
+        // GenericStorableCodec in order to get decoders for different
+        // generations. It is assigned a value after the class is loaded via a
+        // public static method. It can only be assigned once.
+        if (layout != null) {
+            cf.addField(Modifiers.PRIVATE.toStatic(true), CODEC_FIELD_NAME, weakRefType);
+            MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toStatic(true), ASSIGN_CODEC_METHOD_NAME,
+                                         null, new TypeDesc[] {weakRefType});
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadStaticField(CODEC_FIELD_NAME, weakRefType);
+            Label done = b.createLabel();
+            b.ifNullBranch(done, false);
+            b.loadLocal(b.getParameter(0));
+            b.storeStaticField(CODEC_FIELD_NAME, weakRefType);
+            done.setLocation();
+            b.returnVoid();
+        }
+
+        // Add constructor that accepts a RawSupport.
+        {
+            TypeDesc[] params = {rawSupportType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeSuperConstructor(params);
+            b.returnVoid();
+        }
+
+        // Add constructor that accepts a RawSupport, an encoded key, and an
+        // encoded data.
+        {
+            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.loadLocal(b.getParameter(1));
+            b.loadLocal(b.getParameter(2));
+            b.invokeSuperConstructor(params);
+            b.returnVoid();
+        }
+
+        // Implement protected abstract methods inherited from parent class.
+
+        // byte[] encodeKey()
+        {
+            // Encode the primary key into a byte array that supports correct
+            // ordering. No special key comparator is needed.
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.ENCODE_KEY_METHOD_NAME,
+                                         byteArrayType, null);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // TODO: Consider caching generated key. Rebuild if null or if pk is dirty.
+
+            // assembler            = b
+            // properties           = null (defaults to all key properties)
+            // instanceVar          = null (null means "this")
+            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
+            // useReadMethods       = false (will read fields directly)
+            // partialStartVar      = null (only support encoding all properties)
+            // partialEndVar        = null (only support encoding all properties)
+            LocalVariable encodedVar =
+                encodingStrategy.buildKeyEncoding(b, null, null, null, false, null, null);
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        }
+
+        // byte[] encodeData()
+        {
+            // Encoding non-primary key data properties.
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.ENCODE_DATA_METHOD_NAME,
+                                         byteArrayType, null);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // assembler            = b
+            // properties           = null (defaults to all non-key properties)
+            // instanceVar          = null (null means "this")
+            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
+            // useReadMethods       = false (will read fields directly)
+            // generation           = generation
+            LocalVariable encodedVar =
+                encodingStrategy.buildDataEncoding(b, null, null, null, false, generation);
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        }
+
+        // void decodeKey(byte[])
+        {
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.DECODE_KEY_METHOD_NAME,
+                                         null, byteArrayParam);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // assembler            = b
+            // properties           = null (defaults to all key properties)
+            // instanceVar          = null (null means "this")
+            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
+            // useWriteMethods      = false (will set fields directly)
+            // encodedVar           = references byte array with encoded key
+            encodingStrategy.buildKeyDecoding(b, null, null, null, false, b.getParameter(0));
+
+            b.returnVoid();
+        }
+
+        // void decodeData(byte[])
+        {
+            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
+                                         RawStorableGenerator.DECODE_DATA_METHOD_NAME,
+                                         null, byteArrayParam);
+            CodeBuilder b = new CodeBuilder(mi);
+            Label altGenerationHandler = b.createLabel();
+
+            // assembler            = b
+            // properties           = null (defaults to all non-key properties)
+            // instanceVar          = null (null means "this")
+            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
+            // useWriteMethods      = false (will set fields directly)
+            // generation           = generation
+            // altGenerationHandler = altGenerationHandler
+            // encodedVar           = references byte array with encoded data
+            encodingStrategy.buildDataDecoding
+                (b, null, null, null, false, generation, altGenerationHandler, b.getParameter(0));
+
+            b.returnVoid();
+
+            // Support decoding alternate generation.
+
+            altGenerationHandler.setLocation();
+            LocalVariable actualGeneration = b.createLocalVariable(null, TypeDesc.INT);
+            b.storeLocal(actualGeneration);
+
+            b.loadStaticField(CODEC_FIELD_NAME, weakRefType);
+            b.invokeVirtual(weakRefType, "get", TypeDesc.OBJECT, null);
+            b.dup();
+            Label haveCodec = b.createLabel();
+            b.ifNullBranch(haveCodec, false);
+
+            // Codec got reclaimed, which is unlikely to happen during normal
+            // use since it must be referenced by the storage object.
+            b.pop(); // Don't need the duped codec instance.
+            CodeBuilderUtil.throwException(b, IllegalStateException.class, "Codec missing");
+
+            haveCodec.setLocation();
+            b.checkCast(codecType);
+            b.loadLocal(actualGeneration);
+            b.invokeVirtual(codecType, "getDecoder", decoderType, new TypeDesc[] {TypeDesc.INT});
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeInterface(decoderType, "decode", null,
+                              new TypeDesc[] {TypeDesc.forClass(Storable.class), byteArrayType});
+
+            b.returnVoid();
+        }
+
+        return ci.defineClass(cf);
+    }
+
+    private final GenericStorableCodecFactory mFactory;
+
+    private final Class<S> mType;
+
+    private final Class<? extends S> mStorableClass;
+
+    // Weakly reference the encoding strategy because of the way
+    // GenericStorableCodec instances are cached in a SoftValuedHashMap.
+    // GenericStorableCodec can still be reclaimed by the garbage collector.
+    private final WeakReference<GenericEncodingStrategy<S>> mEncodingStrategy;
+
+    private final GenericInstanceFactory mInstanceFactory;
+
+    private final SearchKeyFactory<S> mPrimaryKeyFactory;
+
+    // Maps OrderedProperty[] keys to SearchKeyFactory instances.
+    private final Map mSearchKeyFactories = new SoftValuedHashMap();
+
+    private final Layout mLayout;
+
+    // Maps layout generations to Decoders.
+    private IntHashMap mDecoders;
+
+    private GenericStorableCodec(GenericStorableCodecFactory factory,
+                                 Class<S> type, Class<? extends S> storableClass,
+                                 GenericEncodingStrategy<S> encodingStrategy,
+                                 Layout layout) {
+        mFactory = factory;
+        mType = type;
+        mStorableClass = storableClass;
+        mEncodingStrategy = new WeakReference<GenericEncodingStrategy<S>>(encodingStrategy);
+        mInstanceFactory = QuickConstructorGenerator
+            .getInstance(storableClass, GenericInstanceFactory.class);
+        mPrimaryKeyFactory = getSearchKeyFactory(encodingStrategy.gatherAllKeyProperties());
+        mLayout = layout;
+
+        if (layout != null) {
+            try {
+                // Assign static reference back to this codec.
+                Method m = storableClass.getMethod
+                    (ASSIGN_CODEC_METHOD_NAME, WeakReference.class);
+                m.invoke(null, new WeakReference(this));
+            } catch (Exception e) {
+                ThrowUnchecked.fireFirstDeclaredCause(e);
+            }
+        }
+    }
+
+    /**
+     * Returns the type of Storable that code is generated for.
+     */
+    public final Class<S> getStorableType() {
+        return mType;
+    }
+
+    /**
+     * Instantiate a Storable with no key or value defined yet.
+     *
+     * @param support binds generated storable with a storage layer
+     */
+    @SuppressWarnings("unchecked")
+    public S instantiate(RawSupport<S> support) {
+        return (S) mInstanceFactory.instantiate(support);
+    }
+
+    /**
+     * Instantiate a Storable with a specific key and value.
+     *
+     * @param support binds generated storable with a storage layer
+     */
+    @SuppressWarnings("unchecked")
+    public S instantiate(RawSupport<S> support, byte[] key, byte[] value)
+        throws FetchException
+    {
+        return (S) mInstanceFactory.instantiate(support, key, value);
+    }
+
+    public StorableIndex<S> getPrimaryKeyIndex() {
+        return getEncodingStrategy().getPrimaryKeyIndex();
+    }
+
+    public int getPrimaryKeyPrefixLength() {
+        return getEncodingStrategy().getConstantKeyPrefixLength();
+    }
+
+    public byte[] encodePrimaryKey(S storable) {
+        return mPrimaryKeyFactory.encodeSearchKey(storable);
+    }
+
+    public byte[] encodePrimaryKey(S storable, int rangeStart, int rangeEnd) {
+        return mPrimaryKeyFactory.encodeSearchKey(storable, rangeStart, rangeEnd);
+    }
+
+    public byte[] encodePrimaryKey(Object[] values) {
+        return mPrimaryKeyFactory.encodeSearchKey(values);
+    }
+
+    public byte[] encodePrimaryKey(Object[] values, int rangeStart, int rangeEnd) {
+        return mPrimaryKeyFactory.encodeSearchKey(values, rangeStart, rangeEnd);
+    }
+
+    public byte[] encodePrimaryKeyPrefix() {
+        return mPrimaryKeyFactory.encodeSearchKeyPrefix();
+    }
+
+    /**
+     * Returns a concrete Storable implementation, which is fully
+     * thread-safe. It has two constructors defined:
+     *
+     * <pre>
+     * public &lt;init&gt;(Storage, RawSupport);
+     *
+     * public &lt;init&gt;(Storage, RawSupport, byte[] key, byte[] value);
+     * </pre>
+     *
+     * Convenience methods are provided in this class to instantiate the
+     * generated Storable.
+     */
+    public Class<? extends S> getStorableClass() {
+        return mStorableClass;
+    }
+
+    /**
+     * Returns a search key factory, which is useful for implementing indexes
+     * and queries.
+     *
+     * @param properties properties to build the search key from
+     */
+    @SuppressWarnings("unchecked")
+    public SearchKeyFactory<S> getSearchKeyFactory(OrderedProperty<S>[] properties) {
+        // This KeyFactory makes arrays work as hashtable keys.
+        Object key = org.cojen.util.KeyFactory.createKey(properties);
+
+        synchronized (mSearchKeyFactories) {
+            SearchKeyFactory<S> factory = (SearchKeyFactory<S>) mSearchKeyFactories.get(key);
+            if (factory == null) {
+                factory = generateSearchKeyFactory(properties);
+                mSearchKeyFactories.put(key, factory);
+            }
+            return factory;
+        }
+    }
+
+    /**
+     * Returns a data decoder for the given generation.
+     *
+     * @throws FetchNoneException if generation is unknown
+     */
+    public Decoder<S> getDecoder(int generation) throws FetchNoneException, FetchException {
+        try {
+            synchronized (mLayout) {
+                IntHashMap decoders = mDecoders;
+                if (decoders == null) {
+                    mDecoders = decoders = new IntHashMap();
+                }
+                Decoder<S> decoder = (Decoder<S>) decoders.get(generation);
+                if (decoder == null) {
+                    decoder = generateDecoder(generation);
+                    mDecoders.put(generation, decoder);
+                }
+                return decoder;
+            }
+        } catch (NullPointerException e) {
+            if (mLayout == null) {
+                throw new FetchNoneException("Layout evolution not supported");
+            }
+            throw e;
+        }
+    }
+
+    private GenericEncodingStrategy<S> getEncodingStrategy() {
+        // Should never be null, even though it is weakly referenced. As long
+        // as this class can be reached by the cache, the encoding strategy
+        // object exists since it is the cache key.
+        return mEncodingStrategy.get();
+    }
+
+    @SuppressWarnings("unchecked")
+    private SearchKeyFactory<S> generateSearchKeyFactory(OrderedProperty<S>[] properties) {
+        GenericEncodingStrategy encodingStrategy = getEncodingStrategy();
+
+        ClassInjector ci;
+        {
+            StringBuilder b = new StringBuilder();
+            b.append(mType.getName());
+            b.append('$');
+            for (OrderedProperty property : properties) {
+                if (property.getDirection() == Direction.UNSPECIFIED) {
+                    property = property.direction(Direction.ASCENDING);
+                }
+                try {
+                    property.appendTo(b);
+                } catch (java.io.IOException e) {
+                    // Not gonna happen
+                }
+            }
+            String prefix = b.toString();
+            ci = ClassInjector.create(prefix, mStorableClass.getClassLoader());
+        }
+
+        ClassFile cf = new ClassFile(ci.getClassName());
+        cf.addInterface(SearchKeyFactory.class);
+        cf.markSynthetic();
+        cf.setSourceFile(GenericStorableCodec.class.getName());
+        cf.setTarget("1.5");
+
+        // Add public no-arg constructor.
+        cf.addDefaultConstructor();
+
+        // Declare some types.
+        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+        final TypeDesc objectArrayType = TypeDesc.forClass(Object[].class);
+        final TypeDesc instanceType = TypeDesc.forClass(mStorableClass);
+
+        // Define encodeSearchKey(Storable).
+        try {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
+                 new TypeDesc[] {storableType});
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadLocal(b.getParameter(0));
+            b.checkCast(instanceType);
+            LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
+            b.storeLocal(instanceVar);
+
+            // assembler            = b
+            // properties           = properties to encode
+            // instanceVar          = instanceVar which references storable instance
+            // adapterInstanceClass = null (null means use instanceVar)
+            // useReadMethods       = false (will read fields directly)
+            // partialStartVar      = null (only support encoding all properties)
+            // partialEndVar        = null (only support encoding all properties)
+            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+                (b, properties, instanceVar, null, false, null, null);
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        } catch (SupportException e) {
+            // Shouldn't happen since all properties were checked in order
+            // to create this StorableCodec.
+            throw new UndeclaredThrowableException(e);
+        }
+
+        // Define encodeSearchKey(Storable, int, int).
+        try {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
+                 new TypeDesc[] {storableType, TypeDesc.INT, TypeDesc.INT});
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadLocal(b.getParameter(0));
+            b.checkCast(instanceType);
+            LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
+            b.storeLocal(instanceVar);
+
+            // assembler            = b
+            // properties           = properties to encode
+            // instanceVar          = instanceVar which references storable instance
+            // adapterInstanceClass = null (null means use instanceVar)
+            // useReadMethods       = false (will read fields directly)
+            // partialStartVar      = int parameter 1, references start property index
+            // partialEndVar        = int parameter 2, references end property index
+            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+                (b, properties, instanceVar, null, false, b.getParameter(1), b.getParameter(2));
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        } catch (SupportException e) {
+            // Shouldn't happen since all properties were checked in order
+            // to create this StorableCodec.
+            throw new UndeclaredThrowableException(e);
+        }
+
+        // The Storable class that we generated earlier is a subclass of the
+        // abstract class defined by StorableGenerator. StorableGenerator
+        // creates static final adapter instances, with protected
+        // access. Calling getSuperclass results in the exact class that
+        // StorableGenerator made, which is where the fields are.
+        final Class<?> adapterInstanceClass = getStorableClass().getSuperclass();
+
+        // Define encodeSearchKey(Object[] values).
+        try {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
+                 new TypeDesc[] {objectArrayType});
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // assembler            = b
+            // properties           = properties to encode
+            // instanceVar          = parameter 0, an object array
+            // adapterInstanceClass = adapterInstanceClass - see comment above
+            // useReadMethods       = false (will read fields directly)
+            // partialStartVar      = null (only support encoding all properties)
+            // partialEndVar        = null (only support encoding all properties)
+            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+                (b, properties, b.getParameter(0), adapterInstanceClass, false, null, null);
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        } catch (SupportException e) {
+            // Shouldn't happen since all properties were checked in order
+            // to create this StorableCodec.
+            throw new UndeclaredThrowableException(e);
+        }
+
+        // Define encodeSearchKey(Object[] values, int, int).
+        try {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
+                 new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // assembler            = b
+            // properties           = properties to encode
+            // instanceVar          = parameter 0, an object array
+            // adapterInstanceClass = adapterInstanceClass - see comment above
+            // useReadMethods       = false (will read fields directly)
+            // partialStartVar      = int parameter 1, references start property index
+            // partialEndVar        = int parameter 2, references end property index
+            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+                (b, properties, b.getParameter(0), adapterInstanceClass,
+                 false, b.getParameter(1), b.getParameter(2));
+
+            b.loadLocal(encodedVar);
+            b.returnValue(byteArrayType);
+        } catch (SupportException e) {
+            // Shouldn't happen since all properties were checked in order
+            // to create this StorableCodec.
+            throw new UndeclaredThrowableException(e);
+        }
+
+        // Define encodeSearchKeyPrefix().
+        try {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PUBLIC, "encodeSearchKeyPrefix", byteArrayType, null);
+            CodeBuilder b = new CodeBuilder(mi);
+
+            if (encodingStrategy.getKeyPrefixPadding() == 0 &&
+                encodingStrategy.getKeySuffixPadding() == 0) {
+                // Return null instead of a zero-length array.
+                b.loadNull();
+                b.returnValue(byteArrayType);
+            } else {
+                // Build array once and re-use. Trust that no one modifies it.
+                cf.addField(Modifiers.PRIVATE.toStatic(true).toFinal(true),
+                            BLANK_KEY_FIELD_NAME, byteArrayType);
+                b.loadStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
+                b.returnValue(byteArrayType);
+
+                // Create static initializer to set field.
+                mi = cf.addInitializer();
+                b = new CodeBuilder(mi);
+
+                // assembler            = b
+                // properties           = no parameters - we just want the key prefix
+                // instanceVar          = null (no parameters means we don't need this)
+                // adapterInstanceClass = null (no parameters means we don't need this)
+                // useReadMethods       = false (no parameters means we don't need this)
+                // partialStartVar      = null (no parameters means we don't need this)
+                // partialEndVar        = null (no parameters means we don't need this)
+                LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+                    (b, new OrderedProperty[0], null, null, false, null, null);
+
+                b.loadLocal(encodedVar);
+                b.storeStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
+                b.returnVoid();
+            }
+        } catch (SupportException e) {
+            // Shouldn't happen since all properties were checked in order
+            // to create this StorableCodec.
+            throw new UndeclaredThrowableException(e);
+        }
+
+        Class<? extends SearchKeyFactory> clazz = ci.defineClass(cf);
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException e) {
+            throw new UndeclaredThrowableException(e);
+        } catch (IllegalAccessException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    private Decoder<S> generateDecoder(int generation) throws FetchException {
+        // Create an encoding strategy against the reconstructed storable.
+        GenericEncodingStrategy<? extends Storable> altStrategy;
+        try {
+            Class<? extends Storable> altStorable = mLayout.getGeneration(generation)
+                .reconstruct(mStorableClass.getClassLoader());
+            altStrategy = mFactory.createStrategy(altStorable, null);
+        } catch (RepositoryException e) {
+            throw new CorruptEncodingException(e);
+        }
+
+        ClassInjector ci = ClassInjector.create(mType.getName(), mStorableClass.getClassLoader());
+        ClassFile cf = new ClassFile(ci.getClassName());
+        cf.addInterface(Decoder.class);
+        cf.markSynthetic();
+        cf.setSourceFile(GenericStorableCodec.class.getName());
+        cf.setTarget("1.5");
+
+        // Add public no-arg constructor.
+        cf.addDefaultConstructor();
+
+        // Declare some types.
+        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+
+        // Define the required decode method.
+        MethodInfo mi = cf.addMethod
+            (Modifiers.PUBLIC, "decode", null, new TypeDesc[] {storableType, byteArrayType});
+        CodeBuilder b = new CodeBuilder(mi);
+
+        LocalVariable uncastDestVar = b.getParameter(0);
+        b.loadLocal(uncastDestVar);
+        LocalVariable destVar = b.createLocalVariable(null, TypeDesc.forClass(mStorableClass));
+        b.checkCast(destVar.getType());
+        b.storeLocal(destVar);
+        LocalVariable dataVar = b.getParameter(1);
+
+        // assembler            = b
+        // properties           = null (defaults to all non-key properties)
+        // instanceVar          = "dest" storable
+        // adapterInstanceClass = null (null means use instanceVar, in this case is "dest")
+        // useWriteMethods      = false (will set fields directly)
+        // generation           = generation
+        // altGenerationHandler = null (generation should match)
+        // encodedVar           = "data" byte array
+        try {
+            altStrategy.buildDataDecoding
+                (b, null, destVar, null, false, generation, null, dataVar);
+        } catch (SupportException e) {
+            throw new CorruptEncodingException(e);
+        }
+
+        b.returnVoid();
+
+        Class<? extends Decoder> clazz = ci.defineClass(cf);
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException e) {
+            throw new UndeclaredThrowableException(e);
+        } catch (IllegalAccessException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    /**
+     * Creates custom raw search keys for {@link Storable} types. It is
+     * intended for supporting queries and indexes.
+     */
+    public interface SearchKeyFactory<S extends Storable> {
+        /**
+         * Build a search key by extracting all the desired properties from the
+         * given storable.
+         *
+         * @param storable extract a subset of properties from this instance
+         * @return raw search key
+         */
+        byte[] encodeSearchKey(S storable);
+
+        /**
+         * Build a search key by extracting all the desired properties from the
+         * given storable.
+         *
+         * @param storable extract a subset of properties from this instance
+         * @param rangeStart index of first property to use. Its value must be less
+         * than the count of properties used by this factory.
+         * @param rangeEnd index of last property to use, exlusive. Its value must
+         * be less than or equal to the count of properties used by this factory.
+         * @return raw search key
+         */
+        byte[] encodeSearchKey(S storable, int rangeStart, int rangeEnd);
+
+        /**
+         * Build a search key by supplying property values without a storable.
+         *
+         * @param values values to build into a key. It must be long enough to
+         * accommodate all of properties used by this factory.
+         * @return raw search key
+         */
+        byte[] encodeSearchKey(Object[] values);
+
+        /**
+         * Build a search key by supplying property values without a storable.
+         *
+         * @param values values to build into a key. The length may be less than
+         * the amount of properties used by this factory. It must not be less than the
+         * difference between rangeStart and rangeEnd.
+         * @param rangeStart index of first property to use. Its value must be less
+         * than the count of properties used by this factory.
+         * @param rangeEnd index of last property to use, exlusive. Its value must
+         * be less than or equal to the count of properties used by this factory.
+         * @return raw search key
+         */
+        byte[] encodeSearchKey(Object[] values, int rangeStart, int rangeEnd);
+
+        /**
+         * Returns the search key for when there are no values. Returned value
+         * may be null.
+         */
+        byte[] encodeSearchKeyPrefix();
+    }
+
+    /**
+     * Used for decoding different generations of Storable.
+     */
+    public interface Decoder<S extends Storable> {
+        /**
+         * @param dest storable to receive decoded properties
+         * @param data decoded into properties, some of which may be dropped if
+         * destination storable doesn't have it
+         */
+        void decode(S dest, byte[] data);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java
new file mode 100644
index 0000000..ab640e6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.layout.Layout;
+
+/**
+ * Factory for generic codec that supports any kind of storable by
+ * auto-generating and caching storable implementations.
+ *
+ * @author Brian S O'Neill
+ */
+public class GenericStorableCodecFactory implements StorableCodecFactory {
+    public GenericStorableCodecFactory() {
+    }
+
+    /**
+     * Returns null to let repository decide what the name should be.
+     */
+    public String getStorageName(Class<? extends Storable> type) throws SupportException {
+        return null;
+    }
+
+    /**
+     * @param type type of storable to create codec for
+     * @param pkIndex suggested index for primary key (optional)
+     * @param isMaster when true, version properties and sequences are managed
+     * @param layout when non-null, encode a storable layout generation
+     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
+     * 128..max is encoded in four bytes, with the most significant bit set.
+     * @throws SupportException if type is not supported
+     */
+    @SuppressWarnings("unchecked")
+    public <S extends Storable> GenericStorableCodec<S> createCodec(Class<S> type,
+                                                                    StorableIndex pkIndex,
+                                                                    boolean isMaster,
+                                                                    Layout layout)
+        throws SupportException
+    {
+        return GenericStorableCodec.getInstance
+            (this, createStrategy(type, pkIndex), isMaster, layout);
+    }
+
+    /**
+     * Override to return a different EncodingStrategy.
+     *
+     * @param type type of Storable to generate code for
+     * @param pkIndex specifies sequence and ordering of key properties (optional)
+     */
+    protected <S extends Storable> GenericEncodingStrategy<S> createStrategy
+        (Class<S> type, StorableIndex<S> pkIndex)
+        throws SupportException
+    {
+        return new GenericEncodingStrategy<S>(type, pkIndex);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
new file mode 100644
index 0000000..a3254b1
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.CorruptEncodingException;
+
+import static com.amazon.carbonado.raw.KeyEncoder.*;
+
+/**
+ * A very low-level class that decodes key components encoded by methods of
+ * {@link KeyEncoder}.
+ *
+ * @author Brian S O'Neill
+ */
+public class KeyDecoder extends DataDecoder {
+
+    /**
+     * Decodes a signed integer from exactly 4 bytes, as encoded for descending
+     * order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed integer value
+     */
+    public static int decodeIntDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        return ~decodeInt(src, srcOffset);
+    }
+
+    /**
+     * Decodes a signed Integer object from exactly 1 or 5 bytes, as encoded
+     * for descending order. If null is returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Integer object or null
+     */
+    public static Integer decodeIntegerObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeIntDesc(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed long from exactly 8 bytes, as encoded for descending
+     * order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed long value
+     */
+    public static long decodeLongDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        return ~decodeLong(src, srcOffset);
+    }
+
+    /**
+     * Decodes a signed Long object from exactly 1 or 9 bytes, as encoded for
+     * descending order. If null is returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Long object or null
+     */
+    public static Long decodeLongObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeLongDesc(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed byte from exactly 1 byte, as encoded for descending
+     * order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed byte value
+     */
+    public static byte decodeByteDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (byte)(src[srcOffset] ^ 0x7f);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Byte object from exactly 1 or 2 bytes, as encoded for
+     * descending order. If null is returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Byte object or null
+     */
+    public static Byte decodeByteObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeByteDesc(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed short from exactly 2 bytes, as encoded for descending
+     * order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed short value
+     */
+    public static short decodeShortDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x7fff);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a signed Short object from exactly 1 or 3 bytes, as encoded for
+     * descending order. If null is returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return signed Short object or null
+     */
+    public static Short decodeShortObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeShortDesc(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a char from exactly 2 bytes, as encoded for descending order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return char value
+     */
+    public static char decodeCharDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return (char)~((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff));
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a Character object from exactly 1 or 3 bytes, as encoded for
+     * descending order. If null is returned, then 1 byte was read.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Character object or null
+     */
+    public static Character decodeCharacterObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            int b = src[srcOffset];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            return decodeCharDesc(src, srcOffset + 1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a boolean from exactly 1 byte, as encoded for descending order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return boolean value
+     */
+    public static boolean decodeBooleanDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            return src[srcOffset] == 127;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a Boolean object from exactly 1 byte, as encoded for descending
+     * order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Boolean object or null
+     */
+    public static Boolean decodeBooleanObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        try {
+            switch (src[srcOffset]) {
+            case NULL_BYTE_LOW: case NULL_BYTE_HIGH:
+                return null;
+            case (byte)127:
+                return Boolean.TRUE;
+            default:
+                return Boolean.FALSE;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes a float from exactly 4 bytes, as encoded for descending order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return float value
+     */
+    public static float decodeFloatDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        int bits = decodeFloatBits(src, srcOffset);
+        if (bits >= 0) {
+            bits ^= 0x7fffffff;
+        }
+        return Float.intBitsToFloat(bits);
+    }
+
+    /**
+     * Decodes a Float object from exactly 4 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Float object or null
+     */
+    public static Float decodeFloatObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        int bits = decodeFloatBits(src, srcOffset);
+        if (bits >= 0) {
+            bits ^= 0x7fffffff;
+        }
+        return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits);
+    }
+
+    /**
+     * Decodes a double from exactly 8 bytes, as encoded for descending order.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return double value
+     */
+    public static double decodeDoubleDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        long bits = decodeDoubleBits(src, srcOffset);
+        if (bits >= 0) {
+            bits ^= 0x7fffffffffffffffL;
+        }
+        return Double.longBitsToDouble(bits);
+    }
+
+    /**
+     * Decodes a Double object from exactly 8 bytes.
+     *
+     * @param src source of encoded bytes
+     * @param srcOffset offset into source array
+     * @return Double object or null
+     */
+    public static Double decodeDoubleObjDesc(byte[] src, int srcOffset)
+        throws CorruptEncodingException
+    {
+        long bits = decodeDoubleBits(src, srcOffset);
+        if (bits >= 0) {
+            bits ^= 0x7fffffffffffffffL;
+        }
+        return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits);
+    }
+
+    /**
+     * Decodes the given byte array as originally encoded for ascending order.
+     * The decoding stops when any kind of terminator or illegal byte has been
+     * read. The decoded bytes are stored in valueRef.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded byte array is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decode(byte[] src, int srcOffset, byte[][] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            return decode(src, srcOffset, valueRef, 0);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes the given byte array as originally encoded for descending order.
+     * The decoding stops when any kind of terminator or illegal byte has been
+     * read. The decoded bytes are stored in valueRef.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded byte array is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decodeDesc(byte[] src, int srcOffset, byte[][] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            return decode(src, srcOffset, valueRef, -1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * @param xorMask 0 for normal decoding, -1 for descending decoding
+     */
+    private static int decode(byte[] src, int srcOffset, byte[][] valueRef, int xorMask) {
+        // Scan ahead, looking for terminator.
+        int srcEnd = srcOffset;
+        while (true) {
+            byte b = src[srcEnd++];
+            if (-32 <= b && b < 32) {
+                if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                    if ((srcEnd - 1) <= srcOffset) {
+                        valueRef[0] = null;
+                        return 1;
+                    }
+                }
+                break;
+            }
+        }
+
+        if (srcEnd - srcOffset == 1) {
+            valueRef[0] = EMPTY_BYTE_ARRAY;
+            return 1;
+        }
+
+        // Value is decoded from base-32768.
+
+        int valueLength = ((srcEnd - srcOffset - 1) * 120) >> 7;
+        byte[] value = new byte[valueLength];
+        int valueOffset = 0;
+
+        final int originalOffset = srcOffset;
+
+        int accumBits = 0;
+        int accum = 0;
+
+        while (true) {
+            int d = (src[srcOffset++] ^ xorMask) & 0xff;
+            int b;
+            if (srcOffset == srcEnd ||
+                (b = (src[srcOffset++] ^ xorMask) & 0xff) < 32 || b > 223) {
+                // Handle special case where one byte was emitted for digit.
+                d -= 32;
+                // To produce digit, multiply d by 192 and add 191 to adjust
+                // for missing remainder. The lower bits are discarded anyhow.
+                d = (d << 7) + (d << 6) + 191;
+
+                // Shift decoded digit into accumulator.
+                accumBits += 15;
+                accum = (accum << 15) | d;
+
+                break;
+            }
+
+            d -= 32;
+            // To produce digit, multiply d by 192 and add in remainder.
+            d = ((d << 7) + (d << 6)) + b - 32;
+
+            // Shift decoded digit into accumulator.
+            accumBits += 15;
+            accum = (accum << 15) | d;
+
+            if (accumBits == 15) {
+                value[valueOffset++] = (byte)(accum >> 7);
+            } else {
+                value[valueOffset++] = (byte)(accum >> (accumBits - 8));
+                accumBits -= 8;
+                value[valueOffset++] = (byte)(accum >> (accumBits - 8));
+            }
+            accumBits -= 8;
+        }
+
+        if (accumBits >= 8 && valueOffset < valueLength) {
+            value[valueOffset] = (byte)(accum >> (accumBits - 8));
+        }
+
+        valueRef[0] = value;
+
+        return srcOffset - originalOffset;
+    }
+
+    /**
+     * Decodes an encoded string from the given byte array.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded string is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decodeString(byte[] src, int srcOffset, String[] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            return decodeString(src, srcOffset, valueRef, 0);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes an encoded string from the given byte array as originally
+     * encoded for descending order.
+     *
+     * @param src source of encoded data
+     * @param srcOffset offset into encoded data
+     * @param valueRef decoded string is stored in element 0, which may be null
+     * @return amount of bytes read from source
+     * @throws CorruptEncodingException if source data is corrupt
+     */
+    public static int decodeStringDesc(byte[] src, int srcOffset, String[] valueRef)
+        throws CorruptEncodingException
+    {
+        try {
+            return decodeString(src, srcOffset, valueRef, -1);
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * @param xorMask 0 for normal decoding, -1 for descending decoding
+     */
+    private static int decodeString(byte[] src, int srcOffset, String[] valueRef, int xorMask)
+        throws CorruptEncodingException
+    {
+        // Scan ahead, looking for terminator.
+        int srcEnd = srcOffset;
+        while (true) {
+            byte b = src[srcEnd++];
+            if (-2 <= b && b < 2) {
+                if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                    if ((srcEnd - 1) <= srcOffset) {
+                        valueRef[0] = null;
+                        return 1;
+                    }
+                }
+                break;
+            }
+        }
+
+        if (srcEnd - srcOffset == 1) {
+            valueRef[0] = "";
+            return 1;
+        }
+
+        // Allocate a character array which may be longer than needed once
+        // bytes are decoded into characters.
+        char[] value = new char[srcEnd - srcOffset];
+        int valueOffset = 0;
+
+        final int originalOffset = srcOffset;
+
+        while (srcOffset < srcEnd) {
+            int c = (src[srcOffset++] ^ xorMask) & 0xff;
+            switch (c >> 5) {
+            case 0: case 1: case 2: case 3:
+                // 0xxxxxxx
+                value[valueOffset++] = (char)(c - 2);
+                break;
+            case 4: case 5:
+                // 10xxxxxx xxxxxxxx
+
+                c = c & 0x3f;
+                // Multiply by 192, add in remainder, remove offset of 2, and de-normalize.
+                value[valueOffset++] =
+                    (char)((c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 94);
+
+                break;
+            case 6:
+                // 110xxxxx xxxxxxxx xxxxxxxx
+
+                c = c & 0x1f;
+                // Multiply by 192, add in remainder...
+                c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) - 32;
+                // ...multiply by 192, add in remainder, remove offset of 2, and de-normalize.
+                c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 12382;
+
+                if (c >= 0x10000) {
+                    // Split into surrogate pair.
+                    c -= 0x10000;
+                    value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff));
+                    value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff));
+                } else {
+                    value[valueOffset++] = (char)c;
+                }
+
+                break;
+            default:
+                // 111xxxxx
+                // Illegal.
+                throw new CorruptEncodingException
+                    ("Corrupt encoded string data (source offset = "
+                     + (srcOffset - 1) + ')');
+            }
+        }
+
+        valueRef[0] = new String(value, 0, valueOffset - 1);
+
+        return srcEnd - originalOffset;
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * KeyEncoder#encodeSingleDesc}.
+     */
+    public static byte[] decodeSingleDesc(byte[] src) throws CorruptEncodingException {
+        return decodeSingleDesc(src, 0, 0);
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * KeyEncoder#encodeSingleDesc}.
+     *
+     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
+     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
+     */
+    public static byte[] decodeSingleDesc(byte[] src, int prefixPadding, int suffixPadding)
+        throws CorruptEncodingException
+    {
+        try {
+            int length = src.length - suffixPadding - prefixPadding;
+            if (length == 0) {
+                return EMPTY_BYTE_ARRAY;
+            }
+            byte[] dst = new byte[length];
+            while (--length >= 0) {
+                dst[length] = (byte) (~src[prefixPadding + length]);
+            }
+            return dst;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * KeyEncoder#encodeSingleNullableDesc}.
+     */
+    public static byte[] decodeSingleNullableDesc(byte[] src) throws CorruptEncodingException {
+        return decodeSingleNullableDesc(src, 0, 0);
+    }
+
+    /**
+     * Decodes the given byte array which was encoded by {@link
+     * KeyEncoder#encodeSingleNullableDesc}.
+     *
+     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
+     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
+     */
+    public static byte[] decodeSingleNullableDesc(byte[] src, int prefixPadding, int suffixPadding)
+        throws CorruptEncodingException
+    {
+        try {
+            byte b = src[prefixPadding];
+            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
+                return null;
+            }
+            int length = src.length - suffixPadding - 1 - prefixPadding;
+            if (length == 0) {
+                return EMPTY_BYTE_ARRAY;
+            }
+            byte[] dst = new byte[length];
+            while (--length >= 0) {
+                dst[length] = (byte) (~src[1 + prefixPadding + length]);
+            }
+            return dst;
+        } catch (IndexOutOfBoundsException e) {
+            throw new CorruptEncodingException(null, e);
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
new file mode 100644
index 0000000..44c5af5
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
@@ -0,0 +1,741 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+/**
+ * A very low-level class that supports encoding of primitive data into unique,
+ * sortable byte array keys. If the data to encode is of a variable size, then
+ * it is written in base-32768, using only byte values 32..223. This allows
+ * special values such as nulls and terminators to be unambiguously
+ * encoded. Terminators for variable data can be encoded using 1 for ascending
+ * order and 254 for descending order. Nulls can be encoded as 255 for high
+ * ordering and 0 for low ordering.
+ *
+ * @author Brian S O'Neill
+ * @see KeyDecoder
+ */
+public class KeyEncoder extends DataEncoder {
+
+    /** Byte to terminate variable data encoded for ascending order */
+    static final byte TERMINATOR = (byte)1;
+
+    /**
+     * Encodes the given signed integer into exactly 4 bytes for descending
+     * order.
+     *
+     * @param value signed integer value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(int value, byte[] dst, int dstOffset) {
+        encode(~value, dst, dstOffset);
+    }
+
+    /**
+     * Encodes the given signed Integer object into exactly 1 or 5 bytes for
+     * descending order. If the Integer object is never expected to be null,
+     * consider encoding as an int primitive.
+     *
+     * @param value optional signed Integer value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(Integer value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_LOW;
+            encode(~value.intValue(), dst, dstOffset + 1);
+            return 5;
+        }
+    }
+
+    /**
+     * Encodes the given signed long into exactly 8 bytes for descending order.
+     *
+     * @param value signed long value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(long value, byte[] dst, int dstOffset) {
+        encode(~value, dst, dstOffset);
+    }
+
+    /**
+     * Encodes the given signed Long object into exactly 1 or 9 bytes for
+     * descending order. If the Long object is never expected to be null,
+     * consider encoding as a long primitive.
+     *
+     * @param value optional signed Long value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(Long value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_LOW;
+            encode(~value.longValue(), dst, dstOffset + 1);
+            return 9;
+        }
+    }
+
+    /**
+     * Encodes the given signed byte into exactly 1 byte for descending order.
+     *
+     * @param value signed byte value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(byte value, byte[] dst, int dstOffset) {
+        dst[dstOffset] = (byte)(value ^ 0x7f);
+    }
+
+    /**
+     * Encodes the given signed Byte object into exactly 1 or 2 bytes for
+     * descending order. If the Byte object is never expected to be null,
+     * consider encoding as a byte primitive.
+     *
+     * @param value optional signed Byte value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(Byte value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_LOW;
+            dst[dstOffset + 1] = (byte)(value ^ 0x7f);
+            return 2;
+        }
+    }
+
+    /**
+     * Encodes the given signed short into exactly 2 bytes for descending
+     * order.
+     *
+     * @param value signed short value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(short value, byte[] dst, int dstOffset) {
+        encode((short) ~value, dst, dstOffset);
+    }
+
+    /**
+     * Encodes the given signed Short object into exactly 1 or 3 bytes for
+     * descending order. If the Short object is never expected to be null,
+     * consider encoding as a short primitive.
+     *
+     * @param value optional signed Short value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(Short value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_LOW;
+            encode((short) ~value.shortValue(), dst, dstOffset + 1);
+            return 3;
+        }
+    }
+
+    /**
+     * Encodes the given character into exactly 2 bytes for descending order.
+     *
+     * @param value character value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(char value, byte[] dst, int dstOffset) {
+        encode((char) ~value, dst, dstOffset);
+    }
+
+    /**
+     * Encodes the given Character object into exactly 1 or 3 bytes for
+     * descending order. If the Character object is never expected to be null,
+     * consider encoding as a char primitive.
+     *
+     * @param value optional Character value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(Character value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        } else {
+            dst[dstOffset] = NOT_NULL_BYTE_LOW;
+            encode((char) ~value.charValue(), dst, dstOffset + 1);
+            return 3;
+        }
+    }
+
+    /**
+     * Encodes the given boolean into exactly 1 byte for descending order.
+     *
+     * @param value boolean value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(boolean value, byte[] dst, int dstOffset) {
+        dst[dstOffset] = value ? (byte)127 : (byte)128;
+    }
+
+    /**
+     * Encodes the given Boolean object into exactly 1 byte for descending
+     * order.
+     *
+     * @param value optional Boolean value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(Boolean value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+        } else {
+            dst[dstOffset] = value.booleanValue() ? (byte)127 : (byte)128;
+        }
+    }
+
+    /**
+     * Encodes the given float into exactly 4 bytes for descending order.
+     *
+     * @param value float value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(float value, byte[] dst, int dstOffset) {
+        int bits = Float.floatToIntBits(value);
+        if (bits >= 0) {
+            bits ^= 0x7fffffff;
+        }
+        dst[dstOffset    ] = (byte)(bits >> 24);
+        dst[dstOffset + 1] = (byte)(bits >> 16);
+        dst[dstOffset + 2] = (byte)(bits >> 8);
+        dst[dstOffset + 3] = (byte)bits;
+    }
+
+    /**
+     * Encodes the given Float object into exactly 4 bytes for descending
+     * order. A non-canonical NaN value is used to represent null.
+     *
+     * @param value optional Float value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(Float value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            encode(~0x7fffffff, dst, dstOffset);
+        } else {
+            encodeDesc(value.floatValue(), dst, dstOffset);
+        }
+    }
+
+    /**
+     * Encodes the given double into exactly 8 bytes for descending order.
+     *
+     * @param value double value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(double value, byte[] dst, int dstOffset) {
+        long bits = Double.doubleToLongBits(value);
+        if (bits >= 0) {
+            bits ^= 0x7fffffffffffffffL;
+        }
+        int w = (int)(bits >> 32);
+        dst[dstOffset    ] = (byte)(w >> 24);
+        dst[dstOffset + 1] = (byte)(w >> 16);
+        dst[dstOffset + 2] = (byte)(w >> 8);
+        dst[dstOffset + 3] = (byte)w;
+        w = (int)bits;
+        dst[dstOffset + 4] = (byte)(w >> 24);
+        dst[dstOffset + 5] = (byte)(w >> 16);
+        dst[dstOffset + 6] = (byte)(w >> 8);
+        dst[dstOffset + 7] = (byte)w;
+    }
+
+    /**
+     * Encodes the given Double object into exactly 8 bytes for descending
+     * order. A non-canonical NaN value is used to represent null.
+     *
+     * @param value optional Double value to encode
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     */
+    public static void encodeDesc(Double value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            encode(~0x7fffffffffffffffL, dst, dstOffset);
+        } else {
+            encodeDesc(value.doubleValue(), dst, dstOffset);
+        }
+    }
+
+    /**
+     * Encodes the given optional unsigned byte array into a variable amount of
+     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
+     * the amount written can be determined by calling calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(byte[] value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_HIGH;
+            return 1;
+        }
+        return encode(value, 0, value.length, dst, dstOffset, 0);
+    }
+
+    /**
+     * Encodes the given optional unsigned byte array into a variable amount of
+     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
+     * the amount written can be determined by calling calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param valueOffset offset into byte array
+     * @param valueLength length of data in byte array
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(byte[] value, int valueOffset, int valueLength,
+                             byte[] dst, int dstOffset) {
+        return encode(value, valueOffset, valueLength, dst, dstOffset, 0);
+    }
+
+    /**
+     * Encodes the given optional unsigned byte array into a variable amount of
+     * bytes for descending order. If the byte array is null, exactly 1 byte is
+     * written. Otherwise, the amount written is determined by calling
+     * calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(byte[] value, byte[] dst, int dstOffset) {
+        if (value == null) {
+            dst[dstOffset] = NULL_BYTE_LOW;
+            return 1;
+        }
+        return encode(value, 0, value.length, dst, dstOffset, -1);
+    }
+
+    /**
+     * Encodes the given optional unsigned byte array into a variable amount of
+     * bytes for descending order. If the byte array is null, exactly 1 byte is
+     * written. Otherwise, the amount written is determined by calling
+     * calculateEncodedLength.
+     *
+     * @param value byte array value to encode, may be null
+     * @param valueOffset offset into byte array
+     * @param valueLength length of data in byte array
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(byte[] value, int valueOffset, int valueLength,
+                                 byte[] dst, int dstOffset) {
+        return encode(value, valueOffset, valueLength, dst, dstOffset, -1);
+    }
+
+    /**
+     * @param xorMask 0 for normal encoding, -1 for descending encoding
+     */
+    private static int encode(byte[] value, int valueOffset, int valueLength,
+                              byte[] dst, int dstOffset, int xorMask) {
+        if (value == null) {
+            dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask);
+            return 1;
+        }
+
+        final int originalOffset = dstOffset;
+
+        // Value is encoded in base-32768.
+
+        int accumBits = 0;
+        int accum = 0;
+
+        final int end = valueOffset + valueLength;
+        for (int i=valueOffset; i<end; i++) {
+            if (accumBits <= 7) {
+                accumBits += 8;
+                accum = (accum << 8) | (value[i] & 0xff);
+                if (accumBits == 15) {
+                    emitDigit(accum, dst, dstOffset, xorMask);
+                    dstOffset += 2;
+                    accum = 0;
+                    accumBits = 0;
+                }
+            } else {
+                int supply = 15 - accumBits;
+                accum = (accum << supply) | ((value[i] & 0xff) >> (8 - supply));
+                emitDigit(accum, dst, dstOffset, xorMask);
+                dstOffset += 2;
+                accumBits = 8 - supply;
+                accum = value[i] & ((1 << accumBits) - 1);
+            }
+        }
+
+        if (accumBits > 0) {
+            // Pad with zeros.
+            accum <<= (15 - accumBits);
+            if (accumBits <= 7) {
+                // Since amount of significant bits is small, emit only the
+                // upper half of the digit. The following code is modified from
+                // emitDigit.
+
+                int a = (accum * 21845) >> 22;
+                if (accum - ((a << 7) + (a << 6)) == 192) {
+                    a++;
+                }
+                dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
+            } else {
+                emitDigit(accum, dst, dstOffset, xorMask);
+                dstOffset += 2;
+            }
+        }
+
+        // Append terminator.
+        dst[dstOffset++] = (byte)(TERMINATOR ^ xorMask);
+
+        return dstOffset - originalOffset;
+    }
+
+    /**
+     * Emits a base-32768 digit using exactly two bytes. The first byte is in the range
+     * 32..202 and the second byte is in the range 32..223.
+     *
+     * @param value digit value in the range 0..32767
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @param xorMask 0 for normal encoding, -1 for descending encoding
+     */
+    private static void emitDigit(int value, byte[] dst, int dstOffset, int xorMask) {
+        // The first byte is computed as ((value / 192) + 32) and the second
+        // byte is computed as ((value % 192) + 32). To speed things up a bit,
+        // the integer division and remainder operations are replaced with a
+        // scaled multiplication.
+
+        // approximate value / 192
+        int a = (value * 21845) >> 22;
+
+        // approximate value % 192
+        // Note: the value 192 was chosen as a divisor because a multiply by
+        // 192 can be replaced with two summed shifts.
+        int b = value - ((a << 7) + (a << 6));
+        if (b == 192) {
+            // Fix error.
+            a++;
+            b = 0;
+        }
+
+        dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
+        dst[dstOffset] = (byte)((b + 32) ^ xorMask);
+    }
+
+    /**
+     * Returns the amount of bytes required to encode a byte array of the given
+     * length.
+     *
+     * @param value byte array value to encode, may be null
+     * @return amount of bytes needed to encode
+     */
+    public static int calculateEncodedLength(byte[] value) {
+        return value == null ? 1 : calculateEncodedLength(value, 0, value.length);
+    }
+
+    /**
+     * Returns the amount of bytes required to encode the given byte array.
+     *
+     * @param value byte array value to encode, may be null
+     * @param valueOffset offset into byte array
+     * @param valueLength length of data in byte array
+     * @return amount of bytes needed to encode
+     */
+    public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) {
+        // The add of 119 is used to force ceiling rounding.
+        return value == null ? 1 : (((valueLength << 7) + 119) / 120 + 1);
+    }
+
+    /**
+     * Encodes the given optional String into a variable amount of bytes. The
+     * amount written can be determined by calling
+     * calculateEncodedStringLength.
+     * <p>
+     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
+     * characters are usually written in one byte. This encoding is more
+     * efficient than UTF-8, but it isn't compatible with UTF-8.
+     *
+     * @param value String value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encode(String value, byte[] dst, int dstOffset) {
+        return encode(value, dst, dstOffset, 0);
+    }
+
+    /**
+     * Encodes the given optional String into a variable amount of bytes for
+     * descending order. The amount written can be determined by calling
+     * calculateEncodedStringLength.
+     * <p>
+     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
+     * characters are usually written in one byte. This encoding is more
+     * efficient than UTF-8, but it isn't compatible with UTF-8.
+     *
+     * @param value String value to encode, may be null
+     * @param dst destination for encoded bytes
+     * @param dstOffset offset into destination array
+     * @return amount of bytes written
+     */
+    public static int encodeDesc(String value, byte[] dst, int dstOffset) {
+        return encode(value, dst, dstOffset, -1);
+    }
+
+    /**
+     * @param xorMask 0 for normal encoding, -1 for descending encoding
+     */
+    private static int encode(String value, byte[] dst, int dstOffset, int xorMask) {
+        if (value == null) {
+            dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask);
+            return 1;
+        }
+
+        final int originalOffset = dstOffset;
+
+        // All characters have an offset of 2 added, in order to reserve bytes
+        // 0 and 1 for encoding nulls and terminators. This means the ASCII
+        // string "HelloWorld" is actually encoded as "JgnnqYqtnf". This also
+        // means that the ASCII '~' and del characters are encoded in two bytes.
+
+        int length = value.length();
+        for (int i = 0; i < length; i++) {
+            int c = value.charAt(i) + 2;
+            if (c <= 0x7f) {
+                // 0xxxxxxx
+                dst[dstOffset++] = (byte)(c ^ xorMask);
+            } else if (c <= 12415) {
+                // 10xxxxxx xxxxxxxx
+
+                // Second byte cannot have the values 0, 1, 254, or 255 because
+                // they clash with null and terminator bytes. Divide by 192 and
+                // store in first 6 bits. The remainder, with 32 added, goes
+                // into the second byte. Note that (192 * 63 + 191) + 128 == 12415.
+                // 63 is the maximum value that can be represented in 6 bits.
+
+                c -= 128; // c will always be at least 128, so normalize.
+
+                // approximate value / 192
+                int a = (c * 21845) >> 22;
+
+                // approximate value % 192
+                // Note: the value 192 was chosen as a divisor because a multiply by
+                // 192 can be replaced with two summed shifts.
+                c = c - ((a << 7) + (a << 6));
+                if (c == 192) {
+                    // Fix error.
+                    a++;
+                    c = 0;
+                }
+
+                dst[dstOffset++] = (byte)((0x80 | a) ^ xorMask);
+                dst[dstOffset++] = (byte)((c + 32) ^ xorMask);
+            } else {
+                // 110xxxxx xxxxxxxx xxxxxxxx
+
+                if ((c - 2) >= 0xd800 && (c - 2) <= 0xdbff) {
+                    // Found a high surrogate. Verify that surrogate pair is
+                    // well-formed. Low surrogate must follow high surrogate.
+                    if (i + 1 < length) {
+                        int c2 = value.charAt(i + 1);
+                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
+                            c = ((((c - 2) & 0x3ff) << 10) | (c2 & 0x3ff)) + 0x10002;
+                            i++;
+                        }
+                    }
+                }
+
+                // Second and third bytes cannot have the values 0, 1, 254, or
+                // 255 because they clash with null and terminator
+                // bytes. Divide by 192 twice, storing the first and second
+                // remainders in the third and second bytes, respectively.
+                // Note that largest unicode value supported is 2^20 + 65535 ==
+                // 1114111. When divided by 192 twice, the value is 30, which
+                // just barely fits in the 5 available bits of the first byte.
+
+                c -= 12416; // c will always be at least 12416, so normalize.
+
+                int a = (int)((c * 21845L) >> 22);
+                c = c - ((a << 7) + (a << 6));
+                if (c == 192) {
+                    a++;
+                    c = 0;
+                }
+
+                dst[dstOffset + 2] = (byte)((c + 32) ^ xorMask);
+
+                c = (a * 21845) >> 22;
+                a = a - ((c << 7) + (c << 6));
+                if (a == 192) {
+                    c++;
+                    a = 0;
+                }
+
+                dst[dstOffset++] = (byte)((0xc0 | c) ^ xorMask);
+                dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
+                dstOffset++;
+            }
+        }
+
+        // Append terminator.
+        dst[dstOffset++] = (byte)(TERMINATOR ^ xorMask);
+
+        return dstOffset - originalOffset;
+    }
+
+    /**
+     * Returns the amount of bytes required to encode the given String.
+     *
+     * @param value String to encode, may be null
+     */
+    public static int calculateEncodedStringLength(String value) {
+        int encodedLen = 1;
+        if (value != null) {
+            int valueLength = value.length();
+            for (int i = 0; i < valueLength; i++) {
+                int c = value.charAt(i);
+                if (c <= (0x7f - 2)) {
+                    encodedLen++;
+                } else if (c <= (12415 - 2)) {
+                    encodedLen += 2;
+                } else {
+                    if (c >= 0xd800 && c <= 0xdbff) {
+                        // Found a high surrogate. Verify that surrogate pair is
+                        // well-formed. Low surrogate must follow high surrogate.
+                        if (i + 1 < valueLength) {
+                            int c2 = value.charAt(i + 1);
+                            if (c2 >= 0xdc00 && c2 <= 0xdfff) {
+                                i++;
+                            }
+                        }
+                    }
+                    encodedLen += 3;
+                }
+            }
+        }
+        return encodedLen;
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * required property, descending order, whose type is a byte array. The
+     * original byte array is returned if the length is zero.
+     */
+    public static byte[] encodeSingleDesc(byte[] value) {
+        return encodeSingleDesc(value, 0, 0);
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * required property, descending order, whose type is a byte array. The
+     * original byte array is returned if the length and padding lengths are
+     * zero.
+     *
+     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
+     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
+     */
+    public static byte[] encodeSingleDesc(byte[] value, int prefixPadding, int suffixPadding) {
+        int length = value.length;
+        if (prefixPadding <= 0 && suffixPadding <= 0 && length == 0) {
+            return value;
+        }
+        byte[] dst = new byte[prefixPadding + length + suffixPadding];
+        while (--length >= 0) {
+            dst[prefixPadding + length] = (byte) (~value[length]);
+        }
+        return dst;
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * nullable property, descending order, whose type is a byte array.
+     */
+    public static byte[] encodeSingleNullableDesc(byte[] value) {
+        return encodeSingleNullableDesc(value, 0, 0);
+    }
+
+    /**
+     * Encodes the given byte array for use when there is only a single
+     * nullable property, descending order, whose type is a byte array.
+     *
+     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
+     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
+     */
+    public static byte[] encodeSingleNullableDesc(byte[] value,
+                                                  int prefixPadding, int suffixPadding) {
+        if (prefixPadding <= 0 && suffixPadding <= 0) {
+            if (value == null) {
+                return NULL_BYTE_ARRAY_LOW;
+            }
+
+            int length = value.length;
+            if (length == 0) {
+                return NOT_NULL_BYTE_ARRAY_LOW;
+            }
+
+            byte[] dst = new byte[1 + length];
+            dst[0] = NOT_NULL_BYTE_LOW;
+            while (--length >= 0) {
+                dst[1 + length] = (byte) (~value[length]);
+            }
+            return dst;
+        }
+
+        if (value == null) {
+            byte[] dst = new byte[prefixPadding + 1 + suffixPadding];
+            dst[prefixPadding] = NULL_BYTE_LOW;
+            return dst;
+        }
+
+        int length = value.length;
+        byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding];
+        dst[prefixPadding] = NOT_NULL_BYTE_LOW;
+        while (--length >= 0) {
+            dst[prefixPadding + 1 + length] = (byte) (~value[length]);
+        }
+        return dst;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/LayoutPropertyInfo.java b/src/main/java/com/amazon/carbonado/raw/LayoutPropertyInfo.java
new file mode 100644
index 0000000..4049dc2
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/LayoutPropertyInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.reflect.Method;
+
+import org.cojen.classfile.TypeDesc;
+
+import com.amazon.carbonado.layout.LayoutProperty;
+import com.amazon.carbonado.lob.Lob;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+public class LayoutPropertyInfo implements GenericPropertyInfo {
+    private final LayoutProperty mProp;
+    private final TypeDesc mPropertyType;
+    private final TypeDesc mStorageType;
+    private final Method mFromStorage;
+    private final Method mToStorage;
+
+    LayoutPropertyInfo(LayoutProperty property) {
+        this(property, null, null, null);
+    }
+
+    LayoutPropertyInfo(LayoutProperty property,
+                       Class<?> storageType, Method fromStorage, Method toStorage)
+    {
+        mProp = property;
+        mPropertyType = TypeDesc.forDescriptor(property.getPropertyTypeDescriptor());
+        if (storageType == null) {
+            mStorageType = mPropertyType;
+        } else {
+            mStorageType = TypeDesc.forClass(storageType);
+        }
+        mFromStorage = fromStorage;
+        mToStorage = toStorage;
+    }
+
+    public String getPropertyName() {
+        return mProp.getPropertyName();
+    }
+
+    public TypeDesc getPropertyType() {
+        return mPropertyType;
+    }
+
+    public TypeDesc getStorageType() {
+        return mStorageType;
+    }
+
+    public boolean isNullable() {
+        return mProp.isNullable();
+    }
+
+    public boolean isLob() {
+        Class clazz = mPropertyType.toClass();
+        return clazz != null && Lob.class.isAssignableFrom(clazz);
+    }
+
+    public Method getFromStorageAdapter() {
+        return mFromStorage;
+    }
+
+    public Method getToStorageAdapter() {
+        return mToStorage;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/RawCursor.java b/src/main/java/com/amazon/carbonado/raw/RawCursor.java
new file mode 100644
index 0000000..865cdb2
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/RawCursor.java
@@ -0,0 +1,743 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.util.NoSuchElementException;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.cursor.AbstractCursor;
+
+/**
+ * Abstract Cursor implementation for a repository that manipulates raw bytes.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class RawCursor<S> extends AbstractCursor<S> {
+    // States for mState.
+    private static final byte
+        UNINITIALIZED = 0,
+        CLOSED = 1,
+        TRY_NEXT = 2,
+        HAS_NEXT = 3;
+
+    /** Lock object, as passed into the constructor */
+    protected final Lock mLock;
+
+    private final byte[] mStartBound;
+    private final boolean mInclusiveStart;
+    private final byte[] mEndBound;
+    private final boolean mInclusiveEnd;
+    private final int mPrefixLength;
+    private final boolean mReverse;
+
+    private byte mState;
+
+    /**
+     * @param lock operations lock on this object
+     * @param startBound specify the starting key for the cursor, or null if first
+     * @param inclusiveStart true if start bound is inclusive
+     * @param endBound specify the ending key for the cursor, or null if last
+     * @param inclusiveEnd true if end bound is inclusive
+     * @param maxPrefix maximum expected common initial bytes in start and end bound
+     * @param reverse when true, iteration is reversed
+     * @throws IllegalArgumentException if any bound is null but is not inclusive
+     */
+    protected RawCursor(Lock lock,
+                        byte[] startBound, boolean inclusiveStart,
+                        byte[] endBound, boolean inclusiveEnd,
+                        int maxPrefix,
+                        boolean reverse) {
+        mLock = lock == null ? new ReentrantLock() : lock;
+
+        if ((startBound == null && !inclusiveStart) || (endBound == null && !inclusiveEnd)) {
+            throw new IllegalArgumentException();
+        }
+
+        mStartBound = startBound;
+        mInclusiveStart = inclusiveStart;
+        mEndBound = endBound;
+        mInclusiveEnd = inclusiveEnd;
+        mReverse = reverse;
+
+        // Determine common prefix for start and end bound.
+        if (maxPrefix <= 0 || startBound == null && endBound == null) {
+            mPrefixLength = 0;
+        } else {
+            int len = Math.min(maxPrefix, Math.min(startBound.length, endBound.length));
+            int i;
+            for (i=0; i<len; i++) {
+                if (startBound[i] != endBound[i]) {
+                    break;
+                }
+            }
+            mPrefixLength = i;
+        }
+    }
+
+    public void close() throws FetchException {
+        mLock.lock();
+        try {
+            if (mState != CLOSED) {
+                release();
+                // Switch state to closed before committing transaction, to
+                // prevent infinite recursion that results when transaction
+                // exits. Exiting a transaction causes all cursors to close.
+                mState = CLOSED;
+            }
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    public boolean hasNext() throws FetchException {
+        mLock.lock();
+        try {
+            try {
+                switch (mState) {
+                case UNINITIALIZED:
+                    if (mReverse ? toBoundedLast() : toBoundedFirst()) {
+                        mState = HAS_NEXT;
+                        return true;
+                    } else {
+                        mState = TRY_NEXT;
+                    }
+                    break;
+
+                case CLOSED: default:
+                    return false;
+
+                case TRY_NEXT:
+                    if (mReverse ? toBoundedPrevious() : toBoundedNext()) {
+                        mState = HAS_NEXT;
+                        return true;
+                    }
+                    break;
+
+                case HAS_NEXT:
+                    return true;
+                }
+            } catch (FetchException e) {
+                // Auto-close in response to FetchException.
+                try {
+                    close();
+                } catch (FetchException e2) {
+                    // Ignore.
+                }
+                throw e;
+            }
+
+            // Reached the end naturally, so close.
+            close();
+        } finally {
+            mLock.unlock();
+        }
+
+        return false;
+    }
+
+    public S next() throws FetchException, NoSuchElementException {
+        mLock.lock();
+        try {
+            if (!hasNext()) {
+                handleNoSuchElement();
+                throw new NoSuchElementException();
+            }
+            try {
+                S obj = instantiateCurrent();
+                mState = TRY_NEXT;
+                return obj;
+            } catch (FetchException e) {
+                // Auto-close in response to FetchException.
+                try {
+                    close();
+                } catch (FetchException e2) {
+                    // Ignore.
+                }
+                throw e;
+            }
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    public int skipNext(int amount) throws FetchException {
+        if (amount <= 0) {
+            if (amount < 0) {
+                throw new IllegalArgumentException("Cannot skip negative amount: " + amount);
+            }
+            return 0;
+        }
+
+        mLock.lock();
+        try {
+            int actual = 0;
+
+            if (hasNext()) {
+                try {
+                    actual += mReverse ? toBoundedPrevious(amount) : toBoundedNext(amount);
+                } catch (FetchException e) {
+                    // Auto-close in response to FetchException.
+                    try {
+                        close();
+                    } catch (FetchException e2) {
+                        // Ignore.
+                    }
+                    throw e;
+                }
+
+                if (actual >= amount) {
+                    return actual;
+                }
+                mState = TRY_NEXT;
+                // Since state was HAS_NEXT and is forced into TRY_NEXT, actual
+                // amount skipped is effectively one more.
+                actual++;
+            }
+
+            // Reached the end naturally, so close.
+            close();
+
+            return actual;
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    /**
+     * Release any internal resources, called when closed.
+     */
+    protected abstract void release() throws FetchException;
+
+    /**
+     * Returns the contents of the current key being referenced, or null
+     * otherwise. Caller is responsible for making a copy of the key. The array
+     * must not be modified concurrently.
+     *
+     * <p>If cursor is not opened, null must be returned.
+     *
+     * @return currently referenced key bytes or null if no current
+     * @throws IllegalStateException if key is disabled
+     */
+    protected abstract byte[] getCurrentKey() throws FetchException;
+
+    /**
+     * Returns the contents of the current value being referenced, or null
+     * otherwise. Caller is responsible for making a copy of the value. The
+     * array must not be modified concurrently.
+     *
+     * <p>If cursor is not opened, null must be returned.
+     *
+     * @return currently referenced value bytes or null if no current
+     * @throws IllegalStateException if value is disabled
+     */
+    protected abstract byte[] getCurrentValue() throws FetchException;
+
+    /**
+     * An optimization hint which disables key and value acquisition. The
+     * default implementation of this method does nothing.
+     */
+    protected void disableKeyAndValue() {
+    }
+
+    /**
+     * An optimization hint which disables just value acquisition. The default
+     * implementation of this method does nothing.
+     */
+    protected void disableValue() {
+    }
+
+    /**
+     * Enable key and value acquisition again, after they have been
+     * disabled. Calling this method forces the key and value to be
+     * re-acquired, if they had been disabled. Key and value acquisition must
+     * be enabled by default. The default implementation of this method does
+     * nothing.
+     */
+    protected void enableKeyAndValue() throws FetchException {
+    }
+
+    /**
+     * Returns a new Storable instance for the currently referenced entry.
+     *
+     * @return new Storable instance, never null
+     * @throws IllegalStateException if no current entry to instantiate
+     */
+    protected abstract S instantiateCurrent() throws FetchException;
+
+    /**
+     * Move the cursor to the first available entry. If false is returned, the
+     * cursor must be positioned before the first available entry.
+     *
+     * @return true if first entry exists and is now current
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toFirst() throws FetchException;
+
+    /**
+     * Move the cursor to the first available entry at or after the given
+     * key. If false is returned, the cursor must be positioned before the
+     * first available entry. Caller is responsible for preserving contents of
+     * array.
+     *
+     * @param key key to search for
+     * @return true if first entry exists and is now current
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toFirst(byte[] key) throws FetchException;
+
+    /**
+     * Move the cursor to the last available entry. If false is returned, the
+     * cursor must be positioned after the last available entry.
+     *
+     * @return true if last entry exists and is now current
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toLast() throws FetchException;
+
+    /**
+     * Move the cursor to the last available entry at or before the given
+     * key. If false is returned, the cursor must be positioned after the last
+     * available entry. Caller is responsible for preserving contents of array.
+     *
+     * @param key key to search for
+     * @return true if last entry exists and is now current
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toLast(byte[] key) throws FetchException;
+
+    /**
+     * Move the cursor to the next available entry, returning false if none. If
+     * false is returned, the cursor must be positioned after the last
+     * available entry.
+     *
+     * @return true if moved to next entry
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toNext() throws FetchException;
+
+    /**
+     * Move the cursor to the next available entry, incrementing by the amount
+     * given. The actual amount incremented is returned. If the amount is less
+     * then requested, the cursor must be positioned after the last available
+     * entry. Subclasses may wish to override this method with a faster
+     * implementation.
+     *
+     * <p>Calling to toNext(1) is equivalent to calling toNext().
+     *
+     * @param amount positive amount to advance
+     * @return actual amount advanced
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected int toNext(int amount) throws FetchException {
+        if (amount <= 1) {
+            return (amount <= 0) ? 0 : (toNext() ? 1 : 0);
+        }
+
+        int count = 0;
+
+        disableKeyAndValue();
+        try {
+            while (amount > 0) {
+                if (toNext()) {
+                    count++;
+                    amount--;
+                } else {
+                    break;
+                }
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return count;
+    }
+
+    /**
+     * Move the cursor to the next unique key, returning false if none. If
+     * false is returned, the cursor must be positioned after the last
+     * available entry. Subclasses may wish to override this method with a
+     * faster implementation.
+     *
+     * @return true if moved to next unique key
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected boolean toNextKey() throws FetchException {
+        byte[] initialKey = getCurrentKey();
+        if (initialKey == null) {
+            return false;
+        }
+
+        disableValue();
+        try {
+            while (true) {
+                if (toNext()) {
+                    byte[] currentKey = getCurrentKey();
+                    if (currentKey == null) {
+                        return false;
+                    }
+                    if (compareKeysPartially(currentKey, initialKey) > 0) {
+                        break;
+                    }
+                } else {
+                    return false;
+                }
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return true;
+    }
+
+    /**
+     * Move the cursor to the previous available entry, returning false if
+     * none. If false is returned, the cursor must be positioned before the
+     * first available entry.
+     *
+     * @return true if moved to previous entry
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected abstract boolean toPrevious() throws FetchException;
+
+    /**
+     * Move the cursor to the previous available entry, decrementing by the
+     * amount given. The actual amount decremented is returned. If the amount
+     * is less then requested, the cursor must be positioned before the first
+     * available entry. Subclasses may wish to override this method with a
+     * faster implementation.
+     *
+     * <p>Calling to toPrevious(1) is equivalent to calling toPrevious().
+     *
+     * @param amount positive amount to retreat
+     * @return actual amount retreated
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected int toPrevious(int amount) throws FetchException {
+        if (amount <= 1) {
+            return (amount <= 0) ? 0 : (toPrevious() ? 1 : 0);
+        }
+
+        int count = 0;
+
+        disableKeyAndValue();
+        try {
+            while (amount > 0) {
+                if (toPrevious()) {
+                    count++;
+                    amount--;
+                } else {
+                    break;
+                }
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return count;
+    }
+
+    /**
+     * Move the cursor to the previous unique key, returning false if none. If
+     * false is returned, the cursor must be positioned before the first
+     * available entry. Subclasses may wish to override this method with a
+     * faster implementation.
+     *
+     * @return true if moved to previous unique key
+     * @throws IllegalStateException if cursor is not opened
+     */
+    protected boolean toPreviousKey() throws FetchException {
+        byte[] initialKey = getCurrentKey();
+        if (initialKey == null) {
+            return false;
+        }
+
+        disableValue();
+        try {
+            while (true) {
+                if (toPrevious()) {
+                    byte[] currentKey = getCurrentKey();
+                    if (currentKey == null) {
+                        return false;
+                    }
+                    if (compareKeysPartially(getCurrentKey(), initialKey) < 0) {
+                        break;
+                    }
+                } else {
+                    return false;
+                }
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns &lt;0 if key1 is less, 0 if equal (at least partially), &gt;0
+     * if key1 is greater.
+     */
+    protected int compareKeysPartially(byte[] key1, byte[] key2) {
+        int length = Math.min(key1.length, key2.length);
+        for (int i=0; i<length; i++) {
+            int a1 = key1[i];
+            int a2 = key2[i];
+            if (a1 != a2) {
+                return (a1 & 0xff) - (a2 & 0xff);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called right before throwing NoSuchElementException. Subclasses may
+     * override to do special checks or throw a different exception.
+     */
+    protected void handleNoSuchElement() throws FetchException {
+    }
+
+    private boolean prefixMatches() throws FetchException {
+        int prefixLen = mPrefixLength;
+        if (prefixLen > 0) {
+            byte[] prefix = mStartBound;
+            byte[] key = getCurrentKey();
+            if (key == null) {
+                return false;
+            }
+            for (int i=0; i<prefixLen; i++) {
+                if (prefix[i] != key[i]) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    // Calls toFirst, but considers start and end bounds.
+    private boolean toBoundedFirst() throws FetchException {
+        if (mStartBound == null) {
+            if (!toFirst()) {
+                return false;
+            }
+        } else {
+            if (!toFirst(mStartBound.clone())) {
+                return false;
+            }
+            if (!mInclusiveStart) {
+                byte[] currentKey = getCurrentKey();
+                if (currentKey == null) {
+                    return false;
+                }
+                if (compareKeysPartially(mStartBound, currentKey) == 0) {
+                    if (!toNextKey()) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        if (mEndBound != null) {
+            byte[] currentKey = getCurrentKey();
+            if (currentKey == null) {
+                return false;
+            }
+            int result = compareKeysPartially(currentKey, mEndBound);
+            if (result >= 0) {
+                if (result > 0 || !mInclusiveEnd) {
+                    return false;
+                }
+            }
+        }
+
+        return prefixMatches();
+    }
+
+    // Calls toLast, but considers start and end bounds. Caller is responsible
+    // for preserving key.
+    private boolean toBoundedLast() throws FetchException {
+        if (mEndBound == null) {
+            if (!toLast()) {
+                return false;
+            }
+        } else {
+            if (!toLast(mEndBound.clone())) {
+                return false;
+            }
+            if (!mInclusiveEnd) {
+                byte[] currentKey = getCurrentKey();
+                if (currentKey == null) {
+                    return false;
+                }
+                if (compareKeysPartially(mEndBound, currentKey) == 0) {
+                    if (!toPreviousKey()) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        if (mStartBound != null) {
+            byte[] currentKey = getCurrentKey();
+            if (currentKey == null) {
+                return false;
+            }
+            int result = compareKeysPartially(currentKey, mStartBound);
+            if (result <= 0) {
+                if (result < 0 || !mInclusiveStart) {
+                    return false;
+                }
+            }
+        }
+
+        return prefixMatches();
+    }
+
+    // Calls toNext, but considers end bound.
+    private boolean toBoundedNext() throws FetchException {
+        if (!toNext()) {
+            return false;
+        }
+
+        if (mEndBound != null) {
+            byte[] currentKey = getCurrentKey();
+            if (currentKey == null) {
+                return false;
+            }
+            int result = compareKeysPartially(currentKey, mEndBound);
+            if (result >= 0) {
+                if (result > 0 || !mInclusiveEnd) {
+                    return false;
+                }
+            }
+        }
+
+        return prefixMatches();
+    }
+
+    // Calls toNext, but considers end bound.
+    private int toBoundedNext(int amount) throws FetchException {
+        if (mEndBound == null) {
+            return toNext(amount);
+        }
+
+        int count = 0;
+
+        disableValue();
+        try {
+            while (amount > 0) {
+                if (!toNext()) {
+                    break;
+                }
+
+                byte[] currentKey = getCurrentKey();
+                if (currentKey == null) {
+                    break;
+                }
+
+                int result = compareKeysPartially(currentKey, mEndBound);
+                if (result >= 0) {
+                    if (result > 0 || !mInclusiveEnd) {
+                        break;
+                    }
+                }
+
+                if (!prefixMatches()) {
+                    break;
+                }
+
+                count++;
+                amount--;
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return count;
+    }
+
+    // Calls toPrevious, but considers start bound.
+    private boolean toBoundedPrevious() throws FetchException {
+        if (!toPrevious()) {
+            return false;
+        }
+
+        if (mStartBound != null) {
+            byte[] currentKey = getCurrentKey();
+            if (currentKey == null) {
+                return false;
+            }
+            int result = compareKeysPartially(currentKey, mStartBound);
+            if (result <= 0) {
+                if (result < 0 || !mInclusiveStart) {
+                    // Too far now, reset to first.
+                    toBoundedFirst();
+                    return false;
+                }
+            }
+        }
+
+        return prefixMatches();
+    }
+
+    // Calls toPrevious, but considers start bound.
+    private int toBoundedPrevious(int amount) throws FetchException {
+        if (mStartBound == null) {
+            return toPrevious(amount);
+        }
+
+        int count = 0;
+
+        disableValue();
+        try {
+            while (amount > 0) {
+                if (!toPrevious()) {
+                    break;
+                }
+
+                byte[] currentKey = getCurrentKey();
+                if (currentKey == null) {
+                    break;
+                }
+
+                int result = compareKeysPartially(currentKey, mStartBound);
+                if (result <= 0) {
+                    if (result < 0 || !mInclusiveStart) {
+                        // Too far now, reset to first.
+                        toBoundedFirst();
+                        break;
+                    }
+                }
+
+                if (!prefixMatches()) {
+                    break;
+                }
+
+                count++;
+                amount--;
+            }
+        } finally {
+            enableKeyAndValue();
+        }
+
+        return count;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
new file mode 100644
index 0000000..a8c7f6b
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.EnumSet;
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.LocalVariable;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.ClassInjector;
+import org.cojen.util.WeakIdentityMap;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.spi.MasterFeature;
+import com.amazon.carbonado.spi.MasterStorableGenerator;
+import com.amazon.carbonado.spi.MasterSupport;
+import com.amazon.carbonado.spi.StorableGenerator;
+import com.amazon.carbonado.spi.TriggerSupport;
+
+import static com.amazon.carbonado.spi.CommonMethodNames.*;
+
+/**
+ * Generates and caches abstract implementations of {@link Storable} types
+ * which are encoded and decoded in a raw format. The generated abstract
+ * classes extend those created by {@link MasterStorableGenerator}.
+ *
+ * @author Brian S O'Neill
+ * @see GenericStorableCodec
+ * @see RawSupport
+ */
+public class RawStorableGenerator {
+    // Note: All generated fields/methods have a "$" character in them to
+    // prevent name collisions with any inherited fields/methods. User storable
+    // properties are defined as fields which exactly match the property
+    // name. We don't want collisions with those either. Legal bean properties
+    // cannot have "$" in them, so there's nothing to worry about.
+
+    /** Name of protected abstract method in generated storable */
+    public static final String
+        ENCODE_KEY_METHOD_NAME = "encodeKey$",
+        DECODE_KEY_METHOD_NAME = "decodeKey$",
+        ENCODE_DATA_METHOD_NAME = "encodeData$",
+        DECODE_DATA_METHOD_NAME = "decodeData$";
+
+    @SuppressWarnings("unchecked")
+    private static Map<Class, Flavors<? extends Storable>> cCache = new WeakIdentityMap();
+
+    /**
+     * Collection of different abstract class flavors.
+     */
+    static class Flavors<S extends Storable> {
+        private Reference<Class<? extends S>> mMasterFlavor;
+
+        private Reference<Class<? extends S>> mNonMasterFlavor;
+
+        /**
+         * May return null.
+         */
+        Class<? extends S> getClass(boolean isMaster) {
+            Reference<Class<? extends S>> ref;
+            if (isMaster) {
+                ref = mMasterFlavor;
+            } else {
+                ref = mNonMasterFlavor;
+            }
+            return (ref != null) ? ref.get() : null;
+        }
+
+        @SuppressWarnings("unchecked")
+        void setClass(Class<? extends S> clazz, boolean isMaster) {
+            Reference<Class<? extends S>> ref = new SoftReference(clazz);
+            if (isMaster) {
+                mMasterFlavor = ref;
+            } else {
+                mNonMasterFlavor = ref;
+            }
+        }
+    }
+
+    // Can't be instantiated or extended
+    private RawStorableGenerator() {
+    }
+
+    /**
+     * Returns an abstract implementation of the given Storable type, which is
+     * fully thread-safe. The Storable type itself may be an interface or a
+     * class. If it is a class, then it must not be final, and it must have a
+     * public, no-arg constructor. Two constructors are defined for the
+     * abstract implementation:
+     *
+     * <pre>
+     * public &lt;init&gt;(RawSupport);
+
+     * public &lt;init&gt;(RawSupport, byte[] key, byte[] value);
+     * </pre>
+     *
+     * <p>Subclasses must implement the following abstract protected methods,
+     * whose exact names are defined by constants in this class:
+     *
+     * <pre>
+     * // Encode the primary key of this storable.
+     * protected abstract byte[] encodeKey();
+     *
+     * // Encode all properties of this storable excluding the primary key.
+     * protected abstract byte[] encodeData();
+     *
+     * // Decode the primary key into properties of this storable.
+     * // Note: this method is also invoked by the four argument constructor.
+     * protected abstract void decodeKey(byte[]);
+     *
+     * // Decode the data into properties of this storable.
+     * // Note: this method is also invoked by the four argument constructor.
+     * protected abstract void decodeData(byte[]);
+     * </pre>
+     *
+     * @param isMaster when true, version properties, sequences, and triggers are managed
+     * @throws IllegalArgumentException if type is null
+     */
+    @SuppressWarnings("unchecked")
+    public static <S extends Storable> Class<? extends S>
+        getAbstractClass(Class<S> type, boolean isMaster)
+        throws SupportException, IllegalArgumentException
+    {
+        synchronized (cCache) {
+            Class<? extends S> abstractClass;
+
+            Flavors<S> flavors = (Flavors<S>) cCache.get(type);
+
+            if (flavors == null) {
+                flavors = new Flavors<S>();
+                cCache.put(type, flavors);
+            } else if ((abstractClass = flavors.getClass(isMaster)) != null) {
+                return abstractClass;
+            }
+
+            abstractClass = generateAbstractClass(type, isMaster);
+            flavors.setClass(abstractClass, isMaster);
+
+            return abstractClass;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <S extends Storable> Class<? extends S>
+        generateAbstractClass(Class<S> storableClass, boolean isMaster)
+        throws SupportException
+    {
+        EnumSet<MasterFeature> features;
+        if (isMaster) {
+            features = EnumSet.of(MasterFeature.VERSIONING,
+                                  MasterFeature.UPDATE_FULL,
+                                  MasterFeature.INSERT_SEQUENCES,
+                                  MasterFeature.INSERT_CHECK_REQUIRED);
+        } else {
+            features = EnumSet.of(MasterFeature.UPDATE_FULL);
+        }
+
+        final Class<? extends S> abstractClass =
+            MasterStorableGenerator.getAbstractClass(storableClass, features);
+
+        ClassInjector ci = ClassInjector.create
+            (storableClass.getName(), abstractClass.getClassLoader());
+
+        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
+        cf.setModifiers(cf.getModifiers().toAbstract(true));
+        cf.markSynthetic();
+        cf.setSourceFile(RawStorableGenerator.class.getName());
+        cf.setTarget("1.5");
+
+        // Declare some types.
+        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
+        final TypeDesc storageType = TypeDesc.forClass(Storage.class);
+        final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class);
+        final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class);
+        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
+        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+
+        // Add constructor that accepts a RawSupport.
+        {
+            TypeDesc[] params = {rawSupportType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeSuperConstructor(new TypeDesc[] {masterSupportType});
+            b.returnVoid();
+        }
+
+        // Add constructor that accepts a RawSupport, an encoded key, and an
+        // encoded data.
+        {
+            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType};
+            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+            CodeBuilder b = new CodeBuilder(mi);
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            b.invokeSuperConstructor(new TypeDesc[] {masterSupportType});
+
+            params = new TypeDesc[] {byteArrayType};
+
+            b.loadThis();
+            b.loadLocal(b.getParameter(1));
+            b.invokeVirtual(DECODE_KEY_METHOD_NAME, null, params);
+
+            b.loadThis();
+            b.loadLocal(b.getParameter(2));
+            b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
+
+            // Indicate that object is clean by calling markAllPropertiesClean.
+            b.loadThis();
+            b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
+
+            b.returnVoid();
+        }
+
+        // Declare protected abstract methods.
+        {
+            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
+                         ENCODE_KEY_METHOD_NAME, byteArrayType, null);
+            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
+                         DECODE_KEY_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
+            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
+                         ENCODE_DATA_METHOD_NAME, byteArrayType, null);
+            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
+                         DECODE_DATA_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
+        }
+
+        // Add required protected doTryLoad_master method, which delegates to RawSupport.
+        {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PROTECTED.toFinal(true),
+                 MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+            mi.addException(TypeDesc.forClass(FetchException.class));
+            CodeBuilder b = new CodeBuilder(mi);
+
+            b.loadThis();
+            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
+            b.checkCast(rawSupportType);
+            b.loadThis();
+            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
+            TypeDesc[] params = {byteArrayType};
+            b.invokeInterface(rawSupportType, "tryLoad", byteArrayType, params);
+            LocalVariable encodedDataVar = b.createLocalVariable(null, byteArrayType);
+            b.storeLocal(encodedDataVar);
+            b.loadLocal(encodedDataVar);
+            Label notNull = b.createLabel();
+            b.ifNullBranch(notNull, false);
+            b.loadConstant(false);
+            b.returnValue(TypeDesc.BOOLEAN);
+            notNull.setLocation();
+            b.loadThis();
+            b.loadLocal(encodedDataVar);
+            params = new TypeDesc[] {byteArrayType};
+            b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
+            b.loadConstant(true);
+            b.returnValue(TypeDesc.BOOLEAN);
+        }
+
+        // Add required protected doTryInsert_master method, which delegates to RawSupport.
+        {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PROTECTED.toFinal(true),
+                 MasterStorableGenerator.DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+            mi.addException(TypeDesc.forClass(PersistException.class));
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // return rawSupport.tryInsert(this, this.encodeKey$(), this.encodeData$());
+            b.loadThis();
+            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
+            b.checkCast(rawSupportType);
+            b.loadThis(); // pass this to tryInsert method
+            b.loadThis();
+            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
+            b.loadThis();
+            b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
+            TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
+            b.invokeInterface(rawSupportType, "tryInsert", TypeDesc.BOOLEAN, params);
+            b.returnValue(TypeDesc.BOOLEAN);
+        }
+
+        // Add required protected doTryUpdate_master method, which delegates to RawSupport.
+        {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PROTECTED.toFinal(true),
+                 MasterStorableGenerator.DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+            mi.addException(TypeDesc.forClass(PersistException.class));
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // rawSupport.store(this, this.encodeKey$(), this.encodeData$());
+            // return true;
+            b.loadThis();
+            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
+            b.checkCast(rawSupportType);
+            b.loadThis(); // pass this to store method
+            b.loadThis();
+            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
+            b.loadThis();
+            b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
+            TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
+            b.invokeInterface(rawSupportType, "store", null, params);
+            b.loadConstant(true);
+            b.returnValue(TypeDesc.BOOLEAN);
+        }
+
+        // Add required protected doTryDelete_master method, which delegates to RawSupport.
+        {
+            MethodInfo mi = cf.addMethod
+                (Modifiers.PROTECTED.toFinal(true),
+                 MasterStorableGenerator.DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+            mi.addException(TypeDesc.forClass(PersistException.class));
+            CodeBuilder b = new CodeBuilder(mi);
+
+            // return rawSupport.tryDelete(this.encodeKey$());
+            b.loadThis();
+            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
+            b.checkCast(rawSupportType);
+            b.loadThis();
+            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
+
+            TypeDesc[] params = {byteArrayType};
+            b.invokeInterface(rawSupportType, "tryDelete", TypeDesc.BOOLEAN, params);
+            b.returnValue(TypeDesc.BOOLEAN);
+        }
+
+        return ci.defineClass(cf);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/RawSupport.java b/src/main/java/com/amazon/carbonado/raw/RawSupport.java
new file mode 100644
index 0000000..f6253c9
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/RawSupport.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.lob.Blob;
+import com.amazon.carbonado.lob.Clob;
+
+import com.amazon.carbonado.spi.MasterSupport;
+
+/**
+ * Provides runtime support for Storable classes generated by {@link RawStorableGenerator}.
+ *
+ * @author Brian S O'Neill
+ */
+public interface RawSupport<S extends Storable> extends MasterSupport<S> {
+    /**
+     * Try to load the entry referenced by the given key, but return null
+     * if not found.
+     *
+     * @param key non-null key to search for
+     * @return non-null value that was found, or null if not found
+     */
+    byte[] tryLoad(byte[] key) throws FetchException;
+
+    /**
+     * Try to insert the entry referenced by the given key with the given
+     * value.
+     *
+     * @param storable storable object that key and value were derived from
+     * @param key non-null key to insert
+     * @param value non-null value to insert
+     * @return false if unique constraint prevents insert
+     */
+    boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException;
+
+    /**
+     * Try to store the entry referenced by the given key with the given
+     * value. If the entry does not exist, insert it. Otherwise, update it.
+     *
+     * @param storable storable object that key and value were derived from
+     * @param key non-null key to store
+     * @param value non-null value to store
+     */
+    void store(S storable, byte[] key, byte[] value) throws PersistException;
+
+    /**
+     * Try to delete the entry referenced by the given key.
+     *
+     * @param key non-null key to delete
+     * @return true if entry existed and is now deleted
+     */
+    boolean tryDelete(byte[] key) throws PersistException;
+
+    /**
+     * Returns the Blob for the given locator, returning null if not found.
+     */
+    Blob getBlob(long locator) throws FetchException;
+
+    /**
+     * Returns the locator for the given Blob, returning zero if null.
+     *
+     * @throws PersistException if blob is unrecognized
+     */
+    long getLocator(Blob blob) throws PersistException;
+
+    /**
+     * Returns the Clob for the given locator, returning null if not found.
+     */
+    Clob getClob(long locator) throws FetchException;
+
+    /**
+     * Returns the locator for the given Clob, returning zero if null.
+     *
+     * @throws PersistException if blob is unrecognized
+     */
+    long getLocator(Clob clob) throws PersistException;
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/RawUtil.java b/src/main/java/com/amazon/carbonado/raw/RawUtil.java
new file mode 100644
index 0000000..f93316a
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/RawUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+/**
+ * Utilities for manipulating binary data.
+ *
+ * @author Brian S O'Neill
+ */
+public class RawUtil {
+    /**
+     * Adds one to an unsigned integer, represented as a byte array. If
+     * overflowed, value in byte array is 0x00, 0x00, 0x00...
+     *
+     * @param value unsigned integer to increment
+     * @return false if overflowed
+     */
+    public static boolean increment(byte[] value) {
+        for (int i=value.length; --i>=0; ) {
+            byte newValue = (byte) ((value[i] & 0xff) + 1);
+            value[i] = newValue;
+            if (newValue != 0) {
+                // No carry bit, so done adding.
+                return true;
+            }
+        }
+        // This point is reached upon overflow.
+        return false;
+    }
+
+    /**
+     * Subtracts one from an unsigned integer, represented as a byte array. If
+     * overflowed, value in byte array is 0xff, 0xff, 0xff...
+     *
+     * @param value unsigned integer to decrement
+     * @return false if overflowed
+     */
+    public static boolean decrement(byte[] value) {
+        for (int i=value.length; --i>=0; ) {
+            byte newValue = (byte) ((value[i] & 0xff) + -1);
+            value[i] = newValue;
+            if (newValue != -1) {
+                // No borrow bit, so done subtracting.
+                return true;
+            }
+        }
+        // This point is reached upon overflow.
+        return false;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/StorableCodec.java b/src/main/java/com/amazon/carbonado/raw/StorableCodec.java
new file mode 100644
index 0000000..b781691
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/StorableCodec.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+
+import com.amazon.carbonado.info.StorableIndex;
+
+/**
+ * Supports encoding and decoding of storables.
+ *
+ * @author Brian S O'Neill
+ * @see StorableCodecFactory
+ */
+public interface StorableCodec<S extends Storable> {
+    /**
+     * Returns the type of Storable produced by this codec.
+     */
+    Class<S> getStorableType();
+
+    /**
+     * Instantiate a Storable with no key or value defined yet.
+     *
+     * @param support binds generated storable with a storage layer
+     */
+    S instantiate(RawSupport<S> support);
+
+    /**
+     * Instantiate a Storable with a specific key and value.
+     *
+     * @param support binds generated storable with a storage layer
+     */
+    S instantiate(RawSupport<S> support, byte[] key, byte[] value)
+        throws FetchException;
+
+    /**
+     * Returns the sequence and directions of properties that make up the
+     * primary key.
+     */
+    StorableIndex<S> getPrimaryKeyIndex();
+
+    /**
+     * Returns the number of prefix bytes in the primary key, which may be
+     * zero.
+     */
+    int getPrimaryKeyPrefixLength();
+
+    /**
+     * Encode a key by extracting all the primary key properties from the given
+     * storable.
+     *
+     * @param storable extract primary key properties from this instance
+     * @return raw search key
+     */
+    byte[] encodePrimaryKey(S storable);
+
+    /**
+     * Encode a key by extracting all the primary key properties from the given
+     * storable.
+     *
+     * @param storable extract primary key properties from this instance
+     * @param rangeStart index of first property to use. Its value must be less
+     * than the count of primary key properties.
+     * @param rangeEnd index of last property to use, exlusive. Its value must
+     * be less than or equal to the count of primary key properties.
+     * @return raw search key
+     */
+    byte[] encodePrimaryKey(S storable, int rangeStart, int rangeEnd);
+
+    /**
+     * Encode a key by extracting all the primary key properties from the given
+     * storable.
+     *
+     * @param values values to build into a key. It must be long enough to
+     * accommodate all primary key properties.
+     * @return raw search key
+     */
+    byte[] encodePrimaryKey(Object[] values);
+
+    /**
+     * Encode a key by extracting all the primary key properties from the given
+     * storable.
+     *
+     * @param values values to build into a key. The length may be less than
+     * the amount of primary key properties used by this factory. It must not
+     * be less than the difference between rangeStart and rangeEnd.
+     * @param rangeStart index of first property to use. Its value must be less
+     * than the count of primary key properties.
+     * @param rangeEnd index of last property to use, exlusive. Its value must
+     * be less than or equal to the count of primary key properties.
+     * @return raw search key
+     */
+    byte[] encodePrimaryKey(Object[] values, int rangeStart, int rangeEnd);
+
+    /**
+     * Encode the primary key for when there are no values, but there may be a
+     * prefix. Returned value may be null if no prefix is defined.
+     */
+    byte[] encodePrimaryKeyPrefix();
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java
new file mode 100644
index 0000000..1b19490
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.layout.Layout;
+
+/**
+ * Factory for creating instances of {@link StorableCodec}.
+ *
+ * @author Brian S O'Neill
+ */
+public interface StorableCodecFactory {
+    /**
+     * Returns the preferred storage/database name for the given type. Return
+     * null to let repository decide.
+     *
+     * @throws SupportException if type is not supported
+     */
+    String getStorageName(Class<? extends Storable> type) throws SupportException;
+
+    /**
+     * @param type type of storable to create codec for
+     * @param pkIndex suggested index for primary key (optional)
+     * @param isMaster when true, version properties and sequences are managed
+     * @param layout when non-null, attempt to encode a storable layout
+     * generation value in each storable
+     * @throws SupportException if type is not supported
+     */
+    <S extends Storable> StorableCodec<S> createCodec(Class<S> type,
+                                                      StorableIndex pkIndex,
+                                                      boolean isMaster,
+                                                      Layout layout)
+        throws SupportException;
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java b/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java
new file mode 100644
index 0000000..e3a18b1
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.raw;
+
+import java.lang.reflect.Method;
+
+import org.cojen.classfile.CodeAssembler;
+import org.cojen.classfile.TypeDesc;
+
+import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.lob.Lob;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+public class StorablePropertyInfo implements GenericPropertyInfo {
+    private final StorableProperty<?> mProp;
+    private final TypeDesc mPropertyType;
+    private final TypeDesc mStorageType;
+    private final Method mFromStorage;
+    private final Method mToStorage;
+
+    StorablePropertyInfo(StorableProperty<?> property) {
+        this(property, null, null, null);
+    }
+
+    StorablePropertyInfo(StorableProperty<?> property,
+                         Class<?> storageType, Method fromStorage, Method toStorage) {
+        mProp = property;
+        mPropertyType = TypeDesc.forClass(property.getType());
+        if (storageType == null) {
+            mStorageType = mPropertyType;
+        } else {
+            mStorageType = TypeDesc.forClass(storageType);
+        }
+        mFromStorage = fromStorage;
+        mToStorage = toStorage;
+    }
+
+    public String getPropertyName() {
+        return mProp.getName();
+    }
+
+    public TypeDesc getPropertyType() {
+        return mPropertyType;
+    }
+
+    public TypeDesc getStorageType() {
+        return mStorageType;
+    }
+
+    public boolean isNullable() {
+        return mProp.isNullable();
+    }
+
+    public boolean isLob() {
+        Class clazz = mPropertyType.toClass();
+        return clazz != null && Lob.class.isAssignableFrom(clazz);
+    }
+
+    public Method getFromStorageAdapter() {
+        return mFromStorage;
+    }
+
+    public Method getToStorageAdapter() {
+        return mToStorage;
+    }
+
+    public String getReadMethodName() {
+        return mProp.getReadMethodName();
+    }
+
+    public void addInvokeReadMethod(CodeAssembler a) {
+        a.invoke(mProp.getReadMethod());
+    }
+
+    public void addInvokeReadMethod(CodeAssembler a, TypeDesc instanceType) {
+        Class clazz = instanceType.toClass();
+        if (clazz == null) {
+            // Can't know if instance should be invoked as an interface or as a
+            // virtual method.
+            throw new IllegalArgumentException("Instance type has no known class");
+        }
+        if (clazz.isInterface()) {
+            a.invokeInterface(instanceType, getReadMethodName(), getPropertyType(), null);
+        } else {
+            a.invokeVirtual(instanceType, getReadMethodName(), getPropertyType(), null);
+        }
+    }
+
+    public String getWriteMethodName() {
+        return mProp.getWriteMethodName();
+    }
+
+    public void addInvokeWriteMethod(CodeAssembler a) {
+        a.invoke(mProp.getWriteMethod());
+    }
+
+    public void addInvokeWriteMethod(CodeAssembler a, TypeDesc instanceType) {
+        Class clazz = instanceType.toClass();
+        if (clazz == null) {
+            // Can't know if instance should be invoked as an interface or as a
+            // virtual method.
+            throw new IllegalArgumentException("Instance type has no known class");
+        }
+        if (clazz.isInterface()) {
+            a.invokeInterface(instanceType,
+                              getWriteMethodName(), null, new TypeDesc[] {getPropertyType()});
+        } else {
+            a.invokeVirtual(instanceType,
+                            getWriteMethodName(), null, new TypeDesc[] {getPropertyType()});
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/raw/package-info.java b/src/main/java/com/amazon/carbonado/raw/package-info.java
new file mode 100644
index 0000000..c92afd8
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/raw/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides support for repositories that encode/decode storables in a raw
+ * binary format.
+ */
+package com.amazon.carbonado.raw;
diff --git a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java b/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java
index 1f6ab6e..83621e9 100644
--- a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java
+++ b/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java
@@ -52,7 +52,7 @@ import com.amazon.carbonado.info.StorableProperty;
 
 import static com.amazon.carbonado.spi.CommonMethodNames.*;
 
-import com.amazon.carbonado.spi.raw.GenericEncodingStrategy;
+import com.amazon.carbonado.raw.GenericEncodingStrategy;
 
 /**
  * Support for general-purpose serialization of storables.
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java
deleted file mode 100644
index f908635..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.util.Map;
-
-import org.cojen.classfile.ClassFile;
-import org.cojen.classfile.CodeBuilder;
-import org.cojen.classfile.MethodInfo;
-import org.cojen.classfile.Modifiers;
-import org.cojen.classfile.TypeDesc;
-import org.cojen.util.ClassInjector;
-import org.cojen.util.WeakIdentityMap;
-
-import com.amazon.carbonado.CorruptEncodingException;
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.Direction;
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.info.StorableIntrospector;
-import com.amazon.carbonado.info.StorableProperty;
-
-import com.amazon.carbonado.util.QuickConstructorGenerator;
-
-/**
- * Allows codecs to be defined for storables that have a custom encoding.
- *
- * @author Brian S O'Neill
- * @see CustomStorableCodecFactory
- */
-public abstract class CustomStorableCodec<S extends Storable> implements StorableCodec<S> {
-    // Generated storable instances maintain a reference to user-defined
-    // concrete subclass of this class.
-    private static final String CUSTOM_STORABLE_CODEC_FIELD_NAME = "customStorableCodec$";
-
-    @SuppressWarnings("unchecked")
-    private static Map<Class, RawStorableGenerator.Flavors<? extends Storable>> cCache =
-        new WeakIdentityMap();
-
-    /**
-     * Returns a storable implementation that calls into CustomStorableCodec
-     * implementation for encoding and decoding.
-     */
-    @SuppressWarnings("unchecked")
-    static <S extends Storable> Class<? extends S>
-        getStorableClass(Class<S> type, boolean isMaster)
-        throws SupportException
-    {
-        synchronized (cCache) {
-            Class<? extends S> storableClass;
-
-            RawStorableGenerator.Flavors<S> flavors =
-                (RawStorableGenerator.Flavors<S>) cCache.get(type);
-
-            if (flavors == null) {
-                flavors = new RawStorableGenerator.Flavors<S>();
-                cCache.put(type, flavors);
-            } else if ((storableClass = flavors.getClass(isMaster)) != null) {
-                return storableClass;
-            }
-
-            storableClass = generateStorableClass(type, isMaster);
-            flavors.setClass(storableClass, isMaster);
-
-            return storableClass;
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <S extends Storable> Class<? extends S>
-        generateStorableClass(Class<S> type, boolean isMaster)
-        throws SupportException
-    {
-        final Class<? extends S> abstractClass =
-            RawStorableGenerator.getAbstractClass(type, isMaster);
-
-        ClassInjector ci = ClassInjector.create
-            (type.getName(), abstractClass.getClassLoader());
-
-        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
-        cf.markSynthetic();
-        cf.setSourceFile(CustomStorableCodec.class.getName());
-        cf.setTarget("1.5");
-
-        // Declare some types.
-        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-        final TypeDesc[] byteArrayParam = {byteArrayType};
-        final TypeDesc customStorableCodecType = TypeDesc.forClass(CustomStorableCodec.class);
-
-        // Add field for saving reference to concrete CustomStorableCodec.
-        cf.addField(Modifiers.PRIVATE.toFinal(true),
-                    CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-
-        // Add constructor that accepts a RawSupport and a CustomStorableCodec.
-        {
-            TypeDesc[] params = {rawSupportType, customStorableCodecType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // Call super class constructor.
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            params = new TypeDesc[] {rawSupportType};
-            b.invokeSuperConstructor(params);
-
-            // Set private reference to customStorableCodec.
-            b.loadThis();
-            b.loadLocal(b.getParameter(1));
-            b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-
-            b.returnVoid();
-        }
-
-        // Add constructor that accepts a RawSupport, an encoded key, an
-        // encoded data, and a CustomStorableCodec.
-        {
-            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType,
-                                 customStorableCodecType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // Set private reference to customStorableCodec before calling
-            // super constructor. This is necessary because super class
-            // constructor will call our decode methods, which need the
-            // customStorableCodec. This trick is not allowed in Java, but the
-            // virtual machine verifier allows it.
-            b.loadThis();
-            b.loadLocal(b.getParameter(3));
-            b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-
-            // Now call super class constructor.
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.loadLocal(b.getParameter(1));
-            b.loadLocal(b.getParameter(2));
-            params = new TypeDesc[] {rawSupportType, byteArrayType, byteArrayType};
-            b.invokeSuperConstructor(params);
-
-            b.returnVoid();
-        }
-
-        // Implement protected abstract methods inherited from parent class.
-
-        // byte[] encodeKey()
-        {
-            // Encode the primary key into a byte array that supports correct
-            // ordering. No special key comparator is needed.
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.ENCODE_KEY_METHOD_NAME,
-                                         byteArrayType, null);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            b.loadThis();
-            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-            TypeDesc[] params = {TypeDesc.forClass(Storable.class)};
-            b.loadThis();
-            b.invokeVirtual(customStorableCodecType, "encodePrimaryKey", byteArrayType, params);
-            b.returnValue(byteArrayType);
-        }
-
-        // byte[] encodeData()
-        {
-            // Encoding non-primary key data properties.
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.ENCODE_DATA_METHOD_NAME,
-                                         byteArrayType, null);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            b.loadThis();
-            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-            TypeDesc[] params = {TypeDesc.forClass(Storable.class)};
-            b.loadThis();
-            b.invokeVirtual(customStorableCodecType, "encodeData", byteArrayType, params);
-            b.returnValue(byteArrayType);
-        }
-
-        // void decodeKey(byte[])
-        {
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.DECODE_KEY_METHOD_NAME,
-                                         null, byteArrayParam);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            b.loadThis();
-            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-            TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType};
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeVirtual(customStorableCodecType, "decodePrimaryKey", null, params);
-            b.returnVoid();
-        }
-
-        // void decodeData(byte[])
-        {
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.DECODE_DATA_METHOD_NAME,
-                                         null, byteArrayParam);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            b.loadThis();
-            b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType);
-            TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType};
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeVirtual(customStorableCodecType, "decodeData", null, params);
-            b.returnVoid();
-        }
-
-        return ci.defineClass(cf);
-    }
-
-    private final Class<S> mType;
-    private final int mPkPropertyCount;
-    private final InstanceFactory mInstanceFactory;
-
-    public interface InstanceFactory {
-        Storable instantiate(RawSupport support, CustomStorableCodec codec);
-
-        Storable instantiate(RawSupport support, byte[] key, byte[] value,
-                             CustomStorableCodec codec)
-            throws FetchException;
-    }
-
-    /**
-     * @param isMaster when true, version properties and sequences are managed
-     * @throws SupportException if Storable is not supported
-     */
-    public CustomStorableCodec(Class<S> type, boolean isMaster) throws SupportException {
-        mType = type;
-        mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount();
-        Class<? extends S> storableClass = getStorableClass(type, isMaster);
-        mInstanceFactory = QuickConstructorGenerator
-            .getInstance(storableClass, InstanceFactory.class);
-    }
-
-    public Class<S> getStorableType() {
-        return mType;
-    }
-
-    @SuppressWarnings("unchecked")
-    public S instantiate(RawSupport<S> support) {
-        return (S) mInstanceFactory.instantiate(support, this);
-    }
-
-    @SuppressWarnings("unchecked")
-    public S instantiate(RawSupport<S> support, byte[] key, byte[] value)
-        throws FetchException
-    {
-        return (S) mInstanceFactory.instantiate(support, key, value, this);
-    }
-
-    public byte[] encodePrimaryKey(S storable) {
-        return encodePrimaryKey(storable, 0, mPkPropertyCount);
-    }
-
-    public byte[] encodePrimaryKey(Object[] values) {
-        return encodePrimaryKey(values, 0, mPkPropertyCount);
-    }
-
-    /**
-     * Convenient access to all the storable properties.
-     */
-    public Map<String, ? extends StorableProperty<S>> getAllProperties() {
-        return StorableIntrospector.examine(getStorableType()).getAllProperties();
-    }
-
-    /**
-     * Convenient way to define the clustered primary key index
-     * descriptor. Direction can be specified by prefixing the property name
-     * with a '+' or '-'. If unspecified, direction is assumed to be ascending.
-     */
-    @SuppressWarnings("unchecked")
-    public StorableIndex<S> buildPkIndex(String... propertyNames) {
-        Map<String, ? extends StorableProperty<S>> map = getAllProperties();
-        int length = propertyNames.length;
-        StorableProperty<S>[] properties = new StorableProperty[length];
-        Direction[] directions = new Direction[length];
-        for (int i=0; i<length; i++) {
-            String name = propertyNames[i];
-            char c = name.charAt(0);
-            Direction dir = Direction.fromCharacter(c);
-            if (dir != Direction.UNSPECIFIED || c == Direction.UNSPECIFIED.toCharacter()) {
-                name = name.substring(1);
-            } else {
-                // Default to ascending if not specified.
-                dir = Direction.ASCENDING;
-            }
-            if ((properties[i] = map.get(name)) == null) {
-                throw new IllegalArgumentException("Unknown property: " + name);
-            }
-            directions[i] = dir;
-        }
-        return new StorableIndex<S>(properties, directions, true, true);
-    }
-
-    /**
-     * Decode the primary key into properties of the storable.
-     */
-    public abstract void decodePrimaryKey(S storable, byte[] bytes)
-        throws CorruptEncodingException;
-
-    /**
-     * Encode all properties of the storable excluding the primary key.
-     */
-    public abstract byte[] encodeData(S storable);
-
-    /**
-     * Decode the data into properties of the storable.
-     */
-    public abstract void decodeData(S storable, byte[] bytes)
-        throws CorruptEncodingException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java
deleted file mode 100644
index 64d19e8..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.layout.Layout;
-
-/**
- * Factory for custom storable codecs.
- *
- * @author Brian S O'Neill
- */
-public abstract class CustomStorableCodecFactory implements StorableCodecFactory {
-    public CustomStorableCodecFactory() {
-    }
-
-    /**
-     * Returns null to let repository decide what the name should be.
-     */
-    public String getStorageName(Class<? extends Storable> type) throws SupportException {
-        return null;
-    }
-
-    /**
-     * @param type type of storable to create codec for
-     * @param pkIndex ignored
-     * @param isMaster when true, version properties and sequences are managed
-     * @param layout when non-null, attempt to encode a storable layout
-     * generation value in each storable
-     * @throws SupportException if type is not supported
-     */
-    public <S extends Storable> CustomStorableCodec<S> createCodec(Class<S> type,
-                                                                   StorableIndex pkIndex,
-                                                                   boolean isMaster,
-                                                                   Layout layout)
-        throws SupportException
-    {
-        return createCodec(type, isMaster, layout);
-    }
-
-    /**
-     * @param type type of storable to create codec for
-     * @param isMaster when true, version properties and sequences are managed
-     * @param layout when non-null, attempt to encode a storable layout
-     * generation value in each storable
-     * @throws SupportException if type is not supported
-     */
-    protected abstract <S extends Storable> CustomStorableCodec<S>
-        createCodec(Class<S> type, boolean isMaster, Layout layout)
-        throws SupportException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java b/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java
deleted file mode 100644
index 66f2f2f..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.CorruptEncodingException;
-
-import static com.amazon.carbonado.spi.raw.DataEncoder.*;
-
-/**
- * A very low-level class that decodes key components encoded by methods of
- * {@link DataEncoder}.
- *
- * @author Brian S O'Neill
- */
-public class DataDecoder {
-    static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
-
-    /**
-     * Decodes a signed integer from exactly 4 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed integer value
-     */
-    public static int decodeInt(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int value = (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) |
-                ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff);
-            return value ^ 0x80000000;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Integer object from exactly 1 or 5 bytes. If null is
-     * returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Integer object or null
-     */
-    public static Integer decodeIntegerObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeInt(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed long from exactly 8 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed long value
-     */
-    public static long decodeLong(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return
-                (((long)(((src[srcOffset    ]       ) << 24) |
-                         ((src[srcOffset + 1] & 0xff) << 16) |
-                         ((src[srcOffset + 2] & 0xff) << 8 ) |
-                         ((src[srcOffset + 3] & 0xff)      )) ^ 0x80000000 ) << 32) |
-                (((long)(((src[srcOffset + 4]       ) << 24) |
-                         ((src[srcOffset + 5] & 0xff) << 16) |
-                         ((src[srcOffset + 6] & 0xff) << 8 ) |
-                         ((src[srcOffset + 7] & 0xff)      )) & 0xffffffffL)      );
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Long object from exactly 1 or 9 bytes. If null is
-     * returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Long object or null
-     */
-    public static Long decodeLongObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeLong(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed byte from exactly 1 byte.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed byte value
-     */
-    public static byte decodeByte(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (byte)(src[srcOffset] ^ 0x80);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Byte object from exactly 1 or 2 bytes. If null is
-     * returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Byte object or null
-     */
-    public static Byte decodeByteObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeByte(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed short from exactly 2 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed short value
-     */
-    public static short decodeShort(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x8000);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Short object from exactly 1 or 3 bytes. If null is
-     * returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Short object or null
-     */
-    public static Short decodeShortObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeShort(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a char from exactly 2 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return char value
-     */
-    public static char decodeChar(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (char)((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff));
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a Character object from exactly 1 or 3 bytes. If null is
-     * returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Character object or null
-     */
-    public static Character decodeCharacterObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeChar(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a boolean from exactly 1 byte.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return boolean value
-     */
-    public static boolean decodeBoolean(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return src[srcOffset] == (byte)128;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a Boolean object from exactly 1 byte.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Boolean object or null
-     */
-    public static Boolean decodeBooleanObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            switch (src[srcOffset]) {
-            case NULL_BYTE_LOW: case NULL_BYTE_HIGH:
-                return null;
-            case (byte)128:
-                return Boolean.TRUE;
-            default:
-                return Boolean.FALSE;
-            }
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a float from exactly 4 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return float value
-     */
-    public static float decodeFloat(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        int bits = decodeFloatBits(src, srcOffset);
-        bits ^= (bits < 0) ? 0x80000000 : 0xffffffff;
-        return Float.intBitsToFloat(bits);
-    }
-
-    /**
-     * Decodes a Float object from exactly 4 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Float object or null
-     */
-    public static Float decodeFloatObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        int bits = decodeFloatBits(src, srcOffset);
-        bits ^= (bits < 0) ? 0x80000000 : 0xffffffff;
-        return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits);
-    }
-
-    protected static int decodeFloatBits(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) |
-                ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a double from exactly 8 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return double value
-     */
-    public static double decodeDouble(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        long bits = decodeDoubleBits(src, srcOffset);
-        bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL;
-        return Double.longBitsToDouble(bits);
-    }
-
-    /**
-     * Decodes a Double object from exactly 8 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Double object or null
-     */
-    public static Double decodeDoubleObj(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        long bits = decodeDoubleBits(src, srcOffset);
-        bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL;
-        return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits);
-    }
-
-    protected static long decodeDoubleBits(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return
-                (((long)(((src[srcOffset    ]       ) << 24) |
-                         ((src[srcOffset + 1] & 0xff) << 16) |
-                         ((src[srcOffset + 2] & 0xff) << 8 ) |
-                         ((src[srcOffset + 3] & 0xff)      )) ) << 32) |
-                (((long)(((src[srcOffset + 4]       ) << 24) |
-                         ((src[srcOffset + 5] & 0xff) << 16) |
-                         ((src[srcOffset + 6] & 0xff) << 8 ) |
-                         ((src[srcOffset + 7] & 0xff)      )) & 0xffffffffL));
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes the given byte array.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded byte array is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decode(byte[] src, int srcOffset, byte[][] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            final int originalOffset = srcOffset;
-
-            int b = src[srcOffset++] & 0xff;
-            if (b >= 0xf8) {
-                valueRef[0] = null;
-                return 1;
-            }
-
-            int valueLength;
-            if (b <= 0x7f) {
-                valueLength = b;
-            } else if (b <= 0xbf) {
-                valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff);
-            } else if (b <= 0xdf) {
-                valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) |
-                    (src[srcOffset++] & 0xff);
-            } else if (b <= 0xef) {
-                valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) |
-                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
-            } else {
-                valueLength = ((b & 0x07) << 24) | ((src[srcOffset++] & 0xff) << 16) |
-                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
-            }
-
-            if (valueLength == 0) {
-                valueRef[0] = EMPTY_BYTE_ARRAY;
-            } else {
-                byte[] value = new byte[valueLength];
-                System.arraycopy(src, srcOffset, value, 0, valueLength);
-                valueRef[0]= value;
-            }
-
-            return srcOffset - originalOffset + valueLength;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes an encoded string from the given byte array.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded string is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decodeString(byte[] src, int srcOffset, String[] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            final int originalOffset = srcOffset;
-
-            int b = src[srcOffset++] & 0xff;
-            if (b >= 0xf8) {
-                valueRef[0] = null;
-                return 1;
-            }
-
-            int valueLength;
-            if (b <= 0x7f) {
-                valueLength = b;
-            } else if (b <= 0xbf) {
-                valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff);
-            } else if (b <= 0xdf) {
-                valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) |
-                    (src[srcOffset++] & 0xff);
-            } else if (b <= 0xef) {
-                valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) |
-                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
-            } else {
-                valueLength = ((src[srcOffset++] & 0xff) << 24) |
-                    ((src[srcOffset++] & 0xff) << 16) |
-                    ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff);
-            }
-
-            if (valueLength == 0) {
-                valueRef[0] = "";
-                return srcOffset - originalOffset;
-            }
-
-            char[] value = new char[valueLength];
-            int valueOffset = 0;
-
-            while (valueOffset < valueLength) {
-                int c = src[srcOffset++] & 0xff;
-                switch (c >> 5) {
-                case 0: case 1: case 2: case 3:
-                    // 0xxxxxxx
-                    value[valueOffset++] = (char)c;
-                    break;
-                case 4: case 5:
-                    // 10xxxxxx xxxxxxxx
-                    value[valueOffset++] = (char)(((c & 0x3f) << 8) | (src[srcOffset++] & 0xff));
-                    break;
-                case 6:
-                    // 110xxxxx xxxxxxxx xxxxxxxx
-                    c = ((c & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8)
-                        | (src[srcOffset++] & 0xff);
-                    if (c >= 0x10000) {
-                        // Split into surrogate pair.
-                        c -= 0x10000;
-                        value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff));
-                        value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff));
-                    } else {
-                        value[valueOffset++] = (char)c;
-                    }
-                    break;
-                default:
-                    // 111xxxxx
-                    // Illegal.
-                    throw new CorruptEncodingException
-                        ("Corrupt encoded string data (source offset = "
-                         + (srcOffset - 1) + ')');
-                }
-            }
-
-            valueRef[0] = new String(value);
-
-            return srcOffset - originalOffset;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * DataEncoder#encodeSingle}.
-     *
-     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
-     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
-     */
-    public static byte[] decodeSingle(byte[] src, int prefixPadding, int suffixPadding)
-        throws CorruptEncodingException
-    {
-        try {
-            int length = src.length - suffixPadding - prefixPadding;
-            if (length == 0) {
-                return EMPTY_BYTE_ARRAY;
-            }
-            if (prefixPadding <= 0 && suffixPadding <= 0) {
-                return src;
-            }
-            byte[] dst = new byte[length];
-            System.arraycopy(src, prefixPadding, dst, 0, length);
-            return dst;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * DataEncoder#encodeSingleNullable}.
-     */
-    public static byte[] decodeSingleNullable(byte[] src) throws CorruptEncodingException {
-        return decodeSingleNullable(src, 0, 0);
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * DataEncoder#encodeSingleNullable}.
-     *
-     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
-     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
-     */
-    public static byte[] decodeSingleNullable(byte[] src, int prefixPadding, int suffixPadding)
-        throws CorruptEncodingException
-    {
-        try {
-            byte b = src[prefixPadding];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            int length = src.length - suffixPadding - 1 - prefixPadding;
-            if (length == 0) {
-                return EMPTY_BYTE_ARRAY;
-            }
-            byte[] value = new byte[length];
-            System.arraycopy(src, 1 + prefixPadding, value, 0, length);
-            return value;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java b/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java
deleted file mode 100644
index 15b8dca..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-/**
- * A very low-level class that supports encoding of primitive data. For
- * encoding data into keys, see {@link KeyEncoder}.
- *
- * @author Brian S O'Neill
- * @see DataDecoder
- */
-public class DataEncoder {
-    // Note: Most of these methods are inherited by KeyEncoder, which is why
-    // they are encoded for supporting proper ordering.
-
-    /** Byte to use for null, low ordering */
-    static final byte NULL_BYTE_LOW = 0;
-
-    /** Byte to use for null, high ordering */
-    static final byte NULL_BYTE_HIGH = (byte)~NULL_BYTE_LOW;
-
-    /** Byte to use for not-null, low ordering */
-    static final byte NOT_NULL_BYTE_HIGH = (byte)128;
-
-    /** Byte to use for not-null, high ordering */
-    static final byte NOT_NULL_BYTE_LOW = (byte)~NOT_NULL_BYTE_HIGH;
-
-    static final byte[] NULL_BYTE_ARRAY_HIGH = {NULL_BYTE_HIGH};
-    static final byte[] NULL_BYTE_ARRAY_LOW = {NULL_BYTE_LOW};
-    static final byte[] NOT_NULL_BYTE_ARRAY_HIGH = {NOT_NULL_BYTE_HIGH};
-    static final byte[] NOT_NULL_BYTE_ARRAY_LOW = {NOT_NULL_BYTE_LOW};
-
-    /**
-     * Encodes the given signed integer into exactly 4 bytes.
-     *
-     * @param value signed integer value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(int value, byte[] dst, int dstOffset) {
-        value ^= 0x80000000;
-        dst[dstOffset    ] = (byte)(value >> 24);
-        dst[dstOffset + 1] = (byte)(value >> 16);
-        dst[dstOffset + 2] = (byte)(value >> 8);
-        dst[dstOffset + 3] = (byte)value;
-    }
-
-    /**
-     * Encodes the given signed Integer object into exactly 1 or 5 bytes. If
-     * the Integer object is never expected to be null, consider encoding as an
-     * int primitive.
-     *
-     * @param value optional signed Integer value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(Integer value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
-            encode(value.intValue(), dst, dstOffset + 1);
-            return 5;
-        }
-    }
-
-    /**
-     * Encodes the given signed long into exactly 8 bytes.
-     *
-     * @param value signed long value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(long value, byte[] dst, int dstOffset) {
-        int w = ((int)(value >> 32)) ^ 0x80000000;
-        dst[dstOffset    ] = (byte)(w >> 24);
-        dst[dstOffset + 1] = (byte)(w >> 16);
-        dst[dstOffset + 2] = (byte)(w >> 8);
-        dst[dstOffset + 3] = (byte)w;
-        w = (int)value;
-        dst[dstOffset + 4] = (byte)(w >> 24);
-        dst[dstOffset + 5] = (byte)(w >> 16);
-        dst[dstOffset + 6] = (byte)(w >> 8);
-        dst[dstOffset + 7] = (byte)w;
-    }
-
-    /**
-     * Encodes the given signed Long object into exactly 1 or 9 bytes. If the
-     * Long object is never expected to be null, consider encoding as a long
-     * primitive.
-     *
-     * @param value optional signed Long value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(Long value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
-            encode(value.longValue(), dst, dstOffset + 1);
-            return 9;
-        }
-    }
-
-    /**
-     * Encodes the given signed byte into exactly 1 byte.
-     *
-     * @param value signed byte value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(byte value, byte[] dst, int dstOffset) {
-        dst[dstOffset] = (byte)(value ^ 0x80);
-    }
-
-    /**
-     * Encodes the given signed Byte object into exactly 1 or 2 bytes. If the
-     * Byte object is never expected to be null, consider encoding as a byte
-     * primitive.
-     *
-     * @param value optional signed Byte value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(Byte value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
-            dst[dstOffset + 1] = (byte)(value ^ 0x80);
-            return 2;
-        }
-    }
-
-    /**
-     * Encodes the given signed short into exactly 2 bytes.
-     *
-     * @param value signed short value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(short value, byte[] dst, int dstOffset) {
-        value ^= 0x8000;
-        dst[dstOffset    ] = (byte)(value >> 8);
-        dst[dstOffset + 1] = (byte)value;
-    }
-
-    /**
-     * Encodes the given signed Short object into exactly 1 or 3 bytes. If the
-     * Short object is never expected to be null, consider encoding as a short
-     * primitive.
-     *
-     * @param value optional signed Short value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(Short value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
-            encode(value.shortValue(), dst, dstOffset + 1);
-            return 3;
-        }
-    }
-
-    /**
-     * Encodes the given character into exactly 2 bytes.
-     *
-     * @param value character value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(char value, byte[] dst, int dstOffset) {
-        dst[dstOffset    ] = (byte)(value >> 8);
-        dst[dstOffset + 1] = (byte)value;
-    }
-
-    /**
-     * Encodes the given Character object into exactly 1 or 3 bytes. If the
-     * Character object is never expected to be null, consider encoding as a
-     * char primitive.
-     *
-     * @param value optional Character value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(Character value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_HIGH;
-            encode(value.charValue(), dst, dstOffset + 1);
-            return 3;
-        }
-    }
-
-    /**
-     * Encodes the given boolean into exactly 1 byte.
-     *
-     * @param value boolean value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(boolean value, byte[] dst, int dstOffset) {
-        dst[dstOffset] = value ? (byte)128 : (byte)127;
-    }
-
-    /**
-     * Encodes the given Boolean object into exactly 1 byte.
-     *
-     * @param value optional Boolean value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(Boolean value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-        } else {
-            dst[dstOffset] = value.booleanValue() ? (byte)128 : (byte)127;
-        }
-    }
-
-    /**
-     * Encodes the given float into exactly 4 bytes.
-     *
-     * @param value float value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(float value, byte[] dst, int dstOffset) {
-        int bits = Float.floatToIntBits(value);
-        bits ^= (bits < 0) ? 0xffffffff : 0x80000000;
-        dst[dstOffset    ] = (byte)(bits >> 24);
-        dst[dstOffset + 1] = (byte)(bits >> 16);
-        dst[dstOffset + 2] = (byte)(bits >> 8);
-        dst[dstOffset + 3] = (byte)bits;
-    }
-
-    /**
-     * Encodes the given Float object into exactly 4 bytes. A non-canonical NaN
-     * value is used to represent null.
-     *
-     * @param value optional Float value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(Float value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            encode(0x7fffffff, dst, dstOffset);
-        } else {
-            encode(value.floatValue(), dst, dstOffset);
-        }
-    }
-
-    /**
-     * Encodes the given double into exactly 8 bytes.
-     *
-     * @param value double value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(double value, byte[] dst, int dstOffset) {
-        long bits = Double.doubleToLongBits(value);
-        bits ^= (bits < 0) ? 0xffffffffffffffffL : 0x8000000000000000L;
-        int w = (int)(bits >> 32);
-        dst[dstOffset    ] = (byte)(w >> 24);
-        dst[dstOffset + 1] = (byte)(w >> 16);
-        dst[dstOffset + 2] = (byte)(w >> 8);
-        dst[dstOffset + 3] = (byte)w;
-        w = (int)bits;
-        dst[dstOffset + 4] = (byte)(w >> 24);
-        dst[dstOffset + 5] = (byte)(w >> 16);
-        dst[dstOffset + 6] = (byte)(w >> 8);
-        dst[dstOffset + 7] = (byte)w;
-    }
-
-    /**
-     * Encodes the given Double object into exactly 8 bytes. A non-canonical
-     * NaN value is used to represent null.
-     *
-     * @param value optional Double value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encode(Double value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            encode(0x7fffffffffffffffL, dst, dstOffset);
-        } else {
-            encode(value.doubleValue(), dst, dstOffset);
-        }
-    }
-
-    /**
-     * Encodes the given optional byte array into a variable amount of
-     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
-     * the amount written can be determined by calling calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(byte[] value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        }
-        return encode(value, 0, value.length, dst, dstOffset);
-    }
-
-    /**
-     * Encodes the given optional byte array into a variable amount of
-     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
-     * the amount written can be determined by calling calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param valueOffset offset into byte array
-     * @param valueLength length of data in byte array
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(byte[] value, int valueOffset, int valueLength,
-                             byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        }
-
-        // Write the value length first, in a variable amount of bytes.
-        int amt = writeLength(valueLength, dst, dstOffset);
-
-        // Now write the value.
-        System.arraycopy(value, valueOffset, dst, dstOffset + amt, valueLength);
-
-        return amt + valueLength;
-    }
-
-    /**
-     * Returns the amount of bytes required to encode the given byte array.
-     *
-     * @param value byte array value to encode, may be null
-     * @return amount of bytes needed to encode
-     */
-    public static int calculateEncodedLength(byte[] value) {
-        return value == null ? 1 : calculateEncodedLength(value, 0, value.length);
-    }
-
-    /**
-     * Returns the amount of bytes required to encode the given byte array.
-     *
-     * @param value byte array value to encode, may be null
-     * @param valueOffset offset into byte array
-     * @param valueLength length of data in byte array
-     * @return amount of bytes needed to encode
-     */
-    public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) {
-        if (value == null) {
-            return 1;
-        } else if (valueLength < 128) {
-            return 1 + valueLength;
-        } else if (valueLength < 16384) {
-            return 2 + valueLength;
-        } else if (valueLength < 2097152) {
-            return 3 + valueLength;
-        } else if (valueLength < 268435456) {
-            return 4 + valueLength;
-        } else {
-            return 5 + valueLength;
-        }
-    }
-
-    /**
-     * Encodes the given optional String into a variable amount of bytes. The
-     * amount written can be determined by calling
-     * calculateEncodedStringLength.
-     * <p>
-     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
-     * characters are written in one byte. This encoding is more efficient than
-     * UTF-8, but it isn't compatible with UTF-8.
-     *
-     * @param value String value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(String value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        }
-        final int originalOffset = dstOffset;
-
-        int valueLength = value.length();
-
-        // Write the value length first, in a variable amount of bytes.
-        dstOffset += writeLength(valueLength, dst, dstOffset);
-
-        for (int i = 0; i < valueLength; i++) {
-            int c = value.charAt(i);
-            if (c <= 0x7f) {
-                dst[dstOffset++] = (byte)c;
-            } else if (c <= 0x3fff) {
-                dst[dstOffset++] = (byte)(0x80 | (c >> 8));
-                dst[dstOffset++] = (byte)(c & 0xff);
-            } else {
-                if (c >= 0xd800 && c <= 0xdbff) {
-                    // Found a high surrogate. Verify that surrogate pair is
-                    // well-formed. Low surrogate must follow high surrogate.
-                    if (i + 1 < valueLength) {
-                        int c2 = value.charAt(i + 1);
-                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
-                            c = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff));
-                            i++;
-                        }
-                    }
-                }
-                dst[dstOffset++] = (byte)(0xc0 | (c >> 16));
-                dst[dstOffset++] = (byte)((c >> 8) & 0xff);
-                dst[dstOffset++] = (byte)(c & 0xff);
-            }
-        }
-
-        return dstOffset - originalOffset;
-    }
-
-    /**
-     * Returns the amount of bytes required to encode the given String.
-     *
-     * @param value String to encode, may be null
-     */
-    public static int calculateEncodedStringLength(String value) {
-        if (value == null) {
-            return 1;
-        }
-
-        int valueLength = value.length();
-        int encodedLen;
-
-        if (valueLength < 128) {
-            encodedLen = 1;
-        } else if (valueLength < 16384) {
-            encodedLen = 2;
-        } else if (valueLength < 2097152) {
-            encodedLen = 3;
-        } else if (valueLength < 268435456) {
-            encodedLen = 4;
-        } else {
-            encodedLen = 5;
-        }
-
-        for (int i = 0; i < valueLength; i++) {
-            int c = value.charAt(i);
-            if (c <= 0x7f) {
-                encodedLen++;
-            } else if (c <= 0x3fff) {
-                encodedLen += 2;
-            } else {
-                if (c >= 0xd800 && c <= 0xdbff) {
-                    // Found a high surrogate. Verify that surrogate pair is
-                    // well-formed. Low surrogate must follow high surrogate.
-                    if (i + 1 < valueLength) {
-                        int c2 = value.charAt(i + 1);
-                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
-                            i++;
-                        }
-                    }
-                }
-                encodedLen += 3;
-            }
-        }
-
-        return encodedLen;
-    }
-
-    private static int writeLength(int valueLength, byte[] dst, int dstOffset) {
-        if (valueLength < 128) {
-            dst[dstOffset] = (byte)valueLength;
-            return 1;
-        } else if (valueLength < 16384) {
-            dst[dstOffset++] = (byte)((valueLength >> 8) | 0x80);
-            dst[dstOffset] = (byte)valueLength;
-            return 2;
-        } else if (valueLength < 2097152) {
-            dst[dstOffset++] = (byte)((valueLength >> 16) | 0xc0);
-            dst[dstOffset++] = (byte)(valueLength >> 8);
-            dst[dstOffset] = (byte)valueLength;
-            return 3;
-        } else if (valueLength < 268435456) {
-            dst[dstOffset++] = (byte)((valueLength >> 24) | 0xe0);
-            dst[dstOffset++] = (byte)(valueLength >> 16);
-            dst[dstOffset++] = (byte)(valueLength >> 8);
-            dst[dstOffset] = (byte)valueLength;
-            return 4;
-        } else {
-            dst[dstOffset++] = (byte)0xf0;
-            dst[dstOffset++] = (byte)(valueLength >> 24);
-            dst[dstOffset++] = (byte)(valueLength >> 16);
-            dst[dstOffset++] = (byte)(valueLength >> 8);
-            dst[dstOffset] = (byte)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.
-     *
-     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
-     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
-     */
-    public static byte[] encodeSingle(byte[] value, int prefixPadding, int suffixPadding) {
-        if (prefixPadding <= 0 && suffixPadding <= 0) {
-            return value;
-        }
-        int length = value.length;
-        byte[] dst = new byte[prefixPadding + length + suffixPadding];
-        System.arraycopy(value, 0, dst, prefixPadding, length);
-        return dst;
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * nullable property, whose type is a byte array.
-     */
-    public static byte[] encodeSingleNullable(byte[] value) {
-        return encodeSingleNullable(value, 0, 0);
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * nullable property, whose type is a byte array.
-     *
-     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
-     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
-     */
-    public static byte[] encodeSingleNullable(byte[] value, int prefixPadding, int suffixPadding) {
-        if (prefixPadding <= 0 && suffixPadding <= 0) {
-            if (value == null) {
-                return NULL_BYTE_ARRAY_HIGH;
-            }
-
-            int length = value.length;
-            if (length == 0) {
-                return NOT_NULL_BYTE_ARRAY_HIGH;
-            }
-
-            byte[] dst = new byte[1 + length];
-            dst[0] = NOT_NULL_BYTE_HIGH;
-            System.arraycopy(value, 0, dst, 1, length);
-            return dst;
-        }
-
-        if (value == null) {
-            byte[] dst = new byte[prefixPadding + 1 + suffixPadding];
-            dst[prefixPadding] = NULL_BYTE_HIGH;
-            return dst;
-        }
-
-        int length = value.length;
-        byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding];
-        dst[prefixPadding] = NOT_NULL_BYTE_HIGH;
-        System.arraycopy(value, 0, dst, prefixPadding + 1, length);
-        return dst;
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java
deleted file mode 100644
index cd408e9..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java
+++ /dev/null
@@ -1,1963 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.Map;
-
-import org.cojen.classfile.CodeAssembler;
-import org.cojen.classfile.Label;
-import org.cojen.classfile.LocalVariable;
-import org.cojen.classfile.Opcode;
-import org.cojen.classfile.TypeDesc;
-import org.cojen.util.BeanIntrospector;
-import org.cojen.util.BeanProperty;
-
-import com.amazon.carbonado.CorruptEncodingException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.lob.Blob;
-import com.amazon.carbonado.lob.Clob;
-import com.amazon.carbonado.lob.Lob;
-
-import com.amazon.carbonado.spi.StorableGenerator;
-import com.amazon.carbonado.spi.TriggerSupport;
-
-import com.amazon.carbonado.info.ChainedProperty;
-import com.amazon.carbonado.info.Direction;
-import com.amazon.carbonado.info.OrderedProperty;
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.info.StorableIntrospector;
-import com.amazon.carbonado.info.StorableProperty;
-import com.amazon.carbonado.info.StorablePropertyAdapter;
-
-/**
- * Generates bytecode instructions for encoding/decoding Storable properties
- * to/from raw bytes.
- *
- * <p>Note: subclasses must override and specialize the hashCode and equals
- * methods. Failure to do so interferes with {@link StorableCodecFactory}'s
- * generated code cache.
- *
- * @author Brian S O'Neill
- */
-public class GenericEncodingStrategy<S extends Storable> {
-    private final Class<S> mType;
-    private final StorableIndex<S> mPkIndex;
-
-    private final int mKeyPrefixPadding;
-    private final int mKeySuffixPadding;
-    private final int mDataPrefixPadding;
-    private final int mDataSuffixPadding;
-
-    /**
-     * @param type type of Storable to generate code for
-     * @param pkIndex specifies sequence and ordering of key properties (optional)
-     */
-    public GenericEncodingStrategy(Class<S> type, StorableIndex<S> pkIndex) {
-        this(type, pkIndex, 0, 0, 0, 0);
-    }
-
-    /**
-     * @param type type of Storable to generate code for
-     * @param pkIndex specifies sequence and ordering of key properties (optional)
-     * @param keyPrefixPadding amount of padding bytes at start of keys
-     * @param keySuffixPadding amount of padding bytes at end of keys
-     * @param dataPrefixPadding amount of padding bytes at start of data values
-     * @param dataSuffixPadding amount of padding bytes at end of data values
-     */
-    @SuppressWarnings("unchecked")
-    public GenericEncodingStrategy(Class<S> type, StorableIndex<S> pkIndex,
-                                   int keyPrefixPadding, int keySuffixPadding,
-                                   int dataPrefixPadding, int dataSuffixPadding) {
-        mType = type;
-
-        if (keyPrefixPadding < 0 || keySuffixPadding < 0 ||
-            dataPrefixPadding < 0 || dataSuffixPadding < 0) {
-            throw new IllegalArgumentException();
-        }
-        mKeyPrefixPadding = keyPrefixPadding;
-        mKeySuffixPadding = keySuffixPadding;
-        mDataPrefixPadding = dataPrefixPadding;
-        mDataSuffixPadding = dataSuffixPadding;
-
-        if (pkIndex == null) {
-            Map<String, ? extends StorableProperty<S>> map =
-                StorableIntrospector.examine(mType).getPrimaryKeyProperties();
-
-            StorableProperty<S>[] properties = new StorableProperty[map.size()];
-            map.values().toArray(properties);
-
-            Direction[] directions = new Direction[map.size()];
-            Arrays.fill(directions, Direction.UNSPECIFIED);
-
-            pkIndex = new StorableIndex<S>(properties, directions, true);
-        }
-
-        mPkIndex = pkIndex;
-    }
-
-    /**
-     * Generates bytecode instructions to encode properties. The encoding is
-     * suitable for "key" encoding, which means it is correctly comparable.
-     *
-     * <p>Note: if a partialStartVar is provided and this strategy has a key
-     * prefix, the prefix is allocated only if the runtime value of
-     * partialStartVar is zero. Likewise, if a partialEndVar is provided and
-     * this strategy has a key suffix, the suffix is allocated only of the
-     * runtime value of partialEndVar is one less than the property count.
-     *
-     * @param assembler code assembler to receive bytecode instructions
-     * @param properties specific properties to encode, defaults to all key
-     * properties if null
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are read from the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @param useReadMethods when true, access properties by public read
-     * methods instead of protected fields - should be used if class being
-     * generated doesn't have access to these fields
-     * @param partialStartVar optional variable for supporting partial key
-     * generation. It must be an int, whose runtime value must be less than the
-     * properties array length. It marks the range start of the partial
-     * property range.
-     * @param partialEndVar optional variable for supporting partial key
-     * generation. It must be an int, whose runtime value must be less than or
-     * equal to the properties array length. It marks the range end (exclusive)
-     * of the partial property range.
-     *
-     * @return local variable referencing a byte array with encoded key
-     *
-     * @throws SupportException if any property type is not supported
-     * @throws IllegalArgumentException if assembler is null, or if instanceVar
-     * is not the correct instance type, or if partial variable types are not
-     * ints
-     */
-    public LocalVariable buildKeyEncoding(CodeAssembler assembler,
-                                          OrderedProperty<S>[] properties,
-                                          LocalVariable instanceVar,
-                                          Class<?> adapterInstanceClass,
-                                          boolean useReadMethods,
-                                          LocalVariable partialStartVar,
-                                          LocalVariable partialEndVar)
-        throws SupportException
-    {
-        properties = ensureKeyProperties(properties);
-        return buildEncoding(true, assembler,
-                             extractProperties(properties), extractDirections(properties),
-                             instanceVar, adapterInstanceClass,
-                             useReadMethods,
-                             -1, // no generation support
-                             partialStartVar, partialEndVar);
-    }
-
-    /**
-     * Generates bytecode instructions to decode properties. 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 key
-     * properties if null
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are placed into the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @param useWriteMethods when true, set properties by public write
-     * methods instead of protected fields - should be used if class being
-     * generated doesn't have access to these fields
-     * @param encodedVar required variable, which must be a byte array. At
-     * runtime, it references an encoded key.
-     *
-     * @throws SupportException if any property type is not supported
-     * @throws IllegalArgumentException if assembler is null, or if instanceVar
-     * is not the correct instance type, or if encodedVar is not a byte array
-     */
-    public void buildKeyDecoding(CodeAssembler assembler,
-                                 OrderedProperty<S>[] properties,
-                                 LocalVariable instanceVar,
-                                 Class<?> adapterInstanceClass,
-                                 boolean useWriteMethods,
-                                 LocalVariable encodedVar)
-        throws SupportException
-    {
-        properties = ensureKeyProperties(properties);
-        buildDecoding(true, assembler,
-                      extractProperties(properties), extractDirections(properties),
-                      instanceVar, adapterInstanceClass, useWriteMethods,
-                      -1, null, // no generation support
-                      encodedVar);
-    }
-
-    /**
-     * Generates bytecode instructions to encode properties. The encoding is
-     * suitable for "data" encoding, which means it is not correctly
-     * comparable, but it is more efficient than key encoding. Partial encoding
-     * is not supported.
-     *
-     * @param assembler code assembler to receive bytecode instructions
-     * @param properties specific properties to encode, defaults to all non-key
-     * properties if null
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are read from the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @param useReadMethods when true, access properties by public read
-     * methods instead of protected fields
-     * @param generation when non-negative, write a storable layout generation
-     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
-     * 128..max is encoded in four bytes, with the most significant bit set.
-     *
-     * @return local variable referencing a byte array with encoded data
-     *
-     * @throws SupportException if any property type is not supported
-     * @throws IllegalArgumentException if assembler is null, or if instanceVar
-     * is not the correct instance type
-     */
-    public LocalVariable buildDataEncoding(CodeAssembler assembler,
-                                           StorableProperty<S>[] properties,
-                                           LocalVariable instanceVar,
-                                           Class<?> adapterInstanceClass,
-                                           boolean useReadMethods,
-                                           int generation)
-        throws SupportException
-    {
-        properties = ensureDataProperties(properties);
-        return buildEncoding(false, assembler,
-                             properties, null,
-                             instanceVar, adapterInstanceClass,
-                             useReadMethods, generation, null, null);
-    }
-
-    /**
-     * Generates bytecode instructions to decode properties. 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 non-key
-     * properties if null
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are placed into the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @param useWriteMethods when true, set properties by public write
-     * methods instead of protected fields - should be used if class being
-     * generated doesn't have access to these fields
-     * @param generation when non-negative, decoder expects a storable layout
-     * generation value to match this value. Otherwise, it throws a
-     * CorruptEncodingException.
-     * @param altGenerationHandler if non-null and a generation is provided,
-     * this label defines an alternate generation handler. It is executed
-     * instead of throwing a CorruptEncodingException if the generation doesn't
-     * match. The actual generation is available on the top of the stack for
-     * the handler to consume.
-     * @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 assembler is null, or if instanceVar
-     * is not the correct instance type, or if encodedVar is not a byte array
-     */
-    public void buildDataDecoding(CodeAssembler assembler,
-                                  StorableProperty<S>[] properties,
-                                  LocalVariable instanceVar,
-                                  Class<?> adapterInstanceClass,
-                                  boolean useWriteMethods,
-                                  int generation,
-                                  Label altGenerationHandler,
-                                  LocalVariable encodedVar)
-        throws SupportException
-    {
-        properties = ensureDataProperties(properties);
-        buildDecoding(false, assembler, properties, null,
-                      instanceVar, adapterInstanceClass, useWriteMethods,
-                      generation, altGenerationHandler, encodedVar);
-    }
-
-    /**
-     * Returns the type of Storable that code is generated for.
-     */
-    public final Class<S> getType() {
-        return mType;
-    }
-
-    /**
-     * Returns true if the type of the given property type is supported. The
-     * types currently supported are primitives, primitive wrapper objects,
-     * Strings, and byte arrays.
-     */
-    public boolean isSupported(Class<?> propertyType) {
-        return isSupported(TypeDesc.forClass(propertyType));
-    }
-
-    /**
-     * Returns true if the type of the given property type is supported. The
-     * types currently supported are primitives, primitive wrapper objects,
-     * Strings, byte arrays and Lobs.
-     */
-    public boolean isSupported(TypeDesc propertyType) {
-        if (propertyType.toPrimitiveType() != null) {
-            return true;
-        }
-        return propertyType == TypeDesc.STRING ||
-            propertyType == TypeDesc.forClass(byte[].class) ||
-            propertyType.toClass() != null && Lob.class.isAssignableFrom(propertyType.toClass());
-    }
-
-    public int getKeyPrefixPadding() {
-        return mKeyPrefixPadding;
-    }
-
-    public int getKeySuffixPadding() {
-        return mKeySuffixPadding;
-    }
-
-    public int getDataPrefixPadding() {
-        return mDataPrefixPadding;
-    }
-
-    public int getDataSuffixPadding() {
-        return mDataSuffixPadding;
-    }
-
-    /**
-     * Returns amount of prefix key bytes that encoding strategy instance
-     * produces which are always the same. Default implementation returns 0.
-     */
-    public int getConstantKeyPrefixLength() {
-        return 0;
-    }
-
-    @Override
-    public int hashCode() {
-        return mType.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof GenericEncodingStrategy) {
-            GenericEncodingStrategy other = (GenericEncodingStrategy) obj;
-            return mType == other.mType
-                && mKeyPrefixPadding == other.mKeyPrefixPadding
-                && mKeySuffixPadding == other.mKeySuffixPadding
-                && mDataPrefixPadding == other.mDataPrefixPadding
-                && mDataSuffixPadding == other.mDataSuffixPadding;
-        }
-        return false;
-    }
-
-    /**
-     * Returns all key properties in the form of an index.
-     */
-    protected StorableIndex<S> getPrimaryKeyIndex() {
-        return mPkIndex;
-    }
-
-    /**
-     * Returns all key properties as ordered properties, possibly with
-     * unspecified directions.
-     */
-    protected OrderedProperty<S>[] gatherAllKeyProperties() {
-        return mPkIndex.getOrderedProperties();
-    }
-
-    /**
-     * Returns all data properties for storable.
-     */
-    @SuppressWarnings("unchecked")
-    protected StorableProperty<S>[] gatherAllDataProperties() {
-        Map<String, ? extends StorableProperty<S>> map =
-            StorableIntrospector.examine(mType).getDataProperties();
-
-        StorableProperty<S>[] properties = new StorableProperty[map.size()];
-
-        int ordinal = 0;
-        for (StorableProperty<S> property : map.values()) {
-            properties[ordinal++] = property;
-        }
-
-        return properties;
-    }
-
-    protected StorablePropertyInfo checkSupport(StorableProperty<S> property)
-        throws SupportException
-    {
-        if (isSupported(property.getType())) {
-            return new StorablePropertyInfo(property);
-        }
-
-        // Look for an adapter that will allow this property to be supported.
-        if (property.getAdapter() != null) {
-            StorablePropertyAdapter adapter = property.getAdapter();
-            for (Class<?> storageType : adapter.getStorageTypePreferences()) {
-                if (!isSupported(storageType)) {
-                    continue;
-                }
-
-                if (property.isNullable() && storageType.isPrimitive()) {
-                    continue;
-                }
-
-                Method fromStorage, toStorage;
-                fromStorage = adapter.findAdaptMethod(storageType, property.getType());
-                if (fromStorage == null) {
-                    continue;
-                }
-                toStorage = adapter.findAdaptMethod(property.getType(), storageType);
-                if (toStorage != null) {
-                    return new StorablePropertyInfo(property, storageType, fromStorage, toStorage);
-                }
-            }
-        }
-
-        throw notSupported(property);
-    }
-
-    @SuppressWarnings("unchecked")
-    protected StorablePropertyInfo[] checkSupport(StorableProperty<S>[] properties)
-        throws SupportException
-    {
-        int length = properties.length;
-        StorablePropertyInfo[] infos = new StorablePropertyInfo[length];
-        for (int i=0; i<length; i++) {
-            infos[i] = checkSupport(properties[i]);
-        }
-        return infos;
-    }
-
-    private SupportException notSupported(StorableProperty<S> property) {
-        return notSupported(property.getName(), property.getType().getName());
-    }
-
-    private SupportException notSupported(String propertyName, String typeName) {
-        return new SupportException
-            ("Type \"" + typeName +
-             "\" not supported for property \"" + propertyName + '"');
-    }
-
-    private OrderedProperty<S>[] ensureKeyProperties(OrderedProperty<S>[] properties) {
-        if (properties == null) {
-            properties = gatherAllKeyProperties();
-        } else {
-            for (Object prop : properties) {
-                if (prop == null) {
-                    throw new IllegalArgumentException();
-                }
-            }
-        }
-        return properties;
-    }
-
-    @SuppressWarnings("unchecked")
-    private StorableProperty<S>[] extractProperties(OrderedProperty<S>[] ordered) {
-        StorableProperty<S>[] properties = new StorableProperty[ordered.length];
-        for (int i=0; i<ordered.length; i++) {
-            ChainedProperty chained = ordered[i].getChainedProperty();
-            if (chained.getChainCount() > 0) {
-                throw new IllegalArgumentException();
-            }
-            properties[i] = chained.getPrimeProperty();
-        }
-        return properties;
-    }
-
-    private Direction[] extractDirections(OrderedProperty<S>[] ordered) {
-        Direction[] directions = new Direction[ordered.length];
-        for (int i=0; i<ordered.length; i++) {
-            directions[i] = ordered[i].getDirection();
-        }
-        return directions;
-    }
-
-    private StorableProperty<S>[] ensureDataProperties(StorableProperty<S>[] properties) {
-        if (properties == null) {
-            properties = gatherAllDataProperties();
-        } else {
-            for (Object prop : properties) {
-                if (prop == null) {
-                    throw new IllegalArgumentException();
-                }
-            }
-        }
-        return properties;
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////
-    /////////////////////////////////////////////////////////////////////////////////
-    /////////////////////////////////////////////////////////////////////////////////
-
-    private LocalVariable buildEncoding(boolean forKey,
-                                        CodeAssembler a,
-                                        StorableProperty<S>[] properties,
-                                        Direction[] directions,
-                                        LocalVariable instanceVar,
-                                        Class<?> adapterInstanceClass,
-                                        boolean useReadMethods,
-                                        int generation,
-                                        LocalVariable partialStartVar,
-                                        LocalVariable partialEndVar)
-        throws SupportException
-    {
-        if (a == null) {
-            throw new IllegalArgumentException();
-        }
-        if (partialStartVar != null && partialStartVar.getType() != TypeDesc.INT) {
-            throw new IllegalArgumentException();
-        }
-        if (partialEndVar != null && partialEndVar.getType() != TypeDesc.INT) {
-            throw new IllegalArgumentException();
-        }
-
-        // Encoding order is:
-        //
-        // 1. Prefix
-        // 2. Generation prefix
-        // 3. Properties
-        // 4. Suffix
-
-        final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
-
-        final int generationPrefix;
-        if (generation < 0) {
-            generationPrefix = 0;
-        } else if (generation < 128) {
-            generationPrefix = 1;
-        } else {
-            generationPrefix = 4;
-        }
-
-        final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
-
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-        final LocalVariable encodedVar = a.createLocalVariable(null, byteArrayType);
-
-        StorablePropertyInfo[] infos = checkSupport(properties);
-
-        if (properties.length == 1) {
-            // Ignore partial key encoding variables, since there can't be a
-            // partial of one property.
-            partialStartVar = null;
-            partialEndVar = null;
-
-            StorableProperty<S> property = properties[0];
-            StorablePropertyInfo info = infos[0];
-
-            if (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
-                // identify a null reference.
-
-                loadPropertyValue(a, info, 0, useReadMethods,
-                                  instanceVar, adapterInstanceClass, partialStartVar);
-
-                boolean descending =
-                    forKey && directions != null && directions[0] == Direction.DESCENDING;
-
-                TypeDesc[] params;
-                if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
-                    a.loadConstant(prefix + generationPrefix);
-                    a.loadConstant(suffix);
-                    params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT};
-                } else {
-                    params = new TypeDesc[] {byteArrayType};
-                }
-
-                if (property.isNullable()) {
-                    if (descending) {
-                        a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleNullableDesc",
-                                       byteArrayType, params);
-                    } else {
-                        a.invokeStatic(DataEncoder.class.getName(), "encodeSingleNullable",
-                                       byteArrayType, params);
-                    }
-                } else if (descending) {
-                    a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleDesc",
-                                   byteArrayType, params);
-                } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
-                    a.invokeStatic(DataEncoder.class.getName(), "encodeSingle",
-                                   byteArrayType, params);
-                } else {
-                    // Just return raw property value - no need to cache it either.
-                }
-
-                a.storeLocal(encodedVar);
-
-                encodeGeneration(a, encodedVar, prefix, generation);
-
-                return encodedVar;
-            }
-        }
-
-        boolean doPartial = forKey && (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) {
-            // Only include prefix as static if no runtime check is needed
-            // against runtime partial start value.
-            staticLength += prefix + generationPrefix;
-        }
-        if (!forKey || partialEndVar == null) {
-            // Only include suffix as static if no runtime check is needed
-            // against runtime partial end value.
-            staticLength += suffix;
-        }
-
-        boolean hasVariableLength;
-        if (doPartial) {
-            hasVariableLength = true;
-        } else {
-            hasVariableLength = false;
-            for (GenericPropertyInfo info : infos) {
-                int len = staticEncodingLength(info);
-                if (len >= 0) {
-                    staticLength += len;
-                } else {
-                    staticLength += ~len;
-                    hasVariableLength = true;
-                }
-            }
-        }
-
-        // Generate code that loops over all the properties that have a
-        // variable length. Load each property and perform the necessary
-        // tests to determine the exact encoding length.
-
-        boolean hasStackVar = false;
-        if (hasVariableLength) {
-            Label[] entryPoints = null;
-
-            if (partialStartVar != null) {
-                // Will jump into an arbitrary location, so always have a stack
-                // variable available.
-                a.loadConstant(0);
-                hasStackVar = true;
-
-                entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
-            }
-
-            Label exitPoint = a.createLabel();
-
-            for (int i=0; i<properties.length; i++) {
-                StorableProperty<S> property = properties[i];
-                StorablePropertyInfo info = infos[i];
-
-                if (doPartial) {
-                    if (entryPoints != null) {
-                        entryPoints[i].setLocation();
-                    }
-                    if (partialEndVar != null) {
-                        // Add code to jump out of partial.
-                        a.loadConstant(i);
-                        a.loadLocal(partialEndVar);
-                        a.ifComparisonBranch(exitPoint, ">=");
-                    }
-                } else if (staticEncodingLength(info) >= 0) {
-                    continue;
-                }
-
-                TypeDesc propType = info.getStorageType();
-
-                if (propType.isPrimitive()) {
-                    // This should only ever get executed if implementing
-                    // partial support. Otherwise, the static encoding length
-                    // would have been already calculated.
-                    a.loadConstant(staticEncodingLength(info));
-                    if (hasStackVar) {
-                        a.math(Opcode.IADD);
-                    } else {
-                        hasStackVar = true;
-                    }
-                } else if (propType.toPrimitiveType() != null) {
-                    int amt = 0;
-                    switch (propType.toPrimitiveType().getTypeCode()) {
-                    case TypeDesc.BYTE_CODE:
-                    case TypeDesc.BOOLEAN_CODE:
-                        amt = 1;
-                        break;
-                    case TypeDesc.SHORT_CODE:
-                    case TypeDesc.CHAR_CODE:
-                        amt = 2;
-                        break;
-                    case TypeDesc.INT_CODE:
-                    case TypeDesc.FLOAT_CODE:
-                        amt = 4;
-                        break;
-                    case TypeDesc.LONG_CODE:
-                    case TypeDesc.DOUBLE_CODE:
-                        amt = 8;
-                        break;
-                    }
-
-                    int extra = 0;
-                    if (doPartial) {
-                        // If value is null, then there may be a one byte size
-                        // adjust for the null value. Otherwise it is the extra
-                        // amount plus the size to encode the raw primitive
-                        // value. If doPartial is false, then this extra amount
-                        // was already accounted for in the static encoding
-                        // length.
-
-                        switch (propType.toPrimitiveType().getTypeCode()) {
-                        case TypeDesc.BYTE_CODE:
-                        case TypeDesc.SHORT_CODE:
-                        case TypeDesc.CHAR_CODE:
-                        case TypeDesc.INT_CODE:
-                        case TypeDesc.LONG_CODE:
-                            extra = 1;
-                        }
-                    }
-
-                    if (!property.isNullable() || (doPartial && extra == 0)) {
-                        a.loadConstant(amt);
-                        if (hasStackVar) {
-                            a.math(Opcode.IADD);
-                        }
-                        hasStackVar = true;
-                    } else {
-                        // Load property to test for null.
-                        loadPropertyValue(a, info, i, useReadMethods,
-                                          instanceVar, adapterInstanceClass, partialStartVar);
-
-                        Label isNull = a.createLabel();
-                        a.ifNullBranch(isNull, true);
-
-                        a.loadConstant(amt);
-
-                        if (hasStackVar) {
-                            a.math(Opcode.IADD);
-                            isNull.setLocation();
-                            if (extra > 0) {
-                                a.loadConstant(extra);
-                                a.math(Opcode.IADD);
-                            }
-                        } else {
-                            hasStackVar = true;
-                            // Make sure that there is a zero (or extra) value on
-                            // the stack if the isNull branch is taken.
-                            Label notNull = a.createLabel();
-                            a.branch(notNull);
-                            isNull.setLocation();
-                            a.loadConstant(extra);
-                            notNull.setLocation();
-                        }
-                    }
-                } else if (propType == TypeDesc.STRING) {
-                    // Load property to test for null.
-                    loadPropertyValue(a, info, i, useReadMethods,
-                                      instanceVar, adapterInstanceClass, partialStartVar);
-
-                    String className =
-                        (forKey ? KeyEncoder.class : DataEncoder.class).getName();
-                    a.invokeStatic(className, "calculateEncodedStringLength",
-                                   TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING});
-                    if (hasStackVar) {
-                        a.math(Opcode.IADD);
-                    } else {
-                        hasStackVar = true;
-                    }
-                } else if (propType.toClass() == byte[].class) {
-                    // Load property to test for null.
-                    loadPropertyValue(a, info, i, useReadMethods,
-                                      instanceVar, adapterInstanceClass, partialStartVar);
-
-                    String className =
-                        (forKey ? KeyEncoder.class : DataEncoder.class).getName();
-                    a.invokeStatic(className, "calculateEncodedLength",
-                                   TypeDesc.INT, new TypeDesc[] {byteArrayType});
-                    if (hasStackVar) {
-                        a.math(Opcode.IADD);
-                    } else {
-                        hasStackVar = true;
-                    }
-                } else if (info.isLob()) {
-                    // Lob locator is a long, or 8 bytes.
-                    a.loadConstant(8);
-                    if (hasStackVar) {
-                        a.math(Opcode.IADD);
-                    } else {
-                        hasStackVar = true;
-                    }
-                } else {
-                    throw notSupported(property);
-                }
-            }
-
-            exitPoint.setLocation();
-
-            if (forKey && partialStartVar != null && (prefix > 0 || generationPrefix > 0)) {
-                // Prefix must be allocated only if runtime value of
-                // partialStartVar is zero.
-                a.loadLocal(partialStartVar);
-                Label noPrefix = a.createLabel();
-                a.ifZeroComparisonBranch(noPrefix, "!=");
-                a.loadConstant(prefix + generationPrefix);
-                if (hasStackVar) {
-                    a.math(Opcode.IADD);
-                } else {
-                    hasStackVar = true;
-                }
-                noPrefix.setLocation();
-            }
-
-            if (forKey && partialEndVar != null && suffix > 0) {
-                // Suffix must be allocated only if runtime value of
-                // partialEndVar is equal to property count.
-                a.loadLocal(partialEndVar);
-                Label noSuffix = a.createLabel();
-                a.loadConstant(properties.length);
-                a.ifComparisonBranch(noSuffix, "!=");
-                a.loadConstant(suffix);
-                if (hasStackVar) {
-                    a.math(Opcode.IADD);
-                } else {
-                    hasStackVar = true;
-                }
-                noSuffix.setLocation();
-            }
-        }
-
-        // Allocate a byte array of the exact size.
-        if (hasStackVar) {
-            if (staticLength > 0) {
-                a.loadConstant(staticLength);
-                a.math(Opcode.IADD);
-            }
-        } else {
-            a.loadConstant(staticLength);
-        }
-        a.newObject(byteArrayType);
-        a.storeLocal(encodedVar);
-
-        // Now encode into the byte array.
-
-        int constantOffset = 0;
-        LocalVariable offset = null;
-
-        if (!forKey || 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);
-        }
-
-        Label[] entryPoints = null;
-
-        if (forKey && 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);
-            if (prefix > 0) {
-                // Prefix is allocated only if partial start is zero. Check if
-                // offset should be adjusted to skip over it.
-                a.loadLocal(partialStartVar);
-                Label noPrefix = a.createLabel();
-                a.ifZeroComparisonBranch(noPrefix, "!=");
-                a.loadConstant(prefix + generationPrefix);
-                a.math(Opcode.IADD);
-                encodeGeneration(a, encodedVar, prefix, generation);
-                noPrefix.setLocation();
-            }
-            a.storeLocal(offset);
-
-            entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length);
-        }
-
-        Label exitPoint = a.createLabel();
-
-        for (int i=0; i<properties.length; i++) {
-            StorableProperty<S> property = properties[i];
-            StorablePropertyInfo info = infos[i];
-
-            if (doPartial) {
-                if (entryPoints != null) {
-                    entryPoints[i].setLocation();
-                }
-                if (partialEndVar != null) {
-                    // Add code to jump out of partial.
-                    a.loadConstant(i);
-                    a.loadLocal(partialEndVar);
-                    a.ifComparisonBranch(exitPoint, ">=");
-                }
-            }
-
-            if (info.isLob()) {
-                // Need RawSupport instance for getting locator from Lob.
-                pushRawSupport(a, instanceVar);
-            }
-
-            boolean fromInstance = loadPropertyValue
-                (a, info, i, useReadMethods, instanceVar, adapterInstanceClass, partialStartVar);
-
-            TypeDesc propType = info.getStorageType();
-            if (!property.isNullable() && propType.toPrimitiveType() != null) {
-                // Since property type is a required primitive wrapper, convert
-                // to a primitive rather than encoding using the form that
-                // distinguishes null.
-
-                // Property value that was passed in may be null, which is not
-                // allowed.
-                if (!fromInstance && !propType.isPrimitive()) {
-                    a.dup();
-                    Label notNull = a.createLabel();
-                    a.ifNullBranch(notNull, false);
-
-                    TypeDesc errorType = TypeDesc.forClass(IllegalArgumentException.class);
-                    a.newObject(errorType);
-                    a.dup();
-                    a.loadConstant("Value for property \"" + property.getName() +
-                                   "\" cannot be null");
-                    a.invokeConstructor(errorType, new TypeDesc[] {TypeDesc.STRING});
-                    a.throwObject();
-
-                    notNull.setLocation();
-                }
-
-                a.convert(propType, propType.toPrimitiveType());
-                propType = propType.toPrimitiveType();
-            }
-
-            if (info.isLob()) {
-                // Extract locator from RawSupport.
-                getLobLocator(a, info);
-
-                // Locator is a long, so switch the type to be encoded properly.
-                propType = TypeDesc.LONG;
-            }
-
-            // Fill out remaining parameters before calling specific method
-            // to encode property value.
-            a.loadLocal(encodedVar);
-            if (offset == null) {
-                a.loadConstant(constantOffset);
-            } else {
-                a.loadLocal(offset);
-            }
-
-            boolean descending =
-                forKey && directions != null && directions[i] == Direction.DESCENDING;
-
-            int amt = encodeProperty(a, propType, forKey, descending);
-
-            if (amt > 0) {
-                if (i + 1 < properties.length) {
-                    // Only adjust offset if there are more properties.
-
-                    if (offset == null) {
-                        constantOffset += amt;
-                    } else {
-                        a.loadConstant(amt);
-                        a.loadLocal(offset);
-                        a.math(Opcode.IADD);
-                        a.storeLocal(offset);
-                    }
-                }
-            } else {
-                if (i + 1 >= properties.length) {
-                    // Don't need to keep track of offset anymore.
-                    a.pop();
-                } else {
-                    // Only adjust offset if there are more properties.
-                    if (offset == null) {
-                        if (constantOffset > 0) {
-                            a.loadConstant(constantOffset);
-                            a.math(Opcode.IADD);
-                        }
-                        offset = a.createLocalVariable(null, TypeDesc.INT);
-                    } else {
-                        a.loadLocal(offset);
-                        a.math(Opcode.IADD);
-                    }
-                    a.storeLocal(offset);
-                }
-            }
-        }
-
-        exitPoint.setLocation();
-
-        return encodedVar;
-    }
-
-    /**
-     * Generates code to load a property value onto the operand stack.
-     *
-     * @param info info for property to load
-     * @param ordinal zero-based property ordinal, used only if instanceVar
-     * refers to an object array.
-     * @param useReadMethod when true, access property by public read method
-     * instead of protected field
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are read from the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @param partialStartVar optional variable for supporting partial key
-     * generation. It must be an int, whose runtime value must be less than the
-     * properties array length. It marks the range start of the partial
-     * property range.
-     * @return true if property was loaded from instance, false if loaded from
-     * value array
-     */
-    protected boolean loadPropertyValue(CodeAssembler a,
-                                        StorablePropertyInfo info, int ordinal,
-                                        boolean useReadMethod,
-                                        LocalVariable instanceVar,
-                                        Class<?> adapterInstanceClass,
-                                        LocalVariable partialStartVar)
-    {
-        TypeDesc type = info.getPropertyType();
-        TypeDesc storageType = info.getStorageType();
-
-        boolean isObjectArrayInstanceVar = instanceVar != null
-            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
-
-        boolean useAdapterInstance = adapterInstanceClass != null
-            && info.getToStorageAdapter() != null
-            && (useReadMethod || isObjectArrayInstanceVar);
-
-        if (useAdapterInstance) {
-            // Push adapter instance to stack to be used later.
-            String fieldName =
-                info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
-            TypeDesc adapterType = TypeDesc.forClass
-                (info.getToStorageAdapter().getDeclaringClass());
-            a.loadStaticField
-                (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType);
-        }
-
-        if (instanceVar == null) {
-            a.loadThis();
-            if (useReadMethod) {
-                info.addInvokeReadMethod(a);
-            } else {
-                // Access property value directly from protected field of "this".
-                if (info.getToStorageAdapter() == null) {
-                    a.loadField(info.getPropertyName(), type);
-                } else {
-                    // Invoke adapter method.
-                    a.invokeVirtual(info.getReadMethodName() + '$', storageType, null);
-                }
-            }
-        } else if (!isObjectArrayInstanceVar) {
-            a.loadLocal(instanceVar);
-            if (useReadMethod) {
-                info.addInvokeReadMethod(a, instanceVar.getType());
-            } else {
-                // Access property value directly from protected field of
-                // referenced instance. Assumes code is being defined in the
-                // same package or a subclass.
-                if (info.getToStorageAdapter() == null) {
-                    a.loadField(instanceVar.getType(), info.getPropertyName(), type);
-                } else {
-                    // Invoke adapter method.
-                    a.invokeVirtual(instanceVar.getType(),
-                                    info.getReadMethodName() + '$', storageType, null);
-                }
-            }
-        } else {
-            // Access property value from object array.
-
-            a.loadLocal(instanceVar);
-            a.loadConstant(ordinal);
-            if (ordinal > 0 && partialStartVar != null) {
-                a.loadLocal(partialStartVar);
-                a.math(Opcode.ISUB);
-            }
-
-            a.loadFromArray(TypeDesc.OBJECT);
-            a.checkCast(type.toObjectType());
-            if (type.isPrimitive()) {
-                a.convert(type.toObjectType(), type);
-            }
-        }
-
-        if (useAdapterInstance) {
-            // Invoke adapter method on instance pushed earlier.
-            a.invoke(info.getToStorageAdapter());
-        }
-
-        return !isObjectArrayInstanceVar;
-    }
-
-    /**
-     * 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
-     * returned more than zero.
-     */
-    private int staticEncodingLength(GenericPropertyInfo info) {
-        TypeDesc type = info.getStorageType();
-        TypeDesc primType = type.toPrimitiveType();
-
-        if (primType == null) {
-            if (info.isLob()) {
-                // Lob locator is stored as a long.
-                return 8;
-            }
-        } else {
-            if (info.isNullable()) {
-                // Type is a primitive wrapper.
-                switch (primType.getTypeCode()) {
-                case TypeDesc.BYTE_CODE:
-                    return ~1;
-                case TypeDesc.BOOLEAN_CODE:
-                    return 1;
-                case TypeDesc.SHORT_CODE:
-                case TypeDesc.CHAR_CODE:
-                    return ~1;
-                case TypeDesc.INT_CODE:
-                    return ~1;
-                case TypeDesc.FLOAT_CODE:
-                    return 4;
-                case TypeDesc.LONG_CODE:
-                    return ~1;
-                case TypeDesc.DOUBLE_CODE:
-                    return 8;
-                }
-            } else {
-                // Type is primitive or a required primitive wrapper.
-                switch (type.getTypeCode()) {
-                case TypeDesc.BYTE_CODE:
-                case TypeDesc.BOOLEAN_CODE:
-                    return 1;
-                case TypeDesc.SHORT_CODE:
-                case TypeDesc.CHAR_CODE:
-                    return 2;
-                case TypeDesc.INT_CODE:
-                case TypeDesc.FLOAT_CODE:
-                    return 4;
-                case TypeDesc.LONG_CODE:
-                case TypeDesc.DOUBLE_CODE:
-                    return 8;
-                }
-            }
-        }
-
-        return ~0;
-    }
-
-    /**
-     * @param partialStartVar must not be null
-     */
-    private Label[] jumpToPartialEntryPoints(CodeAssembler a, LocalVariable partialStartVar,
-                                             int propertyCount) {
-        // Create all the entry points for offset var, whose locations will be
-        // set later.
-        int[] cases = new int[propertyCount];
-        Label[] entryPoints = new Label[propertyCount];
-        for (int i=0; i<propertyCount; i++) {
-            cases[i] = i;
-            entryPoints[i] = a.createLabel();
-        }
-
-        // Now jump in!
-        Label errorLoc = a.createLabel();
-        a.loadLocal(partialStartVar);
-        a.switchBranch(cases, entryPoints, errorLoc);
-
-        errorLoc.setLocation();
-        TypeDesc errorType = TypeDesc.forClass(IllegalArgumentException.class);
-        a.newObject(errorType);
-        a.dup();
-        a.loadConstant("Illegal partial start offset");
-        a.invokeConstructor(errorType, new TypeDesc[] {TypeDesc.STRING});
-        a.throwObject();
-
-        return entryPoints;
-    }
-
-    /**
-     * Generates code that calls an encoding method in DataEncoder or
-     * KeyEncoder. Parameters must already be on the stack.
-     *
-     * @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) {
-        TypeDesc[] params = new TypeDesc[] {
-            type, TypeDesc.forClass(byte[].class), TypeDesc.INT
-        };
-
-        if (type.isPrimitive()) {
-            if (forKey && descending) {
-                a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", null, params);
-            } else {
-                a.invokeStatic(DataEncoder.class.getName(), "encode", null, params);
-            }
-
-            switch (type.getTypeCode()) {
-            case TypeDesc.BYTE_CODE:
-            case TypeDesc.BOOLEAN_CODE:
-                return 1;
-            case TypeDesc.SHORT_CODE:
-            case TypeDesc.CHAR_CODE:
-                return 2;
-            default:
-            case TypeDesc.INT_CODE:
-            case TypeDesc.FLOAT_CODE:
-                return 4;
-            case TypeDesc.LONG_CODE:
-            case TypeDesc.DOUBLE_CODE:
-                return 8;
-            }
-        } else if (type.toPrimitiveType() != null) {
-            // Type is a primitive wrapper.
-
-            int adjust;
-            TypeDesc retType;
-
-            switch (type.toPrimitiveType().getTypeCode()) {
-            case TypeDesc.BOOLEAN_CODE:
-                adjust = 1;
-                retType = null;
-                break;
-            case TypeDesc.FLOAT_CODE:
-                adjust = 4;
-                retType = null;
-                break;
-            case TypeDesc.DOUBLE_CODE:
-                adjust = 8;
-                retType = null;
-                break;
-            default:
-                adjust = 0;
-                retType = TypeDesc.INT;
-            }
-
-            if (forKey && descending) {
-                a.invokeStatic(KeyEncoder.class.getName(), "encodeDesc", retType, params);
-            } else {
-                a.invokeStatic(DataEncoder.class.getName(), "encode", retType, params);
-            }
-
-            return adjust;
-        } else {
-            // Type is a String or byte array.
-            if (forKey) {
-                if (descending) {
-                    a.invokeStatic
-                        (KeyEncoder.class.getName(), "encodeDesc", TypeDesc.INT, params);
-                } else {
-                    a.invokeStatic(KeyEncoder.class.getName(), "encode", TypeDesc.INT, params);
-                }
-            } else {
-                a.invokeStatic(DataEncoder.class.getName(), "encode", TypeDesc.INT, params);
-            }
-            return 0;
-        }
-    }
-
-    /**
-     * Generates code that stores a one or four byte generation value into a
-     * byte array referenced by the local variable.
-     *
-     * @param generation if less than zero, no code is generated
-     */
-    private void encodeGeneration(CodeAssembler a, LocalVariable encodedVar,
-                                  int offset, int generation)
-    {
-        if (offset < 0) {
-            throw new IllegalArgumentException();
-        }
-        if (generation < 0) {
-            return;
-        }
-        if (generation < 128) {
-            a.loadLocal(encodedVar);
-            a.loadConstant(offset);
-            a.loadConstant((byte) generation);
-            a.storeToArray(TypeDesc.BYTE);
-        } else {
-            generation |= 0x80000000;
-            for (int i=0; i<4; i++) {
-                a.loadLocal(encodedVar);
-                a.loadConstant(offset + i);
-                a.loadConstant((byte) (generation >> (8 * (3 - i))));
-                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.
-     */
-    private void pushRawSupport(CodeAssembler a, LocalVariable instanceVar)
-        throws SupportException
-    {
-        boolean isObjectArrayInstanceVar = instanceVar != null
-            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
-
-        if (isObjectArrayInstanceVar) {
-            throw new SupportException("Lob properties not supported");
-        }
-
-        if (instanceVar == null) {
-            a.loadThis();
-        } else {
-            a.loadLocal(instanceVar);
-        }
-
-        a.loadField(StorableGenerator.SUPPORT_FIELD_NAME,
-                    TypeDesc.forClass(TriggerSupport.class));
-        a.checkCast(TypeDesc.forClass(RawSupport.class));
-    }
-
-    /**
-     * Generates code to get a Lob locator value from RawSupport. RawSupport
-     * instance and Lob instance must be on the stack. Result is a long locator
-     * value on the stack.
-     */
-    private void getLobLocator(CodeAssembler a, StorablePropertyInfo info) {
-        if (!info.isLob()) {
-            throw new IllegalArgumentException();
-        }
-        a.invokeInterface(TypeDesc.forClass(RawSupport.class), "getLocator",
-                          TypeDesc.LONG, new TypeDesc[] {info.getStorageType()});
-    }
-
-    /**
-     * Generates code to get a Lob from a locator from RawSupport. RawSupport
-     * instance and long locator must be on the stack. Result is a Lob on the
-     * stack, which may be null.
-     */
-    private void getLobFromLocator(CodeAssembler a, StorablePropertyInfo info) {
-        if (!info.isLob()) {
-            throw new IllegalArgumentException();
-        }
-
-        TypeDesc type = info.getStorageType();
-        String name;
-        if (Blob.class.isAssignableFrom(type.toClass())) {
-            name = "getBlob";
-        } else if (Clob.class.isAssignableFrom(type.toClass())) {
-            name = "getClob";
-        } else {
-            throw new IllegalArgumentException();
-        }
-
-        a.invokeInterface(TypeDesc.forClass(RawSupport.class), name,
-                          type, new TypeDesc[] {TypeDesc.LONG});
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////
-    /////////////////////////////////////////////////////////////////////////////////
-    /////////////////////////////////////////////////////////////////////////////////
-
-    private void buildDecoding(boolean forKey,
-                               CodeAssembler a,
-                               StorableProperty<S>[] properties,
-                               Direction[] directions,
-                               LocalVariable instanceVar,
-                               Class<?> adapterInstanceClass,
-                               boolean useWriteMethods,
-                               int generation,
-                               Label altGenerationHandler,
-                               LocalVariable encodedVar)
-        throws SupportException
-    {
-        if (a == null) {
-            throw new IllegalArgumentException();
-        }
-        if (encodedVar == null || encodedVar.getType() != TypeDesc.forClass(byte[].class)) {
-            throw new IllegalArgumentException();
-        }
-
-        // Decoding order is:
-        //
-        // 1. Prefix
-        // 2. Generation prefix
-        // 3. Properties
-        // 4. Suffix
-
-        final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding;
-
-        final int generationPrefix;
-        if (generation < 0) {
-            generationPrefix = 0;
-        } else if (generation < 128) {
-            generationPrefix = 1;
-        } else {
-            generationPrefix = 4;
-        }
-
-        final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding;
-
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-
-        StorablePropertyInfo[] infos = checkSupport(properties);
-
-        decodeGeneration(a, encodedVar, prefix, generation, altGenerationHandler);
-
-        if (properties.length == 1) {
-            StorableProperty<S> property = properties[0];
-            StorablePropertyInfo info = infos[0];
-
-            if (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.
-
-                // Push to stack in preparation for storing a property.
-                pushDecodingInstanceVar(a, 0, instanceVar);
-
-                a.loadLocal(encodedVar);
-
-                boolean descending =
-                    forKey && directions != null && directions[0] == Direction.DESCENDING;
-
-                TypeDesc[] params;
-                if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
-                    a.loadConstant(prefix + generationPrefix);
-                    a.loadConstant(suffix);
-                    params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT};
-                } else {
-                    params = new TypeDesc[] {byteArrayType};
-                }
-
-                if (property.isNullable()) {
-                    if (descending) {
-                        a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleNullableDesc",
-                                       byteArrayType, params);
-                    } else {
-                        a.invokeStatic(DataDecoder.class.getName(), "decodeSingleNullable",
-                                       byteArrayType, params);
-                    }
-                } else if (descending) {
-                    a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleDesc",
-                                   byteArrayType, params);
-                } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) {
-                    a.invokeStatic(DataDecoder.class.getName(), "decodeSingle",
-                                   byteArrayType, params);
-                } else {
-                    // Just store raw property value.
-                }
-
-                storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
-                return;
-            }
-        }
-
-        // Now decode from the byte array.
-
-        int constantOffset = prefix + generationPrefix;
-        LocalVariable offset = null;
-        // References to local variables which will hold references.
-        LocalVariable[] stringRef = new LocalVariable[1];
-        LocalVariable[] byteArrayRef = new LocalVariable[1];
-        LocalVariable[] valueRefRef = new LocalVariable[1];
-
-        for (int i=0; i<infos.length; i++) {
-            StorablePropertyInfo info = infos[i];
-
-            // Push to stack in preparation for storing a property.
-            pushDecodingInstanceVar(a, i, instanceVar);
-
-            TypeDesc storageType = info.getStorageType();
-
-            if (info.isLob()) {
-                // Need RawSupport instance for getting Lob from locator.
-                pushRawSupport(a, instanceVar);
-                // Locator is encoded as a long.
-                storageType = TypeDesc.LONG;
-            }
-
-            a.loadLocal(encodedVar);
-            if (offset == null) {
-                a.loadConstant(constantOffset);
-            } else {
-                a.loadLocal(offset);
-            }
-
-            boolean descending =
-                forKey && directions != null && directions[i] == Direction.DESCENDING;
-
-            int amt = decodeProperty(a, info, storageType, forKey, descending,
-                                     stringRef, byteArrayRef, valueRefRef);
-
-            if (info.isLob()) {
-                getLobFromLocator(a, info);
-            }
-
-            if (amt != 0) {
-                if (i + 1 < properties.length) {
-                    // Only adjust offset if there are more properties.
-
-                    if (amt > 0) {
-                        if (offset == null) {
-                            constantOffset += amt;
-                        } else {
-                            a.loadConstant(amt);
-                            a.loadLocal(offset);
-                            a.math(Opcode.IADD);
-                            a.storeLocal(offset);
-                        }
-                    } 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));
-                        Label cont = a.createLabel();
-                        a.branch(cont);
-                        notNull.setLocation();
-                        a.loadConstant(~amt + (offset == null ? constantOffset : 0));
-                        cont.setLocation();
-
-                        if (offset == null) {
-                            offset = a.createLocalVariable(null, TypeDesc.INT);
-                        } else {
-                            a.loadLocal(offset);
-                            a.math(Opcode.IADD);
-                        }
-                        a.storeLocal(offset);
-                    }
-                }
-            } else {
-                if (i + 1 >= properties.length) {
-                    // Don't need to keep track of offset anymore.
-                    a.pop();
-                } else {
-                    // Only adjust offset if there are more properties.
-                    if (offset == null) {
-                        if (constantOffset > 0) {
-                            a.loadConstant(constantOffset);
-                            a.math(Opcode.IADD);
-                        }
-                        offset = a.createLocalVariable(null, TypeDesc.INT);
-                    } else {
-                        a.loadLocal(offset);
-                        a.math(Opcode.IADD);
-                    }
-                    a.storeLocal(offset);
-                }
-
-                // Get the value out of the ref array so that it can be stored.
-                a.loadLocal(valueRefRef[0]);
-                a.loadConstant(0);
-                a.loadFromArray(valueRefRef[0].getType());
-            }
-
-            storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
-        }
-    }
-
-    /**
-     * Generates code that calls a decoding method in DataDecoder or
-     * KeyDecoder. Parameters must already be on the stack.
-     *
-     * @return 0 if an int amount is pushed onto the stack, or a positive value
-     * if offset adjust amount is constant, or a negative value if offset
-     * adjust is constant or one more
-     */
-    private int decodeProperty(CodeAssembler a,
-                               GenericPropertyInfo info, TypeDesc storageType,
-                               boolean forKey, boolean descending,
-                               LocalVariable[] stringRefRef, LocalVariable[] byteArrayRefRef,
-                               LocalVariable[] valueRefRef)
-        throws SupportException
-    {
-        TypeDesc primType = storageType.toPrimitiveType();
-
-        if (primType != null) {
-            String methodName;
-            TypeDesc returnType;
-            int adjust;
-
-            if (primType != storageType && info.isNullable()) {
-                // Property type is a nullable boxed primitive.
-                returnType = storageType;
-
-                switch (primType.getTypeCode()) {
-                case TypeDesc.BYTE_CODE:
-                    methodName = "decodeByteObj";
-                    adjust = ~2;
-                    break;
-                case TypeDesc.BOOLEAN_CODE:
-                    methodName = "decodeBooleanObj";
-                    adjust = 1;
-                    break;
-                case TypeDesc.SHORT_CODE:
-                    methodName = "decodeShortObj";
-                    adjust = ~3;
-                    break;
-                case TypeDesc.CHAR_CODE:
-                    methodName = "decodeCharacterObj";
-                    adjust = ~3;
-                    break;
-                default:
-                case TypeDesc.INT_CODE:
-                    methodName = "decodeIntegerObj";
-                    adjust = ~5;
-                    break;
-                case TypeDesc.FLOAT_CODE:
-                    methodName = "decodeFloatObj";
-                    adjust = 4;
-                    break;
-                case TypeDesc.LONG_CODE:
-                    methodName = "decodeLongObj";
-                    adjust = ~9;
-                    break;
-                case TypeDesc.DOUBLE_CODE:
-                    methodName = "decodeDoubleObj";
-                    adjust = 8;
-                    break;
-                }
-            } else {
-                // Property type is a primitive or a boxed primitive.
-                returnType = primType;
-
-                switch (primType.getTypeCode()) {
-                case TypeDesc.BYTE_CODE:
-                    methodName = "decodeByte";
-                    adjust = 1;
-                    break;
-                case TypeDesc.BOOLEAN_CODE:
-                    methodName = "decodeBoolean";
-                    adjust = 1;
-                    break;
-                case TypeDesc.SHORT_CODE:
-                    methodName = "decodeShort";
-                    adjust = 2;
-                    break;
-                case TypeDesc.CHAR_CODE:
-                    methodName = "decodeChar";
-                    adjust = 2;
-                    break;
-                default:
-                case TypeDesc.INT_CODE:
-                    methodName = "decodeInt";
-                    adjust = 4;
-                    break;
-                case TypeDesc.FLOAT_CODE:
-                    methodName = "decodeFloat";
-                    adjust = 4;
-                    break;
-                case TypeDesc.LONG_CODE:
-                    methodName = "decodeLong";
-                    adjust = 8;
-                    break;
-                case TypeDesc.DOUBLE_CODE:
-                    methodName = "decodeDouble";
-                    adjust = 8;
-                    break;
-                }
-            }
-
-            TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT};
-            if (forKey && descending) {
-                a.invokeStatic
-                    (KeyDecoder.class.getName(), methodName + "Desc", returnType, params);
-            } else {
-                a.invokeStatic
-                    (DataDecoder.class.getName(), methodName, returnType, params);
-            }
-
-            if (returnType.isPrimitive()) {
-                if (!storageType.isPrimitive()) {
-                    // Wrap it.
-                    a.convert(returnType, storageType);
-                }
-            }
-
-            return adjust;
-        } else {
-            String className = (forKey ? KeyDecoder.class : DataDecoder.class).getName();
-            String methodName;
-            TypeDesc refType;
-
-            if (storageType == TypeDesc.STRING) {
-                methodName = (forKey && descending) ? "decodeStringDesc" : "decodeString";
-                refType = TypeDesc.forClass(String[].class);
-                if (stringRefRef[0] == null) {
-                    stringRefRef[0] = a.createLocalVariable(null, refType);
-                    a.loadConstant(1);
-                    a.newObject(refType);
-                    a.storeLocal(stringRefRef[0]);
-                }
-                a.loadLocal(stringRefRef[0]);
-                valueRefRef[0] = stringRefRef[0];
-            } else if (storageType.toClass() == byte[].class) {
-                methodName = (forKey && descending) ? "decodeDesc" : "decode";
-                refType = TypeDesc.forClass(byte[][].class);
-                if (byteArrayRefRef[0] == null) {
-                    byteArrayRefRef[0] = a.createLocalVariable(null, refType);
-                    a.loadConstant(1);
-                    a.newObject(refType);
-                    a.storeLocal(byteArrayRefRef[0]);
-                }
-                a.loadLocal(byteArrayRefRef[0]);
-                valueRefRef[0] = byteArrayRefRef[0];
-            } else {
-                throw notSupported(info.getPropertyName(), storageType.getFullName());
-            }
-
-            TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT, refType};
-            a.invokeStatic(className, methodName, TypeDesc.INT, params);
-
-            return 0;
-        }
-    }
-
-    /**
-     * Push decoding instanceVar to stack in preparation to calling
-     * storePropertyValue.
-     *
-     * @param ordinal zero-based property ordinal, used only if instanceVar
-     * refers to an object array.
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are written to the runtime value of this array instead
-     * of a Storable instance.
-     * @see #storePropertyValue storePropertyValue
-     */
-    protected void pushDecodingInstanceVar(CodeAssembler a, int ordinal,
-                                           LocalVariable instanceVar) {
-        if (instanceVar == null) {
-            // Push this to stack in preparation for storing a property.
-            a.loadThis();
-        } else if (instanceVar.getType() != TypeDesc.forClass(Object[].class)) {
-            // Push reference to stack in preparation for storing a property.
-            a.loadLocal(instanceVar);
-        } else {
-            // Push array and index to stack in preparation for storing a property.
-            a.loadLocal(instanceVar);
-            a.loadConstant(ordinal);
-        }
-    }
-
-    /**
-     * Generates code to store a property value into an instance which is
-     * already on the operand stack. If instance is an Object array, index into
-     * array must also be on the operand stack.
-     *
-     * @param info info for property to store to
-     * @param useWriteMethod when true, set property by public write method
-     * instead of protected field
-     * @param instanceVar local variable referencing Storable instance,
-     * defaults to "this" if null. If variable type is an Object array, then
-     * property values are written to the runtime value of this array instead
-     * of a Storable instance.
-     * @param adapterInstanceClass class containing static references to
-     * adapter instances - defaults to instanceVar
-     * @see #pushDecodingInstanceVar pushDecodingInstanceVar
-     */
-    protected void storePropertyValue(CodeAssembler a, StorablePropertyInfo info,
-                                      boolean useWriteMethod,
-                                      LocalVariable instanceVar,
-                                      Class<?> adapterInstanceClass) {
-        TypeDesc type = info.getPropertyType();
-        TypeDesc storageType = info.getStorageType();
-
-        boolean isObjectArrayInstanceVar = instanceVar != null
-            && instanceVar.getType() == TypeDesc.forClass(Object[].class);
-
-        boolean useAdapterInstance = adapterInstanceClass != null
-            && info.getToStorageAdapter() != null
-            && (useWriteMethod || isObjectArrayInstanceVar);
-
-        if (useAdapterInstance) {
-            // Push adapter instance to adapt property value. It must be on the
-            // stack before the property value, so swap.
-
-            // Store unadapted property to temp var in order to be swapped.
-            LocalVariable temp = a.createLocalVariable(null, storageType);
-            a.storeLocal(temp);
-
-            String fieldName =
-                info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0;
-            TypeDesc adapterType = TypeDesc.forClass
-                (info.getToStorageAdapter().getDeclaringClass());
-            a.loadStaticField
-                (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType);
-
-            a.loadLocal(temp);
-            a.invoke(info.getFromStorageAdapter());
-
-            // Stack now contains property adapted to its publicly declared type.
-        }
-
-        if (instanceVar == null) {
-            if (useWriteMethod) {
-                info.addInvokeWriteMethod(a);
-            } else {
-                // Set property value directly to protected field of instance.
-                if (info.getToStorageAdapter() == null) {
-                    a.storeField(info.getPropertyName(), type);
-                } else {
-                    // Invoke adapter method.
-                    a.invokeVirtual(info.getWriteMethodName() + '$',
-                                    null, new TypeDesc[] {storageType});
-                }
-            }
-        } else if (!isObjectArrayInstanceVar) {
-            TypeDesc instanceVarType = instanceVar.getType();
-
-            // Drop properties that are missing or whose types are incompatible.
-            doDrop: {
-                Class instanceVarClass = instanceVarType.toClass();
-                if (instanceVarClass != null) {
-                    Map<String, BeanProperty> props =
-                        BeanIntrospector.getAllProperties(instanceVarClass);
-                    BeanProperty prop = props.get(info.getPropertyName());
-                    if (prop != null) {
-                        if (prop.getType() == type.toClass()) {
-                            break doDrop;
-                        }
-                        // Types differ, but if primitive types, perform conversion.
-                        TypeDesc primType = type.toPrimitiveType();
-                        if (primType != null) {
-                            TypeDesc propType = TypeDesc.forClass(prop.getType());
-                            TypeDesc primPropType = propType.toPrimitiveType();
-                            if (primPropType != null) {
-                                // Apply conversion and store property.
-                                a.convert(type, propType);
-                                type = propType;
-                                break doDrop;
-                            }
-                        }
-                    }
-                }
-
-                // Drop missing or incompatible property.
-                if (storageType.isDoubleWord()) {
-                    a.pop2();
-                } else {
-                    a.pop();
-                }
-                return;
-            }
-
-            if (useWriteMethod) {
-                info.addInvokeWriteMethod(a, instanceVarType);
-            } else {
-                // Set property value directly to protected field of referenced
-                // instance. Assumes code is being defined in the same package
-                // or a subclass.
-                if (info.getToStorageAdapter() == null) {
-                    a.storeField(instanceVarType, info.getPropertyName(), type);
-                } else {
-                    // Invoke adapter method.
-                    a.invokeVirtual(instanceVarType, info.getWriteMethodName() + '$',
-                                    null, new TypeDesc[] {storageType});
-                }
-            }
-        } else {
-            // Set property value to object array. No need to check if we
-            // should call a write method because arrays don't have write
-            // methods.
-            if (type.isPrimitive()) {
-                a.convert(type, type.toObjectType());
-            }
-            a.storeToArray(TypeDesc.OBJECT);
-        }
-    }
-
-    /**
-     * Generates code that ensures a matching generation value exists in the
-     * byte array referenced by the local variable, throwing a
-     * CorruptEncodingException otherwise.
-     *
-     * @param generation if less than zero, no code is generated
-     */
-    private void decodeGeneration(CodeAssembler a, LocalVariable encodedVar,
-                                  int offset, int generation, Label altGenerationHandler)
-    {
-        if (offset < 0) {
-            throw new IllegalArgumentException();
-        }
-        if (generation < 0) {
-            return;
-        }
-
-        LocalVariable actualGeneration = a.createLocalVariable(null, TypeDesc.INT);
-        a.loadLocal(encodedVar);
-        a.loadConstant(offset);
-        a.loadFromArray(TypeDesc.BYTE);
-        a.storeLocal(actualGeneration);
-        a.loadLocal(actualGeneration);
-        Label compareGeneration = a.createLabel();
-        a.ifZeroComparisonBranch(compareGeneration, ">=");
-
-        // Decode four byte generation format.
-        a.loadLocal(actualGeneration);
-        a.loadConstant(24);
-        a.math(Opcode.ISHL);
-        a.loadConstant(0x7fffffff);
-        a.math(Opcode.IAND);
-        for (int i=1; i<4; i++) {
-            a.loadLocal(encodedVar);
-            a.loadConstant(offset + i);
-            a.loadFromArray(TypeDesc.BYTE);
-            a.loadConstant(0xff);
-            a.math(Opcode.IAND);
-            int shift = 8 * (3 - i);
-            if (shift > 0) {
-                a.loadConstant(shift);
-                a.math(Opcode.ISHL);
-            }
-            a.math(Opcode.IOR);
-        }
-        a.storeLocal(actualGeneration);
-
-        compareGeneration.setLocation();
-
-        a.loadConstant(generation);
-        a.loadLocal(actualGeneration);
-        Label generationMatches = a.createLabel();
-        a.ifComparisonBranch(generationMatches, "==");
-
-        if (altGenerationHandler != null) {
-            a.loadLocal(actualGeneration);
-            a.branch(altGenerationHandler);
-        } else {
-            // Throw CorruptEncodingException.
-
-            TypeDesc corruptEncodingEx = TypeDesc.forClass(CorruptEncodingException.class);
-            a.newObject(corruptEncodingEx);
-            a.dup();
-            a.loadConstant(generation);    // expected generation
-            a.loadLocal(actualGeneration); // actual generation
-            a.invokeConstructor(corruptEncodingEx, new TypeDesc[] {TypeDesc.INT, TypeDesc.INT});
-            a.throwObject();
-        }
-
-        generationMatches.setLocation();
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java
deleted file mode 100644
index 5a6a4cb..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-
-/**
- * Can be used with {@link com.amazon.carbonado.util.QuickConstructorGenerator}
- * for instantiating generic storable instances.
- *
- * @author Brian S O'Neill
- */
-public interface GenericInstanceFactory {
-    Storable instantiate(RawSupport support);
-
-    Storable instantiate(RawSupport support, byte[] key, byte[] value)
-        throws FetchException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java
deleted file mode 100644
index c734f03..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.reflect.Method;
-
-import org.cojen.classfile.TypeDesc;
-
-/**
- * Minimal information required by {@link GenericEncodingStrategy} to encode
- * and decode a storable property or layout property.
- *
- * @author Brian S O'Neill
- */
-public interface GenericPropertyInfo {
-    String getPropertyName();
-
-    /**
-     * Returns the user specified property type.
-     */
-    TypeDesc getPropertyType();
-
-    /**
-     * Returns the storage supported type. If it differs from the property
-     * type, then adapter methods must also exist.
-     */
-    TypeDesc getStorageType();
-
-    boolean isNullable();
-
-    boolean isLob();
-
-    /**
-     * Returns the optional method used to adapt the property from the
-     * storage supported type to the user visible type.
-     */
-    Method getFromStorageAdapter();
-
-    /**
-     * Returns the optional method used to adapt the property from the user
-     * visible type to the storage supported type.
-     */
-    Method getToStorageAdapter();
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java
deleted file mode 100644
index 7a98540..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java
+++ /dev/null
@@ -1,813 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
-import java.lang.reflect.UndeclaredThrowableException;
-import java.util.Map;
-
-import org.cojen.classfile.ClassFile;
-import org.cojen.classfile.CodeBuilder;
-import org.cojen.classfile.Label;
-import org.cojen.classfile.LocalVariable;
-import org.cojen.classfile.MethodInfo;
-import org.cojen.classfile.Modifiers;
-import org.cojen.classfile.TypeDesc;
-import org.cojen.util.ClassInjector;
-import org.cojen.util.IntHashMap;
-import org.cojen.util.KeyFactory;
-import org.cojen.util.SoftValuedHashMap;
-
-import com.amazon.carbonado.CorruptEncodingException;
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.FetchNoneException;
-import com.amazon.carbonado.RepositoryException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-import com.amazon.carbonado.info.Direction;
-import com.amazon.carbonado.info.OrderedProperty;
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.layout.Layout;
-import com.amazon.carbonado.spi.CodeBuilderUtil;
-import com.amazon.carbonado.util.ThrowUnchecked;
-import com.amazon.carbonado.util.QuickConstructorGenerator;
-
-/**
- * Generic codec that supports any kind of storable by auto-generating and
- * caching storable implementations.
- *
- * @author Brian S O'Neill
- * @see GenericStorableCodecFactory
- */
-public class GenericStorableCodec<S extends Storable> implements StorableCodec<S> {
-    private static final String BLANK_KEY_FIELD_NAME = "blankKey$";
-    private static final String CODEC_FIELD_NAME = "codec$";
-    private static final String ASSIGN_CODEC_METHOD_NAME = "assignCodec$";
-
-    // Maps GenericEncodingStrategy instances to GenericStorableCodec instances.
-    private static final Map cCache = new SoftValuedHashMap();
-
-    /**
-     * Returns an instance of the codec. The Storable type itself may be an
-     * interface or a class. If it is a class, then it must not be final, and
-     * it must have a public, no-arg constructor.
-     *
-     * @param isMaster when true, version properties and sequences are managed
-     * @param layout when non-null, encode a storable layout generation
-     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
-     * 128..max is encoded in four bytes, with the most significant bit set.
-     * @throws SupportException if Storable is not supported
-     * @throws amazon.carbonado.MalformedTypeException if Storable type is not well-formed
-     * @throws IllegalArgumentException if type is null
-     */
-    @SuppressWarnings("unchecked")
-    static synchronized <S extends Storable> GenericStorableCodec<S> getInstance
-        (GenericStorableCodecFactory factory,
-         GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
-        throws SupportException
-    {
-        Object key;
-        if (layout == null) {
-            key = KeyFactory.createKey(new Object[] {encodingStrategy, isMaster});
-        } else {
-            key = KeyFactory.createKey
-                (new Object[] {encodingStrategy, isMaster, factory, layout.getGeneration()});
-        }
-
-        GenericStorableCodec<S> codec = (GenericStorableCodec<S>) cCache.get(key);
-        if (codec == null) {
-            codec = new GenericStorableCodec<S>
-                (factory,
-                 encodingStrategy.getType(),
-                 generateStorable(encodingStrategy, isMaster, layout),
-                 encodingStrategy,
-                 layout);
-            cCache.put(key, codec);
-        }
-
-        return codec;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <S extends Storable> Class<? extends S> generateStorable
-        (GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
-        throws SupportException
-    {
-        final Class<S> storableClass = encodingStrategy.getType();
-        final Class<? extends S> abstractClass =
-            RawStorableGenerator.getAbstractClass(storableClass, isMaster);
-        final int generation = layout == null ? -1 : layout.getGeneration();
-
-        ClassInjector ci = ClassInjector.create
-            (storableClass.getName(), abstractClass.getClassLoader());
-
-        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
-        cf.markSynthetic();
-        cf.setSourceFile(GenericStorableCodec.class.getName());
-        cf.setTarget("1.5");
-
-        // Declare some types.
-        final TypeDesc storageType = TypeDesc.forClass(Storage.class);
-        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-        final TypeDesc[] byteArrayParam = {byteArrayType};
-        final TypeDesc codecType = TypeDesc.forClass(GenericStorableCodec.class);
-        final TypeDesc decoderType = TypeDesc.forClass(Decoder.class);
-        final TypeDesc weakRefType = TypeDesc.forClass(WeakReference.class);
-
-        // If Layout provided, then keep a (weak) static reference to this
-        // GenericStorableCodec in order to get decoders for different
-        // generations. It is assigned a value after the class is loaded via a
-        // public static method. It can only be assigned once.
-        if (layout != null) {
-            cf.addField(Modifiers.PRIVATE.toStatic(true), CODEC_FIELD_NAME, weakRefType);
-            MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toStatic(true), ASSIGN_CODEC_METHOD_NAME,
-                                         null, new TypeDesc[] {weakRefType});
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadStaticField(CODEC_FIELD_NAME, weakRefType);
-            Label done = b.createLabel();
-            b.ifNullBranch(done, false);
-            b.loadLocal(b.getParameter(0));
-            b.storeStaticField(CODEC_FIELD_NAME, weakRefType);
-            done.setLocation();
-            b.returnVoid();
-        }
-
-        // Add constructor that accepts a RawSupport.
-        {
-            TypeDesc[] params = {rawSupportType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeSuperConstructor(params);
-            b.returnVoid();
-        }
-
-        // Add constructor that accepts a RawSupport, an encoded key, and an
-        // encoded data.
-        {
-            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.loadLocal(b.getParameter(1));
-            b.loadLocal(b.getParameter(2));
-            b.invokeSuperConstructor(params);
-            b.returnVoid();
-        }
-
-        // Implement protected abstract methods inherited from parent class.
-
-        // byte[] encodeKey()
-        {
-            // Encode the primary key into a byte array that supports correct
-            // ordering. No special key comparator is needed.
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.ENCODE_KEY_METHOD_NAME,
-                                         byteArrayType, null);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // TODO: Consider caching generated key. Rebuild if null or if pk is dirty.
-
-            // assembler            = b
-            // properties           = null (defaults to all key properties)
-            // instanceVar          = null (null means "this")
-            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
-            // useReadMethods       = false (will read fields directly)
-            // partialStartVar      = null (only support encoding all properties)
-            // partialEndVar        = null (only support encoding all properties)
-            LocalVariable encodedVar =
-                encodingStrategy.buildKeyEncoding(b, null, null, null, false, null, null);
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        }
-
-        // byte[] encodeData()
-        {
-            // Encoding non-primary key data properties.
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.ENCODE_DATA_METHOD_NAME,
-                                         byteArrayType, null);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // assembler            = b
-            // properties           = null (defaults to all non-key properties)
-            // instanceVar          = null (null means "this")
-            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
-            // useReadMethods       = false (will read fields directly)
-            // generation           = generation
-            LocalVariable encodedVar =
-                encodingStrategy.buildDataEncoding(b, null, null, null, false, generation);
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        }
-
-        // void decodeKey(byte[])
-        {
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.DECODE_KEY_METHOD_NAME,
-                                         null, byteArrayParam);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // assembler            = b
-            // properties           = null (defaults to all key properties)
-            // instanceVar          = null (null means "this")
-            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
-            // useWriteMethods      = false (will set fields directly)
-            // encodedVar           = references byte array with encoded key
-            encodingStrategy.buildKeyDecoding(b, null, null, null, false, b.getParameter(0));
-
-            b.returnVoid();
-        }
-
-        // void decodeData(byte[])
-        {
-            MethodInfo mi = cf.addMethod(Modifiers.PROTECTED,
-                                         RawStorableGenerator.DECODE_DATA_METHOD_NAME,
-                                         null, byteArrayParam);
-            CodeBuilder b = new CodeBuilder(mi);
-            Label altGenerationHandler = b.createLabel();
-
-            // assembler            = b
-            // properties           = null (defaults to all non-key properties)
-            // instanceVar          = null (null means "this")
-            // adapterInstanceClass = null (null means use instanceVar, in this case is "this")
-            // useWriteMethods      = false (will set fields directly)
-            // generation           = generation
-            // altGenerationHandler = altGenerationHandler
-            // encodedVar           = references byte array with encoded data
-            encodingStrategy.buildDataDecoding
-                (b, null, null, null, false, generation, altGenerationHandler, b.getParameter(0));
-
-            b.returnVoid();
-
-            // Support decoding alternate generation.
-
-            altGenerationHandler.setLocation();
-            LocalVariable actualGeneration = b.createLocalVariable(null, TypeDesc.INT);
-            b.storeLocal(actualGeneration);
-
-            b.loadStaticField(CODEC_FIELD_NAME, weakRefType);
-            b.invokeVirtual(weakRefType, "get", TypeDesc.OBJECT, null);
-            b.dup();
-            Label haveCodec = b.createLabel();
-            b.ifNullBranch(haveCodec, false);
-
-            // Codec got reclaimed, which is unlikely to happen during normal
-            // use since it must be referenced by the storage object.
-            b.pop(); // Don't need the duped codec instance.
-            CodeBuilderUtil.throwException(b, IllegalStateException.class, "Codec missing");
-
-            haveCodec.setLocation();
-            b.checkCast(codecType);
-            b.loadLocal(actualGeneration);
-            b.invokeVirtual(codecType, "getDecoder", decoderType, new TypeDesc[] {TypeDesc.INT});
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeInterface(decoderType, "decode", null,
-                              new TypeDesc[] {TypeDesc.forClass(Storable.class), byteArrayType});
-
-            b.returnVoid();
-        }
-
-        return ci.defineClass(cf);
-    }
-
-    private final GenericStorableCodecFactory mFactory;
-
-    private final Class<S> mType;
-
-    private final Class<? extends S> mStorableClass;
-
-    // Weakly reference the encoding strategy because of the way
-    // GenericStorableCodec instances are cached in a SoftValuedHashMap.
-    // GenericStorableCodec can still be reclaimed by the garbage collector.
-    private final WeakReference<GenericEncodingStrategy<S>> mEncodingStrategy;
-
-    private final GenericInstanceFactory mInstanceFactory;
-
-    private final SearchKeyFactory<S> mPrimaryKeyFactory;
-
-    // Maps OrderedProperty[] keys to SearchKeyFactory instances.
-    private final Map mSearchKeyFactories = new SoftValuedHashMap();
-
-    private final Layout mLayout;
-
-    // Maps layout generations to Decoders.
-    private IntHashMap mDecoders;
-
-    private GenericStorableCodec(GenericStorableCodecFactory factory,
-                                 Class<S> type, Class<? extends S> storableClass,
-                                 GenericEncodingStrategy<S> encodingStrategy,
-                                 Layout layout) {
-        mFactory = factory;
-        mType = type;
-        mStorableClass = storableClass;
-        mEncodingStrategy = new WeakReference<GenericEncodingStrategy<S>>(encodingStrategy);
-        mInstanceFactory = QuickConstructorGenerator
-            .getInstance(storableClass, GenericInstanceFactory.class);
-        mPrimaryKeyFactory = getSearchKeyFactory(encodingStrategy.gatherAllKeyProperties());
-        mLayout = layout;
-
-        if (layout != null) {
-            try {
-                // Assign static reference back to this codec.
-                Method m = storableClass.getMethod
-                    (ASSIGN_CODEC_METHOD_NAME, WeakReference.class);
-                m.invoke(null, new WeakReference(this));
-            } catch (Exception e) {
-                ThrowUnchecked.fireFirstDeclaredCause(e);
-            }
-        }
-    }
-
-    /**
-     * Returns the type of Storable that code is generated for.
-     */
-    public final Class<S> getStorableType() {
-        return mType;
-    }
-
-    /**
-     * Instantiate a Storable with no key or value defined yet.
-     *
-     * @param support binds generated storable with a storage layer
-     */
-    @SuppressWarnings("unchecked")
-    public S instantiate(RawSupport<S> support) {
-        return (S) mInstanceFactory.instantiate(support);
-    }
-
-    /**
-     * Instantiate a Storable with a specific key and value.
-     *
-     * @param support binds generated storable with a storage layer
-     */
-    @SuppressWarnings("unchecked")
-    public S instantiate(RawSupport<S> support, byte[] key, byte[] value)
-        throws FetchException
-    {
-        return (S) mInstanceFactory.instantiate(support, key, value);
-    }
-
-    public StorableIndex<S> getPrimaryKeyIndex() {
-        return getEncodingStrategy().getPrimaryKeyIndex();
-    }
-
-    public int getPrimaryKeyPrefixLength() {
-        return getEncodingStrategy().getConstantKeyPrefixLength();
-    }
-
-    public byte[] encodePrimaryKey(S storable) {
-        return mPrimaryKeyFactory.encodeSearchKey(storable);
-    }
-
-    public byte[] encodePrimaryKey(S storable, int rangeStart, int rangeEnd) {
-        return mPrimaryKeyFactory.encodeSearchKey(storable, rangeStart, rangeEnd);
-    }
-
-    public byte[] encodePrimaryKey(Object[] values) {
-        return mPrimaryKeyFactory.encodeSearchKey(values);
-    }
-
-    public byte[] encodePrimaryKey(Object[] values, int rangeStart, int rangeEnd) {
-        return mPrimaryKeyFactory.encodeSearchKey(values, rangeStart, rangeEnd);
-    }
-
-    public byte[] encodePrimaryKeyPrefix() {
-        return mPrimaryKeyFactory.encodeSearchKeyPrefix();
-    }
-
-    /**
-     * Returns a concrete Storable implementation, which is fully
-     * thread-safe. It has two constructors defined:
-     *
-     * <pre>
-     * public &lt;init&gt;(Storage, RawSupport);
-     *
-     * public &lt;init&gt;(Storage, RawSupport, byte[] key, byte[] value);
-     * </pre>
-     *
-     * Convenience methods are provided in this class to instantiate the
-     * generated Storable.
-     */
-    public Class<? extends S> getStorableClass() {
-        return mStorableClass;
-    }
-
-    /**
-     * Returns a search key factory, which is useful for implementing indexes
-     * and queries.
-     *
-     * @param properties properties to build the search key from
-     */
-    @SuppressWarnings("unchecked")
-    public SearchKeyFactory<S> getSearchKeyFactory(OrderedProperty<S>[] properties) {
-        // This KeyFactory makes arrays work as hashtable keys.
-        Object key = org.cojen.util.KeyFactory.createKey(properties);
-
-        synchronized (mSearchKeyFactories) {
-            SearchKeyFactory<S> factory = (SearchKeyFactory<S>) mSearchKeyFactories.get(key);
-            if (factory == null) {
-                factory = generateSearchKeyFactory(properties);
-                mSearchKeyFactories.put(key, factory);
-            }
-            return factory;
-        }
-    }
-
-    /**
-     * Returns a data decoder for the given generation.
-     *
-     * @throws FetchNoneException if generation is unknown
-     */
-    public Decoder<S> getDecoder(int generation) throws FetchNoneException, FetchException {
-        try {
-            synchronized (mLayout) {
-                IntHashMap decoders = mDecoders;
-                if (decoders == null) {
-                    mDecoders = decoders = new IntHashMap();
-                }
-                Decoder<S> decoder = (Decoder<S>) decoders.get(generation);
-                if (decoder == null) {
-                    decoder = generateDecoder(generation);
-                    mDecoders.put(generation, decoder);
-                }
-                return decoder;
-            }
-        } catch (NullPointerException e) {
-            if (mLayout == null) {
-                throw new FetchNoneException("Layout evolution not supported");
-            }
-            throw e;
-        }
-    }
-
-    private GenericEncodingStrategy<S> getEncodingStrategy() {
-        // Should never be null, even though it is weakly referenced. As long
-        // as this class can be reached by the cache, the encoding strategy
-        // object exists since it is the cache key.
-        return mEncodingStrategy.get();
-    }
-
-    @SuppressWarnings("unchecked")
-    private SearchKeyFactory<S> generateSearchKeyFactory(OrderedProperty<S>[] properties) {
-        GenericEncodingStrategy encodingStrategy = getEncodingStrategy();
-
-        ClassInjector ci;
-        {
-            StringBuilder b = new StringBuilder();
-            b.append(mType.getName());
-            b.append('$');
-            for (OrderedProperty property : properties) {
-                if (property.getDirection() == Direction.UNSPECIFIED) {
-                    property = property.direction(Direction.ASCENDING);
-                }
-                try {
-                    property.appendTo(b);
-                } catch (java.io.IOException e) {
-                    // Not gonna happen
-                }
-            }
-            String prefix = b.toString();
-            ci = ClassInjector.create(prefix, mStorableClass.getClassLoader());
-        }
-
-        ClassFile cf = new ClassFile(ci.getClassName());
-        cf.addInterface(SearchKeyFactory.class);
-        cf.markSynthetic();
-        cf.setSourceFile(GenericStorableCodec.class.getName());
-        cf.setTarget("1.5");
-
-        // Add public no-arg constructor.
-        cf.addDefaultConstructor();
-
-        // Declare some types.
-        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-        final TypeDesc objectArrayType = TypeDesc.forClass(Object[].class);
-        final TypeDesc instanceType = TypeDesc.forClass(mStorableClass);
-
-        // Define encodeSearchKey(Storable).
-        try {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
-                 new TypeDesc[] {storableType});
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadLocal(b.getParameter(0));
-            b.checkCast(instanceType);
-            LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
-            b.storeLocal(instanceVar);
-
-            // assembler            = b
-            // properties           = properties to encode
-            // instanceVar          = instanceVar which references storable instance
-            // adapterInstanceClass = null (null means use instanceVar)
-            // useReadMethods       = false (will read fields directly)
-            // partialStartVar      = null (only support encoding all properties)
-            // partialEndVar        = null (only support encoding all properties)
-            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
-                (b, properties, instanceVar, null, false, null, null);
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        } catch (SupportException e) {
-            // Shouldn't happen since all properties were checked in order
-            // to create this StorableCodec.
-            throw new UndeclaredThrowableException(e);
-        }
-
-        // Define encodeSearchKey(Storable, int, int).
-        try {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
-                 new TypeDesc[] {storableType, TypeDesc.INT, TypeDesc.INT});
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadLocal(b.getParameter(0));
-            b.checkCast(instanceType);
-            LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
-            b.storeLocal(instanceVar);
-
-            // assembler            = b
-            // properties           = properties to encode
-            // instanceVar          = instanceVar which references storable instance
-            // adapterInstanceClass = null (null means use instanceVar)
-            // useReadMethods       = false (will read fields directly)
-            // partialStartVar      = int parameter 1, references start property index
-            // partialEndVar        = int parameter 2, references end property index
-            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
-                (b, properties, instanceVar, null, false, b.getParameter(1), b.getParameter(2));
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        } catch (SupportException e) {
-            // Shouldn't happen since all properties were checked in order
-            // to create this StorableCodec.
-            throw new UndeclaredThrowableException(e);
-        }
-
-        // The Storable class that we generated earlier is a subclass of the
-        // abstract class defined by StorableGenerator. StorableGenerator
-        // creates static final adapter instances, with protected
-        // access. Calling getSuperclass results in the exact class that
-        // StorableGenerator made, which is where the fields are.
-        final Class<?> adapterInstanceClass = getStorableClass().getSuperclass();
-
-        // Define encodeSearchKey(Object[] values).
-        try {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
-                 new TypeDesc[] {objectArrayType});
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // assembler            = b
-            // properties           = properties to encode
-            // instanceVar          = parameter 0, an object array
-            // adapterInstanceClass = adapterInstanceClass - see comment above
-            // useReadMethods       = false (will read fields directly)
-            // partialStartVar      = null (only support encoding all properties)
-            // partialEndVar        = null (only support encoding all properties)
-            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
-                (b, properties, b.getParameter(0), adapterInstanceClass, false, null, null);
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        } catch (SupportException e) {
-            // Shouldn't happen since all properties were checked in order
-            // to create this StorableCodec.
-            throw new UndeclaredThrowableException(e);
-        }
-
-        // Define encodeSearchKey(Object[] values, int, int).
-        try {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PUBLIC, "encodeSearchKey", byteArrayType,
-                 new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // assembler            = b
-            // properties           = properties to encode
-            // instanceVar          = parameter 0, an object array
-            // adapterInstanceClass = adapterInstanceClass - see comment above
-            // useReadMethods       = false (will read fields directly)
-            // partialStartVar      = int parameter 1, references start property index
-            // partialEndVar        = int parameter 2, references end property index
-            LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
-                (b, properties, b.getParameter(0), adapterInstanceClass,
-                 false, b.getParameter(1), b.getParameter(2));
-
-            b.loadLocal(encodedVar);
-            b.returnValue(byteArrayType);
-        } catch (SupportException e) {
-            // Shouldn't happen since all properties were checked in order
-            // to create this StorableCodec.
-            throw new UndeclaredThrowableException(e);
-        }
-
-        // Define encodeSearchKeyPrefix().
-        try {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PUBLIC, "encodeSearchKeyPrefix", byteArrayType, null);
-            CodeBuilder b = new CodeBuilder(mi);
-
-            if (encodingStrategy.getKeyPrefixPadding() == 0 &&
-                encodingStrategy.getKeySuffixPadding() == 0) {
-                // Return null instead of a zero-length array.
-                b.loadNull();
-                b.returnValue(byteArrayType);
-            } else {
-                // Build array once and re-use. Trust that no one modifies it.
-                cf.addField(Modifiers.PRIVATE.toStatic(true).toFinal(true),
-                            BLANK_KEY_FIELD_NAME, byteArrayType);
-                b.loadStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
-                b.returnValue(byteArrayType);
-
-                // Create static initializer to set field.
-                mi = cf.addInitializer();
-                b = new CodeBuilder(mi);
-
-                // assembler            = b
-                // properties           = no parameters - we just want the key prefix
-                // instanceVar          = null (no parameters means we don't need this)
-                // adapterInstanceClass = null (no parameters means we don't need this)
-                // useReadMethods       = false (no parameters means we don't need this)
-                // partialStartVar      = null (no parameters means we don't need this)
-                // partialEndVar        = null (no parameters means we don't need this)
-                LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
-                    (b, new OrderedProperty[0], null, null, false, null, null);
-
-                b.loadLocal(encodedVar);
-                b.storeStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
-                b.returnVoid();
-            }
-        } catch (SupportException e) {
-            // Shouldn't happen since all properties were checked in order
-            // to create this StorableCodec.
-            throw new UndeclaredThrowableException(e);
-        }
-
-        Class<? extends SearchKeyFactory> clazz = ci.defineClass(cf);
-        try {
-            return clazz.newInstance();
-        } catch (InstantiationException e) {
-            throw new UndeclaredThrowableException(e);
-        } catch (IllegalAccessException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-
-    private Decoder<S> generateDecoder(int generation) throws FetchException {
-        // Create an encoding strategy against the reconstructed storable.
-        GenericEncodingStrategy<? extends Storable> altStrategy;
-        try {
-            Class<? extends Storable> altStorable = mLayout.getGeneration(generation)
-                .reconstruct(mStorableClass.getClassLoader());
-            altStrategy = mFactory.createStrategy(altStorable, null);
-        } catch (RepositoryException e) {
-            throw new CorruptEncodingException(e);
-        }
-
-        ClassInjector ci = ClassInjector.create(mType.getName(), mStorableClass.getClassLoader());
-        ClassFile cf = new ClassFile(ci.getClassName());
-        cf.addInterface(Decoder.class);
-        cf.markSynthetic();
-        cf.setSourceFile(GenericStorableCodec.class.getName());
-        cf.setTarget("1.5");
-
-        // Add public no-arg constructor.
-        cf.addDefaultConstructor();
-
-        // Declare some types.
-        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-
-        // Define the required decode method.
-        MethodInfo mi = cf.addMethod
-            (Modifiers.PUBLIC, "decode", null, new TypeDesc[] {storableType, byteArrayType});
-        CodeBuilder b = new CodeBuilder(mi);
-
-        LocalVariable uncastDestVar = b.getParameter(0);
-        b.loadLocal(uncastDestVar);
-        LocalVariable destVar = b.createLocalVariable(null, TypeDesc.forClass(mStorableClass));
-        b.checkCast(destVar.getType());
-        b.storeLocal(destVar);
-        LocalVariable dataVar = b.getParameter(1);
-
-        // assembler            = b
-        // properties           = null (defaults to all non-key properties)
-        // instanceVar          = "dest" storable
-        // adapterInstanceClass = null (null means use instanceVar, in this case is "dest")
-        // useWriteMethods      = false (will set fields directly)
-        // generation           = generation
-        // altGenerationHandler = null (generation should match)
-        // encodedVar           = "data" byte array
-        try {
-            altStrategy.buildDataDecoding
-                (b, null, destVar, null, false, generation, null, dataVar);
-        } catch (SupportException e) {
-            throw new CorruptEncodingException(e);
-        }
-
-        b.returnVoid();
-
-        Class<? extends Decoder> clazz = ci.defineClass(cf);
-        try {
-            return clazz.newInstance();
-        } catch (InstantiationException e) {
-            throw new UndeclaredThrowableException(e);
-        } catch (IllegalAccessException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-
-    /**
-     * Creates custom raw search keys for {@link Storable} types. It is
-     * intended for supporting queries and indexes.
-     */
-    public interface SearchKeyFactory<S extends Storable> {
-        /**
-         * Build a search key by extracting all the desired properties from the
-         * given storable.
-         *
-         * @param storable extract a subset of properties from this instance
-         * @return raw search key
-         */
-        byte[] encodeSearchKey(S storable);
-
-        /**
-         * Build a search key by extracting all the desired properties from the
-         * given storable.
-         *
-         * @param storable extract a subset of properties from this instance
-         * @param rangeStart index of first property to use. Its value must be less
-         * than the count of properties used by this factory.
-         * @param rangeEnd index of last property to use, exlusive. Its value must
-         * be less than or equal to the count of properties used by this factory.
-         * @return raw search key
-         */
-        byte[] encodeSearchKey(S storable, int rangeStart, int rangeEnd);
-
-        /**
-         * Build a search key by supplying property values without a storable.
-         *
-         * @param values values to build into a key. It must be long enough to
-         * accommodate all of properties used by this factory.
-         * @return raw search key
-         */
-        byte[] encodeSearchKey(Object[] values);
-
-        /**
-         * Build a search key by supplying property values without a storable.
-         *
-         * @param values values to build into a key. The length may be less than
-         * the amount of properties used by this factory. It must not be less than the
-         * difference between rangeStart and rangeEnd.
-         * @param rangeStart index of first property to use. Its value must be less
-         * than the count of properties used by this factory.
-         * @param rangeEnd index of last property to use, exlusive. Its value must
-         * be less than or equal to the count of properties used by this factory.
-         * @return raw search key
-         */
-        byte[] encodeSearchKey(Object[] values, int rangeStart, int rangeEnd);
-
-        /**
-         * Returns the search key for when there are no values. Returned value
-         * may be null.
-         */
-        byte[] encodeSearchKeyPrefix();
-    }
-
-    /**
-     * Used for decoding different generations of Storable.
-     */
-    public interface Decoder<S extends Storable> {
-        /**
-         * @param dest storable to receive decoded properties
-         * @param data decoded into properties, some of which may be dropped if
-         * destination storable doesn't have it
-         */
-        void decode(S dest, byte[] data);
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java
deleted file mode 100644
index fefa880..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.layout.Layout;
-
-/**
- * Factory for generic codec that supports any kind of storable by
- * auto-generating and caching storable implementations.
- *
- * @author Brian S O'Neill
- */
-public class GenericStorableCodecFactory implements StorableCodecFactory {
-    public GenericStorableCodecFactory() {
-    }
-
-    /**
-     * Returns null to let repository decide what the name should be.
-     */
-    public String getStorageName(Class<? extends Storable> type) throws SupportException {
-        return null;
-    }
-
-    /**
-     * @param type type of storable to create codec for
-     * @param pkIndex suggested index for primary key (optional)
-     * @param isMaster when true, version properties and sequences are managed
-     * @param layout when non-null, encode a storable layout generation
-     * value in one or four bytes. Generation 0..127 is encoded in one byte, and
-     * 128..max is encoded in four bytes, with the most significant bit set.
-     * @throws SupportException if type is not supported
-     */
-    @SuppressWarnings("unchecked")
-    public <S extends Storable> GenericStorableCodec<S> createCodec(Class<S> type,
-                                                                    StorableIndex pkIndex,
-                                                                    boolean isMaster,
-                                                                    Layout layout)
-        throws SupportException
-    {
-        return GenericStorableCodec.getInstance
-            (this, createStrategy(type, pkIndex), isMaster, layout);
-    }
-
-    /**
-     * Override to return a different EncodingStrategy.
-     *
-     * @param type type of Storable to generate code for
-     * @param pkIndex specifies sequence and ordering of key properties (optional)
-     */
-    protected <S extends Storable> GenericEncodingStrategy<S> createStrategy
-        (Class<S> type, StorableIndex<S> pkIndex)
-        throws SupportException
-    {
-        return new GenericEncodingStrategy<S>(type, pkIndex);
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java b/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java
deleted file mode 100644
index 127216f..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java
+++ /dev/null
@@ -1,646 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.CorruptEncodingException;
-
-import static com.amazon.carbonado.spi.raw.KeyEncoder.*;
-
-/**
- * A very low-level class that decodes key components encoded by methods of
- * {@link KeyEncoder}.
- *
- * @author Brian S O'Neill
- */
-public class KeyDecoder extends DataDecoder {
-
-    /**
-     * Decodes a signed integer from exactly 4 bytes, as encoded for descending
-     * order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed integer value
-     */
-    public static int decodeIntDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        return ~decodeInt(src, srcOffset);
-    }
-
-    /**
-     * Decodes a signed Integer object from exactly 1 or 5 bytes, as encoded
-     * for descending order. If null is returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Integer object or null
-     */
-    public static Integer decodeIntegerObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeIntDesc(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed long from exactly 8 bytes, as encoded for descending
-     * order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed long value
-     */
-    public static long decodeLongDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        return ~decodeLong(src, srcOffset);
-    }
-
-    /**
-     * Decodes a signed Long object from exactly 1 or 9 bytes, as encoded for
-     * descending order. If null is returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Long object or null
-     */
-    public static Long decodeLongObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeLongDesc(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed byte from exactly 1 byte, as encoded for descending
-     * order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed byte value
-     */
-    public static byte decodeByteDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (byte)(src[srcOffset] ^ 0x7f);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Byte object from exactly 1 or 2 bytes, as encoded for
-     * descending order. If null is returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Byte object or null
-     */
-    public static Byte decodeByteObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeByteDesc(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed short from exactly 2 bytes, as encoded for descending
-     * order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed short value
-     */
-    public static short decodeShortDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x7fff);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a signed Short object from exactly 1 or 3 bytes, as encoded for
-     * descending order. If null is returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return signed Short object or null
-     */
-    public static Short decodeShortObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeShortDesc(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a char from exactly 2 bytes, as encoded for descending order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return char value
-     */
-    public static char decodeCharDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return (char)~((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff));
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a Character object from exactly 1 or 3 bytes, as encoded for
-     * descending order. If null is returned, then 1 byte was read.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Character object or null
-     */
-    public static Character decodeCharacterObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            int b = src[srcOffset];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            return decodeCharDesc(src, srcOffset + 1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a boolean from exactly 1 byte, as encoded for descending order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return boolean value
-     */
-    public static boolean decodeBooleanDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            return src[srcOffset] == 127;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a Boolean object from exactly 1 byte, as encoded for descending
-     * order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Boolean object or null
-     */
-    public static Boolean decodeBooleanObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        try {
-            switch (src[srcOffset]) {
-            case NULL_BYTE_LOW: case NULL_BYTE_HIGH:
-                return null;
-            case (byte)127:
-                return Boolean.TRUE;
-            default:
-                return Boolean.FALSE;
-            }
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes a float from exactly 4 bytes, as encoded for descending order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return float value
-     */
-    public static float decodeFloatDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        int bits = decodeFloatBits(src, srcOffset);
-        if (bits >= 0) {
-            bits ^= 0x7fffffff;
-        }
-        return Float.intBitsToFloat(bits);
-    }
-
-    /**
-     * Decodes a Float object from exactly 4 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Float object or null
-     */
-    public static Float decodeFloatObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        int bits = decodeFloatBits(src, srcOffset);
-        if (bits >= 0) {
-            bits ^= 0x7fffffff;
-        }
-        return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits);
-    }
-
-    /**
-     * Decodes a double from exactly 8 bytes, as encoded for descending order.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return double value
-     */
-    public static double decodeDoubleDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        long bits = decodeDoubleBits(src, srcOffset);
-        if (bits >= 0) {
-            bits ^= 0x7fffffffffffffffL;
-        }
-        return Double.longBitsToDouble(bits);
-    }
-
-    /**
-     * Decodes a Double object from exactly 8 bytes.
-     *
-     * @param src source of encoded bytes
-     * @param srcOffset offset into source array
-     * @return Double object or null
-     */
-    public static Double decodeDoubleObjDesc(byte[] src, int srcOffset)
-        throws CorruptEncodingException
-    {
-        long bits = decodeDoubleBits(src, srcOffset);
-        if (bits >= 0) {
-            bits ^= 0x7fffffffffffffffL;
-        }
-        return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits);
-    }
-
-    /**
-     * Decodes the given byte array as originally encoded for ascending order.
-     * The decoding stops when any kind of terminator or illegal byte has been
-     * read. The decoded bytes are stored in valueRef.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded byte array is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decode(byte[] src, int srcOffset, byte[][] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            return decode(src, srcOffset, valueRef, 0);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes the given byte array as originally encoded for descending order.
-     * The decoding stops when any kind of terminator or illegal byte has been
-     * read. The decoded bytes are stored in valueRef.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded byte array is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decodeDesc(byte[] src, int srcOffset, byte[][] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            return decode(src, srcOffset, valueRef, -1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * @param xorMask 0 for normal decoding, -1 for descending decoding
-     */
-    private static int decode(byte[] src, int srcOffset, byte[][] valueRef, int xorMask) {
-        // Scan ahead, looking for terminator.
-        int srcEnd = srcOffset;
-        while (true) {
-            byte b = src[srcEnd++];
-            if (-32 <= b && b < 32) {
-                if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                    if ((srcEnd - 1) <= srcOffset) {
-                        valueRef[0] = null;
-                        return 1;
-                    }
-                }
-                break;
-            }
-        }
-
-        if (srcEnd - srcOffset == 1) {
-            valueRef[0] = EMPTY_BYTE_ARRAY;
-            return 1;
-        }
-
-        // Value is decoded from base-32768.
-
-        int valueLength = ((srcEnd - srcOffset - 1) * 120) >> 7;
-        byte[] value = new byte[valueLength];
-        int valueOffset = 0;
-
-        final int originalOffset = srcOffset;
-
-        int accumBits = 0;
-        int accum = 0;
-
-        while (true) {
-            int d = (src[srcOffset++] ^ xorMask) & 0xff;
-            int b;
-            if (srcOffset == srcEnd ||
-                (b = (src[srcOffset++] ^ xorMask) & 0xff) < 32 || b > 223) {
-                // Handle special case where one byte was emitted for digit.
-                d -= 32;
-                // To produce digit, multiply d by 192 and add 191 to adjust
-                // for missing remainder. The lower bits are discarded anyhow.
-                d = (d << 7) + (d << 6) + 191;
-
-                // Shift decoded digit into accumulator.
-                accumBits += 15;
-                accum = (accum << 15) | d;
-
-                break;
-            }
-
-            d -= 32;
-            // To produce digit, multiply d by 192 and add in remainder.
-            d = ((d << 7) + (d << 6)) + b - 32;
-
-            // Shift decoded digit into accumulator.
-            accumBits += 15;
-            accum = (accum << 15) | d;
-
-            if (accumBits == 15) {
-                value[valueOffset++] = (byte)(accum >> 7);
-            } else {
-                value[valueOffset++] = (byte)(accum >> (accumBits - 8));
-                accumBits -= 8;
-                value[valueOffset++] = (byte)(accum >> (accumBits - 8));
-            }
-            accumBits -= 8;
-        }
-
-        if (accumBits >= 8 && valueOffset < valueLength) {
-            value[valueOffset] = (byte)(accum >> (accumBits - 8));
-        }
-
-        valueRef[0] = value;
-
-        return srcOffset - originalOffset;
-    }
-
-    /**
-     * Decodes an encoded string from the given byte array.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded string is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decodeString(byte[] src, int srcOffset, String[] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            return decodeString(src, srcOffset, valueRef, 0);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes an encoded string from the given byte array as originally
-     * encoded for descending order.
-     *
-     * @param src source of encoded data
-     * @param srcOffset offset into encoded data
-     * @param valueRef decoded string is stored in element 0, which may be null
-     * @return amount of bytes read from source
-     * @throws CorruptEncodingException if source data is corrupt
-     */
-    public static int decodeStringDesc(byte[] src, int srcOffset, String[] valueRef)
-        throws CorruptEncodingException
-    {
-        try {
-            return decodeString(src, srcOffset, valueRef, -1);
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * @param xorMask 0 for normal decoding, -1 for descending decoding
-     */
-    private static int decodeString(byte[] src, int srcOffset, String[] valueRef, int xorMask)
-        throws CorruptEncodingException
-    {
-        // Scan ahead, looking for terminator.
-        int srcEnd = srcOffset;
-        while (true) {
-            byte b = src[srcEnd++];
-            if (-2 <= b && b < 2) {
-                if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                    if ((srcEnd - 1) <= srcOffset) {
-                        valueRef[0] = null;
-                        return 1;
-                    }
-                }
-                break;
-            }
-        }
-
-        if (srcEnd - srcOffset == 1) {
-            valueRef[0] = "";
-            return 1;
-        }
-
-        // Allocate a character array which may be longer than needed once
-        // bytes are decoded into characters.
-        char[] value = new char[srcEnd - srcOffset];
-        int valueOffset = 0;
-
-        final int originalOffset = srcOffset;
-
-        while (srcOffset < srcEnd) {
-            int c = (src[srcOffset++] ^ xorMask) & 0xff;
-            switch (c >> 5) {
-            case 0: case 1: case 2: case 3:
-                // 0xxxxxxx
-                value[valueOffset++] = (char)(c - 2);
-                break;
-            case 4: case 5:
-                // 10xxxxxx xxxxxxxx
-
-                c = c & 0x3f;
-                // Multiply by 192, add in remainder, remove offset of 2, and de-normalize.
-                value[valueOffset++] =
-                    (char)((c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 94);
-
-                break;
-            case 6:
-                // 110xxxxx xxxxxxxx xxxxxxxx
-
-                c = c & 0x1f;
-                // Multiply by 192, add in remainder...
-                c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) - 32;
-                // ...multiply by 192, add in remainder, remove offset of 2, and de-normalize.
-                c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 12382;
-
-                if (c >= 0x10000) {
-                    // Split into surrogate pair.
-                    c -= 0x10000;
-                    value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff));
-                    value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff));
-                } else {
-                    value[valueOffset++] = (char)c;
-                }
-
-                break;
-            default:
-                // 111xxxxx
-                // Illegal.
-                throw new CorruptEncodingException
-                    ("Corrupt encoded string data (source offset = "
-                     + (srcOffset - 1) + ')');
-            }
-        }
-
-        valueRef[0] = new String(value, 0, valueOffset - 1);
-
-        return srcEnd - originalOffset;
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * KeyEncoder#encodeSingleDesc}.
-     */
-    public static byte[] decodeSingleDesc(byte[] src) throws CorruptEncodingException {
-        return decodeSingleDesc(src, 0, 0);
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * KeyEncoder#encodeSingleDesc}.
-     *
-     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
-     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
-     */
-    public static byte[] decodeSingleDesc(byte[] src, int prefixPadding, int suffixPadding)
-        throws CorruptEncodingException
-    {
-        try {
-            int length = src.length - suffixPadding - prefixPadding;
-            if (length == 0) {
-                return EMPTY_BYTE_ARRAY;
-            }
-            byte[] dst = new byte[length];
-            while (--length >= 0) {
-                dst[length] = (byte) (~src[prefixPadding + length]);
-            }
-            return dst;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * KeyEncoder#encodeSingleNullableDesc}.
-     */
-    public static byte[] decodeSingleNullableDesc(byte[] src) throws CorruptEncodingException {
-        return decodeSingleNullableDesc(src, 0, 0);
-    }
-
-    /**
-     * Decodes the given byte array which was encoded by {@link
-     * KeyEncoder#encodeSingleNullableDesc}.
-     *
-     * @param prefixPadding amount of extra bytes to skip from start of encoded byte array
-     * @param suffixPadding amount of extra bytes to skip at end of encoded byte array
-     */
-    public static byte[] decodeSingleNullableDesc(byte[] src, int prefixPadding, int suffixPadding)
-        throws CorruptEncodingException
-    {
-        try {
-            byte b = src[prefixPadding];
-            if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) {
-                return null;
-            }
-            int length = src.length - suffixPadding - 1 - prefixPadding;
-            if (length == 0) {
-                return EMPTY_BYTE_ARRAY;
-            }
-            byte[] dst = new byte[length];
-            while (--length >= 0) {
-                dst[length] = (byte) (~src[1 + prefixPadding + length]);
-            }
-            return dst;
-        } catch (IndexOutOfBoundsException e) {
-            throw new CorruptEncodingException(null, e);
-        }
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java b/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java
deleted file mode 100644
index dd0faf9..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java
+++ /dev/null
@@ -1,741 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-/**
- * A very low-level class that supports encoding of primitive data into unique,
- * sortable byte array keys. If the data to encode is of a variable size, then
- * it is written in base-32768, using only byte values 32..223. This allows
- * special values such as nulls and terminators to be unambiguously
- * encoded. Terminators for variable data can be encoded using 1 for ascending
- * order and 254 for descending order. Nulls can be encoded as 255 for high
- * ordering and 0 for low ordering.
- *
- * @author Brian S O'Neill
- * @see KeyDecoder
- */
-public class KeyEncoder extends DataEncoder {
-
-    /** Byte to terminate variable data encoded for ascending order */
-    static final byte TERMINATOR = (byte)1;
-
-    /**
-     * Encodes the given signed integer into exactly 4 bytes for descending
-     * order.
-     *
-     * @param value signed integer value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(int value, byte[] dst, int dstOffset) {
-        encode(~value, dst, dstOffset);
-    }
-
-    /**
-     * Encodes the given signed Integer object into exactly 1 or 5 bytes for
-     * descending order. If the Integer object is never expected to be null,
-     * consider encoding as an int primitive.
-     *
-     * @param value optional signed Integer value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(Integer value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_LOW;
-            encode(~value.intValue(), dst, dstOffset + 1);
-            return 5;
-        }
-    }
-
-    /**
-     * Encodes the given signed long into exactly 8 bytes for descending order.
-     *
-     * @param value signed long value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(long value, byte[] dst, int dstOffset) {
-        encode(~value, dst, dstOffset);
-    }
-
-    /**
-     * Encodes the given signed Long object into exactly 1 or 9 bytes for
-     * descending order. If the Long object is never expected to be null,
-     * consider encoding as a long primitive.
-     *
-     * @param value optional signed Long value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(Long value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_LOW;
-            encode(~value.longValue(), dst, dstOffset + 1);
-            return 9;
-        }
-    }
-
-    /**
-     * Encodes the given signed byte into exactly 1 byte for descending order.
-     *
-     * @param value signed byte value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(byte value, byte[] dst, int dstOffset) {
-        dst[dstOffset] = (byte)(value ^ 0x7f);
-    }
-
-    /**
-     * Encodes the given signed Byte object into exactly 1 or 2 bytes for
-     * descending order. If the Byte object is never expected to be null,
-     * consider encoding as a byte primitive.
-     *
-     * @param value optional signed Byte value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(Byte value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_LOW;
-            dst[dstOffset + 1] = (byte)(value ^ 0x7f);
-            return 2;
-        }
-    }
-
-    /**
-     * Encodes the given signed short into exactly 2 bytes for descending
-     * order.
-     *
-     * @param value signed short value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(short value, byte[] dst, int dstOffset) {
-        encode((short) ~value, dst, dstOffset);
-    }
-
-    /**
-     * Encodes the given signed Short object into exactly 1 or 3 bytes for
-     * descending order. If the Short object is never expected to be null,
-     * consider encoding as a short primitive.
-     *
-     * @param value optional signed Short value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(Short value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_LOW;
-            encode((short) ~value.shortValue(), dst, dstOffset + 1);
-            return 3;
-        }
-    }
-
-    /**
-     * Encodes the given character into exactly 2 bytes for descending order.
-     *
-     * @param value character value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(char value, byte[] dst, int dstOffset) {
-        encode((char) ~value, dst, dstOffset);
-    }
-
-    /**
-     * Encodes the given Character object into exactly 1 or 3 bytes for
-     * descending order. If the Character object is never expected to be null,
-     * consider encoding as a char primitive.
-     *
-     * @param value optional Character value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(Character value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        } else {
-            dst[dstOffset] = NOT_NULL_BYTE_LOW;
-            encode((char) ~value.charValue(), dst, dstOffset + 1);
-            return 3;
-        }
-    }
-
-    /**
-     * Encodes the given boolean into exactly 1 byte for descending order.
-     *
-     * @param value boolean value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(boolean value, byte[] dst, int dstOffset) {
-        dst[dstOffset] = value ? (byte)127 : (byte)128;
-    }
-
-    /**
-     * Encodes the given Boolean object into exactly 1 byte for descending
-     * order.
-     *
-     * @param value optional Boolean value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(Boolean value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-        } else {
-            dst[dstOffset] = value.booleanValue() ? (byte)127 : (byte)128;
-        }
-    }
-
-    /**
-     * Encodes the given float into exactly 4 bytes for descending order.
-     *
-     * @param value float value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(float value, byte[] dst, int dstOffset) {
-        int bits = Float.floatToIntBits(value);
-        if (bits >= 0) {
-            bits ^= 0x7fffffff;
-        }
-        dst[dstOffset    ] = (byte)(bits >> 24);
-        dst[dstOffset + 1] = (byte)(bits >> 16);
-        dst[dstOffset + 2] = (byte)(bits >> 8);
-        dst[dstOffset + 3] = (byte)bits;
-    }
-
-    /**
-     * Encodes the given Float object into exactly 4 bytes for descending
-     * order. A non-canonical NaN value is used to represent null.
-     *
-     * @param value optional Float value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(Float value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            encode(~0x7fffffff, dst, dstOffset);
-        } else {
-            encodeDesc(value.floatValue(), dst, dstOffset);
-        }
-    }
-
-    /**
-     * Encodes the given double into exactly 8 bytes for descending order.
-     *
-     * @param value double value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(double value, byte[] dst, int dstOffset) {
-        long bits = Double.doubleToLongBits(value);
-        if (bits >= 0) {
-            bits ^= 0x7fffffffffffffffL;
-        }
-        int w = (int)(bits >> 32);
-        dst[dstOffset    ] = (byte)(w >> 24);
-        dst[dstOffset + 1] = (byte)(w >> 16);
-        dst[dstOffset + 2] = (byte)(w >> 8);
-        dst[dstOffset + 3] = (byte)w;
-        w = (int)bits;
-        dst[dstOffset + 4] = (byte)(w >> 24);
-        dst[dstOffset + 5] = (byte)(w >> 16);
-        dst[dstOffset + 6] = (byte)(w >> 8);
-        dst[dstOffset + 7] = (byte)w;
-    }
-
-    /**
-     * Encodes the given Double object into exactly 8 bytes for descending
-     * order. A non-canonical NaN value is used to represent null.
-     *
-     * @param value optional Double value to encode
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     */
-    public static void encodeDesc(Double value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            encode(~0x7fffffffffffffffL, dst, dstOffset);
-        } else {
-            encodeDesc(value.doubleValue(), dst, dstOffset);
-        }
-    }
-
-    /**
-     * Encodes the given optional unsigned byte array into a variable amount of
-     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
-     * the amount written can be determined by calling calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(byte[] value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_HIGH;
-            return 1;
-        }
-        return encode(value, 0, value.length, dst, dstOffset, 0);
-    }
-
-    /**
-     * Encodes the given optional unsigned byte array into a variable amount of
-     * bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
-     * the amount written can be determined by calling calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param valueOffset offset into byte array
-     * @param valueLength length of data in byte array
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(byte[] value, int valueOffset, int valueLength,
-                             byte[] dst, int dstOffset) {
-        return encode(value, valueOffset, valueLength, dst, dstOffset, 0);
-    }
-
-    /**
-     * Encodes the given optional unsigned byte array into a variable amount of
-     * bytes for descending order. If the byte array is null, exactly 1 byte is
-     * written. Otherwise, the amount written is determined by calling
-     * calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(byte[] value, byte[] dst, int dstOffset) {
-        if (value == null) {
-            dst[dstOffset] = NULL_BYTE_LOW;
-            return 1;
-        }
-        return encode(value, 0, value.length, dst, dstOffset, -1);
-    }
-
-    /**
-     * Encodes the given optional unsigned byte array into a variable amount of
-     * bytes for descending order. If the byte array is null, exactly 1 byte is
-     * written. Otherwise, the amount written is determined by calling
-     * calculateEncodedLength.
-     *
-     * @param value byte array value to encode, may be null
-     * @param valueOffset offset into byte array
-     * @param valueLength length of data in byte array
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(byte[] value, int valueOffset, int valueLength,
-                                 byte[] dst, int dstOffset) {
-        return encode(value, valueOffset, valueLength, dst, dstOffset, -1);
-    }
-
-    /**
-     * @param xorMask 0 for normal encoding, -1 for descending encoding
-     */
-    private static int encode(byte[] value, int valueOffset, int valueLength,
-                              byte[] dst, int dstOffset, int xorMask) {
-        if (value == null) {
-            dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask);
-            return 1;
-        }
-
-        final int originalOffset = dstOffset;
-
-        // Value is encoded in base-32768.
-
-        int accumBits = 0;
-        int accum = 0;
-
-        final int end = valueOffset + valueLength;
-        for (int i=valueOffset; i<end; i++) {
-            if (accumBits <= 7) {
-                accumBits += 8;
-                accum = (accum << 8) | (value[i] & 0xff);
-                if (accumBits == 15) {
-                    emitDigit(accum, dst, dstOffset, xorMask);
-                    dstOffset += 2;
-                    accum = 0;
-                    accumBits = 0;
-                }
-            } else {
-                int supply = 15 - accumBits;
-                accum = (accum << supply) | ((value[i] & 0xff) >> (8 - supply));
-                emitDigit(accum, dst, dstOffset, xorMask);
-                dstOffset += 2;
-                accumBits = 8 - supply;
-                accum = value[i] & ((1 << accumBits) - 1);
-            }
-        }
-
-        if (accumBits > 0) {
-            // Pad with zeros.
-            accum <<= (15 - accumBits);
-            if (accumBits <= 7) {
-                // Since amount of significant bits is small, emit only the
-                // upper half of the digit. The following code is modified from
-                // emitDigit.
-
-                int a = (accum * 21845) >> 22;
-                if (accum - ((a << 7) + (a << 6)) == 192) {
-                    a++;
-                }
-                dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
-            } else {
-                emitDigit(accum, dst, dstOffset, xorMask);
-                dstOffset += 2;
-            }
-        }
-
-        // Append terminator.
-        dst[dstOffset++] = (byte)(TERMINATOR ^ xorMask);
-
-        return dstOffset - originalOffset;
-    }
-
-    /**
-     * Emits a base-32768 digit using exactly two bytes. The first byte is in the range
-     * 32..202 and the second byte is in the range 32..223.
-     *
-     * @param value digit value in the range 0..32767
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @param xorMask 0 for normal encoding, -1 for descending encoding
-     */
-    private static void emitDigit(int value, byte[] dst, int dstOffset, int xorMask) {
-        // The first byte is computed as ((value / 192) + 32) and the second
-        // byte is computed as ((value % 192) + 32). To speed things up a bit,
-        // the integer division and remainder operations are replaced with a
-        // scaled multiplication.
-
-        // approximate value / 192
-        int a = (value * 21845) >> 22;
-
-        // approximate value % 192
-        // Note: the value 192 was chosen as a divisor because a multiply by
-        // 192 can be replaced with two summed shifts.
-        int b = value - ((a << 7) + (a << 6));
-        if (b == 192) {
-            // Fix error.
-            a++;
-            b = 0;
-        }
-
-        dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
-        dst[dstOffset] = (byte)((b + 32) ^ xorMask);
-    }
-
-    /**
-     * Returns the amount of bytes required to encode a byte array of the given
-     * length.
-     *
-     * @param value byte array value to encode, may be null
-     * @return amount of bytes needed to encode
-     */
-    public static int calculateEncodedLength(byte[] value) {
-        return value == null ? 1 : calculateEncodedLength(value, 0, value.length);
-    }
-
-    /**
-     * Returns the amount of bytes required to encode the given byte array.
-     *
-     * @param value byte array value to encode, may be null
-     * @param valueOffset offset into byte array
-     * @param valueLength length of data in byte array
-     * @return amount of bytes needed to encode
-     */
-    public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) {
-        // The add of 119 is used to force ceiling rounding.
-        return value == null ? 1 : (((valueLength << 7) + 119) / 120 + 1);
-    }
-
-    /**
-     * Encodes the given optional String into a variable amount of bytes. The
-     * amount written can be determined by calling
-     * calculateEncodedStringLength.
-     * <p>
-     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
-     * characters are usually written in one byte. This encoding is more
-     * efficient than UTF-8, but it isn't compatible with UTF-8.
-     *
-     * @param value String value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encode(String value, byte[] dst, int dstOffset) {
-        return encode(value, dst, dstOffset, 0);
-    }
-
-    /**
-     * Encodes the given optional String into a variable amount of bytes for
-     * descending order. The amount written can be determined by calling
-     * calculateEncodedStringLength.
-     * <p>
-     * Strings are encoded in a fashion similar to UTF-8, in that ASCII
-     * characters are usually written in one byte. This encoding is more
-     * efficient than UTF-8, but it isn't compatible with UTF-8.
-     *
-     * @param value String value to encode, may be null
-     * @param dst destination for encoded bytes
-     * @param dstOffset offset into destination array
-     * @return amount of bytes written
-     */
-    public static int encodeDesc(String value, byte[] dst, int dstOffset) {
-        return encode(value, dst, dstOffset, -1);
-    }
-
-    /**
-     * @param xorMask 0 for normal encoding, -1 for descending encoding
-     */
-    private static int encode(String value, byte[] dst, int dstOffset, int xorMask) {
-        if (value == null) {
-            dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask);
-            return 1;
-        }
-
-        final int originalOffset = dstOffset;
-
-        // All characters have an offset of 2 added, in order to reserve bytes
-        // 0 and 1 for encoding nulls and terminators. This means the ASCII
-        // string "HelloWorld" is actually encoded as "JgnnqYqtnf". This also
-        // means that the ASCII '~' and del characters are encoded in two bytes.
-
-        int length = value.length();
-        for (int i = 0; i < length; i++) {
-            int c = value.charAt(i) + 2;
-            if (c <= 0x7f) {
-                // 0xxxxxxx
-                dst[dstOffset++] = (byte)(c ^ xorMask);
-            } else if (c <= 12415) {
-                // 10xxxxxx xxxxxxxx
-
-                // Second byte cannot have the values 0, 1, 254, or 255 because
-                // they clash with null and terminator bytes. Divide by 192 and
-                // store in first 6 bits. The remainder, with 32 added, goes
-                // into the second byte. Note that (192 * 63 + 191) + 128 == 12415.
-                // 63 is the maximum value that can be represented in 6 bits.
-
-                c -= 128; // c will always be at least 128, so normalize.
-
-                // approximate value / 192
-                int a = (c * 21845) >> 22;
-
-                // approximate value % 192
-                // Note: the value 192 was chosen as a divisor because a multiply by
-                // 192 can be replaced with two summed shifts.
-                c = c - ((a << 7) + (a << 6));
-                if (c == 192) {
-                    // Fix error.
-                    a++;
-                    c = 0;
-                }
-
-                dst[dstOffset++] = (byte)((0x80 | a) ^ xorMask);
-                dst[dstOffset++] = (byte)((c + 32) ^ xorMask);
-            } else {
-                // 110xxxxx xxxxxxxx xxxxxxxx
-
-                if ((c - 2) >= 0xd800 && (c - 2) <= 0xdbff) {
-                    // Found a high surrogate. Verify that surrogate pair is
-                    // well-formed. Low surrogate must follow high surrogate.
-                    if (i + 1 < length) {
-                        int c2 = value.charAt(i + 1);
-                        if (c2 >= 0xdc00 && c2 <= 0xdfff) {
-                            c = ((((c - 2) & 0x3ff) << 10) | (c2 & 0x3ff)) + 0x10002;
-                            i++;
-                        }
-                    }
-                }
-
-                // Second and third bytes cannot have the values 0, 1, 254, or
-                // 255 because they clash with null and terminator
-                // bytes. Divide by 192 twice, storing the first and second
-                // remainders in the third and second bytes, respectively.
-                // Note that largest unicode value supported is 2^20 + 65535 ==
-                // 1114111. When divided by 192 twice, the value is 30, which
-                // just barely fits in the 5 available bits of the first byte.
-
-                c -= 12416; // c will always be at least 12416, so normalize.
-
-                int a = (int)((c * 21845L) >> 22);
-                c = c - ((a << 7) + (a << 6));
-                if (c == 192) {
-                    a++;
-                    c = 0;
-                }
-
-                dst[dstOffset + 2] = (byte)((c + 32) ^ xorMask);
-
-                c = (a * 21845) >> 22;
-                a = a - ((c << 7) + (c << 6));
-                if (a == 192) {
-                    c++;
-                    a = 0;
-                }
-
-                dst[dstOffset++] = (byte)((0xc0 | c) ^ xorMask);
-                dst[dstOffset++] = (byte)((a + 32) ^ xorMask);
-                dstOffset++;
-            }
-        }
-
-        // Append terminator.
-        dst[dstOffset++] = (byte)(TERMINATOR ^ xorMask);
-
-        return dstOffset - originalOffset;
-    }
-
-    /**
-     * Returns the amount of bytes required to encode the given String.
-     *
-     * @param value String to encode, may be null
-     */
-    public static int calculateEncodedStringLength(String value) {
-        int encodedLen = 1;
-        if (value != null) {
-            int valueLength = value.length();
-            for (int i = 0; i < valueLength; i++) {
-                int c = value.charAt(i);
-                if (c <= (0x7f - 2)) {
-                    encodedLen++;
-                } else if (c <= (12415 - 2)) {
-                    encodedLen += 2;
-                } else {
-                    if (c >= 0xd800 && c <= 0xdbff) {
-                        // Found a high surrogate. Verify that surrogate pair is
-                        // well-formed. Low surrogate must follow high surrogate.
-                        if (i + 1 < valueLength) {
-                            int c2 = value.charAt(i + 1);
-                            if (c2 >= 0xdc00 && c2 <= 0xdfff) {
-                                i++;
-                            }
-                        }
-                    }
-                    encodedLen += 3;
-                }
-            }
-        }
-        return encodedLen;
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * required property, descending order, whose type is a byte array. The
-     * original byte array is returned if the length is zero.
-     */
-    public static byte[] encodeSingleDesc(byte[] value) {
-        return encodeSingleDesc(value, 0, 0);
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * required property, descending order, whose type is a byte array. The
-     * original byte array is returned if the length and padding lengths are
-     * zero.
-     *
-     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
-     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
-     */
-    public static byte[] encodeSingleDesc(byte[] value, int prefixPadding, int suffixPadding) {
-        int length = value.length;
-        if (prefixPadding <= 0 && suffixPadding <= 0 && length == 0) {
-            return value;
-        }
-        byte[] dst = new byte[prefixPadding + length + suffixPadding];
-        while (--length >= 0) {
-            dst[prefixPadding + length] = (byte) (~value[length]);
-        }
-        return dst;
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * nullable property, descending order, whose type is a byte array.
-     */
-    public static byte[] encodeSingleNullableDesc(byte[] value) {
-        return encodeSingleNullableDesc(value, 0, 0);
-    }
-
-    /**
-     * Encodes the given byte array for use when there is only a single
-     * nullable property, descending order, whose type is a byte array.
-     *
-     * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array
-     * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array
-     */
-    public static byte[] encodeSingleNullableDesc(byte[] value,
-                                                  int prefixPadding, int suffixPadding) {
-        if (prefixPadding <= 0 && suffixPadding <= 0) {
-            if (value == null) {
-                return NULL_BYTE_ARRAY_LOW;
-            }
-
-            int length = value.length;
-            if (length == 0) {
-                return NOT_NULL_BYTE_ARRAY_LOW;
-            }
-
-            byte[] dst = new byte[1 + length];
-            dst[0] = NOT_NULL_BYTE_LOW;
-            while (--length >= 0) {
-                dst[1 + length] = (byte) (~value[length]);
-            }
-            return dst;
-        }
-
-        if (value == null) {
-            byte[] dst = new byte[prefixPadding + 1 + suffixPadding];
-            dst[prefixPadding] = NULL_BYTE_LOW;
-            return dst;
-        }
-
-        int length = value.length;
-        byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding];
-        dst[prefixPadding] = NOT_NULL_BYTE_LOW;
-        while (--length >= 0) {
-            dst[prefixPadding + 1 + length] = (byte) (~value[length]);
-        }
-        return dst;
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java
deleted file mode 100644
index 362c17c..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.reflect.Method;
-
-import org.cojen.classfile.TypeDesc;
-
-import com.amazon.carbonado.layout.LayoutProperty;
-import com.amazon.carbonado.lob.Lob;
-
-/**
- *
- *
- * @author Brian S O'Neill
- */
-public class LayoutPropertyInfo implements GenericPropertyInfo {
-    private final LayoutProperty mProp;
-    private final TypeDesc mPropertyType;
-    private final TypeDesc mStorageType;
-    private final Method mFromStorage;
-    private final Method mToStorage;
-
-    LayoutPropertyInfo(LayoutProperty property) {
-        this(property, null, null, null);
-    }
-
-    LayoutPropertyInfo(LayoutProperty property,
-                       Class<?> storageType, Method fromStorage, Method toStorage)
-    {
-        mProp = property;
-        mPropertyType = TypeDesc.forDescriptor(property.getPropertyTypeDescriptor());
-        if (storageType == null) {
-            mStorageType = mPropertyType;
-        } else {
-            mStorageType = TypeDesc.forClass(storageType);
-        }
-        mFromStorage = fromStorage;
-        mToStorage = toStorage;
-    }
-
-    public String getPropertyName() {
-        return mProp.getPropertyName();
-    }
-
-    public TypeDesc getPropertyType() {
-        return mPropertyType;
-    }
-
-    public TypeDesc getStorageType() {
-        return mStorageType;
-    }
-
-    public boolean isNullable() {
-        return mProp.isNullable();
-    }
-
-    public boolean isLob() {
-        Class clazz = mPropertyType.toClass();
-        return clazz != null && Lob.class.isAssignableFrom(clazz);
-    }
-
-    public Method getFromStorageAdapter() {
-        return mFromStorage;
-    }
-
-    public Method getToStorageAdapter() {
-        return mToStorage;
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java b/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java
deleted file mode 100644
index b665191..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java
+++ /dev/null
@@ -1,743 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.util.NoSuchElementException;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.cursor.AbstractCursor;
-
-/**
- * Abstract Cursor implementation for a repository that manipulates raw bytes.
- *
- * @author Brian S O'Neill
- */
-public abstract class RawCursor<S> extends AbstractCursor<S> {
-    // States for mState.
-    private static final byte
-        UNINITIALIZED = 0,
-        CLOSED = 1,
-        TRY_NEXT = 2,
-        HAS_NEXT = 3;
-
-    /** Lock object, as passed into the constructor */
-    protected final Lock mLock;
-
-    private final byte[] mStartBound;
-    private final boolean mInclusiveStart;
-    private final byte[] mEndBound;
-    private final boolean mInclusiveEnd;
-    private final int mPrefixLength;
-    private final boolean mReverse;
-
-    private byte mState;
-
-    /**
-     * @param lock operations lock on this object
-     * @param startBound specify the starting key for the cursor, or null if first
-     * @param inclusiveStart true if start bound is inclusive
-     * @param endBound specify the ending key for the cursor, or null if last
-     * @param inclusiveEnd true if end bound is inclusive
-     * @param maxPrefix maximum expected common initial bytes in start and end bound
-     * @param reverse when true, iteration is reversed
-     * @throws IllegalArgumentException if any bound is null but is not inclusive
-     */
-    protected RawCursor(Lock lock,
-                        byte[] startBound, boolean inclusiveStart,
-                        byte[] endBound, boolean inclusiveEnd,
-                        int maxPrefix,
-                        boolean reverse) {
-        mLock = lock == null ? new ReentrantLock() : lock;
-
-        if ((startBound == null && !inclusiveStart) || (endBound == null && !inclusiveEnd)) {
-            throw new IllegalArgumentException();
-        }
-
-        mStartBound = startBound;
-        mInclusiveStart = inclusiveStart;
-        mEndBound = endBound;
-        mInclusiveEnd = inclusiveEnd;
-        mReverse = reverse;
-
-        // Determine common prefix for start and end bound.
-        if (maxPrefix <= 0 || startBound == null && endBound == null) {
-            mPrefixLength = 0;
-        } else {
-            int len = Math.min(maxPrefix, Math.min(startBound.length, endBound.length));
-            int i;
-            for (i=0; i<len; i++) {
-                if (startBound[i] != endBound[i]) {
-                    break;
-                }
-            }
-            mPrefixLength = i;
-        }
-    }
-
-    public void close() throws FetchException {
-        mLock.lock();
-        try {
-            if (mState != CLOSED) {
-                release();
-                // Switch state to closed before committing transaction, to
-                // prevent infinite recursion that results when transaction
-                // exits. Exiting a transaction causes all cursors to close.
-                mState = CLOSED;
-            }
-        } finally {
-            mLock.unlock();
-        }
-    }
-
-    public boolean hasNext() throws FetchException {
-        mLock.lock();
-        try {
-            try {
-                switch (mState) {
-                case UNINITIALIZED:
-                    if (mReverse ? toBoundedLast() : toBoundedFirst()) {
-                        mState = HAS_NEXT;
-                        return true;
-                    } else {
-                        mState = TRY_NEXT;
-                    }
-                    break;
-
-                case CLOSED: default:
-                    return false;
-
-                case TRY_NEXT:
-                    if (mReverse ? toBoundedPrevious() : toBoundedNext()) {
-                        mState = HAS_NEXT;
-                        return true;
-                    }
-                    break;
-
-                case HAS_NEXT:
-                    return true;
-                }
-            } catch (FetchException e) {
-                // Auto-close in response to FetchException.
-                try {
-                    close();
-                } catch (FetchException e2) {
-                    // Ignore.
-                }
-                throw e;
-            }
-
-            // Reached the end naturally, so close.
-            close();
-        } finally {
-            mLock.unlock();
-        }
-
-        return false;
-    }
-
-    public S next() throws FetchException, NoSuchElementException {
-        mLock.lock();
-        try {
-            if (!hasNext()) {
-                handleNoSuchElement();
-                throw new NoSuchElementException();
-            }
-            try {
-                S obj = instantiateCurrent();
-                mState = TRY_NEXT;
-                return obj;
-            } catch (FetchException e) {
-                // Auto-close in response to FetchException.
-                try {
-                    close();
-                } catch (FetchException e2) {
-                    // Ignore.
-                }
-                throw e;
-            }
-        } finally {
-            mLock.unlock();
-        }
-    }
-
-    public int skipNext(int amount) throws FetchException {
-        if (amount <= 0) {
-            if (amount < 0) {
-                throw new IllegalArgumentException("Cannot skip negative amount: " + amount);
-            }
-            return 0;
-        }
-
-        mLock.lock();
-        try {
-            int actual = 0;
-
-            if (hasNext()) {
-                try {
-                    actual += mReverse ? toBoundedPrevious(amount) : toBoundedNext(amount);
-                } catch (FetchException e) {
-                    // Auto-close in response to FetchException.
-                    try {
-                        close();
-                    } catch (FetchException e2) {
-                        // Ignore.
-                    }
-                    throw e;
-                }
-
-                if (actual >= amount) {
-                    return actual;
-                }
-                mState = TRY_NEXT;
-                // Since state was HAS_NEXT and is forced into TRY_NEXT, actual
-                // amount skipped is effectively one more.
-                actual++;
-            }
-
-            // Reached the end naturally, so close.
-            close();
-
-            return actual;
-        } finally {
-            mLock.unlock();
-        }
-    }
-
-    /**
-     * Release any internal resources, called when closed.
-     */
-    protected abstract void release() throws FetchException;
-
-    /**
-     * Returns the contents of the current key being referenced, or null
-     * otherwise. Caller is responsible for making a copy of the key. The array
-     * must not be modified concurrently.
-     *
-     * <p>If cursor is not opened, null must be returned.
-     *
-     * @return currently referenced key bytes or null if no current
-     * @throws IllegalStateException if key is disabled
-     */
-    protected abstract byte[] getCurrentKey() throws FetchException;
-
-    /**
-     * Returns the contents of the current value being referenced, or null
-     * otherwise. Caller is responsible for making a copy of the value. The
-     * array must not be modified concurrently.
-     *
-     * <p>If cursor is not opened, null must be returned.
-     *
-     * @return currently referenced value bytes or null if no current
-     * @throws IllegalStateException if value is disabled
-     */
-    protected abstract byte[] getCurrentValue() throws FetchException;
-
-    /**
-     * An optimization hint which disables key and value acquisition. The
-     * default implementation of this method does nothing.
-     */
-    protected void disableKeyAndValue() {
-    }
-
-    /**
-     * An optimization hint which disables just value acquisition. The default
-     * implementation of this method does nothing.
-     */
-    protected void disableValue() {
-    }
-
-    /**
-     * Enable key and value acquisition again, after they have been
-     * disabled. Calling this method forces the key and value to be
-     * re-acquired, if they had been disabled. Key and value acquisition must
-     * be enabled by default. The default implementation of this method does
-     * nothing.
-     */
-    protected void enableKeyAndValue() throws FetchException {
-    }
-
-    /**
-     * Returns a new Storable instance for the currently referenced entry.
-     *
-     * @return new Storable instance, never null
-     * @throws IllegalStateException if no current entry to instantiate
-     */
-    protected abstract S instantiateCurrent() throws FetchException;
-
-    /**
-     * Move the cursor to the first available entry. If false is returned, the
-     * cursor must be positioned before the first available entry.
-     *
-     * @return true if first entry exists and is now current
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toFirst() throws FetchException;
-
-    /**
-     * Move the cursor to the first available entry at or after the given
-     * key. If false is returned, the cursor must be positioned before the
-     * first available entry. Caller is responsible for preserving contents of
-     * array.
-     *
-     * @param key key to search for
-     * @return true if first entry exists and is now current
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toFirst(byte[] key) throws FetchException;
-
-    /**
-     * Move the cursor to the last available entry. If false is returned, the
-     * cursor must be positioned after the last available entry.
-     *
-     * @return true if last entry exists and is now current
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toLast() throws FetchException;
-
-    /**
-     * Move the cursor to the last available entry at or before the given
-     * key. If false is returned, the cursor must be positioned after the last
-     * available entry. Caller is responsible for preserving contents of array.
-     *
-     * @param key key to search for
-     * @return true if last entry exists and is now current
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toLast(byte[] key) throws FetchException;
-
-    /**
-     * Move the cursor to the next available entry, returning false if none. If
-     * false is returned, the cursor must be positioned after the last
-     * available entry.
-     *
-     * @return true if moved to next entry
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toNext() throws FetchException;
-
-    /**
-     * Move the cursor to the next available entry, incrementing by the amount
-     * given. The actual amount incremented is returned. If the amount is less
-     * then requested, the cursor must be positioned after the last available
-     * entry. Subclasses may wish to override this method with a faster
-     * implementation.
-     *
-     * <p>Calling to toNext(1) is equivalent to calling toNext().
-     *
-     * @param amount positive amount to advance
-     * @return actual amount advanced
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected int toNext(int amount) throws FetchException {
-        if (amount <= 1) {
-            return (amount <= 0) ? 0 : (toNext() ? 1 : 0);
-        }
-
-        int count = 0;
-
-        disableKeyAndValue();
-        try {
-            while (amount > 0) {
-                if (toNext()) {
-                    count++;
-                    amount--;
-                } else {
-                    break;
-                }
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return count;
-    }
-
-    /**
-     * Move the cursor to the next unique key, returning false if none. If
-     * false is returned, the cursor must be positioned after the last
-     * available entry. Subclasses may wish to override this method with a
-     * faster implementation.
-     *
-     * @return true if moved to next unique key
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected boolean toNextKey() throws FetchException {
-        byte[] initialKey = getCurrentKey();
-        if (initialKey == null) {
-            return false;
-        }
-
-        disableValue();
-        try {
-            while (true) {
-                if (toNext()) {
-                    byte[] currentKey = getCurrentKey();
-                    if (currentKey == null) {
-                        return false;
-                    }
-                    if (compareKeysPartially(currentKey, initialKey) > 0) {
-                        break;
-                    }
-                } else {
-                    return false;
-                }
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return true;
-    }
-
-    /**
-     * Move the cursor to the previous available entry, returning false if
-     * none. If false is returned, the cursor must be positioned before the
-     * first available entry.
-     *
-     * @return true if moved to previous entry
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected abstract boolean toPrevious() throws FetchException;
-
-    /**
-     * Move the cursor to the previous available entry, decrementing by the
-     * amount given. The actual amount decremented is returned. If the amount
-     * is less then requested, the cursor must be positioned before the first
-     * available entry. Subclasses may wish to override this method with a
-     * faster implementation.
-     *
-     * <p>Calling to toPrevious(1) is equivalent to calling toPrevious().
-     *
-     * @param amount positive amount to retreat
-     * @return actual amount retreated
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected int toPrevious(int amount) throws FetchException {
-        if (amount <= 1) {
-            return (amount <= 0) ? 0 : (toPrevious() ? 1 : 0);
-        }
-
-        int count = 0;
-
-        disableKeyAndValue();
-        try {
-            while (amount > 0) {
-                if (toPrevious()) {
-                    count++;
-                    amount--;
-                } else {
-                    break;
-                }
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return count;
-    }
-
-    /**
-     * Move the cursor to the previous unique key, returning false if none. If
-     * false is returned, the cursor must be positioned before the first
-     * available entry. Subclasses may wish to override this method with a
-     * faster implementation.
-     *
-     * @return true if moved to previous unique key
-     * @throws IllegalStateException if cursor is not opened
-     */
-    protected boolean toPreviousKey() throws FetchException {
-        byte[] initialKey = getCurrentKey();
-        if (initialKey == null) {
-            return false;
-        }
-
-        disableValue();
-        try {
-            while (true) {
-                if (toPrevious()) {
-                    byte[] currentKey = getCurrentKey();
-                    if (currentKey == null) {
-                        return false;
-                    }
-                    if (compareKeysPartially(getCurrentKey(), initialKey) < 0) {
-                        break;
-                    }
-                } else {
-                    return false;
-                }
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns &lt;0 if key1 is less, 0 if equal (at least partially), &gt;0
-     * if key1 is greater.
-     */
-    protected int compareKeysPartially(byte[] key1, byte[] key2) {
-        int length = Math.min(key1.length, key2.length);
-        for (int i=0; i<length; i++) {
-            int a1 = key1[i];
-            int a2 = key2[i];
-            if (a1 != a2) {
-                return (a1 & 0xff) - (a2 & 0xff);
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Called right before throwing NoSuchElementException. Subclasses may
-     * override to do special checks or throw a different exception.
-     */
-    protected void handleNoSuchElement() throws FetchException {
-    }
-
-    private boolean prefixMatches() throws FetchException {
-        int prefixLen = mPrefixLength;
-        if (prefixLen > 0) {
-            byte[] prefix = mStartBound;
-            byte[] key = getCurrentKey();
-            if (key == null) {
-                return false;
-            }
-            for (int i=0; i<prefixLen; i++) {
-                if (prefix[i] != key[i]) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    // Calls toFirst, but considers start and end bounds.
-    private boolean toBoundedFirst() throws FetchException {
-        if (mStartBound == null) {
-            if (!toFirst()) {
-                return false;
-            }
-        } else {
-            if (!toFirst(mStartBound.clone())) {
-                return false;
-            }
-            if (!mInclusiveStart) {
-                byte[] currentKey = getCurrentKey();
-                if (currentKey == null) {
-                    return false;
-                }
-                if (compareKeysPartially(mStartBound, currentKey) == 0) {
-                    if (!toNextKey()) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        if (mEndBound != null) {
-            byte[] currentKey = getCurrentKey();
-            if (currentKey == null) {
-                return false;
-            }
-            int result = compareKeysPartially(currentKey, mEndBound);
-            if (result >= 0) {
-                if (result > 0 || !mInclusiveEnd) {
-                    return false;
-                }
-            }
-        }
-
-        return prefixMatches();
-    }
-
-    // Calls toLast, but considers start and end bounds. Caller is responsible
-    // for preserving key.
-    private boolean toBoundedLast() throws FetchException {
-        if (mEndBound == null) {
-            if (!toLast()) {
-                return false;
-            }
-        } else {
-            if (!toLast(mEndBound.clone())) {
-                return false;
-            }
-            if (!mInclusiveEnd) {
-                byte[] currentKey = getCurrentKey();
-                if (currentKey == null) {
-                    return false;
-                }
-                if (compareKeysPartially(mEndBound, currentKey) == 0) {
-                    if (!toPreviousKey()) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        if (mStartBound != null) {
-            byte[] currentKey = getCurrentKey();
-            if (currentKey == null) {
-                return false;
-            }
-            int result = compareKeysPartially(currentKey, mStartBound);
-            if (result <= 0) {
-                if (result < 0 || !mInclusiveStart) {
-                    return false;
-                }
-            }
-        }
-
-        return prefixMatches();
-    }
-
-    // Calls toNext, but considers end bound.
-    private boolean toBoundedNext() throws FetchException {
-        if (!toNext()) {
-            return false;
-        }
-
-        if (mEndBound != null) {
-            byte[] currentKey = getCurrentKey();
-            if (currentKey == null) {
-                return false;
-            }
-            int result = compareKeysPartially(currentKey, mEndBound);
-            if (result >= 0) {
-                if (result > 0 || !mInclusiveEnd) {
-                    return false;
-                }
-            }
-        }
-
-        return prefixMatches();
-    }
-
-    // Calls toNext, but considers end bound.
-    private int toBoundedNext(int amount) throws FetchException {
-        if (mEndBound == null) {
-            return toNext(amount);
-        }
-
-        int count = 0;
-
-        disableValue();
-        try {
-            while (amount > 0) {
-                if (!toNext()) {
-                    break;
-                }
-
-                byte[] currentKey = getCurrentKey();
-                if (currentKey == null) {
-                    break;
-                }
-
-                int result = compareKeysPartially(currentKey, mEndBound);
-                if (result >= 0) {
-                    if (result > 0 || !mInclusiveEnd) {
-                        break;
-                    }
-                }
-
-                if (!prefixMatches()) {
-                    break;
-                }
-
-                count++;
-                amount--;
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return count;
-    }
-
-    // Calls toPrevious, but considers start bound.
-    private boolean toBoundedPrevious() throws FetchException {
-        if (!toPrevious()) {
-            return false;
-        }
-
-        if (mStartBound != null) {
-            byte[] currentKey = getCurrentKey();
-            if (currentKey == null) {
-                return false;
-            }
-            int result = compareKeysPartially(currentKey, mStartBound);
-            if (result <= 0) {
-                if (result < 0 || !mInclusiveStart) {
-                    // Too far now, reset to first.
-                    toBoundedFirst();
-                    return false;
-                }
-            }
-        }
-
-        return prefixMatches();
-    }
-
-    // Calls toPrevious, but considers start bound.
-    private int toBoundedPrevious(int amount) throws FetchException {
-        if (mStartBound == null) {
-            return toPrevious(amount);
-        }
-
-        int count = 0;
-
-        disableValue();
-        try {
-            while (amount > 0) {
-                if (!toPrevious()) {
-                    break;
-                }
-
-                byte[] currentKey = getCurrentKey();
-                if (currentKey == null) {
-                    break;
-                }
-
-                int result = compareKeysPartially(currentKey, mStartBound);
-                if (result <= 0) {
-                    if (result < 0 || !mInclusiveStart) {
-                        // Too far now, reset to first.
-                        toBoundedFirst();
-                        break;
-                    }
-                }
-
-                if (!prefixMatches()) {
-                    break;
-                }
-
-                count++;
-                amount--;
-            }
-        } finally {
-            enableKeyAndValue();
-        }
-
-        return count;
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java
deleted file mode 100644
index 6d6fbe5..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
-import java.util.EnumSet;
-import java.util.Map;
-
-import org.cojen.classfile.ClassFile;
-import org.cojen.classfile.CodeBuilder;
-import org.cojen.classfile.Label;
-import org.cojen.classfile.LocalVariable;
-import org.cojen.classfile.MethodInfo;
-import org.cojen.classfile.Modifiers;
-import org.cojen.classfile.TypeDesc;
-import org.cojen.util.ClassInjector;
-import org.cojen.util.WeakIdentityMap;
-
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.PersistException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.spi.MasterFeature;
-import com.amazon.carbonado.spi.MasterStorableGenerator;
-import com.amazon.carbonado.spi.MasterSupport;
-import com.amazon.carbonado.spi.StorableGenerator;
-import com.amazon.carbonado.spi.TriggerSupport;
-
-import static com.amazon.carbonado.spi.CommonMethodNames.*;
-
-/**
- * Generates and caches abstract implementations of {@link Storable} types
- * which are encoded and decoded in a raw format. The generated abstract
- * classes extend those created by {@link MasterStorableGenerator}.
- *
- * @author Brian S O'Neill
- * @see GenericStorableCodec
- * @see RawSupport
- */
-public class RawStorableGenerator {
-    // Note: All generated fields/methods have a "$" character in them to
-    // prevent name collisions with any inherited fields/methods. User storable
-    // properties are defined as fields which exactly match the property
-    // name. We don't want collisions with those either. Legal bean properties
-    // cannot have "$" in them, so there's nothing to worry about.
-
-    /** Name of protected abstract method in generated storable */
-    public static final String
-        ENCODE_KEY_METHOD_NAME = "encodeKey$",
-        DECODE_KEY_METHOD_NAME = "decodeKey$",
-        ENCODE_DATA_METHOD_NAME = "encodeData$",
-        DECODE_DATA_METHOD_NAME = "decodeData$";
-
-    @SuppressWarnings("unchecked")
-    private static Map<Class, Flavors<? extends Storable>> cCache = new WeakIdentityMap();
-
-    /**
-     * Collection of different abstract class flavors.
-     */
-    static class Flavors<S extends Storable> {
-        private Reference<Class<? extends S>> mMasterFlavor;
-
-        private Reference<Class<? extends S>> mNonMasterFlavor;
-
-        /**
-         * May return null.
-         */
-        Class<? extends S> getClass(boolean isMaster) {
-            Reference<Class<? extends S>> ref;
-            if (isMaster) {
-                ref = mMasterFlavor;
-            } else {
-                ref = mNonMasterFlavor;
-            }
-            return (ref != null) ? ref.get() : null;
-        }
-
-        @SuppressWarnings("unchecked")
-        void setClass(Class<? extends S> clazz, boolean isMaster) {
-            Reference<Class<? extends S>> ref = new SoftReference(clazz);
-            if (isMaster) {
-                mMasterFlavor = ref;
-            } else {
-                mNonMasterFlavor = ref;
-            }
-        }
-    }
-
-    // Can't be instantiated or extended
-    private RawStorableGenerator() {
-    }
-
-    /**
-     * Returns an abstract implementation of the given Storable type, which is
-     * fully thread-safe. The Storable type itself may be an interface or a
-     * class. If it is a class, then it must not be final, and it must have a
-     * public, no-arg constructor. Two constructors are defined for the
-     * abstract implementation:
-     *
-     * <pre>
-     * public &lt;init&gt;(RawSupport);
-
-     * public &lt;init&gt;(RawSupport, byte[] key, byte[] value);
-     * </pre>
-     *
-     * <p>Subclasses must implement the following abstract protected methods,
-     * whose exact names are defined by constants in this class:
-     *
-     * <pre>
-     * // Encode the primary key of this storable.
-     * protected abstract byte[] encodeKey();
-     *
-     * // Encode all properties of this storable excluding the primary key.
-     * protected abstract byte[] encodeData();
-     *
-     * // Decode the primary key into properties of this storable.
-     * // Note: this method is also invoked by the four argument constructor.
-     * protected abstract void decodeKey(byte[]);
-     *
-     * // Decode the data into properties of this storable.
-     * // Note: this method is also invoked by the four argument constructor.
-     * protected abstract void decodeData(byte[]);
-     * </pre>
-     *
-     * @param isMaster when true, version properties, sequences, and triggers are managed
-     * @throws IllegalArgumentException if type is null
-     */
-    @SuppressWarnings("unchecked")
-    public static <S extends Storable> Class<? extends S>
-        getAbstractClass(Class<S> type, boolean isMaster)
-        throws SupportException, IllegalArgumentException
-    {
-        synchronized (cCache) {
-            Class<? extends S> abstractClass;
-
-            Flavors<S> flavors = (Flavors<S>) cCache.get(type);
-
-            if (flavors == null) {
-                flavors = new Flavors<S>();
-                cCache.put(type, flavors);
-            } else if ((abstractClass = flavors.getClass(isMaster)) != null) {
-                return abstractClass;
-            }
-
-            abstractClass = generateAbstractClass(type, isMaster);
-            flavors.setClass(abstractClass, isMaster);
-
-            return abstractClass;
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <S extends Storable> Class<? extends S>
-        generateAbstractClass(Class<S> storableClass, boolean isMaster)
-        throws SupportException
-    {
-        EnumSet<MasterFeature> features;
-        if (isMaster) {
-            features = EnumSet.of(MasterFeature.VERSIONING,
-                                  MasterFeature.UPDATE_FULL,
-                                  MasterFeature.INSERT_SEQUENCES,
-                                  MasterFeature.INSERT_CHECK_REQUIRED);
-        } else {
-            features = EnumSet.of(MasterFeature.UPDATE_FULL);
-        }
-
-        final Class<? extends S> abstractClass =
-            MasterStorableGenerator.getAbstractClass(storableClass, features);
-
-        ClassInjector ci = ClassInjector.create
-            (storableClass.getName(), abstractClass.getClassLoader());
-
-        ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
-        cf.setModifiers(cf.getModifiers().toAbstract(true));
-        cf.markSynthetic();
-        cf.setSourceFile(RawStorableGenerator.class.getName());
-        cf.setTarget("1.5");
-
-        // Declare some types.
-        final TypeDesc storableType = TypeDesc.forClass(Storable.class);
-        final TypeDesc storageType = TypeDesc.forClass(Storage.class);
-        final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class);
-        final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class);
-        final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
-        final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
-
-        // Add constructor that accepts a RawSupport.
-        {
-            TypeDesc[] params = {rawSupportType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeSuperConstructor(new TypeDesc[] {masterSupportType});
-            b.returnVoid();
-        }
-
-        // Add constructor that accepts a RawSupport, an encoded key, and an
-        // encoded data.
-        {
-            TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType};
-            MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
-            CodeBuilder b = new CodeBuilder(mi);
-            b.loadThis();
-            b.loadLocal(b.getParameter(0));
-            b.invokeSuperConstructor(new TypeDesc[] {masterSupportType});
-
-            params = new TypeDesc[] {byteArrayType};
-
-            b.loadThis();
-            b.loadLocal(b.getParameter(1));
-            b.invokeVirtual(DECODE_KEY_METHOD_NAME, null, params);
-
-            b.loadThis();
-            b.loadLocal(b.getParameter(2));
-            b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
-
-            // Indicate that object is clean by calling markAllPropertiesClean.
-            b.loadThis();
-            b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
-
-            b.returnVoid();
-        }
-
-        // Declare protected abstract methods.
-        {
-            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
-                         ENCODE_KEY_METHOD_NAME, byteArrayType, null);
-            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
-                         DECODE_KEY_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
-            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
-                         ENCODE_DATA_METHOD_NAME, byteArrayType, null);
-            cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
-                         DECODE_DATA_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
-        }
-
-        // Add required protected doTryLoad_master method, which delegates to RawSupport.
-        {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PROTECTED.toFinal(true),
-                 MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
-            mi.addException(TypeDesc.forClass(FetchException.class));
-            CodeBuilder b = new CodeBuilder(mi);
-
-            b.loadThis();
-            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
-            b.checkCast(rawSupportType);
-            b.loadThis();
-            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
-            TypeDesc[] params = {byteArrayType};
-            b.invokeInterface(rawSupportType, "tryLoad", byteArrayType, params);
-            LocalVariable encodedDataVar = b.createLocalVariable(null, byteArrayType);
-            b.storeLocal(encodedDataVar);
-            b.loadLocal(encodedDataVar);
-            Label notNull = b.createLabel();
-            b.ifNullBranch(notNull, false);
-            b.loadConstant(false);
-            b.returnValue(TypeDesc.BOOLEAN);
-            notNull.setLocation();
-            b.loadThis();
-            b.loadLocal(encodedDataVar);
-            params = new TypeDesc[] {byteArrayType};
-            b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
-            b.loadConstant(true);
-            b.returnValue(TypeDesc.BOOLEAN);
-        }
-
-        // Add required protected doTryInsert_master method, which delegates to RawSupport.
-        {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PROTECTED.toFinal(true),
-                 MasterStorableGenerator.DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
-            mi.addException(TypeDesc.forClass(PersistException.class));
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // return rawSupport.tryInsert(this, this.encodeKey$(), this.encodeData$());
-            b.loadThis();
-            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
-            b.checkCast(rawSupportType);
-            b.loadThis(); // pass this to tryInsert method
-            b.loadThis();
-            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
-            b.loadThis();
-            b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
-            TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
-            b.invokeInterface(rawSupportType, "tryInsert", TypeDesc.BOOLEAN, params);
-            b.returnValue(TypeDesc.BOOLEAN);
-        }
-
-        // Add required protected doTryUpdate_master method, which delegates to RawSupport.
-        {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PROTECTED.toFinal(true),
-                 MasterStorableGenerator.DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
-            mi.addException(TypeDesc.forClass(PersistException.class));
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // rawSupport.store(this, this.encodeKey$(), this.encodeData$());
-            // return true;
-            b.loadThis();
-            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
-            b.checkCast(rawSupportType);
-            b.loadThis(); // pass this to store method
-            b.loadThis();
-            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
-            b.loadThis();
-            b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
-            TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
-            b.invokeInterface(rawSupportType, "store", null, params);
-            b.loadConstant(true);
-            b.returnValue(TypeDesc.BOOLEAN);
-        }
-
-        // Add required protected doTryDelete_master method, which delegates to RawSupport.
-        {
-            MethodInfo mi = cf.addMethod
-                (Modifiers.PROTECTED.toFinal(true),
-                 MasterStorableGenerator.DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
-            mi.addException(TypeDesc.forClass(PersistException.class));
-            CodeBuilder b = new CodeBuilder(mi);
-
-            // return rawSupport.tryDelete(this.encodeKey$());
-            b.loadThis();
-            b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
-            b.checkCast(rawSupportType);
-            b.loadThis();
-            b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
-
-            TypeDesc[] params = {byteArrayType};
-            b.invokeInterface(rawSupportType, "tryDelete", TypeDesc.BOOLEAN, params);
-            b.returnValue(TypeDesc.BOOLEAN);
-        }
-
-        return ci.defineClass(cf);
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java b/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java
deleted file mode 100644
index 895089d..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.PersistException;
-import com.amazon.carbonado.Storable;
-
-import com.amazon.carbonado.lob.Blob;
-import com.amazon.carbonado.lob.Clob;
-
-import com.amazon.carbonado.spi.MasterSupport;
-
-/**
- * Provides runtime support for Storable classes generated by {@link RawStorableGenerator}.
- *
- * @author Brian S O'Neill
- */
-public interface RawSupport<S extends Storable> extends MasterSupport<S> {
-    /**
-     * Try to load the entry referenced by the given key, but return null
-     * if not found.
-     *
-     * @param key non-null key to search for
-     * @return non-null value that was found, or null if not found
-     */
-    byte[] tryLoad(byte[] key) throws FetchException;
-
-    /**
-     * Try to insert the entry referenced by the given key with the given
-     * value.
-     *
-     * @param storable storable object that key and value were derived from
-     * @param key non-null key to insert
-     * @param value non-null value to insert
-     * @return false if unique constraint prevents insert
-     */
-    boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException;
-
-    /**
-     * Try to store the entry referenced by the given key with the given
-     * value. If the entry does not exist, insert it. Otherwise, update it.
-     *
-     * @param storable storable object that key and value were derived from
-     * @param key non-null key to store
-     * @param value non-null value to store
-     */
-    void store(S storable, byte[] key, byte[] value) throws PersistException;
-
-    /**
-     * Try to delete the entry referenced by the given key.
-     *
-     * @param key non-null key to delete
-     * @return true if entry existed and is now deleted
-     */
-    boolean tryDelete(byte[] key) throws PersistException;
-
-    /**
-     * Returns the Blob for the given locator, returning null if not found.
-     */
-    Blob getBlob(long locator) throws FetchException;
-
-    /**
-     * Returns the locator for the given Blob, returning zero if null.
-     *
-     * @throws PersistException if blob is unrecognized
-     */
-    long getLocator(Blob blob) throws PersistException;
-
-    /**
-     * Returns the Clob for the given locator, returning null if not found.
-     */
-    Clob getClob(long locator) throws FetchException;
-
-    /**
-     * Returns the locator for the given Clob, returning zero if null.
-     *
-     * @throws PersistException if blob is unrecognized
-     */
-    long getLocator(Clob clob) throws PersistException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java b/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java
deleted file mode 100644
index 5224b6c..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-/**
- * Utilities for manipulating binary data.
- *
- * @author Brian S O'Neill
- */
-public class RawUtil {
-    /**
-     * Adds one to an unsigned integer, represented as a byte array. If
-     * overflowed, value in byte array is 0x00, 0x00, 0x00...
-     *
-     * @param value unsigned integer to increment
-     * @return false if overflowed
-     */
-    public static boolean increment(byte[] value) {
-        for (int i=value.length; --i>=0; ) {
-            byte newValue = (byte) ((value[i] & 0xff) + 1);
-            value[i] = newValue;
-            if (newValue != 0) {
-                // No carry bit, so done adding.
-                return true;
-            }
-        }
-        // This point is reached upon overflow.
-        return false;
-    }
-
-    /**
-     * Subtracts one from an unsigned integer, represented as a byte array. If
-     * overflowed, value in byte array is 0xff, 0xff, 0xff...
-     *
-     * @param value unsigned integer to decrement
-     * @return false if overflowed
-     */
-    public static boolean decrement(byte[] value) {
-        for (int i=value.length; --i>=0; ) {
-            byte newValue = (byte) ((value[i] & 0xff) + -1);
-            value[i] = newValue;
-            if (newValue != -1) {
-                // No borrow bit, so done subtracting.
-                return true;
-            }
-        }
-        // This point is reached upon overflow.
-        return false;
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java
deleted file mode 100644
index 307fe7e..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-
-import com.amazon.carbonado.info.StorableIndex;
-
-/**
- * Supports encoding and decoding of storables.
- *
- * @author Brian S O'Neill
- * @see StorableCodecFactory
- */
-public interface StorableCodec<S extends Storable> {
-    /**
-     * Returns the type of Storable produced by this codec.
-     */
-    Class<S> getStorableType();
-
-    /**
-     * Instantiate a Storable with no key or value defined yet.
-     *
-     * @param support binds generated storable with a storage layer
-     */
-    S instantiate(RawSupport<S> support);
-
-    /**
-     * Instantiate a Storable with a specific key and value.
-     *
-     * @param support binds generated storable with a storage layer
-     */
-    S instantiate(RawSupport<S> support, byte[] key, byte[] value)
-        throws FetchException;
-
-    /**
-     * Returns the sequence and directions of properties that make up the
-     * primary key.
-     */
-    StorableIndex<S> getPrimaryKeyIndex();
-
-    /**
-     * Returns the number of prefix bytes in the primary key, which may be
-     * zero.
-     */
-    int getPrimaryKeyPrefixLength();
-
-    /**
-     * Encode a key by extracting all the primary key properties from the given
-     * storable.
-     *
-     * @param storable extract primary key properties from this instance
-     * @return raw search key
-     */
-    byte[] encodePrimaryKey(S storable);
-
-    /**
-     * Encode a key by extracting all the primary key properties from the given
-     * storable.
-     *
-     * @param storable extract primary key properties from this instance
-     * @param rangeStart index of first property to use. Its value must be less
-     * than the count of primary key properties.
-     * @param rangeEnd index of last property to use, exlusive. Its value must
-     * be less than or equal to the count of primary key properties.
-     * @return raw search key
-     */
-    byte[] encodePrimaryKey(S storable, int rangeStart, int rangeEnd);
-
-    /**
-     * Encode a key by extracting all the primary key properties from the given
-     * storable.
-     *
-     * @param values values to build into a key. It must be long enough to
-     * accommodate all primary key properties.
-     * @return raw search key
-     */
-    byte[] encodePrimaryKey(Object[] values);
-
-    /**
-     * Encode a key by extracting all the primary key properties from the given
-     * storable.
-     *
-     * @param values values to build into a key. The length may be less than
-     * the amount of primary key properties used by this factory. It must not
-     * be less than the difference between rangeStart and rangeEnd.
-     * @param rangeStart index of first property to use. Its value must be less
-     * than the count of primary key properties.
-     * @param rangeEnd index of last property to use, exlusive. Its value must
-     * be less than or equal to the count of primary key properties.
-     * @return raw search key
-     */
-    byte[] encodePrimaryKey(Object[] values, int rangeStart, int rangeEnd);
-
-    /**
-     * Encode the primary key for when there are no values, but there may be a
-     * prefix. Returned value may be null if no prefix is defined.
-     */
-    byte[] encodePrimaryKeyPrefix();
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java
deleted file mode 100644
index 26e3858..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.StorableIndex;
-import com.amazon.carbonado.layout.Layout;
-
-/**
- * Factory for creating instances of {@link StorableCodec}.
- *
- * @author Brian S O'Neill
- */
-public interface StorableCodecFactory {
-    /**
-     * Returns the preferred storage/database name for the given type. Return
-     * null to let repository decide.
-     *
-     * @throws SupportException if type is not supported
-     */
-    String getStorageName(Class<? extends Storable> type) throws SupportException;
-
-    /**
-     * @param type type of storable to create codec for
-     * @param pkIndex suggested index for primary key (optional)
-     * @param isMaster when true, version properties and sequences are managed
-     * @param layout when non-null, attempt to encode a storable layout
-     * generation value in each storable
-     * @throws SupportException if type is not supported
-     */
-    <S extends Storable> StorableCodec<S> createCodec(Class<S> type,
-                                                      StorableIndex pkIndex,
-                                                      boolean isMaster,
-                                                      Layout layout)
-        throws SupportException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java
deleted file mode 100644
index f13a56c..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi.raw;
-
-import java.lang.reflect.Method;
-
-import org.cojen.classfile.CodeAssembler;
-import org.cojen.classfile.TypeDesc;
-
-import com.amazon.carbonado.info.StorableProperty;
-import com.amazon.carbonado.lob.Lob;
-
-/**
- *
- *
- * @author Brian S O'Neill
- */
-public class StorablePropertyInfo implements GenericPropertyInfo {
-    private final StorableProperty<?> mProp;
-    private final TypeDesc mPropertyType;
-    private final TypeDesc mStorageType;
-    private final Method mFromStorage;
-    private final Method mToStorage;
-
-    StorablePropertyInfo(StorableProperty<?> property) {
-        this(property, null, null, null);
-    }
-
-    StorablePropertyInfo(StorableProperty<?> property,
-                         Class<?> storageType, Method fromStorage, Method toStorage) {
-        mProp = property;
-        mPropertyType = TypeDesc.forClass(property.getType());
-        if (storageType == null) {
-            mStorageType = mPropertyType;
-        } else {
-            mStorageType = TypeDesc.forClass(storageType);
-        }
-        mFromStorage = fromStorage;
-        mToStorage = toStorage;
-    }
-
-    public String getPropertyName() {
-        return mProp.getName();
-    }
-
-    public TypeDesc getPropertyType() {
-        return mPropertyType;
-    }
-
-    public TypeDesc getStorageType() {
-        return mStorageType;
-    }
-
-    public boolean isNullable() {
-        return mProp.isNullable();
-    }
-
-    public boolean isLob() {
-        Class clazz = mPropertyType.toClass();
-        return clazz != null && Lob.class.isAssignableFrom(clazz);
-    }
-
-    public Method getFromStorageAdapter() {
-        return mFromStorage;
-    }
-
-    public Method getToStorageAdapter() {
-        return mToStorage;
-    }
-
-    public String getReadMethodName() {
-        return mProp.getReadMethodName();
-    }
-
-    public void addInvokeReadMethod(CodeAssembler a) {
-        a.invoke(mProp.getReadMethod());
-    }
-
-    public void addInvokeReadMethod(CodeAssembler a, TypeDesc instanceType) {
-        Class clazz = instanceType.toClass();
-        if (clazz == null) {
-            // Can't know if instance should be invoked as an interface or as a
-            // virtual method.
-            throw new IllegalArgumentException("Instance type has no known class");
-        }
-        if (clazz.isInterface()) {
-            a.invokeInterface(instanceType, getReadMethodName(), getPropertyType(), null);
-        } else {
-            a.invokeVirtual(instanceType, getReadMethodName(), getPropertyType(), null);
-        }
-    }
-
-    public String getWriteMethodName() {
-        return mProp.getWriteMethodName();
-    }
-
-    public void addInvokeWriteMethod(CodeAssembler a) {
-        a.invoke(mProp.getWriteMethod());
-    }
-
-    public void addInvokeWriteMethod(CodeAssembler a, TypeDesc instanceType) {
-        Class clazz = instanceType.toClass();
-        if (clazz == null) {
-            // Can't know if instance should be invoked as an interface or as a
-            // virtual method.
-            throw new IllegalArgumentException("Instance type has no known class");
-        }
-        if (clazz.isInterface()) {
-            a.invokeInterface(instanceType,
-                              getWriteMethodName(), null, new TypeDesc[] {getPropertyType()});
-        } else {
-            a.invokeVirtual(instanceType,
-                            getWriteMethodName(), null, new TypeDesc[] {getPropertyType()});
-        }
-    }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/raw/package-info.java b/src/main/java/com/amazon/carbonado/spi/raw/package-info.java
deleted file mode 100644
index 8d47419..0000000
--- a/src/main/java/com/amazon/carbonado/spi/raw/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Provides support for repositories that encode/decode storables in a raw
- * binary format.
- */
-package com.amazon.carbonado.spi.raw;
-- 
cgit v1.2.3