/*
* 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.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.ThrowUnchecked;
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.SupportException;
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.layout.Layout;
import com.amazon.carbonado.layout.LayoutOptions;
import com.amazon.carbonado.gen.CodeBuilderUtil;
import com.amazon.carbonado.gen.StorableGenerator;
import com.amazon.carbonado.gen.TriggerSupport;
import com.amazon.carbonado.util.QuickConstructorGenerator;
import com.amazon.carbonado.util.SoftValuedCache;
/**
* Generic codec that supports any kind of storable by auto-generating and
* caching storable implementations.
*
* @author Brian S O'Neill
* @see GenericStorableCodecFactory
*/
public class GenericStorableCodec<S extends Storable> implements StorableCodec<S> {
private static final String BLANK_KEY_FIELD_NAME = "blankKey$";
// Maps GenericEncodingStrategy instances to Storable classes.
private static final SoftValuedCache cCache = SoftValuedCache.newCache(11);
/**
* 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.
* @param support binds generated storable with a storage layer
* @throws SupportException if Storable is not supported
* @throws amazon.carbonado.MalformedTypeException if Storable type is not well-formed
* @throws IllegalArgumentException if type is null
*/
@SuppressWarnings("unchecked")
static synchronized <S extends Storable> GenericStorableCodec<S> getInstance
(GenericStorableCodecFactory factory,
GenericEncodingStrategy<S> encodingStrategy, boolean isMaster,
Layout layout, RawSupport support)
throws SupportException
{
Object layoutKey = layout == null ? null : new LayoutKey(layout);
Object key = KeyFactory.createKey(new Object[] {encodingStrategy, isMaster, layoutKey});
Class<? extends S> storableImpl = (Class<? extends S>) cCache.get(key);
if (storableImpl == null) {
storableImpl = generateStorable(encodingStrategy, isMaster, layout);
cCache.put(key, storableImpl);
}
return new GenericStorableCodec<S>
(key,
factory,
encodingStrategy.getType(),
storableImpl,
encodingStrategy,
layout,
support);
}
@SuppressWarnings("unchecked")
private static <S extends Storable> Class<? extends S> generateStorable
(GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
throws SupportException
{
final Class<S> storableClass = encodingStrategy.getType();
final Class<? extends S> abstractClass =
RawStorableGenerator.getAbstractClass(storableClass, isMaster);
final int generation = layout == null ? -1 : layout.getGeneration();
ClassInjector ci = ClassInjector.create
(storableClass.getName(), abstractClass.getClassLoader());
ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
cf.markSynthetic();
cf.setSourceFile(GenericStorableCodec.class.getName());
cf.setTarget("1.5");
// Declare some types.
final TypeDesc storableType = TypeDesc.forClass(Storable.class);
final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class);
final TypeDesc rawSupportType = TypeDesc.forClass(RawSupport.class);
final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
final TypeDesc[] byteArrayParam = {byteArrayType};
// Add constructors.
// 1: Accepts a RawSupport.
// 2: Accepts a RawSupport and an encoded key.
// 3: Accepts a RawSupport, an encoded key and an encoded data.
for (int i=1; i<=3; i++) {
TypeDesc[] params = new TypeDesc[i];
params[0] = rawSupportType;
if (i >= 2) {
params[1] = byteArrayType;
if (i == 3) {
params[2] = byteArrayType;
}
}
MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadLocal(b.getParameter(0));
if (i >= 2) {
b.loadLocal(b.getParameter(1));
if (i == 3) {
b.loadLocal(b.getParameter(2));
}
}
b.invokeSuperConstructor(params);
b.returnVoid();
}
CodeBuilderUtil.definePrepareMethod(cf, storableClass, rawSupportType);
// 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 tryStartDecode = b.createLabel().setLocation();
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.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(rawSupportType);
b.loadThis();
b.loadLocal(actualGeneration);
b.loadLocal(b.getParameter(0));
b.invokeInterface(rawSupportType, "decode", null,
new TypeDesc[] {storableType, TypeDesc.INT, byteArrayType});
b.returnVoid();
Label tryEndDecode = b.createLabel().setLocation();
// If unable to decode, fill out exception.
b.exceptionHandler(tryStartDecode, tryEndDecode,
CorruptEncodingException.class.getName());
TypeDesc exType = TypeDesc.forClass(CorruptEncodingException.class);
LocalVariable exVar = b.createLocalVariable(null, TypeDesc.OBJECT);
b.storeLocal(exVar);
b.loadLocal(exVar);
b.loadThis();
b.invokeVirtual(exType, "setStorableWithPrimaryKey", null,
new TypeDesc[] {storableType});
b.loadLocal(exVar);
b.throwObject();
}
return ci.defineClass(cf);
}
// Maps codec key and OrderedProperty[] keys to SearchKeyFactory instances.
private static final SoftValuedCache cCodecSearchKeyFactories = SoftValuedCache.newCache(11);
// Maps codec key and layout generations to Decoders.
private static final SoftValuedCache cCodecDecoders = SoftValuedCache.newCache(11);
private final Object mCodecKey;
private final GenericStorableCodecFactory mFactory;
private final Class<S> mType;
private final Class<? extends S> mStorableClass;
private final GenericEncodingStrategy<S> mEncodingStrategy;
private final GenericInstanceFactory mInstanceFactory;
private final SearchKeyFactory<S> mPrimaryKeyFactory;
private final Layout mLayout;
private final RawSupport<S> mSupport;
// Maps layout generations to Decoders.
private IntHashMap mDecoders;
/**
* @param codecKey cache key for this GenericStorableCodec instance
*/
private GenericStorableCodec(Object codecKey,
GenericStorableCodecFactory factory,
Class<S> type, Class<? extends S> storableClass,
GenericEncodingStrategy<S> encodingStrategy,
Layout layout, RawSupport<S> support)
{
mCodecKey = codecKey;
mFactory = factory;
mType = type;
mStorableClass = storableClass;
mEncodingStrategy = encodingStrategy;
mInstanceFactory = QuickConstructorGenerator
.getInstance(storableClass, GenericInstanceFactory.class);
mPrimaryKeyFactory = getSearchKeyFactory(encodingStrategy.gatherAllKeyProperties());
mLayout = layout;
mSupport = support;
}
/**
* Returns the type of Storable that code is generated for.
*/
public final Class<S> getStorableType() {
return mType;
}
/**
* Instantiate a Storable with no key or value defined yet. The default
* {@link RawSupport} is supplied to the instance.
*
* @throws IllegalStateException if no default support exists
* @since 1.2
*/
@SuppressWarnings("unchecked")
public S instantiate() {
return (S) mInstanceFactory.instantiate(support());
}
/**
* Instantiate a Storable with no value defined yet. The default {@link
* RawSupport} is supplied to the instance.
*
* @throws IllegalStateException if no default support exists
* @since 1.2
*/
@SuppressWarnings("unchecked")
public S instantiate(byte[] key) throws FetchException {
return (S) mInstanceFactory.instantiate(support(), key);
}
/**
* Instantiate a Storable with a specific key and value. The default
* {@link RawSupport} is supplied to the instance.
*
* @throws IllegalStateException if no default support exists
* @since 1.2
*/
@SuppressWarnings("unchecked")
public S instantiate(byte[] key, byte[] value) throws FetchException {
return (S) mInstanceFactory.instantiate(support(), key, value);
}
/**
* Instantiate a Storable with no key or value defined yet. Any
* {@link RawSupport} can be supplied to the instance.
*
* @param support binds generated storable with a storage layer
*/
@SuppressWarnings("unchecked")
public S instantiate(RawSupport<S> support) {
return (S) mInstanceFactory.instantiate(support);
}
/**
* Instantiate a Storable with a specific key and value. Any
* {@link RawSupport} can be supplied to the instance.
*
* @param support binds generated storable with a storage layer
*/
@SuppressWarnings("unchecked")
public S instantiate(RawSupport<S> support, byte[] key, byte[] value) throws FetchException {
try {
return (S) mInstanceFactory.instantiate(support, key, value);
} catch (CorruptEncodingException e) {
// Try to instantiate just the key and pass what we can to the exception.
try {
e.setStorableWithPrimaryKey(mInstanceFactory.instantiate(support, key));
} catch (FetchException e2) {
// Oh well, can't even decode the key.
}
throw e;
}
}
public StorableIndex<S> getPrimaryKeyIndex() {
return mEncodingStrategy.getPrimaryKeyIndex();
}
public int getPrimaryKeyPrefixLength() {
return mEncodingStrategy.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();
}
/**
* @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;
}
/**
* Returns a concrete Storable implementation, which is fully
* thread-safe. It has two constructors defined:
*
* <pre>
* public <init>(Storage, RawSupport);
*
* public <init>(Storage, RawSupport, byte[] key, byte[] value);
* </pre>
*
* Convenience methods are provided in this class to instantiate the
* generated Storable.
*/
public Class<? extends S> getStorableClass() {
return mStorableClass;
}
/**
* Returns a search key factory, which is useful for implementing indexes
* and queries.
*
* @param properties properties to build the search key from
*/
@SuppressWarnings("unchecked")
public SearchKeyFactory<S> getSearchKeyFactory(OrderedProperty<S>[] properties) {
// This KeyFactory makes arrays work as hashtable keys.
Object key = KeyFactory.createKey(new Object[] {mCodecKey, properties});
synchronized (cCodecSearchKeyFactories) {
SearchKeyFactory<S> factory = (SearchKeyFactory<S>) cCodecSearchKeyFactories.get(key);
if (factory == null) {
factory = generateSearchKeyFactory(properties);
cCodecSearchKeyFactories.put(key, factory);
}
return factory;
}
}
@Override
public void decode(S dest, int generation, byte[] data) throws CorruptEncodingException {
try {
getDecoder(generation).decode(dest, data);
} catch (CorruptEncodingException e) {
throw e;
} catch (RepositoryException e) {
throw new CorruptEncodingException(e);
}
}
/**
* Returns a data decoder for the given generation.
*
* @throws FetchNoneException if generation is unknown
* @deprecated use direct decode method
*/
@Deprecated
public Decoder<S> getDecoder(int generation) throws FetchNoneException, FetchException {
try {
synchronized (mLayout) {
IntHashMap decoders = mDecoders;
if (decoders == null) {
mDecoders = decoders = new IntHashMap();
}
Decoder<S> decoder = (Decoder<S>) decoders.get(generation);
if (decoder == null) {
synchronized (cCodecDecoders) {
Object altLayoutKey = new LayoutKey(mLayout.getGeneration(generation));
Object key = KeyFactory.createKey
// Note: Generation is still required in the key
// because an equivalent layout (with different generation)
// might have been supplied by Layout.getGeneration.
(new Object[] {mCodecKey, generation, altLayoutKey});
decoder = (Decoder<S>) cCodecDecoders.get(key);
if (decoder == null) {
decoder = generateDecoder(generation);
cCodecDecoders.put(key, decoder);
}
}
mDecoders.put(generation, decoder);
}
return decoder;
}
} catch (NullPointerException e) {
if (mLayout == null) {
throw new FetchNoneException("Layout evolution not supported");
}
throw e;
}
}
@SuppressWarnings("unchecked")
private SearchKeyFactory<S> generateSearchKeyFactory(OrderedProperty<S>[] properties) {
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 = mEncodingStrategy.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 = mEncodingStrategy.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 = mEncodingStrategy.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 = mEncodingStrategy.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 (mEncodingStrategy.getKeyPrefixPadding() == 0 &&
mEncodingStrategy.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 = mEncodingStrategy.buildKeyEncoding
(b, new OrderedProperty[0], null, null, false, null, null);
b.loadLocal(encodedVar);
b.storeStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
b.returnVoid();
}
} catch (SupportException e) {
// Shouldn't happen since all properties were checked in order
// to create this StorableCodec.
throw new UndeclaredThrowableException(e);
}
Class<? extends SearchKeyFactory> clazz = ci.defineClass(cf);
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new UndeclaredThrowableException(e);
} catch (IllegalAccessException e) {
throw new UndeclaredThrowableException(e);
}
}
private Decoder<S> generateDecoder(int generation) throws FetchException {
// Create an encoding strategy against the reconstructed storable.
Class<? extends Storable> altStorable;
GenericEncodingStrategy<? extends Storable> altStrategy;
try {
Layout altLayout = mLayout.getGeneration(generation);
altStorable = altLayout.reconstruct(mStorableClass.getClassLoader());
LayoutOptions options = altLayout.getOptions();
if (options == null) {
// Explictly specify no options, to prevent the factory from
// trying to infer what null options means.
options = new LayoutOptions();
}
altStrategy = mFactory.createStrategy(altStorable, null, options);
} 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);
}
// Clear all properties available in the current generation which
// aren't in the alt generation.
Map<String, ? extends StorableProperty> currentProps =
StorableIntrospector.examine(mType).getAllProperties();
Map<String, ? extends StorableProperty> altProps =
StorableIntrospector.examine(altStorable).getAllProperties();
for (StorableProperty prop : currentProps.values()) {
if (prop.isDerived() || prop.isJoin()) {
continue;
}
if (altProps.keySet().contains(prop.getName())) {
continue;
}
b.loadLocal(destVar);
TypeDesc propType = TypeDesc.forClass(prop.getType());
switch (propType.getTypeCode()) {
case TypeDesc.OBJECT_CODE:
b.loadNull();
break;
case TypeDesc.LONG_CODE:
b.loadConstant(0L);
break;
case TypeDesc.FLOAT_CODE:
b.loadConstant(0.0f);
break;
case TypeDesc.DOUBLE_CODE:
b.loadConstant(0.0d);
break;
default:
b.loadConstant(0);
break;
}
b.storeField(destVar.getType(), prop.getName(), propType);
}
b.returnVoid();
Class<? extends Decoder> clazz = ci.defineClass(cf);
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new UndeclaredThrowableException(e);
} catch (IllegalAccessException e) {
throw new UndeclaredThrowableException(e);
}
}
/**
* Creates custom raw search keys for {@link Storable} types. It is
* intended for supporting queries and indexes.
*/
public interface SearchKeyFactory<S extends Storable> {
/**
* Build a search key by extracting all the desired properties from the
* given storable.
*
* @param storable extract a subset of properties from this instance
* @return raw search key
*/
byte[] encodeSearchKey(S storable);
/**
* Build a search key by extracting all the desired properties from the
* given storable.
*
* @param storable extract a subset of properties from this instance
* @param rangeStart index of first property to use. Its value must be less
* than the count of properties used by this factory.
* @param rangeEnd index of last property to use, exlusive. Its value must
* be less than or equal to the count of properties used by this factory.
* @return raw search key
*/
byte[] encodeSearchKey(S storable, int rangeStart, int rangeEnd);
/**
* Build a search key by supplying property values without a storable.
*
* @param values values to build into a key. It must be long enough to
* accommodate all of properties used by this factory.
* @return raw search key
*/
byte[] encodeSearchKey(Object[] values);
/**
* Build a search key by supplying property values without a storable.
*
* @param values values to build into a key. The length may be less than
* the amount of properties used by this factory. It must not be less than the
* difference between rangeStart and rangeEnd.
* @param rangeStart index of first property to use. Its value must be less
* than the count of properties used by this factory.
* @param rangeEnd index of last property to use, exlusive. Its value must
* be less than or equal to the count of properties used by this factory.
* @return raw search key
*/
byte[] encodeSearchKey(Object[] values, int rangeStart, int rangeEnd);
/**
* Returns the search key for when there are no values. Returned value
* may be null.
*/
byte[] encodeSearchKeyPrefix();
}
/**
* Used for decoding different generations of Storable.
*/
public interface Decoder<S extends Storable> {
/**
* @param dest storable to receive decoded properties
* @param data decoded into properties, some of which may be dropped if
* destination storable doesn't have it
*/
void decode(S dest, byte[] data) throws CorruptEncodingException;
}
/**
* Compares layouts for equivalence with respect to class creation and
* sharing.
*/
private static class LayoutKey {
private final WeakReference<Layout> mLayoutRef;
private final int mHashCode;
LayoutKey(Layout layout) {
mLayoutRef = new WeakReference<Layout>(layout);
mHashCode = layout.getStorableTypeName().hashCode() * 7 + layout.getGeneration();
}
@Override
public int hashCode() {
return mHashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof LayoutKey) {
Layout layout = mLayoutRef.get();
if (layout == null) {
return false;
}
LayoutKey other = (LayoutKey) obj;
Layout otherLayout = other.mLayoutRef.get();
if (otherLayout == null) {
return false;
}
try {
return layout.getStorableTypeName()
.equals(otherLayout.getStorableTypeName()) &&
layout.getGeneration() == otherLayout.getGeneration() &&
layout.equalLayouts(otherLayout);
} catch (FetchException e) {
return false;
}
}
return false;
}
}
}