/* * Copyright 2006-2012 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.SupportException; import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableIndex; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.util.QuickConstructorGenerator; /** * Allows codecs to be defined for storables that have a custom encoding. * * @author Brian S O'Neill * @see CustomStorableCodecFactory */ public abstract class CustomStorableCodec<S extends Storable> implements StorableCodec<S> { // Generated storable instances maintain a reference to user-defined // concrete subclass of this class. private static final String CUSTOM_STORABLE_CODEC_FIELD_NAME = "customStorableCodec$"; @SuppressWarnings("unchecked") private static Map<Class, RawStorableGenerator.Flavors<? extends Storable>> cCache = new WeakIdentityMap(); /** * Returns a storable implementation that calls into CustomStorableCodec * implementation for encoding and decoding. */ @SuppressWarnings("unchecked") static <S extends Storable> Class<? extends S> getStorableClass(Class<S> type, boolean isMaster) throws SupportException { synchronized (cCache) { Class<? extends S> storableClass; RawStorableGenerator.Flavors<S> flavors = (RawStorableGenerator.Flavors<S>) cCache.get(type); if (flavors == null) { flavors = new RawStorableGenerator.Flavors<S>(); cCache.put(type, flavors); } else if ((storableClass = flavors.getClass(isMaster)) != null) { return storableClass; } storableClass = generateStorableClass(type, isMaster); flavors.setClass(storableClass, isMaster); return storableClass; } } @SuppressWarnings("unchecked") private static <S extends Storable> Class<? extends S> generateStorableClass(Class<S> type, boolean isMaster) throws SupportException { final Class<? extends S> abstractClass = RawStorableGenerator.getAbstractClass(type, isMaster); ClassInjector ci = ClassInjector.create (type.getName(), abstractClass.getClassLoader()); ClassFile cf = new ClassFile(ci.getClassName(), abstractClass); cf.markSynthetic(); cf.setSourceFile(CustomStorableCodec.class.getName()); cf.setTarget("1.5"); // Declare some types. final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class); final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); final TypeDesc[] byteArrayParam = {byteArrayType}; final TypeDesc customStorableCodecType = TypeDesc.forClass(CustomStorableCodec.class); // Add field for saving reference to concrete CustomStorableCodec. cf.addField(Modifiers.PRIVATE.toFinal(true), CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); // Add constructor that accepts a RawSupport and a CustomStorableCodec. { TypeDesc[] params = {rawSupportType, customStorableCodecType}; MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params); CodeBuilder b = new CodeBuilder(mi); // Call super class constructor. b.loadThis(); b.loadLocal(b.getParameter(0)); params = new TypeDesc[] {rawSupportType}; b.invokeSuperConstructor(params); // Set private reference to customStorableCodec. b.loadThis(); b.loadLocal(b.getParameter(1)); b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); b.returnVoid(); } // Add constructor that accepts a RawSupport, an encoded key, an // encoded data, and a CustomStorableCodec. { TypeDesc[] params = {rawSupportType, byteArrayType, byteArrayType, customStorableCodecType}; MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params); CodeBuilder b = new CodeBuilder(mi); // Set private reference to customStorableCodec before calling // super constructor. This is necessary because super class // constructor will call our decode methods, which need the // customStorableCodec. This trick is not allowed in Java, but the // virtual machine verifier allows it. b.loadThis(); b.loadLocal(b.getParameter(3)); b.storeField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); // Now call super class constructor. b.loadThis(); b.loadLocal(b.getParameter(0)); b.loadLocal(b.getParameter(1)); b.loadLocal(b.getParameter(2)); params = new TypeDesc[] {rawSupportType, byteArrayType, byteArrayType}; b.invokeSuperConstructor(params); b.returnVoid(); } // Implement protected abstract methods inherited from parent class. // byte[] encodeKey() { // Encode the primary key into a byte array that supports correct // ordering. No special key comparator is needed. MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, RawStorableGenerator.ENCODE_KEY_METHOD_NAME, byteArrayType, null); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); TypeDesc[] params = {TypeDesc.forClass(Storable.class)}; b.loadThis(); b.invokeVirtual(customStorableCodecType, "encodePrimaryKey", byteArrayType, params); b.returnValue(byteArrayType); } // byte[] encodeData() { // Encoding non-primary key data properties. MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, RawStorableGenerator.ENCODE_DATA_METHOD_NAME, byteArrayType, null); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); TypeDesc[] params = {TypeDesc.forClass(Storable.class)}; b.loadThis(); b.invokeVirtual(customStorableCodecType, "encodeData", byteArrayType, params); b.returnValue(byteArrayType); } // void decodeKey(byte[]) { MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, RawStorableGenerator.DECODE_KEY_METHOD_NAME, null, byteArrayParam); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType}; b.loadThis(); b.loadLocal(b.getParameter(0)); b.invokeVirtual(customStorableCodecType, "decodePrimaryKey", null, params); b.returnVoid(); } // void decodeData(byte[]) { MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, RawStorableGenerator.DECODE_DATA_METHOD_NAME, null, byteArrayParam); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.loadField(CUSTOM_STORABLE_CODEC_FIELD_NAME, customStorableCodecType); TypeDesc[] params = {TypeDesc.forClass(Storable.class), byteArrayType}; b.loadThis(); b.loadLocal(b.getParameter(0)); b.invokeVirtual(customStorableCodecType, "decodeData", null, params); b.returnVoid(); } return ci.defineClass(cf); } private final Class<S> mType; private final int mPkPropertyCount; private final InstanceFactory mInstanceFactory; // Modified by CustomStorableCodecFactory after construction. This provides // backwards compatibility with implementations of CustomStorableCodecFactory. RawSupport<S> mSupport; public interface InstanceFactory { Storable instantiate(RawSupport support, CustomStorableCodec codec); Storable instantiate(RawSupport support, byte[] key, byte[] value, CustomStorableCodec codec) throws FetchException; } /** * @param isMaster when true, version properties and sequences are managed * @throws SupportException if Storable is not supported */ public CustomStorableCodec(Class<S> type, boolean isMaster) throws SupportException { this(type, isMaster, null); } /** * @param isMaster when true, version properties and sequences are managed * @throws SupportException if Storable is not supported * @since 1.2 */ public CustomStorableCodec(Class<S> type, boolean isMaster, RawSupport<S> support) throws SupportException { mType = type; mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount(); Class<? extends S> storableClass = getStorableClass(type, isMaster); mInstanceFactory = QuickConstructorGenerator .getInstance(storableClass, InstanceFactory.class); mSupport = support; } public Class<S> getStorableType() { return mType; } /** * @since 1.2 */ @SuppressWarnings("unchecked") public S instantiate() { return (S) mInstanceFactory.instantiate(support(), this); } /** * @since 1.2 */ @SuppressWarnings("unchecked") public S instantiate(byte[] key, byte[] value) throws FetchException { return (S) mInstanceFactory.instantiate(support(), key, value, this); } @SuppressWarnings("unchecked") public S instantiate(RawSupport<S> support) { return (S) mInstanceFactory.instantiate(support, this); } @SuppressWarnings("unchecked") public S instantiate(RawSupport<S> support, byte[] key, byte[] value) throws FetchException { return (S) mInstanceFactory.instantiate(support, key, value, this); } public byte[] encodePrimaryKey(S storable) { return encodePrimaryKey(storable, 0, mPkPropertyCount); } public byte[] encodePrimaryKey(Object[] values) { return encodePrimaryKey(values, 0, mPkPropertyCount); } /** * @since 1.2 */ public RawSupport<S> getSupport() { return mSupport; } private RawSupport<S> support() { RawSupport<S> support = mSupport; if (support == null) { throw new IllegalStateException("No RawSupport"); } return support; } /** * Convenient access to all the storable properties. */ public Map<String, ? extends StorableProperty<S>> getAllProperties() { return StorableIntrospector.examine(getStorableType()).getAllProperties(); } /** * Convenient way to define the clustered primary key index * descriptor. Direction can be specified by prefixing the property name * with a '+' or '-'. If unspecified, direction is assumed to be ascending. */ @SuppressWarnings("unchecked") public StorableIndex<S> buildPkIndex(String... propertyNames) { Map<String, ? extends StorableProperty<S>> map = getAllProperties(); int length = propertyNames.length; StorableProperty<S>[] properties = new StorableProperty[length]; Direction[] directions = new Direction[length]; for (int i=0; i<length; i++) { String name = propertyNames[i]; char c = name.charAt(0); Direction dir = Direction.fromCharacter(c); if (dir != Direction.UNSPECIFIED || c == Direction.UNSPECIFIED.toCharacter()) { name = name.substring(1); } else { // Default to ascending if not specified. dir = Direction.ASCENDING; } if ((properties[i] = map.get(name)) == null) { throw new IllegalArgumentException("Unknown property: " + name); } directions[i] = dir; } return new StorableIndex<S>(properties, directions, true, true); } /** * Decode the primary key into properties of the storable. */ public abstract void decodePrimaryKey(S storable, byte[] bytes) throws CorruptEncodingException; /** * Encode all properties of the storable excluding the primary key. */ public abstract byte[] encodeData(S storable); /** * Decode the data into properties of the storable. */ public abstract void decodeData(S storable, byte[] bytes) throws CorruptEncodingException; }