diff options
Diffstat (limited to 'src/main/java/com/amazon/carbonado/raw')
20 files changed, 7573 insertions, 0 deletions
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 <init>(Storage, RawSupport);
+ *
+ * public <init>(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 <0 if key1 is less, 0 if equal (at least partially), >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 <init>(RawSupport);
+
+ * public <init>(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;
|