From 79cebfadf8703afe9bf28786bc4df1af348c876e Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Thu, 5 Oct 2006 03:54:20 +0000 Subject: Moved raw package out of spi package. --- .../amazon/carbonado/raw/CustomStorableCodec.java | 332 ++++ .../carbonado/raw/CustomStorableCodecFactory.java | 70 + .../java/com/amazon/carbonado/raw/DataDecoder.java | 567 ++++++ .../java/com/amazon/carbonado/raw/DataEncoder.java | 595 ++++++ .../carbonado/raw/GenericEncodingStrategy.java | 1963 ++++++++++++++++++++ .../carbonado/raw/GenericInstanceFactory.java | 36 + .../amazon/carbonado/raw/GenericPropertyInfo.java | 60 + .../amazon/carbonado/raw/GenericStorableCodec.java | 813 ++++++++ .../carbonado/raw/GenericStorableCodecFactory.java | 76 + .../java/com/amazon/carbonado/raw/KeyDecoder.java | 646 +++++++ .../java/com/amazon/carbonado/raw/KeyEncoder.java | 741 ++++++++ .../amazon/carbonado/raw/LayoutPropertyInfo.java | 86 + .../java/com/amazon/carbonado/raw/RawCursor.java | 743 ++++++++ .../amazon/carbonado/raw/RawStorableGenerator.java | 355 ++++ .../java/com/amazon/carbonado/raw/RawSupport.java | 97 + .../java/com/amazon/carbonado/raw/RawUtil.java | 66 + .../com/amazon/carbonado/raw/StorableCodec.java | 118 ++ .../amazon/carbonado/raw/StorableCodecFactory.java | 54 + .../amazon/carbonado/raw/StorablePropertyInfo.java | 132 ++ .../com/amazon/carbonado/raw/package-info.java | 23 + .../amazon/carbonado/spi/StorableSerializer.java | 2 +- .../carbonado/spi/raw/CustomStorableCodec.java | 332 ---- .../spi/raw/CustomStorableCodecFactory.java | 70 - .../com/amazon/carbonado/spi/raw/DataDecoder.java | 567 ------ .../com/amazon/carbonado/spi/raw/DataEncoder.java | 595 ------ .../carbonado/spi/raw/GenericEncodingStrategy.java | 1963 -------------------- .../carbonado/spi/raw/GenericInstanceFactory.java | 36 - .../carbonado/spi/raw/GenericPropertyInfo.java | 60 - .../carbonado/spi/raw/GenericStorableCodec.java | 813 -------- .../spi/raw/GenericStorableCodecFactory.java | 76 - .../com/amazon/carbonado/spi/raw/KeyDecoder.java | 646 ------- .../com/amazon/carbonado/spi/raw/KeyEncoder.java | 741 -------- .../carbonado/spi/raw/LayoutPropertyInfo.java | 86 - .../com/amazon/carbonado/spi/raw/RawCursor.java | 743 -------- .../carbonado/spi/raw/RawStorableGenerator.java | 355 ---- .../com/amazon/carbonado/spi/raw/RawSupport.java | 97 - .../java/com/amazon/carbonado/spi/raw/RawUtil.java | 66 - .../amazon/carbonado/spi/raw/StorableCodec.java | 118 -- .../carbonado/spi/raw/StorableCodecFactory.java | 54 - .../carbonado/spi/raw/StorablePropertyInfo.java | 132 -- .../com/amazon/carbonado/spi/raw/package-info.java | 23 - 41 files changed, 7574 insertions(+), 7574 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java create mode 100644 src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java create mode 100644 src/main/java/com/amazon/carbonado/raw/DataDecoder.java create mode 100644 src/main/java/com/amazon/carbonado/raw/DataEncoder.java create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericPropertyInfo.java create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java create mode 100644 src/main/java/com/amazon/carbonado/raw/KeyDecoder.java create mode 100644 src/main/java/com/amazon/carbonado/raw/KeyEncoder.java create mode 100644 src/main/java/com/amazon/carbonado/raw/LayoutPropertyInfo.java create mode 100644 src/main/java/com/amazon/carbonado/raw/RawCursor.java create mode 100644 src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java create mode 100644 src/main/java/com/amazon/carbonado/raw/RawSupport.java create mode 100644 src/main/java/com/amazon/carbonado/raw/RawUtil.java create mode 100644 src/main/java/com/amazon/carbonado/raw/StorableCodec.java create mode 100644 src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java create mode 100644 src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java create mode 100644 src/main/java/com/amazon/carbonado/raw/package-info.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/raw/package-info.java (limited to 'src/main/java') 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 implements StorableCodec { + // 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> cCache = + new WeakIdentityMap(); + + /** + * Returns a storable implementation that calls into CustomStorableCodec + * implementation for encoding and decoding. + */ + @SuppressWarnings("unchecked") + static Class + getStorableClass(Class type, boolean isMaster) + throws SupportException + { + synchronized (cCache) { + Class storableClass; + + RawStorableGenerator.Flavors flavors = + (RawStorableGenerator.Flavors) cCache.get(type); + + if (flavors == null) { + flavors = new RawStorableGenerator.Flavors(); + 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 Class + generateStorableClass(Class type, boolean isMaster) + throws SupportException + { + final Class 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 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 type, boolean isMaster) throws SupportException { + mType = type; + mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount(); + Class storableClass = getStorableClass(type, isMaster); + mInstanceFactory = QuickConstructorGenerator + .getInstance(storableClass, InstanceFactory.class); + } + + public Class getStorableType() { + return mType; + } + + @SuppressWarnings("unchecked") + public S instantiate(RawSupport support) { + return (S) mInstanceFactory.instantiate(support, this); + } + + @SuppressWarnings("unchecked") + public S instantiate(RawSupport 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> 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 buildPkIndex(String... propertyNames) { + Map> map = getAllProperties(); + int length = propertyNames.length; + StorableProperty[] properties = new StorableProperty[length]; + Direction[] directions = new Direction[length]; + for (int i=0; i(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 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 CustomStorableCodec createCodec(Class 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 CustomStorableCodec + createCodec(Class 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. + *

+ * 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. + * + *

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 { + private final Class mType; + private final StorableIndex 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 type, StorableIndex 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 type, StorableIndex 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> map = + StorableIntrospector.examine(mType).getPrimaryKeyProperties(); + + StorableProperty[] properties = new StorableProperty[map.size()]; + map.values().toArray(properties); + + Direction[] directions = new Direction[map.size()]; + Arrays.fill(directions, Direction.UNSPECIFIED); + + pkIndex = new StorableIndex(properties, directions, true); + } + + mPkIndex = pkIndex; + } + + /** + * Generates bytecode instructions to encode properties. The encoding is + * suitable for "key" encoding, which means it is correctly comparable. + * + *

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[] 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[] 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[] 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[] 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 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 getPrimaryKeyIndex() { + return mPkIndex; + } + + /** + * Returns all key properties as ordered properties, possibly with + * unspecified directions. + */ + protected OrderedProperty[] gatherAllKeyProperties() { + return mPkIndex.getOrderedProperties(); + } + + /** + * Returns all data properties for storable. + */ + @SuppressWarnings("unchecked") + protected StorableProperty[] gatherAllDataProperties() { + Map> map = + StorableIntrospector.examine(mType).getDataProperties(); + + StorableProperty[] properties = new StorableProperty[map.size()]; + + int ordinal = 0; + for (StorableProperty property : map.values()) { + properties[ordinal++] = property; + } + + return properties; + } + + protected StorablePropertyInfo checkSupport(StorableProperty 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[] properties) + throws SupportException + { + int length = properties.length; + StorablePropertyInfo[] infos = new StorablePropertyInfo[length]; + for (int i=0; i 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[] ensureKeyProperties(OrderedProperty[] properties) { + if (properties == null) { + properties = gatherAllKeyProperties(); + } else { + for (Object prop : properties) { + if (prop == null) { + throw new IllegalArgumentException(); + } + } + } + return properties; + } + + @SuppressWarnings("unchecked") + private StorableProperty[] extractProperties(OrderedProperty[] ordered) { + StorableProperty[] properties = new StorableProperty[ordered.length]; + for (int i=0; i 0) { + throw new IllegalArgumentException(); + } + properties[i] = chained.getPrimeProperty(); + } + return properties; + } + + private Direction[] extractDirections(OrderedProperty[] ordered) { + Direction[] directions = new Direction[ordered.length]; + for (int i=0; i[] ensureDataProperties(StorableProperty[] 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[] 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 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 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 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> (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[] 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 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 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 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 implements StorableCodec { + 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 GenericStorableCodec getInstance + (GenericStorableCodecFactory factory, + GenericEncodingStrategy 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 codec = (GenericStorableCodec) cCache.get(key); + if (codec == null) { + codec = new GenericStorableCodec + (factory, + encodingStrategy.getType(), + generateStorable(encodingStrategy, isMaster, layout), + encodingStrategy, + layout); + cCache.put(key, codec); + } + + return codec; + } + + @SuppressWarnings("unchecked") + private static Class generateStorable + (GenericEncodingStrategy encodingStrategy, boolean isMaster, Layout layout) + throws SupportException + { + final Class storableClass = encodingStrategy.getType(); + final Class 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 mType; + + private final Class 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> mEncodingStrategy; + + private final GenericInstanceFactory mInstanceFactory; + + private final SearchKeyFactory 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 type, Class storableClass, + GenericEncodingStrategy encodingStrategy, + Layout layout) { + mFactory = factory; + mType = type; + mStorableClass = storableClass; + mEncodingStrategy = new WeakReference>(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 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 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 support, byte[] key, byte[] value) + throws FetchException + { + return (S) mInstanceFactory.instantiate(support, key, value); + } + + public StorableIndex 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: + * + *

+     * public <init>(Storage, RawSupport);
+     *
+     * public <init>(Storage, RawSupport, byte[] key, byte[] value);
+     * 
+ * + * Convenience methods are provided in this class to instantiate the + * generated Storable. + */ + public Class 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 getSearchKeyFactory(OrderedProperty[] properties) { + // This KeyFactory makes arrays work as hashtable keys. + Object key = org.cojen.util.KeyFactory.createKey(properties); + + synchronized (mSearchKeyFactories) { + SearchKeyFactory factory = (SearchKeyFactory) 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 getDecoder(int generation) throws FetchNoneException, FetchException { + try { + synchronized (mLayout) { + IntHashMap decoders = mDecoders; + if (decoders == null) { + mDecoders = decoders = new IntHashMap(); + } + Decoder decoder = (Decoder) 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 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 generateSearchKeyFactory(OrderedProperty[] 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 clazz = ci.defineClass(cf); + try { + return clazz.newInstance(); + } catch (InstantiationException e) { + throw new UndeclaredThrowableException(e); + } catch (IllegalAccessException e) { + throw new UndeclaredThrowableException(e); + } + } + + private Decoder generateDecoder(int generation) throws FetchException { + // Create an encoding strategy against the reconstructed storable. + GenericEncodingStrategy altStrategy; + try { + Class 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 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 { + /** + * 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 { + /** + * @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 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 GenericStorableCodec createCodec(Class 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 GenericEncodingStrategy createStrategy + (Class type, StorableIndex pkIndex) + throws SupportException + { + return new GenericEncodingStrategy(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> (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. + *

+ * 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. + *

+ * 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 extends AbstractCursor { + // 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= 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. + * + *

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. + * + *

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. + * + *

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. + * + *

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 0) { + byte[] prefix = mStartBound; + byte[] key = getCurrentKey(); + if (key == null) { + return false; + } + for (int i=0; i= 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> cCache = new WeakIdentityMap(); + + /** + * Collection of different abstract class flavors. + */ + static class Flavors { + private Reference> mMasterFlavor; + + private Reference> mNonMasterFlavor; + + /** + * May return null. + */ + Class getClass(boolean isMaster) { + Reference> ref; + if (isMaster) { + ref = mMasterFlavor; + } else { + ref = mNonMasterFlavor; + } + return (ref != null) ? ref.get() : null; + } + + @SuppressWarnings("unchecked") + void setClass(Class clazz, boolean isMaster) { + Reference> 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: + * + *

+     * public <init>(RawSupport);
+
+     * public <init>(RawSupport, byte[] key, byte[] value);
+     * 
+ * + *

Subclasses must implement the following abstract protected methods, + * whose exact names are defined by constants in this class: + * + *

+     * // 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[]);
+     * 
+ * + * @param isMaster when true, version properties, sequences, and triggers are managed + * @throws IllegalArgumentException if type is null + */ + @SuppressWarnings("unchecked") + public static Class + getAbstractClass(Class type, boolean isMaster) + throws SupportException, IllegalArgumentException + { + synchronized (cCache) { + Class abstractClass; + + Flavors flavors = (Flavors) cCache.get(type); + + if (flavors == null) { + flavors = new Flavors(); + 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 Class + generateAbstractClass(Class storableClass, boolean isMaster) + throws SupportException + { + EnumSet 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 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 extends MasterSupport { + /** + * 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 { + /** + * Returns the type of Storable produced by this codec. + */ + Class getStorableType(); + + /** + * Instantiate a Storable with no key or value defined yet. + * + * @param support binds generated storable with a storage layer + */ + S instantiate(RawSupport support); + + /** + * Instantiate a Storable with a specific key and value. + * + * @param support binds generated storable with a storage layer + */ + S instantiate(RawSupport support, byte[] key, byte[] value) + throws FetchException; + + /** + * Returns the sequence and directions of properties that make up the + * primary key. + */ + StorableIndex 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 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 + */ + StorableCodec createCodec(Class type, + StorableIndex pkIndex, + boolean isMaster, + Layout layout) + throws SupportException; +} diff --git a/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java b/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java new file mode 100644 index 0000000..e3a18b1 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/raw/StorablePropertyInfo.java @@ -0,0 +1,132 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.raw; + +import java.lang.reflect.Method; + +import org.cojen.classfile.CodeAssembler; +import org.cojen.classfile.TypeDesc; + +import com.amazon.carbonado.info.StorableProperty; +import com.amazon.carbonado.lob.Lob; + +/** + * + * + * @author Brian S O'Neill + */ +public class StorablePropertyInfo implements GenericPropertyInfo { + private final StorableProperty mProp; + private final TypeDesc mPropertyType; + private final TypeDesc mStorageType; + private final Method mFromStorage; + private final Method mToStorage; + + StorablePropertyInfo(StorableProperty property) { + this(property, null, null, null); + } + + StorablePropertyInfo(StorableProperty property, + Class storageType, Method fromStorage, Method toStorage) { + mProp = property; + mPropertyType = TypeDesc.forClass(property.getType()); + if (storageType == null) { + mStorageType = mPropertyType; + } else { + mStorageType = TypeDesc.forClass(storageType); + } + mFromStorage = fromStorage; + mToStorage = toStorage; + } + + public String getPropertyName() { + return mProp.getName(); + } + + public TypeDesc getPropertyType() { + return mPropertyType; + } + + public TypeDesc getStorageType() { + return mStorageType; + } + + public boolean isNullable() { + return mProp.isNullable(); + } + + public boolean isLob() { + Class clazz = mPropertyType.toClass(); + return clazz != null && Lob.class.isAssignableFrom(clazz); + } + + public Method getFromStorageAdapter() { + return mFromStorage; + } + + public Method getToStorageAdapter() { + return mToStorage; + } + + public String getReadMethodName() { + return mProp.getReadMethodName(); + } + + public void addInvokeReadMethod(CodeAssembler a) { + a.invoke(mProp.getReadMethod()); + } + + public void addInvokeReadMethod(CodeAssembler a, TypeDesc instanceType) { + Class clazz = instanceType.toClass(); + if (clazz == null) { + // Can't know if instance should be invoked as an interface or as a + // virtual method. + throw new IllegalArgumentException("Instance type has no known class"); + } + if (clazz.isInterface()) { + a.invokeInterface(instanceType, getReadMethodName(), getPropertyType(), null); + } else { + a.invokeVirtual(instanceType, getReadMethodName(), getPropertyType(), null); + } + } + + public String getWriteMethodName() { + return mProp.getWriteMethodName(); + } + + public void addInvokeWriteMethod(CodeAssembler a) { + a.invoke(mProp.getWriteMethod()); + } + + public void addInvokeWriteMethod(CodeAssembler a, TypeDesc instanceType) { + Class clazz = instanceType.toClass(); + if (clazz == null) { + // Can't know if instance should be invoked as an interface or as a + // virtual method. + throw new IllegalArgumentException("Instance type has no known class"); + } + if (clazz.isInterface()) { + a.invokeInterface(instanceType, + getWriteMethodName(), null, new TypeDesc[] {getPropertyType()}); + } else { + a.invokeVirtual(instanceType, + getWriteMethodName(), null, new TypeDesc[] {getPropertyType()}); + } + } +} diff --git a/src/main/java/com/amazon/carbonado/raw/package-info.java b/src/main/java/com/amazon/carbonado/raw/package-info.java new file mode 100644 index 0000000..c92afd8 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/raw/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides support for repositories that encode/decode storables in a raw + * binary format. + */ +package com.amazon.carbonado.raw; diff --git a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java b/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java index 1f6ab6e..83621e9 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java @@ -52,7 +52,7 @@ import com.amazon.carbonado.info.StorableProperty; import static com.amazon.carbonado.spi.CommonMethodNames.*; -import com.amazon.carbonado.spi.raw.GenericEncodingStrategy; +import com.amazon.carbonado.raw.GenericEncodingStrategy; /** * Support for general-purpose serialization of storables. diff --git a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java deleted file mode 100644 index f908635..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodec.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.util.Map; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.WeakIdentityMap; - -import com.amazon.carbonado.CorruptEncodingException; -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.Direction; -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.info.StorableProperty; - -import com.amazon.carbonado.util.QuickConstructorGenerator; - -/** - * Allows codecs to be defined for storables that have a custom encoding. - * - * @author Brian S O'Neill - * @see CustomStorableCodecFactory - */ -public abstract class CustomStorableCodec implements StorableCodec { - // 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> cCache = - new WeakIdentityMap(); - - /** - * Returns a storable implementation that calls into CustomStorableCodec - * implementation for encoding and decoding. - */ - @SuppressWarnings("unchecked") - static Class - getStorableClass(Class type, boolean isMaster) - throws SupportException - { - synchronized (cCache) { - Class storableClass; - - RawStorableGenerator.Flavors flavors = - (RawStorableGenerator.Flavors) cCache.get(type); - - if (flavors == null) { - flavors = new RawStorableGenerator.Flavors(); - 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 Class - generateStorableClass(Class type, boolean isMaster) - throws SupportException - { - final Class 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 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 type, boolean isMaster) throws SupportException { - mType = type; - mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount(); - Class storableClass = getStorableClass(type, isMaster); - mInstanceFactory = QuickConstructorGenerator - .getInstance(storableClass, InstanceFactory.class); - } - - public Class getStorableType() { - return mType; - } - - @SuppressWarnings("unchecked") - public S instantiate(RawSupport support) { - return (S) mInstanceFactory.instantiate(support, this); - } - - @SuppressWarnings("unchecked") - public S instantiate(RawSupport 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> 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 buildPkIndex(String... propertyNames) { - Map> map = getAllProperties(); - int length = propertyNames.length; - StorableProperty[] properties = new StorableProperty[length]; - Direction[] directions = new Direction[length]; - for (int i=0; i(properties, directions, true, true); - } - - /** - * Decode the primary key into properties of the storable. - */ - public abstract void decodePrimaryKey(S storable, byte[] bytes) - throws CorruptEncodingException; - - /** - * Encode all properties of the storable excluding the primary key. - */ - public abstract byte[] encodeData(S storable); - - /** - * Decode the data into properties of the storable. - */ - public abstract void decodeData(S storable, byte[] bytes) - throws CorruptEncodingException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java deleted file mode 100644 index 64d19e8..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/CustomStorableCodecFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.layout.Layout; - -/** - * Factory for custom storable codecs. - * - * @author Brian S O'Neill - */ -public abstract class CustomStorableCodecFactory implements StorableCodecFactory { - public CustomStorableCodecFactory() { - } - - /** - * Returns null to let repository decide what the name should be. - */ - public String getStorageName(Class 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 CustomStorableCodec createCodec(Class 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 CustomStorableCodec - createCodec(Class type, boolean isMaster, Layout layout) - throws SupportException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java b/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java deleted file mode 100644 index 66f2f2f..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/DataDecoder.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.CorruptEncodingException; - -import static com.amazon.carbonado.spi.raw.DataEncoder.*; - -/** - * A very low-level class that decodes key components encoded by methods of - * {@link DataEncoder}. - * - * @author Brian S O'Neill - */ -public class DataDecoder { - static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - /** - * Decodes a signed integer from exactly 4 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed integer value - */ - public static int decodeInt(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int value = (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) | - ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff); - return value ^ 0x80000000; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Integer object from exactly 1 or 5 bytes. If null is - * returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Integer object or null - */ - public static Integer decodeIntegerObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeInt(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed long from exactly 8 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed long value - */ - public static long decodeLong(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return - (((long)(((src[srcOffset ] ) << 24) | - ((src[srcOffset + 1] & 0xff) << 16) | - ((src[srcOffset + 2] & 0xff) << 8 ) | - ((src[srcOffset + 3] & 0xff) )) ^ 0x80000000 ) << 32) | - (((long)(((src[srcOffset + 4] ) << 24) | - ((src[srcOffset + 5] & 0xff) << 16) | - ((src[srcOffset + 6] & 0xff) << 8 ) | - ((src[srcOffset + 7] & 0xff) )) & 0xffffffffL) ); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Long object from exactly 1 or 9 bytes. If null is - * returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Long object or null - */ - public static Long decodeLongObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeLong(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed byte from exactly 1 byte. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed byte value - */ - public static byte decodeByte(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (byte)(src[srcOffset] ^ 0x80); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Byte object from exactly 1 or 2 bytes. If null is - * returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Byte object or null - */ - public static Byte decodeByteObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeByte(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed short from exactly 2 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed short value - */ - public static short decodeShort(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x8000); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Short object from exactly 1 or 3 bytes. If null is - * returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Short object or null - */ - public static Short decodeShortObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeShort(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a char from exactly 2 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return char value - */ - public static char decodeChar(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (char)((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a Character object from exactly 1 or 3 bytes. If null is - * returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Character object or null - */ - public static Character decodeCharacterObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeChar(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a boolean from exactly 1 byte. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return boolean value - */ - public static boolean decodeBoolean(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return src[srcOffset] == (byte)128; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a Boolean object from exactly 1 byte. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Boolean object or null - */ - public static Boolean decodeBooleanObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - switch (src[srcOffset]) { - case NULL_BYTE_LOW: case NULL_BYTE_HIGH: - return null; - case (byte)128: - return Boolean.TRUE; - default: - return Boolean.FALSE; - } - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a float from exactly 4 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return float value - */ - public static float decodeFloat(byte[] src, int srcOffset) - throws CorruptEncodingException - { - int bits = decodeFloatBits(src, srcOffset); - bits ^= (bits < 0) ? 0x80000000 : 0xffffffff; - return Float.intBitsToFloat(bits); - } - - /** - * Decodes a Float object from exactly 4 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Float object or null - */ - public static Float decodeFloatObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - int bits = decodeFloatBits(src, srcOffset); - bits ^= (bits < 0) ? 0x80000000 : 0xffffffff; - return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits); - } - - protected static int decodeFloatBits(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (src[srcOffset] << 24) | ((src[srcOffset + 1] & 0xff) << 16) | - ((src[srcOffset + 2] & 0xff) << 8) | (src[srcOffset + 3] & 0xff); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a double from exactly 8 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return double value - */ - public static double decodeDouble(byte[] src, int srcOffset) - throws CorruptEncodingException - { - long bits = decodeDoubleBits(src, srcOffset); - bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL; - return Double.longBitsToDouble(bits); - } - - /** - * Decodes a Double object from exactly 8 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Double object or null - */ - public static Double decodeDoubleObj(byte[] src, int srcOffset) - throws CorruptEncodingException - { - long bits = decodeDoubleBits(src, srcOffset); - bits ^= (bits < 0) ? 0x8000000000000000L : 0xffffffffffffffffL; - return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits); - } - - protected static long decodeDoubleBits(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return - (((long)(((src[srcOffset ] ) << 24) | - ((src[srcOffset + 1] & 0xff) << 16) | - ((src[srcOffset + 2] & 0xff) << 8 ) | - ((src[srcOffset + 3] & 0xff) )) ) << 32) | - (((long)(((src[srcOffset + 4] ) << 24) | - ((src[srcOffset + 5] & 0xff) << 16) | - ((src[srcOffset + 6] & 0xff) << 8 ) | - ((src[srcOffset + 7] & 0xff) )) & 0xffffffffL)); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes the given byte array. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded byte array is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decode(byte[] src, int srcOffset, byte[][] valueRef) - throws CorruptEncodingException - { - try { - final int originalOffset = srcOffset; - - int b = src[srcOffset++] & 0xff; - if (b >= 0xf8) { - valueRef[0] = null; - return 1; - } - - int valueLength; - if (b <= 0x7f) { - valueLength = b; - } else if (b <= 0xbf) { - valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff); - } else if (b <= 0xdf) { - valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) | - (src[srcOffset++] & 0xff); - } else if (b <= 0xef) { - valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) | - ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff); - } else { - valueLength = ((b & 0x07) << 24) | ((src[srcOffset++] & 0xff) << 16) | - ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff); - } - - if (valueLength == 0) { - valueRef[0] = EMPTY_BYTE_ARRAY; - } else { - byte[] value = new byte[valueLength]; - System.arraycopy(src, srcOffset, value, 0, valueLength); - valueRef[0]= value; - } - - return srcOffset - originalOffset + valueLength; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes an encoded string from the given byte array. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded string is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decodeString(byte[] src, int srcOffset, String[] valueRef) - throws CorruptEncodingException - { - try { - final int originalOffset = srcOffset; - - int b = src[srcOffset++] & 0xff; - if (b >= 0xf8) { - valueRef[0] = null; - return 1; - } - - int valueLength; - if (b <= 0x7f) { - valueLength = b; - } else if (b <= 0xbf) { - valueLength = ((b & 0x3f) << 8) | (src[srcOffset++] & 0xff); - } else if (b <= 0xdf) { - valueLength = ((b & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) | - (src[srcOffset++] & 0xff); - } else if (b <= 0xef) { - valueLength = ((b & 0x0f) << 24) | ((src[srcOffset++] & 0xff) << 16) | - ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff); - } else { - valueLength = ((src[srcOffset++] & 0xff) << 24) | - ((src[srcOffset++] & 0xff) << 16) | - ((src[srcOffset++] & 0xff) << 8) | (src[srcOffset++] & 0xff); - } - - if (valueLength == 0) { - valueRef[0] = ""; - return srcOffset - originalOffset; - } - - char[] value = new char[valueLength]; - int valueOffset = 0; - - while (valueOffset < valueLength) { - int c = src[srcOffset++] & 0xff; - switch (c >> 5) { - case 0: case 1: case 2: case 3: - // 0xxxxxxx - value[valueOffset++] = (char)c; - break; - case 4: case 5: - // 10xxxxxx xxxxxxxx - value[valueOffset++] = (char)(((c & 0x3f) << 8) | (src[srcOffset++] & 0xff)); - break; - case 6: - // 110xxxxx xxxxxxxx xxxxxxxx - c = ((c & 0x1f) << 16) | ((src[srcOffset++] & 0xff) << 8) - | (src[srcOffset++] & 0xff); - if (c >= 0x10000) { - // Split into surrogate pair. - c -= 0x10000; - value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff)); - value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff)); - } else { - value[valueOffset++] = (char)c; - } - break; - default: - // 111xxxxx - // Illegal. - throw new CorruptEncodingException - ("Corrupt encoded string data (source offset = " - + (srcOffset - 1) + ')'); - } - } - - valueRef[0] = new String(value); - - return srcOffset - originalOffset; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes the given byte array which was encoded by {@link - * DataEncoder#encodeSingle}. - * - * @param prefixPadding amount of extra bytes to skip from start of encoded byte array - * @param suffixPadding amount of extra bytes to skip at end of encoded byte array - */ - public static byte[] decodeSingle(byte[] src, int prefixPadding, int suffixPadding) - throws CorruptEncodingException - { - try { - int length = src.length - suffixPadding - prefixPadding; - if (length == 0) { - return EMPTY_BYTE_ARRAY; - } - if (prefixPadding <= 0 && suffixPadding <= 0) { - return src; - } - byte[] dst = new byte[length]; - System.arraycopy(src, prefixPadding, dst, 0, length); - return dst; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes the given byte array which was encoded by {@link - * DataEncoder#encodeSingleNullable}. - */ - public static byte[] decodeSingleNullable(byte[] src) throws CorruptEncodingException { - return decodeSingleNullable(src, 0, 0); - } - - /** - * Decodes the given byte array which was encoded by {@link - * DataEncoder#encodeSingleNullable}. - * - * @param prefixPadding amount of extra bytes to skip from start of encoded byte array - * @param suffixPadding amount of extra bytes to skip at end of encoded byte array - */ - public static byte[] decodeSingleNullable(byte[] src, int prefixPadding, int suffixPadding) - throws CorruptEncodingException - { - try { - byte b = src[prefixPadding]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - int length = src.length - suffixPadding - 1 - prefixPadding; - if (length == 0) { - return EMPTY_BYTE_ARRAY; - } - byte[] value = new byte[length]; - System.arraycopy(src, 1 + prefixPadding, value, 0, length); - return value; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java b/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java deleted file mode 100644 index 15b8dca..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/DataEncoder.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -/** - * A very low-level class that supports encoding of primitive data. For - * encoding data into keys, see {@link KeyEncoder}. - * - * @author Brian S O'Neill - * @see DataDecoder - */ -public class DataEncoder { - // Note: Most of these methods are inherited by KeyEncoder, which is why - // they are encoded for supporting proper ordering. - - /** Byte to use for null, low ordering */ - static final byte NULL_BYTE_LOW = 0; - - /** Byte to use for null, high ordering */ - static final byte NULL_BYTE_HIGH = (byte)~NULL_BYTE_LOW; - - /** Byte to use for not-null, low ordering */ - static final byte NOT_NULL_BYTE_HIGH = (byte)128; - - /** Byte to use for not-null, high ordering */ - static final byte NOT_NULL_BYTE_LOW = (byte)~NOT_NULL_BYTE_HIGH; - - static final byte[] NULL_BYTE_ARRAY_HIGH = {NULL_BYTE_HIGH}; - static final byte[] NULL_BYTE_ARRAY_LOW = {NULL_BYTE_LOW}; - static final byte[] NOT_NULL_BYTE_ARRAY_HIGH = {NOT_NULL_BYTE_HIGH}; - static final byte[] NOT_NULL_BYTE_ARRAY_LOW = {NOT_NULL_BYTE_LOW}; - - /** - * Encodes the given signed integer into exactly 4 bytes. - * - * @param value signed integer value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(int value, byte[] dst, int dstOffset) { - value ^= 0x80000000; - dst[dstOffset ] = (byte)(value >> 24); - dst[dstOffset + 1] = (byte)(value >> 16); - dst[dstOffset + 2] = (byte)(value >> 8); - dst[dstOffset + 3] = (byte)value; - } - - /** - * Encodes the given signed Integer object into exactly 1 or 5 bytes. If - * the Integer object is never expected to be null, consider encoding as an - * int primitive. - * - * @param value optional signed Integer value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(Integer value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_HIGH; - encode(value.intValue(), dst, dstOffset + 1); - return 5; - } - } - - /** - * Encodes the given signed long into exactly 8 bytes. - * - * @param value signed long value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(long value, byte[] dst, int dstOffset) { - int w = ((int)(value >> 32)) ^ 0x80000000; - dst[dstOffset ] = (byte)(w >> 24); - dst[dstOffset + 1] = (byte)(w >> 16); - dst[dstOffset + 2] = (byte)(w >> 8); - dst[dstOffset + 3] = (byte)w; - w = (int)value; - dst[dstOffset + 4] = (byte)(w >> 24); - dst[dstOffset + 5] = (byte)(w >> 16); - dst[dstOffset + 6] = (byte)(w >> 8); - dst[dstOffset + 7] = (byte)w; - } - - /** - * Encodes the given signed Long object into exactly 1 or 9 bytes. If the - * Long object is never expected to be null, consider encoding as a long - * primitive. - * - * @param value optional signed Long value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(Long value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_HIGH; - encode(value.longValue(), dst, dstOffset + 1); - return 9; - } - } - - /** - * Encodes the given signed byte into exactly 1 byte. - * - * @param value signed byte value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(byte value, byte[] dst, int dstOffset) { - dst[dstOffset] = (byte)(value ^ 0x80); - } - - /** - * Encodes the given signed Byte object into exactly 1 or 2 bytes. If the - * Byte object is never expected to be null, consider encoding as a byte - * primitive. - * - * @param value optional signed Byte value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(Byte value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_HIGH; - dst[dstOffset + 1] = (byte)(value ^ 0x80); - return 2; - } - } - - /** - * Encodes the given signed short into exactly 2 bytes. - * - * @param value signed short value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(short value, byte[] dst, int dstOffset) { - value ^= 0x8000; - dst[dstOffset ] = (byte)(value >> 8); - dst[dstOffset + 1] = (byte)value; - } - - /** - * Encodes the given signed Short object into exactly 1 or 3 bytes. If the - * Short object is never expected to be null, consider encoding as a short - * primitive. - * - * @param value optional signed Short value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(Short value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_HIGH; - encode(value.shortValue(), dst, dstOffset + 1); - return 3; - } - } - - /** - * Encodes the given character into exactly 2 bytes. - * - * @param value character value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(char value, byte[] dst, int dstOffset) { - dst[dstOffset ] = (byte)(value >> 8); - dst[dstOffset + 1] = (byte)value; - } - - /** - * Encodes the given Character object into exactly 1 or 3 bytes. If the - * Character object is never expected to be null, consider encoding as a - * char primitive. - * - * @param value optional Character value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(Character value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_HIGH; - encode(value.charValue(), dst, dstOffset + 1); - return 3; - } - } - - /** - * Encodes the given boolean into exactly 1 byte. - * - * @param value boolean value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(boolean value, byte[] dst, int dstOffset) { - dst[dstOffset] = value ? (byte)128 : (byte)127; - } - - /** - * Encodes the given Boolean object into exactly 1 byte. - * - * @param value optional Boolean value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(Boolean value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - } else { - dst[dstOffset] = value.booleanValue() ? (byte)128 : (byte)127; - } - } - - /** - * Encodes the given float into exactly 4 bytes. - * - * @param value float value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(float value, byte[] dst, int dstOffset) { - int bits = Float.floatToIntBits(value); - bits ^= (bits < 0) ? 0xffffffff : 0x80000000; - dst[dstOffset ] = (byte)(bits >> 24); - dst[dstOffset + 1] = (byte)(bits >> 16); - dst[dstOffset + 2] = (byte)(bits >> 8); - dst[dstOffset + 3] = (byte)bits; - } - - /** - * Encodes the given Float object into exactly 4 bytes. A non-canonical NaN - * value is used to represent null. - * - * @param value optional Float value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(Float value, byte[] dst, int dstOffset) { - if (value == null) { - encode(0x7fffffff, dst, dstOffset); - } else { - encode(value.floatValue(), dst, dstOffset); - } - } - - /** - * Encodes the given double into exactly 8 bytes. - * - * @param value double value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(double value, byte[] dst, int dstOffset) { - long bits = Double.doubleToLongBits(value); - bits ^= (bits < 0) ? 0xffffffffffffffffL : 0x8000000000000000L; - int w = (int)(bits >> 32); - dst[dstOffset ] = (byte)(w >> 24); - dst[dstOffset + 1] = (byte)(w >> 16); - dst[dstOffset + 2] = (byte)(w >> 8); - dst[dstOffset + 3] = (byte)w; - w = (int)bits; - dst[dstOffset + 4] = (byte)(w >> 24); - dst[dstOffset + 5] = (byte)(w >> 16); - dst[dstOffset + 6] = (byte)(w >> 8); - dst[dstOffset + 7] = (byte)w; - } - - /** - * Encodes the given Double object into exactly 8 bytes. A non-canonical - * NaN value is used to represent null. - * - * @param value optional Double value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encode(Double value, byte[] dst, int dstOffset) { - if (value == null) { - encode(0x7fffffffffffffffL, dst, dstOffset); - } else { - encode(value.doubleValue(), dst, dstOffset); - } - } - - /** - * Encodes the given optional byte array into a variable amount of - * bytes. If the byte array is null, exactly 1 byte is written. Otherwise, - * the amount written can be determined by calling calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(byte[] value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } - return encode(value, 0, value.length, dst, dstOffset); - } - - /** - * Encodes the given optional byte array into a variable amount of - * bytes. If the byte array is null, exactly 1 byte is written. Otherwise, - * the amount written can be determined by calling calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param valueOffset offset into byte array - * @param valueLength length of data in byte array - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(byte[] value, int valueOffset, int valueLength, - byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } - - // Write the value length first, in a variable amount of bytes. - int amt = writeLength(valueLength, dst, dstOffset); - - // Now write the value. - System.arraycopy(value, valueOffset, dst, dstOffset + amt, valueLength); - - return amt + valueLength; - } - - /** - * Returns the amount of bytes required to encode the given byte array. - * - * @param value byte array value to encode, may be null - * @return amount of bytes needed to encode - */ - public static int calculateEncodedLength(byte[] value) { - return value == null ? 1 : calculateEncodedLength(value, 0, value.length); - } - - /** - * Returns the amount of bytes required to encode the given byte array. - * - * @param value byte array value to encode, may be null - * @param valueOffset offset into byte array - * @param valueLength length of data in byte array - * @return amount of bytes needed to encode - */ - public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) { - if (value == null) { - return 1; - } else if (valueLength < 128) { - return 1 + valueLength; - } else if (valueLength < 16384) { - return 2 + valueLength; - } else if (valueLength < 2097152) { - return 3 + valueLength; - } else if (valueLength < 268435456) { - return 4 + valueLength; - } else { - return 5 + valueLength; - } - } - - /** - * Encodes the given optional String into a variable amount of bytes. The - * amount written can be determined by calling - * calculateEncodedStringLength. - *

- * Strings are encoded in a fashion similar to UTF-8, in that ASCII - * characters are written in one byte. This encoding is more efficient than - * UTF-8, but it isn't compatible with UTF-8. - * - * @param value String value to encode, may be null - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(String value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } - final int originalOffset = dstOffset; - - int valueLength = value.length(); - - // Write the value length first, in a variable amount of bytes. - dstOffset += writeLength(valueLength, dst, dstOffset); - - for (int i = 0; i < valueLength; i++) { - int c = value.charAt(i); - if (c <= 0x7f) { - dst[dstOffset++] = (byte)c; - } else if (c <= 0x3fff) { - dst[dstOffset++] = (byte)(0x80 | (c >> 8)); - dst[dstOffset++] = (byte)(c & 0xff); - } else { - if (c >= 0xd800 && c <= 0xdbff) { - // Found a high surrogate. Verify that surrogate pair is - // well-formed. Low surrogate must follow high surrogate. - if (i + 1 < valueLength) { - int c2 = value.charAt(i + 1); - if (c2 >= 0xdc00 && c2 <= 0xdfff) { - c = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff)); - i++; - } - } - } - dst[dstOffset++] = (byte)(0xc0 | (c >> 16)); - dst[dstOffset++] = (byte)((c >> 8) & 0xff); - dst[dstOffset++] = (byte)(c & 0xff); - } - } - - return dstOffset - originalOffset; - } - - /** - * Returns the amount of bytes required to encode the given String. - * - * @param value String to encode, may be null - */ - public static int calculateEncodedStringLength(String value) { - if (value == null) { - return 1; - } - - int valueLength = value.length(); - int encodedLen; - - if (valueLength < 128) { - encodedLen = 1; - } else if (valueLength < 16384) { - encodedLen = 2; - } else if (valueLength < 2097152) { - encodedLen = 3; - } else if (valueLength < 268435456) { - encodedLen = 4; - } else { - encodedLen = 5; - } - - for (int i = 0; i < valueLength; i++) { - int c = value.charAt(i); - if (c <= 0x7f) { - encodedLen++; - } else if (c <= 0x3fff) { - encodedLen += 2; - } else { - if (c >= 0xd800 && c <= 0xdbff) { - // Found a high surrogate. Verify that surrogate pair is - // well-formed. Low surrogate must follow high surrogate. - if (i + 1 < valueLength) { - int c2 = value.charAt(i + 1); - if (c2 >= 0xdc00 && c2 <= 0xdfff) { - i++; - } - } - } - encodedLen += 3; - } - } - - return encodedLen; - } - - private static int writeLength(int valueLength, byte[] dst, int dstOffset) { - if (valueLength < 128) { - dst[dstOffset] = (byte)valueLength; - return 1; - } else if (valueLength < 16384) { - dst[dstOffset++] = (byte)((valueLength >> 8) | 0x80); - dst[dstOffset] = (byte)valueLength; - return 2; - } else if (valueLength < 2097152) { - dst[dstOffset++] = (byte)((valueLength >> 16) | 0xc0); - dst[dstOffset++] = (byte)(valueLength >> 8); - dst[dstOffset] = (byte)valueLength; - return 3; - } else if (valueLength < 268435456) { - dst[dstOffset++] = (byte)((valueLength >> 24) | 0xe0); - dst[dstOffset++] = (byte)(valueLength >> 16); - dst[dstOffset++] = (byte)(valueLength >> 8); - dst[dstOffset] = (byte)valueLength; - return 4; - } else { - dst[dstOffset++] = (byte)0xf0; - dst[dstOffset++] = (byte)(valueLength >> 24); - dst[dstOffset++] = (byte)(valueLength >> 16); - dst[dstOffset++] = (byte)(valueLength >> 8); - dst[dstOffset] = (byte)valueLength; - return 5; - } - } - - /** - * Encodes the given byte array for use when there is only a single - * property, whose type is a byte array. The original byte array is - * returned if the padding lengths are zero. - * - * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array - * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array - */ - public static byte[] encodeSingle(byte[] value, int prefixPadding, int suffixPadding) { - if (prefixPadding <= 0 && suffixPadding <= 0) { - return value; - } - int length = value.length; - byte[] dst = new byte[prefixPadding + length + suffixPadding]; - System.arraycopy(value, 0, dst, prefixPadding, length); - return dst; - } - - /** - * Encodes the given byte array for use when there is only a single - * nullable property, whose type is a byte array. - */ - public static byte[] encodeSingleNullable(byte[] value) { - return encodeSingleNullable(value, 0, 0); - } - - /** - * Encodes the given byte array for use when there is only a single - * nullable property, whose type is a byte array. - * - * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array - * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array - */ - public static byte[] encodeSingleNullable(byte[] value, int prefixPadding, int suffixPadding) { - if (prefixPadding <= 0 && suffixPadding <= 0) { - if (value == null) { - return NULL_BYTE_ARRAY_HIGH; - } - - int length = value.length; - if (length == 0) { - return NOT_NULL_BYTE_ARRAY_HIGH; - } - - byte[] dst = new byte[1 + length]; - dst[0] = NOT_NULL_BYTE_HIGH; - System.arraycopy(value, 0, dst, 1, length); - return dst; - } - - if (value == null) { - byte[] dst = new byte[prefixPadding + 1 + suffixPadding]; - dst[prefixPadding] = NULL_BYTE_HIGH; - return dst; - } - - int length = value.length; - byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding]; - dst[prefixPadding] = NOT_NULL_BYTE_HIGH; - System.arraycopy(value, 0, dst, prefixPadding + 1, length); - return dst; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java deleted file mode 100644 index cd408e9..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/GenericEncodingStrategy.java +++ /dev/null @@ -1,1963 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Map; - -import org.cojen.classfile.CodeAssembler; -import org.cojen.classfile.Label; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.Opcode; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.BeanIntrospector; -import org.cojen.util.BeanProperty; - -import com.amazon.carbonado.CorruptEncodingException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.lob.Blob; -import com.amazon.carbonado.lob.Clob; -import com.amazon.carbonado.lob.Lob; - -import com.amazon.carbonado.spi.StorableGenerator; -import com.amazon.carbonado.spi.TriggerSupport; - -import com.amazon.carbonado.info.ChainedProperty; -import com.amazon.carbonado.info.Direction; -import com.amazon.carbonado.info.OrderedProperty; -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.info.StorableProperty; -import com.amazon.carbonado.info.StorablePropertyAdapter; - -/** - * Generates bytecode instructions for encoding/decoding Storable properties - * to/from raw bytes. - * - *

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 { - private final Class mType; - private final StorableIndex 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 type, StorableIndex 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 type, StorableIndex 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> map = - StorableIntrospector.examine(mType).getPrimaryKeyProperties(); - - StorableProperty[] properties = new StorableProperty[map.size()]; - map.values().toArray(properties); - - Direction[] directions = new Direction[map.size()]; - Arrays.fill(directions, Direction.UNSPECIFIED); - - pkIndex = new StorableIndex(properties, directions, true); - } - - mPkIndex = pkIndex; - } - - /** - * Generates bytecode instructions to encode properties. The encoding is - * suitable for "key" encoding, which means it is correctly comparable. - * - *

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[] 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[] 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[] 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[] 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 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 getPrimaryKeyIndex() { - return mPkIndex; - } - - /** - * Returns all key properties as ordered properties, possibly with - * unspecified directions. - */ - protected OrderedProperty[] gatherAllKeyProperties() { - return mPkIndex.getOrderedProperties(); - } - - /** - * Returns all data properties for storable. - */ - @SuppressWarnings("unchecked") - protected StorableProperty[] gatherAllDataProperties() { - Map> map = - StorableIntrospector.examine(mType).getDataProperties(); - - StorableProperty[] properties = new StorableProperty[map.size()]; - - int ordinal = 0; - for (StorableProperty property : map.values()) { - properties[ordinal++] = property; - } - - return properties; - } - - protected StorablePropertyInfo checkSupport(StorableProperty 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[] properties) - throws SupportException - { - int length = properties.length; - StorablePropertyInfo[] infos = new StorablePropertyInfo[length]; - for (int i=0; i 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[] ensureKeyProperties(OrderedProperty[] properties) { - if (properties == null) { - properties = gatherAllKeyProperties(); - } else { - for (Object prop : properties) { - if (prop == null) { - throw new IllegalArgumentException(); - } - } - } - return properties; - } - - @SuppressWarnings("unchecked") - private StorableProperty[] extractProperties(OrderedProperty[] ordered) { - StorableProperty[] properties = new StorableProperty[ordered.length]; - for (int i=0; i 0) { - throw new IllegalArgumentException(); - } - properties[i] = chained.getPrimeProperty(); - } - return properties; - } - - private Direction[] extractDirections(OrderedProperty[] ordered) { - Direction[] directions = new Direction[ordered.length]; - for (int i=0; i[] ensureDataProperties(StorableProperty[] 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[] 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 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 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 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> (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[] 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 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 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 props = - BeanIntrospector.getAllProperties(instanceVarClass); - BeanProperty prop = props.get(info.getPropertyName()); - if (prop != null) { - if (prop.getType() == type.toClass()) { - break doDrop; - } - // Types differ, but if primitive types, perform conversion. - TypeDesc primType = type.toPrimitiveType(); - if (primType != null) { - TypeDesc propType = TypeDesc.forClass(prop.getType()); - TypeDesc primPropType = propType.toPrimitiveType(); - if (primPropType != null) { - // Apply conversion and store property. - a.convert(type, propType); - type = propType; - break doDrop; - } - } - } - } - - // Drop missing or incompatible property. - if (storageType.isDoubleWord()) { - a.pop2(); - } else { - a.pop(); - } - return; - } - - if (useWriteMethod) { - info.addInvokeWriteMethod(a, instanceVarType); - } else { - // Set property value directly to protected field of referenced - // instance. Assumes code is being defined in the same package - // or a subclass. - if (info.getToStorageAdapter() == null) { - a.storeField(instanceVarType, info.getPropertyName(), type); - } else { - // Invoke adapter method. - a.invokeVirtual(instanceVarType, info.getWriteMethodName() + '$', - null, new TypeDesc[] {storageType}); - } - } - } else { - // Set property value to object array. No need to check if we - // should call a write method because arrays don't have write - // methods. - if (type.isPrimitive()) { - a.convert(type, type.toObjectType()); - } - a.storeToArray(TypeDesc.OBJECT); - } - } - - /** - * Generates code that ensures a matching generation value exists in the - * byte array referenced by the local variable, throwing a - * CorruptEncodingException otherwise. - * - * @param generation if less than zero, no code is generated - */ - private void decodeGeneration(CodeAssembler a, LocalVariable encodedVar, - int offset, int generation, Label altGenerationHandler) - { - if (offset < 0) { - throw new IllegalArgumentException(); - } - if (generation < 0) { - return; - } - - LocalVariable actualGeneration = a.createLocalVariable(null, TypeDesc.INT); - a.loadLocal(encodedVar); - a.loadConstant(offset); - a.loadFromArray(TypeDesc.BYTE); - a.storeLocal(actualGeneration); - a.loadLocal(actualGeneration); - Label compareGeneration = a.createLabel(); - a.ifZeroComparisonBranch(compareGeneration, ">="); - - // Decode four byte generation format. - a.loadLocal(actualGeneration); - a.loadConstant(24); - a.math(Opcode.ISHL); - a.loadConstant(0x7fffffff); - a.math(Opcode.IAND); - for (int i=1; i<4; i++) { - a.loadLocal(encodedVar); - a.loadConstant(offset + i); - a.loadFromArray(TypeDesc.BYTE); - a.loadConstant(0xff); - a.math(Opcode.IAND); - int shift = 8 * (3 - i); - if (shift > 0) { - a.loadConstant(shift); - a.math(Opcode.ISHL); - } - a.math(Opcode.IOR); - } - a.storeLocal(actualGeneration); - - compareGeneration.setLocation(); - - a.loadConstant(generation); - a.loadLocal(actualGeneration); - Label generationMatches = a.createLabel(); - a.ifComparisonBranch(generationMatches, "=="); - - if (altGenerationHandler != null) { - a.loadLocal(actualGeneration); - a.branch(altGenerationHandler); - } else { - // Throw CorruptEncodingException. - - TypeDesc corruptEncodingEx = TypeDesc.forClass(CorruptEncodingException.class); - a.newObject(corruptEncodingEx); - a.dup(); - a.loadConstant(generation); // expected generation - a.loadLocal(actualGeneration); // actual generation - a.invokeConstructor(corruptEncodingEx, new TypeDesc[] {TypeDesc.INT, TypeDesc.INT}); - a.throwObject(); - } - - generationMatches.setLocation(); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java deleted file mode 100644 index 5a6a4cb..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/GenericInstanceFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; - -/** - * Can be used with {@link com.amazon.carbonado.util.QuickConstructorGenerator} - * for instantiating generic storable instances. - * - * @author Brian S O'Neill - */ -public interface GenericInstanceFactory { - Storable instantiate(RawSupport support); - - Storable instantiate(RawSupport support, byte[] key, byte[] value) - throws FetchException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java deleted file mode 100644 index c734f03..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/GenericPropertyInfo.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.reflect.Method; - -import org.cojen.classfile.TypeDesc; - -/** - * Minimal information required by {@link GenericEncodingStrategy} to encode - * and decode a storable property or layout property. - * - * @author Brian S O'Neill - */ -public interface GenericPropertyInfo { - String getPropertyName(); - - /** - * Returns the user specified property type. - */ - TypeDesc getPropertyType(); - - /** - * Returns the storage supported type. If it differs from the property - * type, then adapter methods must also exist. - */ - TypeDesc getStorageType(); - - boolean isNullable(); - - boolean isLob(); - - /** - * Returns the optional method used to adapt the property from the - * storage supported type to the user visible type. - */ - Method getFromStorageAdapter(); - - /** - * Returns the optional method used to adapt the property from the user - * visible type to the storage supported type. - */ - Method getToStorageAdapter(); -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java deleted file mode 100644 index 7a98540..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodec.java +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.Map; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.Label; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.IntHashMap; -import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; - -import com.amazon.carbonado.CorruptEncodingException; -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.FetchNoneException; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.SupportException; -import com.amazon.carbonado.info.Direction; -import com.amazon.carbonado.info.OrderedProperty; -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.layout.Layout; -import com.amazon.carbonado.spi.CodeBuilderUtil; -import com.amazon.carbonado.util.ThrowUnchecked; -import com.amazon.carbonado.util.QuickConstructorGenerator; - -/** - * Generic codec that supports any kind of storable by auto-generating and - * caching storable implementations. - * - * @author Brian S O'Neill - * @see GenericStorableCodecFactory - */ -public class GenericStorableCodec implements StorableCodec { - 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 GenericStorableCodec getInstance - (GenericStorableCodecFactory factory, - GenericEncodingStrategy 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 codec = (GenericStorableCodec) cCache.get(key); - if (codec == null) { - codec = new GenericStorableCodec - (factory, - encodingStrategy.getType(), - generateStorable(encodingStrategy, isMaster, layout), - encodingStrategy, - layout); - cCache.put(key, codec); - } - - return codec; - } - - @SuppressWarnings("unchecked") - private static Class generateStorable - (GenericEncodingStrategy encodingStrategy, boolean isMaster, Layout layout) - throws SupportException - { - final Class storableClass = encodingStrategy.getType(); - final Class 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 mType; - - private final Class 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> mEncodingStrategy; - - private final GenericInstanceFactory mInstanceFactory; - - private final SearchKeyFactory 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 type, Class storableClass, - GenericEncodingStrategy encodingStrategy, - Layout layout) { - mFactory = factory; - mType = type; - mStorableClass = storableClass; - mEncodingStrategy = new WeakReference>(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 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 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 support, byte[] key, byte[] value) - throws FetchException - { - return (S) mInstanceFactory.instantiate(support, key, value); - } - - public StorableIndex 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: - * - *

-     * public <init>(Storage, RawSupport);
-     *
-     * public <init>(Storage, RawSupport, byte[] key, byte[] value);
-     * 
- * - * Convenience methods are provided in this class to instantiate the - * generated Storable. - */ - public Class 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 getSearchKeyFactory(OrderedProperty[] properties) { - // This KeyFactory makes arrays work as hashtable keys. - Object key = org.cojen.util.KeyFactory.createKey(properties); - - synchronized (mSearchKeyFactories) { - SearchKeyFactory factory = (SearchKeyFactory) 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 getDecoder(int generation) throws FetchNoneException, FetchException { - try { - synchronized (mLayout) { - IntHashMap decoders = mDecoders; - if (decoders == null) { - mDecoders = decoders = new IntHashMap(); - } - Decoder decoder = (Decoder) 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 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 generateSearchKeyFactory(OrderedProperty[] 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 clazz = ci.defineClass(cf); - try { - return clazz.newInstance(); - } catch (InstantiationException e) { - throw new UndeclaredThrowableException(e); - } catch (IllegalAccessException e) { - throw new UndeclaredThrowableException(e); - } - } - - private Decoder generateDecoder(int generation) throws FetchException { - // Create an encoding strategy against the reconstructed storable. - GenericEncodingStrategy altStrategy; - try { - Class 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 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 { - /** - * 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 { - /** - * @param dest storable to receive decoded properties - * @param data decoded into properties, some of which may be dropped if - * destination storable doesn't have it - */ - void decode(S dest, byte[] data); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java deleted file mode 100644 index fefa880..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/GenericStorableCodecFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.layout.Layout; - -/** - * Factory for generic codec that supports any kind of storable by - * auto-generating and caching storable implementations. - * - * @author Brian S O'Neill - */ -public class GenericStorableCodecFactory implements StorableCodecFactory { - public GenericStorableCodecFactory() { - } - - /** - * Returns null to let repository decide what the name should be. - */ - public String getStorageName(Class 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 GenericStorableCodec createCodec(Class 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 GenericEncodingStrategy createStrategy - (Class type, StorableIndex pkIndex) - throws SupportException - { - return new GenericEncodingStrategy(type, pkIndex); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java b/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java deleted file mode 100644 index 127216f..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/KeyDecoder.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.CorruptEncodingException; - -import static com.amazon.carbonado.spi.raw.KeyEncoder.*; - -/** - * A very low-level class that decodes key components encoded by methods of - * {@link KeyEncoder}. - * - * @author Brian S O'Neill - */ -public class KeyDecoder extends DataDecoder { - - /** - * Decodes a signed integer from exactly 4 bytes, as encoded for descending - * order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed integer value - */ - public static int decodeIntDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - return ~decodeInt(src, srcOffset); - } - - /** - * Decodes a signed Integer object from exactly 1 or 5 bytes, as encoded - * for descending order. If null is returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Integer object or null - */ - public static Integer decodeIntegerObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeIntDesc(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed long from exactly 8 bytes, as encoded for descending - * order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed long value - */ - public static long decodeLongDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - return ~decodeLong(src, srcOffset); - } - - /** - * Decodes a signed Long object from exactly 1 or 9 bytes, as encoded for - * descending order. If null is returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Long object or null - */ - public static Long decodeLongObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeLongDesc(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed byte from exactly 1 byte, as encoded for descending - * order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed byte value - */ - public static byte decodeByteDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (byte)(src[srcOffset] ^ 0x7f); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Byte object from exactly 1 or 2 bytes, as encoded for - * descending order. If null is returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Byte object or null - */ - public static Byte decodeByteObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeByteDesc(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed short from exactly 2 bytes, as encoded for descending - * order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed short value - */ - public static short decodeShortDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (short)(((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)) ^ 0x7fff); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a signed Short object from exactly 1 or 3 bytes, as encoded for - * descending order. If null is returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return signed Short object or null - */ - public static Short decodeShortObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeShortDesc(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a char from exactly 2 bytes, as encoded for descending order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return char value - */ - public static char decodeCharDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return (char)~((src[srcOffset] << 8) | (src[srcOffset + 1] & 0xff)); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a Character object from exactly 1 or 3 bytes, as encoded for - * descending order. If null is returned, then 1 byte was read. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Character object or null - */ - public static Character decodeCharacterObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - int b = src[srcOffset]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - return decodeCharDesc(src, srcOffset + 1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a boolean from exactly 1 byte, as encoded for descending order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return boolean value - */ - public static boolean decodeBooleanDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - return src[srcOffset] == 127; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a Boolean object from exactly 1 byte, as encoded for descending - * order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Boolean object or null - */ - public static Boolean decodeBooleanObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - try { - switch (src[srcOffset]) { - case NULL_BYTE_LOW: case NULL_BYTE_HIGH: - return null; - case (byte)127: - return Boolean.TRUE; - default: - return Boolean.FALSE; - } - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes a float from exactly 4 bytes, as encoded for descending order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return float value - */ - public static float decodeFloatDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - int bits = decodeFloatBits(src, srcOffset); - if (bits >= 0) { - bits ^= 0x7fffffff; - } - return Float.intBitsToFloat(bits); - } - - /** - * Decodes a Float object from exactly 4 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Float object or null - */ - public static Float decodeFloatObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - int bits = decodeFloatBits(src, srcOffset); - if (bits >= 0) { - bits ^= 0x7fffffff; - } - return bits == 0x7fffffff ? null : Float.intBitsToFloat(bits); - } - - /** - * Decodes a double from exactly 8 bytes, as encoded for descending order. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return double value - */ - public static double decodeDoubleDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - long bits = decodeDoubleBits(src, srcOffset); - if (bits >= 0) { - bits ^= 0x7fffffffffffffffL; - } - return Double.longBitsToDouble(bits); - } - - /** - * Decodes a Double object from exactly 8 bytes. - * - * @param src source of encoded bytes - * @param srcOffset offset into source array - * @return Double object or null - */ - public static Double decodeDoubleObjDesc(byte[] src, int srcOffset) - throws CorruptEncodingException - { - long bits = decodeDoubleBits(src, srcOffset); - if (bits >= 0) { - bits ^= 0x7fffffffffffffffL; - } - return bits == 0x7fffffffffffffffL ? null : Double.longBitsToDouble(bits); - } - - /** - * Decodes the given byte array as originally encoded for ascending order. - * The decoding stops when any kind of terminator or illegal byte has been - * read. The decoded bytes are stored in valueRef. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded byte array is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decode(byte[] src, int srcOffset, byte[][] valueRef) - throws CorruptEncodingException - { - try { - return decode(src, srcOffset, valueRef, 0); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes the given byte array as originally encoded for descending order. - * The decoding stops when any kind of terminator or illegal byte has been - * read. The decoded bytes are stored in valueRef. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded byte array is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decodeDesc(byte[] src, int srcOffset, byte[][] valueRef) - throws CorruptEncodingException - { - try { - return decode(src, srcOffset, valueRef, -1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * @param xorMask 0 for normal decoding, -1 for descending decoding - */ - private static int decode(byte[] src, int srcOffset, byte[][] valueRef, int xorMask) { - // Scan ahead, looking for terminator. - int srcEnd = srcOffset; - while (true) { - byte b = src[srcEnd++]; - if (-32 <= b && b < 32) { - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - if ((srcEnd - 1) <= srcOffset) { - valueRef[0] = null; - return 1; - } - } - break; - } - } - - if (srcEnd - srcOffset == 1) { - valueRef[0] = EMPTY_BYTE_ARRAY; - return 1; - } - - // Value is decoded from base-32768. - - int valueLength = ((srcEnd - srcOffset - 1) * 120) >> 7; - byte[] value = new byte[valueLength]; - int valueOffset = 0; - - final int originalOffset = srcOffset; - - int accumBits = 0; - int accum = 0; - - while (true) { - int d = (src[srcOffset++] ^ xorMask) & 0xff; - int b; - if (srcOffset == srcEnd || - (b = (src[srcOffset++] ^ xorMask) & 0xff) < 32 || b > 223) { - // Handle special case where one byte was emitted for digit. - d -= 32; - // To produce digit, multiply d by 192 and add 191 to adjust - // for missing remainder. The lower bits are discarded anyhow. - d = (d << 7) + (d << 6) + 191; - - // Shift decoded digit into accumulator. - accumBits += 15; - accum = (accum << 15) | d; - - break; - } - - d -= 32; - // To produce digit, multiply d by 192 and add in remainder. - d = ((d << 7) + (d << 6)) + b - 32; - - // Shift decoded digit into accumulator. - accumBits += 15; - accum = (accum << 15) | d; - - if (accumBits == 15) { - value[valueOffset++] = (byte)(accum >> 7); - } else { - value[valueOffset++] = (byte)(accum >> (accumBits - 8)); - accumBits -= 8; - value[valueOffset++] = (byte)(accum >> (accumBits - 8)); - } - accumBits -= 8; - } - - if (accumBits >= 8 && valueOffset < valueLength) { - value[valueOffset] = (byte)(accum >> (accumBits - 8)); - } - - valueRef[0] = value; - - return srcOffset - originalOffset; - } - - /** - * Decodes an encoded string from the given byte array. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded string is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decodeString(byte[] src, int srcOffset, String[] valueRef) - throws CorruptEncodingException - { - try { - return decodeString(src, srcOffset, valueRef, 0); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes an encoded string from the given byte array as originally - * encoded for descending order. - * - * @param src source of encoded data - * @param srcOffset offset into encoded data - * @param valueRef decoded string is stored in element 0, which may be null - * @return amount of bytes read from source - * @throws CorruptEncodingException if source data is corrupt - */ - public static int decodeStringDesc(byte[] src, int srcOffset, String[] valueRef) - throws CorruptEncodingException - { - try { - return decodeString(src, srcOffset, valueRef, -1); - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * @param xorMask 0 for normal decoding, -1 for descending decoding - */ - private static int decodeString(byte[] src, int srcOffset, String[] valueRef, int xorMask) - throws CorruptEncodingException - { - // Scan ahead, looking for terminator. - int srcEnd = srcOffset; - while (true) { - byte b = src[srcEnd++]; - if (-2 <= b && b < 2) { - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - if ((srcEnd - 1) <= srcOffset) { - valueRef[0] = null; - return 1; - } - } - break; - } - } - - if (srcEnd - srcOffset == 1) { - valueRef[0] = ""; - return 1; - } - - // Allocate a character array which may be longer than needed once - // bytes are decoded into characters. - char[] value = new char[srcEnd - srcOffset]; - int valueOffset = 0; - - final int originalOffset = srcOffset; - - while (srcOffset < srcEnd) { - int c = (src[srcOffset++] ^ xorMask) & 0xff; - switch (c >> 5) { - case 0: case 1: case 2: case 3: - // 0xxxxxxx - value[valueOffset++] = (char)(c - 2); - break; - case 4: case 5: - // 10xxxxxx xxxxxxxx - - c = c & 0x3f; - // Multiply by 192, add in remainder, remove offset of 2, and de-normalize. - value[valueOffset++] = - (char)((c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 94); - - break; - case 6: - // 110xxxxx xxxxxxxx xxxxxxxx - - c = c & 0x1f; - // Multiply by 192, add in remainder... - c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) - 32; - // ...multiply by 192, add in remainder, remove offset of 2, and de-normalize. - c = (c << 7) + (c << 6) + ((src[srcOffset++] ^ xorMask) & 0xff) + 12382; - - if (c >= 0x10000) { - // Split into surrogate pair. - c -= 0x10000; - value[valueOffset++] = (char)(0xd800 | ((c >> 10) & 0x3ff)); - value[valueOffset++] = (char)(0xdc00 | (c & 0x3ff)); - } else { - value[valueOffset++] = (char)c; - } - - break; - default: - // 111xxxxx - // Illegal. - throw new CorruptEncodingException - ("Corrupt encoded string data (source offset = " - + (srcOffset - 1) + ')'); - } - } - - valueRef[0] = new String(value, 0, valueOffset - 1); - - return srcEnd - originalOffset; - } - - /** - * Decodes the given byte array which was encoded by {@link - * KeyEncoder#encodeSingleDesc}. - */ - public static byte[] decodeSingleDesc(byte[] src) throws CorruptEncodingException { - return decodeSingleDesc(src, 0, 0); - } - - /** - * Decodes the given byte array which was encoded by {@link - * KeyEncoder#encodeSingleDesc}. - * - * @param prefixPadding amount of extra bytes to skip from start of encoded byte array - * @param suffixPadding amount of extra bytes to skip at end of encoded byte array - */ - public static byte[] decodeSingleDesc(byte[] src, int prefixPadding, int suffixPadding) - throws CorruptEncodingException - { - try { - int length = src.length - suffixPadding - prefixPadding; - if (length == 0) { - return EMPTY_BYTE_ARRAY; - } - byte[] dst = new byte[length]; - while (--length >= 0) { - dst[length] = (byte) (~src[prefixPadding + length]); - } - return dst; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } - - /** - * Decodes the given byte array which was encoded by {@link - * KeyEncoder#encodeSingleNullableDesc}. - */ - public static byte[] decodeSingleNullableDesc(byte[] src) throws CorruptEncodingException { - return decodeSingleNullableDesc(src, 0, 0); - } - - /** - * Decodes the given byte array which was encoded by {@link - * KeyEncoder#encodeSingleNullableDesc}. - * - * @param prefixPadding amount of extra bytes to skip from start of encoded byte array - * @param suffixPadding amount of extra bytes to skip at end of encoded byte array - */ - public static byte[] decodeSingleNullableDesc(byte[] src, int prefixPadding, int suffixPadding) - throws CorruptEncodingException - { - try { - byte b = src[prefixPadding]; - if (b == NULL_BYTE_HIGH || b == NULL_BYTE_LOW) { - return null; - } - int length = src.length - suffixPadding - 1 - prefixPadding; - if (length == 0) { - return EMPTY_BYTE_ARRAY; - } - byte[] dst = new byte[length]; - while (--length >= 0) { - dst[length] = (byte) (~src[1 + prefixPadding + length]); - } - return dst; - } catch (IndexOutOfBoundsException e) { - throw new CorruptEncodingException(null, e); - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java b/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java deleted file mode 100644 index dd0faf9..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/KeyEncoder.java +++ /dev/null @@ -1,741 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -/** - * A very low-level class that supports encoding of primitive data into unique, - * sortable byte array keys. If the data to encode is of a variable size, then - * it is written in base-32768, using only byte values 32..223. This allows - * special values such as nulls and terminators to be unambiguously - * encoded. Terminators for variable data can be encoded using 1 for ascending - * order and 254 for descending order. Nulls can be encoded as 255 for high - * ordering and 0 for low ordering. - * - * @author Brian S O'Neill - * @see KeyDecoder - */ -public class KeyEncoder extends DataEncoder { - - /** Byte to terminate variable data encoded for ascending order */ - static final byte TERMINATOR = (byte)1; - - /** - * Encodes the given signed integer into exactly 4 bytes for descending - * order. - * - * @param value signed integer value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(int value, byte[] dst, int dstOffset) { - encode(~value, dst, dstOffset); - } - - /** - * Encodes the given signed Integer object into exactly 1 or 5 bytes for - * descending order. If the Integer object is never expected to be null, - * consider encoding as an int primitive. - * - * @param value optional signed Integer value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(Integer value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_LOW; - encode(~value.intValue(), dst, dstOffset + 1); - return 5; - } - } - - /** - * Encodes the given signed long into exactly 8 bytes for descending order. - * - * @param value signed long value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(long value, byte[] dst, int dstOffset) { - encode(~value, dst, dstOffset); - } - - /** - * Encodes the given signed Long object into exactly 1 or 9 bytes for - * descending order. If the Long object is never expected to be null, - * consider encoding as a long primitive. - * - * @param value optional signed Long value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(Long value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_LOW; - encode(~value.longValue(), dst, dstOffset + 1); - return 9; - } - } - - /** - * Encodes the given signed byte into exactly 1 byte for descending order. - * - * @param value signed byte value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(byte value, byte[] dst, int dstOffset) { - dst[dstOffset] = (byte)(value ^ 0x7f); - } - - /** - * Encodes the given signed Byte object into exactly 1 or 2 bytes for - * descending order. If the Byte object is never expected to be null, - * consider encoding as a byte primitive. - * - * @param value optional signed Byte value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(Byte value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_LOW; - dst[dstOffset + 1] = (byte)(value ^ 0x7f); - return 2; - } - } - - /** - * Encodes the given signed short into exactly 2 bytes for descending - * order. - * - * @param value signed short value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(short value, byte[] dst, int dstOffset) { - encode((short) ~value, dst, dstOffset); - } - - /** - * Encodes the given signed Short object into exactly 1 or 3 bytes for - * descending order. If the Short object is never expected to be null, - * consider encoding as a short primitive. - * - * @param value optional signed Short value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(Short value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_LOW; - encode((short) ~value.shortValue(), dst, dstOffset + 1); - return 3; - } - } - - /** - * Encodes the given character into exactly 2 bytes for descending order. - * - * @param value character value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(char value, byte[] dst, int dstOffset) { - encode((char) ~value, dst, dstOffset); - } - - /** - * Encodes the given Character object into exactly 1 or 3 bytes for - * descending order. If the Character object is never expected to be null, - * consider encoding as a char primitive. - * - * @param value optional Character value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(Character value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } else { - dst[dstOffset] = NOT_NULL_BYTE_LOW; - encode((char) ~value.charValue(), dst, dstOffset + 1); - return 3; - } - } - - /** - * Encodes the given boolean into exactly 1 byte for descending order. - * - * @param value boolean value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(boolean value, byte[] dst, int dstOffset) { - dst[dstOffset] = value ? (byte)127 : (byte)128; - } - - /** - * Encodes the given Boolean object into exactly 1 byte for descending - * order. - * - * @param value optional Boolean value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(Boolean value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - } else { - dst[dstOffset] = value.booleanValue() ? (byte)127 : (byte)128; - } - } - - /** - * Encodes the given float into exactly 4 bytes for descending order. - * - * @param value float value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(float value, byte[] dst, int dstOffset) { - int bits = Float.floatToIntBits(value); - if (bits >= 0) { - bits ^= 0x7fffffff; - } - dst[dstOffset ] = (byte)(bits >> 24); - dst[dstOffset + 1] = (byte)(bits >> 16); - dst[dstOffset + 2] = (byte)(bits >> 8); - dst[dstOffset + 3] = (byte)bits; - } - - /** - * Encodes the given Float object into exactly 4 bytes for descending - * order. A non-canonical NaN value is used to represent null. - * - * @param value optional Float value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(Float value, byte[] dst, int dstOffset) { - if (value == null) { - encode(~0x7fffffff, dst, dstOffset); - } else { - encodeDesc(value.floatValue(), dst, dstOffset); - } - } - - /** - * Encodes the given double into exactly 8 bytes for descending order. - * - * @param value double value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(double value, byte[] dst, int dstOffset) { - long bits = Double.doubleToLongBits(value); - if (bits >= 0) { - bits ^= 0x7fffffffffffffffL; - } - int w = (int)(bits >> 32); - dst[dstOffset ] = (byte)(w >> 24); - dst[dstOffset + 1] = (byte)(w >> 16); - dst[dstOffset + 2] = (byte)(w >> 8); - dst[dstOffset + 3] = (byte)w; - w = (int)bits; - dst[dstOffset + 4] = (byte)(w >> 24); - dst[dstOffset + 5] = (byte)(w >> 16); - dst[dstOffset + 6] = (byte)(w >> 8); - dst[dstOffset + 7] = (byte)w; - } - - /** - * Encodes the given Double object into exactly 8 bytes for descending - * order. A non-canonical NaN value is used to represent null. - * - * @param value optional Double value to encode - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - */ - public static void encodeDesc(Double value, byte[] dst, int dstOffset) { - if (value == null) { - encode(~0x7fffffffffffffffL, dst, dstOffset); - } else { - encodeDesc(value.doubleValue(), dst, dstOffset); - } - } - - /** - * Encodes the given optional unsigned byte array into a variable amount of - * bytes. If the byte array is null, exactly 1 byte is written. Otherwise, - * the amount written can be determined by calling calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(byte[] value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_HIGH; - return 1; - } - return encode(value, 0, value.length, dst, dstOffset, 0); - } - - /** - * Encodes the given optional unsigned byte array into a variable amount of - * bytes. If the byte array is null, exactly 1 byte is written. Otherwise, - * the amount written can be determined by calling calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param valueOffset offset into byte array - * @param valueLength length of data in byte array - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encode(byte[] value, int valueOffset, int valueLength, - byte[] dst, int dstOffset) { - return encode(value, valueOffset, valueLength, dst, dstOffset, 0); - } - - /** - * Encodes the given optional unsigned byte array into a variable amount of - * bytes for descending order. If the byte array is null, exactly 1 byte is - * written. Otherwise, the amount written is determined by calling - * calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(byte[] value, byte[] dst, int dstOffset) { - if (value == null) { - dst[dstOffset] = NULL_BYTE_LOW; - return 1; - } - return encode(value, 0, value.length, dst, dstOffset, -1); - } - - /** - * Encodes the given optional unsigned byte array into a variable amount of - * bytes for descending order. If the byte array is null, exactly 1 byte is - * written. Otherwise, the amount written is determined by calling - * calculateEncodedLength. - * - * @param value byte array value to encode, may be null - * @param valueOffset offset into byte array - * @param valueLength length of data in byte array - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(byte[] value, int valueOffset, int valueLength, - byte[] dst, int dstOffset) { - return encode(value, valueOffset, valueLength, dst, dstOffset, -1); - } - - /** - * @param xorMask 0 for normal encoding, -1 for descending encoding - */ - private static int encode(byte[] value, int valueOffset, int valueLength, - byte[] dst, int dstOffset, int xorMask) { - if (value == null) { - dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask); - return 1; - } - - final int originalOffset = dstOffset; - - // Value is encoded in base-32768. - - int accumBits = 0; - int accum = 0; - - final int end = valueOffset + valueLength; - for (int i=valueOffset; i> (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. - *

- * 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. - *

- * Strings are encoded in a fashion similar to UTF-8, in that ASCII - * characters are usually written in one byte. This encoding is more - * efficient than UTF-8, but it isn't compatible with UTF-8. - * - * @param value String value to encode, may be null - * @param dst destination for encoded bytes - * @param dstOffset offset into destination array - * @return amount of bytes written - */ - public static int encodeDesc(String value, byte[] dst, int dstOffset) { - return encode(value, dst, dstOffset, -1); - } - - /** - * @param xorMask 0 for normal encoding, -1 for descending encoding - */ - private static int encode(String value, byte[] dst, int dstOffset, int xorMask) { - if (value == null) { - dst[dstOffset] = (byte)(NULL_BYTE_HIGH ^ xorMask); - return 1; - } - - final int originalOffset = dstOffset; - - // All characters have an offset of 2 added, in order to reserve bytes - // 0 and 1 for encoding nulls and terminators. This means the ASCII - // string "HelloWorld" is actually encoded as "JgnnqYqtnf". This also - // means that the ASCII '~' and del characters are encoded in two bytes. - - int length = value.length(); - for (int i = 0; i < length; i++) { - int c = value.charAt(i) + 2; - if (c <= 0x7f) { - // 0xxxxxxx - dst[dstOffset++] = (byte)(c ^ xorMask); - } else if (c <= 12415) { - // 10xxxxxx xxxxxxxx - - // Second byte cannot have the values 0, 1, 254, or 255 because - // they clash with null and terminator bytes. Divide by 192 and - // store in first 6 bits. The remainder, with 32 added, goes - // into the second byte. Note that (192 * 63 + 191) + 128 == 12415. - // 63 is the maximum value that can be represented in 6 bits. - - c -= 128; // c will always be at least 128, so normalize. - - // approximate value / 192 - int a = (c * 21845) >> 22; - - // approximate value % 192 - // Note: the value 192 was chosen as a divisor because a multiply by - // 192 can be replaced with two summed shifts. - c = c - ((a << 7) + (a << 6)); - if (c == 192) { - // Fix error. - a++; - c = 0; - } - - dst[dstOffset++] = (byte)((0x80 | a) ^ xorMask); - dst[dstOffset++] = (byte)((c + 32) ^ xorMask); - } else { - // 110xxxxx xxxxxxxx xxxxxxxx - - if ((c - 2) >= 0xd800 && (c - 2) <= 0xdbff) { - // Found a high surrogate. Verify that surrogate pair is - // well-formed. Low surrogate must follow high surrogate. - if (i + 1 < length) { - int c2 = value.charAt(i + 1); - if (c2 >= 0xdc00 && c2 <= 0xdfff) { - c = ((((c - 2) & 0x3ff) << 10) | (c2 & 0x3ff)) + 0x10002; - i++; - } - } - } - - // Second and third bytes cannot have the values 0, 1, 254, or - // 255 because they clash with null and terminator - // bytes. Divide by 192 twice, storing the first and second - // remainders in the third and second bytes, respectively. - // Note that largest unicode value supported is 2^20 + 65535 == - // 1114111. When divided by 192 twice, the value is 30, which - // just barely fits in the 5 available bits of the first byte. - - c -= 12416; // c will always be at least 12416, so normalize. - - int a = (int)((c * 21845L) >> 22); - c = c - ((a << 7) + (a << 6)); - if (c == 192) { - a++; - c = 0; - } - - dst[dstOffset + 2] = (byte)((c + 32) ^ xorMask); - - c = (a * 21845) >> 22; - a = a - ((c << 7) + (c << 6)); - if (a == 192) { - c++; - a = 0; - } - - dst[dstOffset++] = (byte)((0xc0 | c) ^ xorMask); - dst[dstOffset++] = (byte)((a + 32) ^ xorMask); - dstOffset++; - } - } - - // Append terminator. - dst[dstOffset++] = (byte)(TERMINATOR ^ xorMask); - - return dstOffset - originalOffset; - } - - /** - * Returns the amount of bytes required to encode the given String. - * - * @param value String to encode, may be null - */ - public static int calculateEncodedStringLength(String value) { - int encodedLen = 1; - if (value != null) { - int valueLength = value.length(); - for (int i = 0; i < valueLength; i++) { - int c = value.charAt(i); - if (c <= (0x7f - 2)) { - encodedLen++; - } else if (c <= (12415 - 2)) { - encodedLen += 2; - } else { - if (c >= 0xd800 && c <= 0xdbff) { - // Found a high surrogate. Verify that surrogate pair is - // well-formed. Low surrogate must follow high surrogate. - if (i + 1 < valueLength) { - int c2 = value.charAt(i + 1); - if (c2 >= 0xdc00 && c2 <= 0xdfff) { - i++; - } - } - } - encodedLen += 3; - } - } - } - return encodedLen; - } - - /** - * Encodes the given byte array for use when there is only a single - * required property, descending order, whose type is a byte array. The - * original byte array is returned if the length is zero. - */ - public static byte[] encodeSingleDesc(byte[] value) { - return encodeSingleDesc(value, 0, 0); - } - - /** - * Encodes the given byte array for use when there is only a single - * required property, descending order, whose type is a byte array. The - * original byte array is returned if the length and padding lengths are - * zero. - * - * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array - * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array - */ - public static byte[] encodeSingleDesc(byte[] value, int prefixPadding, int suffixPadding) { - int length = value.length; - if (prefixPadding <= 0 && suffixPadding <= 0 && length == 0) { - return value; - } - byte[] dst = new byte[prefixPadding + length + suffixPadding]; - while (--length >= 0) { - dst[prefixPadding + length] = (byte) (~value[length]); - } - return dst; - } - - /** - * Encodes the given byte array for use when there is only a single - * nullable property, descending order, whose type is a byte array. - */ - public static byte[] encodeSingleNullableDesc(byte[] value) { - return encodeSingleNullableDesc(value, 0, 0); - } - - /** - * Encodes the given byte array for use when there is only a single - * nullable property, descending order, whose type is a byte array. - * - * @param prefixPadding amount of extra bytes to allocate at start of encoded byte array - * @param suffixPadding amount of extra bytes to allocate at end of encoded byte array - */ - public static byte[] encodeSingleNullableDesc(byte[] value, - int prefixPadding, int suffixPadding) { - if (prefixPadding <= 0 && suffixPadding <= 0) { - if (value == null) { - return NULL_BYTE_ARRAY_LOW; - } - - int length = value.length; - if (length == 0) { - return NOT_NULL_BYTE_ARRAY_LOW; - } - - byte[] dst = new byte[1 + length]; - dst[0] = NOT_NULL_BYTE_LOW; - while (--length >= 0) { - dst[1 + length] = (byte) (~value[length]); - } - return dst; - } - - if (value == null) { - byte[] dst = new byte[prefixPadding + 1 + suffixPadding]; - dst[prefixPadding] = NULL_BYTE_LOW; - return dst; - } - - int length = value.length; - byte[] dst = new byte[prefixPadding + 1 + length + suffixPadding]; - dst[prefixPadding] = NOT_NULL_BYTE_LOW; - while (--length >= 0) { - dst[prefixPadding + 1 + length] = (byte) (~value[length]); - } - return dst; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java deleted file mode 100644 index 362c17c..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/LayoutPropertyInfo.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.reflect.Method; - -import org.cojen.classfile.TypeDesc; - -import com.amazon.carbonado.layout.LayoutProperty; -import com.amazon.carbonado.lob.Lob; - -/** - * - * - * @author Brian S O'Neill - */ -public class LayoutPropertyInfo implements GenericPropertyInfo { - private final LayoutProperty mProp; - private final TypeDesc mPropertyType; - private final TypeDesc mStorageType; - private final Method mFromStorage; - private final Method mToStorage; - - LayoutPropertyInfo(LayoutProperty property) { - this(property, null, null, null); - } - - LayoutPropertyInfo(LayoutProperty property, - Class storageType, Method fromStorage, Method toStorage) - { - mProp = property; - mPropertyType = TypeDesc.forDescriptor(property.getPropertyTypeDescriptor()); - if (storageType == null) { - mStorageType = mPropertyType; - } else { - mStorageType = TypeDesc.forClass(storageType); - } - mFromStorage = fromStorage; - mToStorage = toStorage; - } - - public String getPropertyName() { - return mProp.getPropertyName(); - } - - public TypeDesc getPropertyType() { - return mPropertyType; - } - - public TypeDesc getStorageType() { - return mStorageType; - } - - public boolean isNullable() { - return mProp.isNullable(); - } - - public boolean isLob() { - Class clazz = mPropertyType.toClass(); - return clazz != null && Lob.class.isAssignableFrom(clazz); - } - - public Method getFromStorageAdapter() { - return mFromStorage; - } - - public Method getToStorageAdapter() { - return mToStorage; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java b/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java deleted file mode 100644 index b665191..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/RawCursor.java +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.util.NoSuchElementException; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.cursor.AbstractCursor; - -/** - * Abstract Cursor implementation for a repository that manipulates raw bytes. - * - * @author Brian S O'Neill - */ -public abstract class RawCursor extends AbstractCursor { - // 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= 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. - * - *

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. - * - *

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. - * - *

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. - * - *

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 0) { - byte[] prefix = mStartBound; - byte[] key = getCurrentKey(); - if (key == null) { - return false; - } - for (int i=0; i= 0) { - if (result > 0 || !mInclusiveEnd) { - return false; - } - } - } - - return prefixMatches(); - } - - // Calls toLast, but considers start and end bounds. Caller is responsible - // for preserving key. - private boolean toBoundedLast() throws FetchException { - if (mEndBound == null) { - if (!toLast()) { - return false; - } - } else { - if (!toLast(mEndBound.clone())) { - return false; - } - if (!mInclusiveEnd) { - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - return false; - } - if (compareKeysPartially(mEndBound, currentKey) == 0) { - if (!toPreviousKey()) { - return false; - } - } - } - } - - if (mStartBound != null) { - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - return false; - } - int result = compareKeysPartially(currentKey, mStartBound); - if (result <= 0) { - if (result < 0 || !mInclusiveStart) { - return false; - } - } - } - - return prefixMatches(); - } - - // Calls toNext, but considers end bound. - private boolean toBoundedNext() throws FetchException { - if (!toNext()) { - return false; - } - - if (mEndBound != null) { - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - return false; - } - int result = compareKeysPartially(currentKey, mEndBound); - if (result >= 0) { - if (result > 0 || !mInclusiveEnd) { - return false; - } - } - } - - return prefixMatches(); - } - - // Calls toNext, but considers end bound. - private int toBoundedNext(int amount) throws FetchException { - if (mEndBound == null) { - return toNext(amount); - } - - int count = 0; - - disableValue(); - try { - while (amount > 0) { - if (!toNext()) { - break; - } - - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - break; - } - - int result = compareKeysPartially(currentKey, mEndBound); - if (result >= 0) { - if (result > 0 || !mInclusiveEnd) { - break; - } - } - - if (!prefixMatches()) { - break; - } - - count++; - amount--; - } - } finally { - enableKeyAndValue(); - } - - return count; - } - - // Calls toPrevious, but considers start bound. - private boolean toBoundedPrevious() throws FetchException { - if (!toPrevious()) { - return false; - } - - if (mStartBound != null) { - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - return false; - } - int result = compareKeysPartially(currentKey, mStartBound); - if (result <= 0) { - if (result < 0 || !mInclusiveStart) { - // Too far now, reset to first. - toBoundedFirst(); - return false; - } - } - } - - return prefixMatches(); - } - - // Calls toPrevious, but considers start bound. - private int toBoundedPrevious(int amount) throws FetchException { - if (mStartBound == null) { - return toPrevious(amount); - } - - int count = 0; - - disableValue(); - try { - while (amount > 0) { - if (!toPrevious()) { - break; - } - - byte[] currentKey = getCurrentKey(); - if (currentKey == null) { - break; - } - - int result = compareKeysPartially(currentKey, mStartBound); - if (result <= 0) { - if (result < 0 || !mInclusiveStart) { - // Too far now, reset to first. - toBoundedFirst(); - break; - } - } - - if (!prefixMatches()) { - break; - } - - count++; - amount--; - } - } finally { - enableKeyAndValue(); - } - - return count; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java deleted file mode 100644 index 6d6fbe5..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/RawStorableGenerator.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.util.EnumSet; -import java.util.Map; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.Label; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.WeakIdentityMap; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.spi.MasterFeature; -import com.amazon.carbonado.spi.MasterStorableGenerator; -import com.amazon.carbonado.spi.MasterSupport; -import com.amazon.carbonado.spi.StorableGenerator; -import com.amazon.carbonado.spi.TriggerSupport; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; - -/** - * Generates and caches abstract implementations of {@link Storable} types - * which are encoded and decoded in a raw format. The generated abstract - * classes extend those created by {@link MasterStorableGenerator}. - * - * @author Brian S O'Neill - * @see GenericStorableCodec - * @see RawSupport - */ -public class RawStorableGenerator { - // Note: All generated fields/methods have a "$" character in them to - // prevent name collisions with any inherited fields/methods. User storable - // properties are defined as fields which exactly match the property - // name. We don't want collisions with those either. Legal bean properties - // cannot have "$" in them, so there's nothing to worry about. - - /** Name of protected abstract method in generated storable */ - public static final String - ENCODE_KEY_METHOD_NAME = "encodeKey$", - DECODE_KEY_METHOD_NAME = "decodeKey$", - ENCODE_DATA_METHOD_NAME = "encodeData$", - DECODE_DATA_METHOD_NAME = "decodeData$"; - - @SuppressWarnings("unchecked") - private static Map> cCache = new WeakIdentityMap(); - - /** - * Collection of different abstract class flavors. - */ - static class Flavors { - private Reference> mMasterFlavor; - - private Reference> mNonMasterFlavor; - - /** - * May return null. - */ - Class getClass(boolean isMaster) { - Reference> ref; - if (isMaster) { - ref = mMasterFlavor; - } else { - ref = mNonMasterFlavor; - } - return (ref != null) ? ref.get() : null; - } - - @SuppressWarnings("unchecked") - void setClass(Class clazz, boolean isMaster) { - Reference> 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: - * - *

-     * public <init>(RawSupport);
-
-     * public <init>(RawSupport, byte[] key, byte[] value);
-     * 
- * - *

Subclasses must implement the following abstract protected methods, - * whose exact names are defined by constants in this class: - * - *

-     * // 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[]);
-     * 
- * - * @param isMaster when true, version properties, sequences, and triggers are managed - * @throws IllegalArgumentException if type is null - */ - @SuppressWarnings("unchecked") - public static Class - getAbstractClass(Class type, boolean isMaster) - throws SupportException, IllegalArgumentException - { - synchronized (cCache) { - Class abstractClass; - - Flavors flavors = (Flavors) cCache.get(type); - - if (flavors == null) { - flavors = new Flavors(); - 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 Class - generateAbstractClass(Class storableClass, boolean isMaster) - throws SupportException - { - EnumSet 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 abstractClass = - MasterStorableGenerator.getAbstractClass(storableClass, features); - - ClassInjector ci = ClassInjector.create - (storableClass.getName(), abstractClass.getClassLoader()); - - ClassFile cf = new ClassFile(ci.getClassName(), abstractClass); - cf.setModifiers(cf.getModifiers().toAbstract(true)); - cf.markSynthetic(); - cf.setSourceFile(RawStorableGenerator.class.getName()); - cf.setTarget("1.5"); - - // Declare some types. - final TypeDesc storableType = TypeDesc.forClass(Storable.class); - final TypeDesc storageType = TypeDesc.forClass(Storage.class); - final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); - final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); - final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class); - final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); - - // Add constructor that accepts a RawSupport. - { - TypeDesc[] params = {rawSupportType}; - MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.loadLocal(b.getParameter(0)); - b.invokeSuperConstructor(new TypeDesc[] {masterSupportType}); - b.returnVoid(); - } - - // Add constructor that accepts a RawSupport, an encoded key, and an - // encoded data. - { - TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType}; - MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.loadLocal(b.getParameter(0)); - b.invokeSuperConstructor(new TypeDesc[] {masterSupportType}); - - params = new TypeDesc[] {byteArrayType}; - - b.loadThis(); - b.loadLocal(b.getParameter(1)); - b.invokeVirtual(DECODE_KEY_METHOD_NAME, null, params); - - b.loadThis(); - b.loadLocal(b.getParameter(2)); - b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params); - - // Indicate that object is clean by calling markAllPropertiesClean. - b.loadThis(); - b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); - - b.returnVoid(); - } - - // Declare protected abstract methods. - { - cf.addMethod(Modifiers.PROTECTED.toAbstract(true), - ENCODE_KEY_METHOD_NAME, byteArrayType, null); - cf.addMethod(Modifiers.PROTECTED.toAbstract(true), - DECODE_KEY_METHOD_NAME, null, new TypeDesc[]{byteArrayType}); - cf.addMethod(Modifiers.PROTECTED.toAbstract(true), - ENCODE_DATA_METHOD_NAME, byteArrayType, null); - cf.addMethod(Modifiers.PROTECTED.toAbstract(true), - DECODE_DATA_METHOD_NAME, null, new TypeDesc[]{byteArrayType}); - } - - // Add required protected doTryLoad_master method, which delegates to RawSupport. - { - MethodInfo mi = cf.addMethod - (Modifiers.PROTECTED.toFinal(true), - MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(FetchException.class)); - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.checkCast(rawSupportType); - b.loadThis(); - b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null); - TypeDesc[] params = {byteArrayType}; - b.invokeInterface(rawSupportType, "tryLoad", byteArrayType, params); - LocalVariable encodedDataVar = b.createLocalVariable(null, byteArrayType); - b.storeLocal(encodedDataVar); - b.loadLocal(encodedDataVar); - Label notNull = b.createLabel(); - b.ifNullBranch(notNull, false); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - notNull.setLocation(); - b.loadThis(); - b.loadLocal(encodedDataVar); - params = new TypeDesc[] {byteArrayType}; - b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add required protected doTryInsert_master method, which delegates to RawSupport. - { - MethodInfo mi = cf.addMethod - (Modifiers.PROTECTED.toFinal(true), - MasterStorableGenerator.DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - CodeBuilder b = new CodeBuilder(mi); - - // return rawSupport.tryInsert(this, this.encodeKey$(), this.encodeData$()); - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.checkCast(rawSupportType); - b.loadThis(); // pass this to tryInsert method - b.loadThis(); - b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null); - b.loadThis(); - b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null); - TypeDesc[] params = {storableType, byteArrayType, byteArrayType}; - b.invokeInterface(rawSupportType, "tryInsert", TypeDesc.BOOLEAN, params); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add required protected doTryUpdate_master method, which delegates to RawSupport. - { - MethodInfo mi = cf.addMethod - (Modifiers.PROTECTED.toFinal(true), - MasterStorableGenerator.DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - CodeBuilder b = new CodeBuilder(mi); - - // rawSupport.store(this, this.encodeKey$(), this.encodeData$()); - // return true; - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.checkCast(rawSupportType); - b.loadThis(); // pass this to store method - b.loadThis(); - b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null); - b.loadThis(); - b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null); - TypeDesc[] params = {storableType, byteArrayType, byteArrayType}; - b.invokeInterface(rawSupportType, "store", null, params); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add required protected doTryDelete_master method, which delegates to RawSupport. - { - MethodInfo mi = cf.addMethod - (Modifiers.PROTECTED.toFinal(true), - MasterStorableGenerator.DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - CodeBuilder b = new CodeBuilder(mi); - - // return rawSupport.tryDelete(this.encodeKey$()); - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.checkCast(rawSupportType); - b.loadThis(); - b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null); - - TypeDesc[] params = {byteArrayType}; - b.invokeInterface(rawSupportType, "tryDelete", TypeDesc.BOOLEAN, params); - b.returnValue(TypeDesc.BOOLEAN); - } - - return ci.defineClass(cf); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java b/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java deleted file mode 100644 index 895089d..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/RawSupport.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Storable; - -import com.amazon.carbonado.lob.Blob; -import com.amazon.carbonado.lob.Clob; - -import com.amazon.carbonado.spi.MasterSupport; - -/** - * Provides runtime support for Storable classes generated by {@link RawStorableGenerator}. - * - * @author Brian S O'Neill - */ -public interface RawSupport extends MasterSupport { - /** - * Try to load the entry referenced by the given key, but return null - * if not found. - * - * @param key non-null key to search for - * @return non-null value that was found, or null if not found - */ - byte[] tryLoad(byte[] key) throws FetchException; - - /** - * Try to insert the entry referenced by the given key with the given - * value. - * - * @param storable storable object that key and value were derived from - * @param key non-null key to insert - * @param value non-null value to insert - * @return false if unique constraint prevents insert - */ - boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException; - - /** - * Try to store the entry referenced by the given key with the given - * value. If the entry does not exist, insert it. Otherwise, update it. - * - * @param storable storable object that key and value were derived from - * @param key non-null key to store - * @param value non-null value to store - */ - void store(S storable, byte[] key, byte[] value) throws PersistException; - - /** - * Try to delete the entry referenced by the given key. - * - * @param key non-null key to delete - * @return true if entry existed and is now deleted - */ - boolean tryDelete(byte[] key) throws PersistException; - - /** - * Returns the Blob for the given locator, returning null if not found. - */ - Blob getBlob(long locator) throws FetchException; - - /** - * Returns the locator for the given Blob, returning zero if null. - * - * @throws PersistException if blob is unrecognized - */ - long getLocator(Blob blob) throws PersistException; - - /** - * Returns the Clob for the given locator, returning null if not found. - */ - Clob getClob(long locator) throws FetchException; - - /** - * Returns the locator for the given Clob, returning zero if null. - * - * @throws PersistException if blob is unrecognized - */ - long getLocator(Clob clob) throws PersistException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java b/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java deleted file mode 100644 index 5224b6c..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/RawUtil.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -/** - * Utilities for manipulating binary data. - * - * @author Brian S O'Neill - */ -public class RawUtil { - /** - * Adds one to an unsigned integer, represented as a byte array. If - * overflowed, value in byte array is 0x00, 0x00, 0x00... - * - * @param value unsigned integer to increment - * @return false if overflowed - */ - public static boolean increment(byte[] value) { - for (int i=value.length; --i>=0; ) { - byte newValue = (byte) ((value[i] & 0xff) + 1); - value[i] = newValue; - if (newValue != 0) { - // No carry bit, so done adding. - return true; - } - } - // This point is reached upon overflow. - return false; - } - - /** - * Subtracts one from an unsigned integer, represented as a byte array. If - * overflowed, value in byte array is 0xff, 0xff, 0xff... - * - * @param value unsigned integer to decrement - * @return false if overflowed - */ - public static boolean decrement(byte[] value) { - for (int i=value.length; --i>=0; ) { - byte newValue = (byte) ((value[i] & 0xff) + -1); - value[i] = newValue; - if (newValue != -1) { - // No borrow bit, so done subtracting. - return true; - } - } - // This point is reached upon overflow. - return false; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java b/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java deleted file mode 100644 index 307fe7e..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodec.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; - -import com.amazon.carbonado.info.StorableIndex; - -/** - * Supports encoding and decoding of storables. - * - * @author Brian S O'Neill - * @see StorableCodecFactory - */ -public interface StorableCodec { - /** - * Returns the type of Storable produced by this codec. - */ - Class getStorableType(); - - /** - * Instantiate a Storable with no key or value defined yet. - * - * @param support binds generated storable with a storage layer - */ - S instantiate(RawSupport support); - - /** - * Instantiate a Storable with a specific key and value. - * - * @param support binds generated storable with a storage layer - */ - S instantiate(RawSupport support, byte[] key, byte[] value) - throws FetchException; - - /** - * Returns the sequence and directions of properties that make up the - * primary key. - */ - StorableIndex getPrimaryKeyIndex(); - - /** - * Returns the number of prefix bytes in the primary key, which may be - * zero. - */ - int getPrimaryKeyPrefixLength(); - - /** - * Encode a key by extracting all the primary key properties from the given - * storable. - * - * @param storable extract primary key properties from this instance - * @return raw search key - */ - byte[] encodePrimaryKey(S storable); - - /** - * Encode a key by extracting all the primary key properties from the given - * storable. - * - * @param storable extract primary key properties from this instance - * @param rangeStart index of first property to use. Its value must be less - * than the count of primary key properties. - * @param rangeEnd index of last property to use, exlusive. Its value must - * be less than or equal to the count of primary key properties. - * @return raw search key - */ - byte[] encodePrimaryKey(S storable, int rangeStart, int rangeEnd); - - /** - * Encode a key by extracting all the primary key properties from the given - * storable. - * - * @param values values to build into a key. It must be long enough to - * accommodate all primary key properties. - * @return raw search key - */ - byte[] encodePrimaryKey(Object[] values); - - /** - * Encode a key by extracting all the primary key properties from the given - * storable. - * - * @param values values to build into a key. The length may be less than - * the amount of primary key properties used by this factory. It must not - * be less than the difference between rangeStart and rangeEnd. - * @param rangeStart index of first property to use. Its value must be less - * than the count of primary key properties. - * @param rangeEnd index of last property to use, exlusive. Its value must - * be less than or equal to the count of primary key properties. - * @return raw search key - */ - byte[] encodePrimaryKey(Object[] values, int rangeStart, int rangeEnd); - - /** - * Encode the primary key for when there are no values, but there may be a - * prefix. Returned value may be null if no prefix is defined. - */ - byte[] encodePrimaryKeyPrefix(); -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java b/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java deleted file mode 100644 index 26e3858..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/StorableCodecFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.StorableIndex; -import com.amazon.carbonado.layout.Layout; - -/** - * Factory for creating instances of {@link StorableCodec}. - * - * @author Brian S O'Neill - */ -public interface StorableCodecFactory { - /** - * Returns the preferred storage/database name for the given type. Return - * null to let repository decide. - * - * @throws SupportException if type is not supported - */ - String getStorageName(Class 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 - */ - StorableCodec createCodec(Class type, - StorableIndex pkIndex, - boolean isMaster, - Layout layout) - throws SupportException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java b/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java deleted file mode 100644 index f13a56c..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/StorablePropertyInfo.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.amazon.carbonado.spi.raw; - -import java.lang.reflect.Method; - -import org.cojen.classfile.CodeAssembler; -import org.cojen.classfile.TypeDesc; - -import com.amazon.carbonado.info.StorableProperty; -import com.amazon.carbonado.lob.Lob; - -/** - * - * - * @author Brian S O'Neill - */ -public class StorablePropertyInfo implements GenericPropertyInfo { - private final StorableProperty mProp; - private final TypeDesc mPropertyType; - private final TypeDesc mStorageType; - private final Method mFromStorage; - private final Method mToStorage; - - StorablePropertyInfo(StorableProperty property) { - this(property, null, null, null); - } - - StorablePropertyInfo(StorableProperty property, - Class storageType, Method fromStorage, Method toStorage) { - mProp = property; - mPropertyType = TypeDesc.forClass(property.getType()); - if (storageType == null) { - mStorageType = mPropertyType; - } else { - mStorageType = TypeDesc.forClass(storageType); - } - mFromStorage = fromStorage; - mToStorage = toStorage; - } - - public String getPropertyName() { - return mProp.getName(); - } - - public TypeDesc getPropertyType() { - return mPropertyType; - } - - public TypeDesc getStorageType() { - return mStorageType; - } - - public boolean isNullable() { - return mProp.isNullable(); - } - - public boolean isLob() { - Class clazz = mPropertyType.toClass(); - return clazz != null && Lob.class.isAssignableFrom(clazz); - } - - public Method getFromStorageAdapter() { - return mFromStorage; - } - - public Method getToStorageAdapter() { - return mToStorage; - } - - public String getReadMethodName() { - return mProp.getReadMethodName(); - } - - public void addInvokeReadMethod(CodeAssembler a) { - a.invoke(mProp.getReadMethod()); - } - - public void addInvokeReadMethod(CodeAssembler a, TypeDesc instanceType) { - Class clazz = instanceType.toClass(); - if (clazz == null) { - // Can't know if instance should be invoked as an interface or as a - // virtual method. - throw new IllegalArgumentException("Instance type has no known class"); - } - if (clazz.isInterface()) { - a.invokeInterface(instanceType, getReadMethodName(), getPropertyType(), null); - } else { - a.invokeVirtual(instanceType, getReadMethodName(), getPropertyType(), null); - } - } - - public String getWriteMethodName() { - return mProp.getWriteMethodName(); - } - - public void addInvokeWriteMethod(CodeAssembler a) { - a.invoke(mProp.getWriteMethod()); - } - - public void addInvokeWriteMethod(CodeAssembler a, TypeDesc instanceType) { - Class clazz = instanceType.toClass(); - if (clazz == null) { - // Can't know if instance should be invoked as an interface or as a - // virtual method. - throw new IllegalArgumentException("Instance type has no known class"); - } - if (clazz.isInterface()) { - a.invokeInterface(instanceType, - getWriteMethodName(), null, new TypeDesc[] {getPropertyType()}); - } else { - a.invokeVirtual(instanceType, - getWriteMethodName(), null, new TypeDesc[] {getPropertyType()}); - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/raw/package-info.java b/src/main/java/com/amazon/carbonado/spi/raw/package-info.java deleted file mode 100644 index 8d47419..0000000 --- a/src/main/java/com/amazon/carbonado/spi/raw/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2006 Amazon Technologies, Inc. or its affiliates. - * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks - * of Amazon Technologies, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Provides support for repositories that encode/decode storables in a raw - * binary format. - */ -package com.amazon.carbonado.spi.raw; -- cgit v1.2.3