/* * 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.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Vector; 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.BeanComparator; 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 static com.amazon.carbonado.gen.StorableGenerator.*; import com.amazon.carbonado.gen.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.StorableInfo; 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 static enum Mode { KEY, DATA, SERIAL }
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(Mode.KEY, 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(Mode.KEY, 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(Mode.DATA, 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(Mode.DATA, assembler, properties, null,
instanceVar, adapterInstanceClass, useWriteMethods,
generation, altGenerationHandler, encodedVar);
}
/**
* Generates bytecode instructions to encode properties and their
* states. This encoding is suitable for short-term serialization only.
*
* @param assembler code assembler to receive bytecode instructions
* @param properties specific properties to decode, defaults to all
* properties if null
* @return local variable referencing a byte array with encoded data
* @throws SupportException if any property type is not supported
* @since 1.2
*/
public LocalVariable buildSerialEncoding(CodeAssembler assembler,
StorableProperty[] properties)
throws SupportException
{
properties = ensureAllProperties(properties);
return buildEncoding
(Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, null);
}
/**
* Generates bytecode instructions to decode properties and their states. 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
* properties if null
* @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 encodedVar is not a byte array
* @since 1.2
*/
public void buildSerialDecoding(CodeAssembler assembler,
StorableProperty[] properties,
LocalVariable encodedVar)
throws SupportException
{
properties = ensureAllProperties(properties);
buildDecoding
(Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, 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;
}
if (propertyType == TypeDesc.STRING || propertyType == TypeDesc.forClass(byte[].class)) {
return true;
}
Class clazz = propertyType.toClass();
if (clazz != null) {
return Lob.class.isAssignableFrom(clazz) ||
BigInteger.class.isAssignableFrom(clazz) ||
BigDecimal.class.isAssignableFrom(clazz);
}
return false;
}
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 non-derived data properties for storable.
*/
@SuppressWarnings("unchecked")
protected StorableProperty[] gatherAllDataProperties() {
Map property : map.values()) {
if (!property.isDerived()) {
list.add(property);
}
}
return list.toArray(new StorableProperty[list.size()]);
}
/**
* Returns all non-join, non-derived properties for storable.
*/
@SuppressWarnings("unchecked")
protected StorableProperty[] gatherAllProperties() {
Map property : map.values()) {
if (!property.isJoin() && !property.isDerived()) {
list.add(property);
}
}
return list.toArray(new StorableProperty[list.size()]);
}
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 StorableProperty[] ensureAllProperties(StorableProperty[] properties) {
if (properties == null) {
properties = gatherAllProperties();
} else {
for (Object prop : properties) {
if (prop == null) {
throw new IllegalArgumentException();
}
}
// Sort to generate more efficient code.
properties = properties.clone();
Arrays.sort(properties,
BeanComparator.forClass(StorableProperty.class).orderBy("number"));
}
return properties;
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
private LocalVariable buildEncoding(Mode mode,
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
// 3. Property states (if Mode.SERIAL)
// 4. Properties
// 5. Suffix
final int prefix;
switch (mode) {
default:
prefix = 0;
break;
case KEY:
prefix = mKeyPrefixPadding;
break;
case DATA:
prefix = mDataPrefixPadding;
break;
}
final int generationPrefix;
if (generation < 0) {
generationPrefix = 0;
} else if (generation < 128) {
generationPrefix = 1;
} else {
generationPrefix = 4;
}
final int suffix;
switch (mode) {
default:
suffix = 0;
break;
case KEY:
suffix = mKeySuffixPadding;
break;
case DATA:
suffix = mDataSuffixPadding;
break;
}
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 (mode != Mode.SERIAL && 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 = mode == Mode.KEY
&& 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);
if (mode == Mode.DATA) {
extraDataEncoding(a, encodedVar, prefix + generationPrefix, suffix);
}
return encodedVar;
}
}
boolean doPartial =
mode == Mode.SERIAL ||
(mode == Mode.KEY && partialStartVar != null) ||
(mode == Mode.KEY && (properties.length == 0 || 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 (mode != Mode.KEY || partialStartVar == null) {
// Only include prefix as static if no runtime check is needed
// against runtime partial start value.
staticLength += prefix + generationPrefix;
}
if (mode != Mode.KEY || (properties.length != 0 && partialEndVar == null)) {
// Only include suffix as static if no runtime check is needed
// against runtime partial end value.
staticLength += suffix;
}
if (mode == Mode.SERIAL) {
// Need room to encode property states. Two bits per property, so
// one byte can encode the state of four properties.
staticLength += (properties.length + 3) / 4;
}
// Stash of properties which are loaded and locally stored before
// entering the first loop. This avoids having to load them twice.
LocalVariable[] stashedProperties = null;
Boolean[] stashedFromInstances = null;
boolean hasVariableLength;
{
hasVariableLength = doPartial;
for (int i=0; i[] properties)
{
LocalVariable stateFieldVar = a.createLocalVariable(null, TypeDesc.INT);
int lastFieldOrdinal = -1;
LocalVariable accumVar = a.createLocalVariable(null, TypeDesc.INT);
int accumShift = 0;
for (int i=0; i nextProperty = properties[i + 1];
if (property.getNumber() + 1 != nextProperty.getNumber()) {
// Properties are not consecutive.
break;
}
if (fieldOrdinal != (nextProperty.getNumber() >> 4)) {
// Property states are stored in different fields.
break;
}
accumPack += 2;
mask |= PROPERTY_STATE_MASK << ((nextProperty.getNumber() & 0xf) * 2);
property = nextProperty;
i++;
}
a.loadConstant(mask);
a.math(Opcode.IAND);
if (stateShift < accumShift) {
a.loadConstant(accumShift - stateShift);
a.math(Opcode.ISHL);
} else if (stateShift > accumShift) {
a.loadConstant(stateShift - accumShift);
a.math(Opcode.IUSHR);
}
if (accumShift != 0) {
a.loadLocal(accumVar);
a.math(Opcode.IOR);
}
a.storeLocal(accumVar);
if ((accumShift += accumPack) >= 8) {
// Accumulator is full, so copy it to byte array.
a.loadLocal(encodedVar);
a.loadConstant(offset++);
a.loadLocal(accumVar);
a.storeToArray(TypeDesc.BYTE);
accumShift = 0;
}
}
if (accumShift > 0) {
// Copy remaining states.
a.loadLocal(encodedVar);
a.loadConstant(offset++);
a.loadLocal(accumVar);
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.
*
* @param instanceVar Storable instance or array of property values. Null
* is storable instance of "this".
*/
protected 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(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, Storable instance, property name 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.forClass(Storable.class),
TypeDesc.STRING, TypeDesc.LONG});
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
private void buildDecoding(Mode mode,
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. Property states (if Mode.SERIAL)
// 4. Properties
// 5. Suffix
final int prefix;
switch (mode) {
default:
prefix = 0;
break;
case KEY:
prefix = mKeyPrefixPadding;
break;
case DATA:
prefix = mDataPrefixPadding;
break;
}
decodeGeneration(a, encodedVar, prefix, generation, altGenerationHandler);
final int generationPrefix;
if (generation < 0) {
generationPrefix = 0;
} else if (generation < 128) {
generationPrefix = 1;
} else {
generationPrefix = 4;
}
final int suffix;
switch (mode) {
default:
suffix = 0;
break;
case KEY:
suffix = mKeySuffixPadding;
break;
case DATA:
suffix = mDataSuffixPadding;
extraDataDecoding(a, encodedVar, prefix + generationPrefix, suffix);
break;
}
final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
StorablePropertyInfo[] infos = checkSupport(properties);
if (properties.length == 1) {
StorableProperty property = properties[0];
StorablePropertyInfo info = infos[0];
if (mode != Mode.SERIAL && 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 = mode == Mode.KEY
&& 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 {
// Always clone the byte array as some implementations
// reuse the byte array (e.g. iterating using a cursor).
a.invokeVirtual(TypeDesc.OBJECT, "clone", TypeDesc.OBJECT, null);
a.checkCast(byteArrayType);
}
storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
return;
}
}
// Now decode properties from the byte array.
int constantOffset = prefix + generationPrefix;
LocalVariable offsetVar = null;
// References to local variables which will hold references.
LocalVariable[] stringRefRef = new LocalVariable[1];
LocalVariable[] byteArrayRefRef = new LocalVariable[1];
LocalVariable[] bigIntegerRefRef = new LocalVariable[1];
LocalVariable[] bigDecimalRefRef = new LocalVariable[1];
LocalVariable[] valueRefRef = new LocalVariable[1];
// Used by SERIAL mode.
List[] properties)
{
Vector nextProperty = properties[i + 1];
if (property.getNumber() + 1 != nextProperty.getNumber()) {
// Properties are not consecutive.
break;
}
if (stateVarOrdinal != (nextProperty.getNumber() >> 4)) {
// Property states are stored in different fields.
break;
}
accumPack += 2;
mask |= PROPERTY_STATE_MASK << ((nextProperty.getNumber() & 0xf) * 2);
property = nextProperty;
i++;
}
a.loadLocal(accumVar);
if (stateShift < accumShift) {
a.loadConstant(accumShift - stateShift);
a.math(Opcode.IUSHR);
} else if (stateShift > accumShift) {
a.loadConstant(stateShift - accumShift);
a.math(Opcode.ISHL);
}
a.loadConstant(mask);
a.math(Opcode.IAND);
a.loadLocal(stateVars.get(stateVarOrdinal));
a.loadConstant(~mask);
a.math(Opcode.IAND);
a.math(Opcode.IOR);
a.storeLocal(stateVars.get(stateVarOrdinal));
accumShift += accumPack;
}
return stateVars;
}
}