/* * 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 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 {
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[] 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.
*
* [] 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[] 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[] 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[] ordered) {
Direction[] directions = new Direction[ordered.length];
for (int i=0; i[] 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[] 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