From 79cebfadf8703afe9bf28786bc4df1af348c876e Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Thu, 5 Oct 2006 03:54:20 +0000 Subject: Moved raw package out of spi package. --- .../carbonado/raw/GenericEncodingStrategy.java | 1963 ++++++++++++++++++++ 1 file changed, 1963 insertions(+) create mode 100644 src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java (limited to 'src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java') diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java new file mode 100644 index 0000000..2cd0807 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java @@ -0,0 +1,1963 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.raw; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; + +import org.cojen.classfile.CodeAssembler; +import org.cojen.classfile.Label; +import org.cojen.classfile.LocalVariable; +import org.cojen.classfile.Opcode; +import org.cojen.classfile.TypeDesc; +import org.cojen.util.BeanIntrospector; +import org.cojen.util.BeanProperty; + +import com.amazon.carbonado.CorruptEncodingException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; + +import com.amazon.carbonado.lob.Blob; +import com.amazon.carbonado.lob.Clob; +import com.amazon.carbonado.lob.Lob; + +import com.amazon.carbonado.spi.StorableGenerator; +import com.amazon.carbonado.spi.TriggerSupport; + +import com.amazon.carbonado.info.ChainedProperty; +import com.amazon.carbonado.info.Direction; +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableIndex; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; +import com.amazon.carbonado.info.StorablePropertyAdapter; + +/** + * Generates bytecode instructions for encoding/decoding Storable properties + * to/from raw bytes. + * + *

Note: subclasses must override and specialize the hashCode and equals + * methods. Failure to do so interferes with {@link StorableCodecFactory}'s + * generated code cache. + * + * @author Brian S O'Neill + */ +public class GenericEncodingStrategy { + private final Class mType; + private final StorableIndex mPkIndex; + + private final int mKeyPrefixPadding; + private final int mKeySuffixPadding; + private final int mDataPrefixPadding; + private final int mDataSuffixPadding; + + /** + * @param type type of Storable to generate code for + * @param pkIndex specifies sequence and ordering of key properties (optional) + */ + public GenericEncodingStrategy(Class type, StorableIndex pkIndex) { + this(type, pkIndex, 0, 0, 0, 0); + } + + /** + * @param type type of Storable to generate code for + * @param pkIndex specifies sequence and ordering of key properties (optional) + * @param keyPrefixPadding amount of padding bytes at start of keys + * @param keySuffixPadding amount of padding bytes at end of keys + * @param dataPrefixPadding amount of padding bytes at start of data values + * @param dataSuffixPadding amount of padding bytes at end of data values + */ + @SuppressWarnings("unchecked") + public GenericEncodingStrategy(Class type, StorableIndex pkIndex, + int keyPrefixPadding, int keySuffixPadding, + int dataPrefixPadding, int dataSuffixPadding) { + mType = type; + + if (keyPrefixPadding < 0 || keySuffixPadding < 0 || + dataPrefixPadding < 0 || dataSuffixPadding < 0) { + throw new IllegalArgumentException(); + } + mKeyPrefixPadding = keyPrefixPadding; + mKeySuffixPadding = keySuffixPadding; + mDataPrefixPadding = dataPrefixPadding; + mDataSuffixPadding = dataSuffixPadding; + + if (pkIndex == null) { + Map> map = + StorableIntrospector.examine(mType).getPrimaryKeyProperties(); + + StorableProperty[] properties = new StorableProperty[map.size()]; + map.values().toArray(properties); + + Direction[] directions = new Direction[map.size()]; + Arrays.fill(directions, Direction.UNSPECIFIED); + + pkIndex = new StorableIndex(properties, directions, true); + } + + mPkIndex = pkIndex; + } + + /** + * Generates bytecode instructions to encode properties. The encoding is + * suitable for "key" encoding, which means it is correctly comparable. + * + *

Note: if a partialStartVar is provided and this strategy has a key + * prefix, the prefix is allocated only if the runtime value of + * partialStartVar is zero. Likewise, if a partialEndVar is provided and + * this strategy has a key suffix, the suffix is allocated only of the + * runtime value of partialEndVar is one less than the property count. + * + * @param assembler code assembler to receive bytecode instructions + * @param properties specific properties to encode, defaults to all key + * properties if null + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are read from the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @param useReadMethods when true, access properties by public read + * methods instead of protected fields - should be used if class being + * generated doesn't have access to these fields + * @param partialStartVar optional variable for supporting partial key + * generation. It must be an int, whose runtime value must be less than the + * properties array length. It marks the range start of the partial + * property range. + * @param partialEndVar optional variable for supporting partial key + * generation. It must be an int, whose runtime value must be less than or + * equal to the properties array length. It marks the range end (exclusive) + * of the partial property range. + * + * @return local variable referencing a byte array with encoded key + * + * @throws SupportException if any property type is not supported + * @throws IllegalArgumentException if assembler is null, or if instanceVar + * is not the correct instance type, or if partial variable types are not + * ints + */ + public LocalVariable buildKeyEncoding(CodeAssembler assembler, + OrderedProperty[] properties, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useReadMethods, + LocalVariable partialStartVar, + LocalVariable partialEndVar) + throws SupportException + { + properties = ensureKeyProperties(properties); + return buildEncoding(true, assembler, + extractProperties(properties), extractDirections(properties), + instanceVar, adapterInstanceClass, + useReadMethods, + -1, // no generation support + partialStartVar, partialEndVar); + } + + /** + * Generates bytecode instructions to decode properties. A + * CorruptEncodingException may be thrown from generated code. + * + * @param assembler code assembler to receive bytecode instructions + * @param properties specific properties to decode, defaults to all key + * properties if null + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are placed into the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @param useWriteMethods when true, set properties by public write + * methods instead of protected fields - should be used if class being + * generated doesn't have access to these fields + * @param encodedVar required variable, which must be a byte array. At + * runtime, it references an encoded key. + * + * @throws SupportException if any property type is not supported + * @throws IllegalArgumentException if assembler is null, or if instanceVar + * is not the correct instance type, or if encodedVar is not a byte array + */ + public void buildKeyDecoding(CodeAssembler assembler, + OrderedProperty[] properties, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useWriteMethods, + LocalVariable encodedVar) + throws SupportException + { + properties = ensureKeyProperties(properties); + buildDecoding(true, assembler, + extractProperties(properties), extractDirections(properties), + instanceVar, adapterInstanceClass, useWriteMethods, + -1, null, // no generation support + encodedVar); + } + + /** + * Generates bytecode instructions to encode properties. The encoding is + * suitable for "data" encoding, which means it is not correctly + * comparable, but it is more efficient than key encoding. Partial encoding + * is not supported. + * + * @param assembler code assembler to receive bytecode instructions + * @param properties specific properties to encode, defaults to all non-key + * properties if null + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are read from the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @param useReadMethods when true, access properties by public read + * methods instead of protected fields + * @param generation when non-negative, write a storable layout generation + * value in one or four bytes. Generation 0..127 is encoded in one byte, and + * 128..max is encoded in four bytes, with the most significant bit set. + * + * @return local variable referencing a byte array with encoded data + * + * @throws SupportException if any property type is not supported + * @throws IllegalArgumentException if assembler is null, or if instanceVar + * is not the correct instance type + */ + public LocalVariable buildDataEncoding(CodeAssembler assembler, + StorableProperty[] properties, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useReadMethods, + int generation) + throws SupportException + { + properties = ensureDataProperties(properties); + return buildEncoding(false, assembler, + properties, null, + instanceVar, adapterInstanceClass, + useReadMethods, generation, null, null); + } + + /** + * Generates bytecode instructions to decode properties. A + * CorruptEncodingException may be thrown from generated code. + * + * @param assembler code assembler to receive bytecode instructions + * @param properties specific properties to decode, defaults to all non-key + * properties if null + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are placed into the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @param useWriteMethods when true, set properties by public write + * methods instead of protected fields - should be used if class being + * generated doesn't have access to these fields + * @param generation when non-negative, decoder expects a storable layout + * generation value to match this value. Otherwise, it throws a + * CorruptEncodingException. + * @param altGenerationHandler if non-null and a generation is provided, + * this label defines an alternate generation handler. It is executed + * instead of throwing a CorruptEncodingException if the generation doesn't + * match. The actual generation is available on the top of the stack for + * the handler to consume. + * @param encodedVar required variable, which must be a byte array. At + * runtime, it references encoded data. + * + * @throws SupportException if any property type is not supported + * @throws IllegalArgumentException if assembler is null, or if instanceVar + * is not the correct instance type, or if encodedVar is not a byte array + */ + public void buildDataDecoding(CodeAssembler assembler, + StorableProperty[] properties, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useWriteMethods, + int generation, + Label altGenerationHandler, + LocalVariable encodedVar) + throws SupportException + { + properties = ensureDataProperties(properties); + buildDecoding(false, assembler, properties, null, + instanceVar, adapterInstanceClass, useWriteMethods, + generation, altGenerationHandler, encodedVar); + } + + /** + * Returns the type of Storable that code is generated for. + */ + public final Class getType() { + return mType; + } + + /** + * Returns true if the type of the given property type is supported. The + * types currently supported are primitives, primitive wrapper objects, + * Strings, and byte arrays. + */ + public boolean isSupported(Class propertyType) { + return isSupported(TypeDesc.forClass(propertyType)); + } + + /** + * Returns true if the type of the given property type is supported. The + * types currently supported are primitives, primitive wrapper objects, + * Strings, byte arrays and Lobs. + */ + public boolean isSupported(TypeDesc propertyType) { + if (propertyType.toPrimitiveType() != null) { + return true; + } + return propertyType == TypeDesc.STRING || + propertyType == TypeDesc.forClass(byte[].class) || + propertyType.toClass() != null && Lob.class.isAssignableFrom(propertyType.toClass()); + } + + public int getKeyPrefixPadding() { + return mKeyPrefixPadding; + } + + public int getKeySuffixPadding() { + return mKeySuffixPadding; + } + + public int getDataPrefixPadding() { + return mDataPrefixPadding; + } + + public int getDataSuffixPadding() { + return mDataSuffixPadding; + } + + /** + * Returns amount of prefix key bytes that encoding strategy instance + * produces which are always the same. Default implementation returns 0. + */ + public int getConstantKeyPrefixLength() { + return 0; + } + + @Override + public int hashCode() { + return mType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof GenericEncodingStrategy) { + GenericEncodingStrategy other = (GenericEncodingStrategy) obj; + return mType == other.mType + && mKeyPrefixPadding == other.mKeyPrefixPadding + && mKeySuffixPadding == other.mKeySuffixPadding + && mDataPrefixPadding == other.mDataPrefixPadding + && mDataSuffixPadding == other.mDataSuffixPadding; + } + return false; + } + + /** + * Returns all key properties in the form of an index. + */ + protected StorableIndex getPrimaryKeyIndex() { + return mPkIndex; + } + + /** + * Returns all key properties as ordered properties, possibly with + * unspecified directions. + */ + protected OrderedProperty[] gatherAllKeyProperties() { + return mPkIndex.getOrderedProperties(); + } + + /** + * Returns all data properties for storable. + */ + @SuppressWarnings("unchecked") + protected StorableProperty[] gatherAllDataProperties() { + Map> map = + StorableIntrospector.examine(mType).getDataProperties(); + + StorableProperty[] properties = new StorableProperty[map.size()]; + + int ordinal = 0; + for (StorableProperty property : map.values()) { + properties[ordinal++] = property; + } + + return properties; + } + + protected StorablePropertyInfo checkSupport(StorableProperty property) + throws SupportException + { + if (isSupported(property.getType())) { + return new StorablePropertyInfo(property); + } + + // Look for an adapter that will allow this property to be supported. + if (property.getAdapter() != null) { + StorablePropertyAdapter adapter = property.getAdapter(); + for (Class storageType : adapter.getStorageTypePreferences()) { + if (!isSupported(storageType)) { + continue; + } + + if (property.isNullable() && storageType.isPrimitive()) { + continue; + } + + Method fromStorage, toStorage; + fromStorage = adapter.findAdaptMethod(storageType, property.getType()); + if (fromStorage == null) { + continue; + } + toStorage = adapter.findAdaptMethod(property.getType(), storageType); + if (toStorage != null) { + return new StorablePropertyInfo(property, storageType, fromStorage, toStorage); + } + } + } + + throw notSupported(property); + } + + @SuppressWarnings("unchecked") + protected StorablePropertyInfo[] checkSupport(StorableProperty[] properties) + throws SupportException + { + int length = properties.length; + StorablePropertyInfo[] infos = new StorablePropertyInfo[length]; + for (int i=0; i property) { + return notSupported(property.getName(), property.getType().getName()); + } + + private SupportException notSupported(String propertyName, String typeName) { + return new SupportException + ("Type \"" + typeName + + "\" not supported for property \"" + propertyName + '"'); + } + + private OrderedProperty[] ensureKeyProperties(OrderedProperty[] properties) { + if (properties == null) { + properties = gatherAllKeyProperties(); + } else { + for (Object prop : properties) { + if (prop == null) { + throw new IllegalArgumentException(); + } + } + } + return properties; + } + + @SuppressWarnings("unchecked") + private StorableProperty[] extractProperties(OrderedProperty[] ordered) { + StorableProperty[] properties = new StorableProperty[ordered.length]; + for (int i=0; i 0) { + throw new IllegalArgumentException(); + } + properties[i] = chained.getPrimeProperty(); + } + return properties; + } + + private Direction[] extractDirections(OrderedProperty[] ordered) { + Direction[] directions = new Direction[ordered.length]; + for (int i=0; i[] ensureDataProperties(StorableProperty[] properties) { + if (properties == null) { + properties = gatherAllDataProperties(); + } else { + for (Object prop : properties) { + if (prop == null) { + throw new IllegalArgumentException(); + } + } + } + return properties; + } + + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + + private LocalVariable buildEncoding(boolean forKey, + CodeAssembler a, + StorableProperty[] properties, + Direction[] directions, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useReadMethods, + int generation, + LocalVariable partialStartVar, + LocalVariable partialEndVar) + throws SupportException + { + if (a == null) { + throw new IllegalArgumentException(); + } + if (partialStartVar != null && partialStartVar.getType() != TypeDesc.INT) { + throw new IllegalArgumentException(); + } + if (partialEndVar != null && partialEndVar.getType() != TypeDesc.INT) { + throw new IllegalArgumentException(); + } + + // Encoding order is: + // + // 1. Prefix + // 2. Generation prefix + // 3. Properties + // 4. Suffix + + final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding; + + final int generationPrefix; + if (generation < 0) { + generationPrefix = 0; + } else if (generation < 128) { + generationPrefix = 1; + } else { + generationPrefix = 4; + } + + final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding; + + final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); + final LocalVariable encodedVar = a.createLocalVariable(null, byteArrayType); + + StorablePropertyInfo[] infos = checkSupport(properties); + + if (properties.length == 1) { + // Ignore partial key encoding variables, since there can't be a + // partial of one property. + partialStartVar = null; + partialEndVar = null; + + StorableProperty property = properties[0]; + StorablePropertyInfo info = infos[0]; + + if (info.getStorageType().toClass() == byte[].class) { + // Since there is only one property, and it is just a byte + // array, optimize by not doing any fancy encoding. If the + // property is optional, then a byte prefix is needed to + // identify a null reference. + + loadPropertyValue(a, info, 0, useReadMethods, + instanceVar, adapterInstanceClass, partialStartVar); + + boolean descending = + forKey && directions != null && directions[0] == Direction.DESCENDING; + + TypeDesc[] params; + if (prefix > 0 || generationPrefix > 0 || suffix > 0) { + a.loadConstant(prefix + generationPrefix); + a.loadConstant(suffix); + params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT}; + } else { + params = new TypeDesc[] {byteArrayType}; + } + + if (property.isNullable()) { + if (descending) { + a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleNullableDesc", + byteArrayType, params); + } else { + a.invokeStatic(DataEncoder.class.getName(), "encodeSingleNullable", + byteArrayType, params); + } + } else if (descending) { + a.invokeStatic(KeyEncoder.class.getName(), "encodeSingleDesc", + byteArrayType, params); + } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) { + a.invokeStatic(DataEncoder.class.getName(), "encodeSingle", + byteArrayType, params); + } else { + // Just return raw property value - no need to cache it either. + } + + a.storeLocal(encodedVar); + + encodeGeneration(a, encodedVar, prefix, generation); + + return encodedVar; + } + } + + boolean doPartial = forKey && (partialStartVar != null || partialEndVar != null); + + // Calculate exactly how many bytes are needed to encode. The length + // is composed of a static and a variable amount. The variable amount + // is determined at runtime. + + int staticLength = 0; + if (!forKey || partialStartVar == null) { + // Only include prefix as static if no runtime check is needed + // against runtime partial start value. + staticLength += prefix + generationPrefix; + } + if (!forKey || partialEndVar == null) { + // Only include suffix as static if no runtime check is needed + // against runtime partial end value. + staticLength += suffix; + } + + boolean hasVariableLength; + if (doPartial) { + hasVariableLength = true; + } else { + hasVariableLength = false; + for (GenericPropertyInfo info : infos) { + int len = staticEncodingLength(info); + if (len >= 0) { + staticLength += len; + } else { + staticLength += ~len; + hasVariableLength = true; + } + } + } + + // Generate code that loops over all the properties that have a + // variable length. Load each property and perform the necessary + // tests to determine the exact encoding length. + + boolean hasStackVar = false; + if (hasVariableLength) { + Label[] entryPoints = null; + + if (partialStartVar != null) { + // Will jump into an arbitrary location, so always have a stack + // variable available. + a.loadConstant(0); + hasStackVar = true; + + entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length); + } + + Label exitPoint = a.createLabel(); + + for (int i=0; i property = properties[i]; + StorablePropertyInfo info = infos[i]; + + if (doPartial) { + if (entryPoints != null) { + entryPoints[i].setLocation(); + } + if (partialEndVar != null) { + // Add code to jump out of partial. + a.loadConstant(i); + a.loadLocal(partialEndVar); + a.ifComparisonBranch(exitPoint, ">="); + } + } else if (staticEncodingLength(info) >= 0) { + continue; + } + + TypeDesc propType = info.getStorageType(); + + if (propType.isPrimitive()) { + // This should only ever get executed if implementing + // partial support. Otherwise, the static encoding length + // would have been already calculated. + a.loadConstant(staticEncodingLength(info)); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + } else if (propType.toPrimitiveType() != null) { + int amt = 0; + switch (propType.toPrimitiveType().getTypeCode()) { + case TypeDesc.BYTE_CODE: + case TypeDesc.BOOLEAN_CODE: + amt = 1; + break; + case TypeDesc.SHORT_CODE: + case TypeDesc.CHAR_CODE: + amt = 2; + break; + case TypeDesc.INT_CODE: + case TypeDesc.FLOAT_CODE: + amt = 4; + break; + case TypeDesc.LONG_CODE: + case TypeDesc.DOUBLE_CODE: + amt = 8; + break; + } + + int extra = 0; + if (doPartial) { + // If value is null, then there may be a one byte size + // adjust for the null value. Otherwise it is the extra + // amount plus the size to encode the raw primitive + // value. If doPartial is false, then this extra amount + // was already accounted for in the static encoding + // length. + + switch (propType.toPrimitiveType().getTypeCode()) { + case TypeDesc.BYTE_CODE: + case TypeDesc.SHORT_CODE: + case TypeDesc.CHAR_CODE: + case TypeDesc.INT_CODE: + case TypeDesc.LONG_CODE: + extra = 1; + } + } + + if (!property.isNullable() || (doPartial && extra == 0)) { + a.loadConstant(amt); + if (hasStackVar) { + a.math(Opcode.IADD); + } + hasStackVar = true; + } else { + // Load property to test for null. + loadPropertyValue(a, info, i, useReadMethods, + instanceVar, adapterInstanceClass, partialStartVar); + + Label isNull = a.createLabel(); + a.ifNullBranch(isNull, true); + + a.loadConstant(amt); + + if (hasStackVar) { + a.math(Opcode.IADD); + isNull.setLocation(); + if (extra > 0) { + a.loadConstant(extra); + a.math(Opcode.IADD); + } + } else { + hasStackVar = true; + // Make sure that there is a zero (or extra) value on + // the stack if the isNull branch is taken. + Label notNull = a.createLabel(); + a.branch(notNull); + isNull.setLocation(); + a.loadConstant(extra); + notNull.setLocation(); + } + } + } else if (propType == TypeDesc.STRING) { + // Load property to test for null. + loadPropertyValue(a, info, i, useReadMethods, + instanceVar, adapterInstanceClass, partialStartVar); + + String className = + (forKey ? KeyEncoder.class : DataEncoder.class).getName(); + a.invokeStatic(className, "calculateEncodedStringLength", + TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + } else if (propType.toClass() == byte[].class) { + // Load property to test for null. + loadPropertyValue(a, info, i, useReadMethods, + instanceVar, adapterInstanceClass, partialStartVar); + + String className = + (forKey ? KeyEncoder.class : DataEncoder.class).getName(); + a.invokeStatic(className, "calculateEncodedLength", + TypeDesc.INT, new TypeDesc[] {byteArrayType}); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + } else if (info.isLob()) { + // Lob locator is a long, or 8 bytes. + a.loadConstant(8); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + } else { + throw notSupported(property); + } + } + + exitPoint.setLocation(); + + if (forKey && partialStartVar != null && (prefix > 0 || generationPrefix > 0)) { + // Prefix must be allocated only if runtime value of + // partialStartVar is zero. + a.loadLocal(partialStartVar); + Label noPrefix = a.createLabel(); + a.ifZeroComparisonBranch(noPrefix, "!="); + a.loadConstant(prefix + generationPrefix); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + noPrefix.setLocation(); + } + + if (forKey && partialEndVar != null && suffix > 0) { + // Suffix must be allocated only if runtime value of + // partialEndVar is equal to property count. + a.loadLocal(partialEndVar); + Label noSuffix = a.createLabel(); + a.loadConstant(properties.length); + a.ifComparisonBranch(noSuffix, "!="); + a.loadConstant(suffix); + if (hasStackVar) { + a.math(Opcode.IADD); + } else { + hasStackVar = true; + } + noSuffix.setLocation(); + } + } + + // Allocate a byte array of the exact size. + if (hasStackVar) { + if (staticLength > 0) { + a.loadConstant(staticLength); + a.math(Opcode.IADD); + } + } else { + a.loadConstant(staticLength); + } + a.newObject(byteArrayType); + a.storeLocal(encodedVar); + + // Now encode into the byte array. + + int constantOffset = 0; + LocalVariable offset = null; + + if (!forKey || partialStartVar == null) { + // Only include prefix as constant offset if no runtime check is + // needed against runtime partial start value. + constantOffset += prefix + generationPrefix; + encodeGeneration(a, encodedVar, prefix, generation); + } + + Label[] entryPoints = null; + + if (forKey && partialStartVar != null) { + // Will jump into an arbitrary location, so put an initial value + // into offset variable. + + offset = a.createLocalVariable(null, TypeDesc.INT); + a.loadConstant(0); + if (prefix > 0) { + // Prefix is allocated only if partial start is zero. Check if + // offset should be adjusted to skip over it. + a.loadLocal(partialStartVar); + Label noPrefix = a.createLabel(); + a.ifZeroComparisonBranch(noPrefix, "!="); + a.loadConstant(prefix + generationPrefix); + a.math(Opcode.IADD); + encodeGeneration(a, encodedVar, prefix, generation); + noPrefix.setLocation(); + } + a.storeLocal(offset); + + entryPoints = jumpToPartialEntryPoints(a, partialStartVar, properties.length); + } + + Label exitPoint = a.createLabel(); + + for (int i=0; i property = properties[i]; + StorablePropertyInfo info = infos[i]; + + if (doPartial) { + if (entryPoints != null) { + entryPoints[i].setLocation(); + } + if (partialEndVar != null) { + // Add code to jump out of partial. + a.loadConstant(i); + a.loadLocal(partialEndVar); + a.ifComparisonBranch(exitPoint, ">="); + } + } + + if (info.isLob()) { + // Need RawSupport instance for getting locator from Lob. + pushRawSupport(a, instanceVar); + } + + boolean fromInstance = loadPropertyValue + (a, info, i, useReadMethods, instanceVar, adapterInstanceClass, partialStartVar); + + TypeDesc propType = info.getStorageType(); + if (!property.isNullable() && propType.toPrimitiveType() != null) { + // Since property type is a required primitive wrapper, convert + // to a primitive rather than encoding using the form that + // distinguishes null. + + // Property value that was passed in may be null, which is not + // allowed. + if (!fromInstance && !propType.isPrimitive()) { + a.dup(); + Label notNull = a.createLabel(); + a.ifNullBranch(notNull, false); + + TypeDesc errorType = TypeDesc.forClass(IllegalArgumentException.class); + a.newObject(errorType); + a.dup(); + a.loadConstant("Value for property \"" + property.getName() + + "\" cannot be null"); + a.invokeConstructor(errorType, new TypeDesc[] {TypeDesc.STRING}); + a.throwObject(); + + notNull.setLocation(); + } + + a.convert(propType, propType.toPrimitiveType()); + propType = propType.toPrimitiveType(); + } + + if (info.isLob()) { + // Extract locator from RawSupport. + getLobLocator(a, info); + + // Locator is a long, so switch the type to be encoded properly. + propType = TypeDesc.LONG; + } + + // Fill out remaining parameters before calling specific method + // to encode property value. + a.loadLocal(encodedVar); + if (offset == null) { + a.loadConstant(constantOffset); + } else { + a.loadLocal(offset); + } + + boolean descending = + forKey && directions != null && directions[i] == Direction.DESCENDING; + + int amt = encodeProperty(a, propType, forKey, descending); + + if (amt > 0) { + if (i + 1 < properties.length) { + // Only adjust offset if there are more properties. + + if (offset == null) { + constantOffset += amt; + } else { + a.loadConstant(amt); + a.loadLocal(offset); + a.math(Opcode.IADD); + a.storeLocal(offset); + } + } + } else { + if (i + 1 >= properties.length) { + // Don't need to keep track of offset anymore. + a.pop(); + } else { + // Only adjust offset if there are more properties. + if (offset == null) { + if (constantOffset > 0) { + a.loadConstant(constantOffset); + a.math(Opcode.IADD); + } + offset = a.createLocalVariable(null, TypeDesc.INT); + } else { + a.loadLocal(offset); + a.math(Opcode.IADD); + } + a.storeLocal(offset); + } + } + } + + exitPoint.setLocation(); + + return encodedVar; + } + + /** + * Generates code to load a property value onto the operand stack. + * + * @param info info for property to load + * @param ordinal zero-based property ordinal, used only if instanceVar + * refers to an object array. + * @param useReadMethod when true, access property by public read method + * instead of protected field + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are read from the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @param partialStartVar optional variable for supporting partial key + * generation. It must be an int, whose runtime value must be less than the + * properties array length. It marks the range start of the partial + * property range. + * @return true if property was loaded from instance, false if loaded from + * value array + */ + protected boolean loadPropertyValue(CodeAssembler a, + StorablePropertyInfo info, int ordinal, + boolean useReadMethod, + LocalVariable instanceVar, + Class adapterInstanceClass, + LocalVariable partialStartVar) + { + TypeDesc type = info.getPropertyType(); + TypeDesc storageType = info.getStorageType(); + + boolean isObjectArrayInstanceVar = instanceVar != null + && instanceVar.getType() == TypeDesc.forClass(Object[].class); + + boolean useAdapterInstance = adapterInstanceClass != null + && info.getToStorageAdapter() != null + && (useReadMethod || isObjectArrayInstanceVar); + + if (useAdapterInstance) { + // Push adapter instance to stack to be used later. + String fieldName = + info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0; + TypeDesc adapterType = TypeDesc.forClass + (info.getToStorageAdapter().getDeclaringClass()); + a.loadStaticField + (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType); + } + + if (instanceVar == null) { + a.loadThis(); + if (useReadMethod) { + info.addInvokeReadMethod(a); + } else { + // Access property value directly from protected field of "this". + if (info.getToStorageAdapter() == null) { + a.loadField(info.getPropertyName(), type); + } else { + // Invoke adapter method. + a.invokeVirtual(info.getReadMethodName() + '$', storageType, null); + } + } + } else if (!isObjectArrayInstanceVar) { + a.loadLocal(instanceVar); + if (useReadMethod) { + info.addInvokeReadMethod(a, instanceVar.getType()); + } else { + // Access property value directly from protected field of + // referenced instance. Assumes code is being defined in the + // same package or a subclass. + if (info.getToStorageAdapter() == null) { + a.loadField(instanceVar.getType(), info.getPropertyName(), type); + } else { + // Invoke adapter method. + a.invokeVirtual(instanceVar.getType(), + info.getReadMethodName() + '$', storageType, null); + } + } + } else { + // Access property value from object array. + + a.loadLocal(instanceVar); + a.loadConstant(ordinal); + if (ordinal > 0 && partialStartVar != null) { + a.loadLocal(partialStartVar); + a.math(Opcode.ISUB); + } + + a.loadFromArray(TypeDesc.OBJECT); + a.checkCast(type.toObjectType()); + if (type.isPrimitive()) { + a.convert(type.toObjectType(), type); + } + } + + if (useAdapterInstance) { + // Invoke adapter method on instance pushed earlier. + a.invoke(info.getToStorageAdapter()); + } + + return !isObjectArrayInstanceVar; + } + + /** + * Returns a negative value if encoding is variable. The minimum static + * amount is computed from the one's compliment. Of the types with variable + * encoding lengths, only for primitives is the minimum static amount + * returned more than zero. + */ + private int staticEncodingLength(GenericPropertyInfo info) { + TypeDesc type = info.getStorageType(); + TypeDesc primType = type.toPrimitiveType(); + + if (primType == null) { + if (info.isLob()) { + // Lob locator is stored as a long. + return 8; + } + } else { + if (info.isNullable()) { + // Type is a primitive wrapper. + switch (primType.getTypeCode()) { + case TypeDesc.BYTE_CODE: + return ~1; + case TypeDesc.BOOLEAN_CODE: + return 1; + case TypeDesc.SHORT_CODE: + case TypeDesc.CHAR_CODE: + return ~1; + case TypeDesc.INT_CODE: + return ~1; + case TypeDesc.FLOAT_CODE: + return 4; + case TypeDesc.LONG_CODE: + return ~1; + case TypeDesc.DOUBLE_CODE: + return 8; + } + } else { + // Type is primitive or a required primitive wrapper. + switch (type.getTypeCode()) { + case TypeDesc.BYTE_CODE: + case TypeDesc.BOOLEAN_CODE: + return 1; + case TypeDesc.SHORT_CODE: + case TypeDesc.CHAR_CODE: + return 2; + case TypeDesc.INT_CODE: + case TypeDesc.FLOAT_CODE: + return 4; + case TypeDesc.LONG_CODE: + case TypeDesc.DOUBLE_CODE: + return 8; + } + } + } + + return ~0; + } + + /** + * @param partialStartVar must not be null + */ + private Label[] jumpToPartialEntryPoints(CodeAssembler a, LocalVariable partialStartVar, + int propertyCount) { + // Create all the entry points for offset var, whose locations will be + // set later. + int[] cases = new int[propertyCount]; + Label[] entryPoints = new Label[propertyCount]; + for (int i=0; i> (8 * (3 - i)))); + a.storeToArray(TypeDesc.BYTE); + } + } + } + + /** + * Generates code to push RawSupport instance to the stack. RawSupport is + * available only in Storable instances. If instanceVar is an Object[], a + * SupportException is thrown. + */ + private void pushRawSupport(CodeAssembler a, LocalVariable instanceVar) + throws SupportException + { + boolean isObjectArrayInstanceVar = instanceVar != null + && instanceVar.getType() == TypeDesc.forClass(Object[].class); + + if (isObjectArrayInstanceVar) { + throw new SupportException("Lob properties not supported"); + } + + if (instanceVar == null) { + a.loadThis(); + } else { + a.loadLocal(instanceVar); + } + + a.loadField(StorableGenerator.SUPPORT_FIELD_NAME, + TypeDesc.forClass(TriggerSupport.class)); + a.checkCast(TypeDesc.forClass(RawSupport.class)); + } + + /** + * Generates code to get a Lob locator value from RawSupport. RawSupport + * instance and Lob instance must be on the stack. Result is a long locator + * value on the stack. + */ + private void getLobLocator(CodeAssembler a, StorablePropertyInfo info) { + if (!info.isLob()) { + throw new IllegalArgumentException(); + } + a.invokeInterface(TypeDesc.forClass(RawSupport.class), "getLocator", + TypeDesc.LONG, new TypeDesc[] {info.getStorageType()}); + } + + /** + * Generates code to get a Lob from a locator from RawSupport. RawSupport + * instance and long locator must be on the stack. Result is a Lob on the + * stack, which may be null. + */ + private void getLobFromLocator(CodeAssembler a, StorablePropertyInfo info) { + if (!info.isLob()) { + throw new IllegalArgumentException(); + } + + TypeDesc type = info.getStorageType(); + String name; + if (Blob.class.isAssignableFrom(type.toClass())) { + name = "getBlob"; + } else if (Clob.class.isAssignableFrom(type.toClass())) { + name = "getClob"; + } else { + throw new IllegalArgumentException(); + } + + a.invokeInterface(TypeDesc.forClass(RawSupport.class), name, + type, new TypeDesc[] {TypeDesc.LONG}); + } + + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + + private void buildDecoding(boolean forKey, + CodeAssembler a, + StorableProperty[] properties, + Direction[] directions, + LocalVariable instanceVar, + Class adapterInstanceClass, + boolean useWriteMethods, + int generation, + Label altGenerationHandler, + LocalVariable encodedVar) + throws SupportException + { + if (a == null) { + throw new IllegalArgumentException(); + } + if (encodedVar == null || encodedVar.getType() != TypeDesc.forClass(byte[].class)) { + throw new IllegalArgumentException(); + } + + // Decoding order is: + // + // 1. Prefix + // 2. Generation prefix + // 3. Properties + // 4. Suffix + + final int prefix = forKey ? mKeyPrefixPadding : mDataPrefixPadding; + + final int generationPrefix; + if (generation < 0) { + generationPrefix = 0; + } else if (generation < 128) { + generationPrefix = 1; + } else { + generationPrefix = 4; + } + + final int suffix = forKey ? mKeySuffixPadding : mDataSuffixPadding; + + final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); + + StorablePropertyInfo[] infos = checkSupport(properties); + + decodeGeneration(a, encodedVar, prefix, generation, altGenerationHandler); + + if (properties.length == 1) { + StorableProperty property = properties[0]; + StorablePropertyInfo info = infos[0]; + + if (info.getStorageType().toClass() == byte[].class) { + // Since there is only one property, and it is just a byte + // array, it doesn't have any fancy encoding. + + // Push to stack in preparation for storing a property. + pushDecodingInstanceVar(a, 0, instanceVar); + + a.loadLocal(encodedVar); + + boolean descending = + forKey && directions != null && directions[0] == Direction.DESCENDING; + + TypeDesc[] params; + if (prefix > 0 || generationPrefix > 0 || suffix > 0) { + a.loadConstant(prefix + generationPrefix); + a.loadConstant(suffix); + params = new TypeDesc[] {byteArrayType, TypeDesc.INT, TypeDesc.INT}; + } else { + params = new TypeDesc[] {byteArrayType}; + } + + if (property.isNullable()) { + if (descending) { + a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleNullableDesc", + byteArrayType, params); + } else { + a.invokeStatic(DataDecoder.class.getName(), "decodeSingleNullable", + byteArrayType, params); + } + } else if (descending) { + a.invokeStatic(KeyDecoder.class.getName(), "decodeSingleDesc", + byteArrayType, params); + } else if (prefix > 0 || generationPrefix > 0 || suffix > 0) { + a.invokeStatic(DataDecoder.class.getName(), "decodeSingle", + byteArrayType, params); + } else { + // Just store raw property value. + } + + storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass); + return; + } + } + + // Now decode from the byte array. + + int constantOffset = prefix + generationPrefix; + LocalVariable offset = null; + // References to local variables which will hold references. + LocalVariable[] stringRef = new LocalVariable[1]; + LocalVariable[] byteArrayRef = new LocalVariable[1]; + LocalVariable[] valueRefRef = new LocalVariable[1]; + + for (int i=0; i 0) { + if (offset == null) { + constantOffset += amt; + } else { + a.loadConstant(amt); + a.loadLocal(offset); + a.math(Opcode.IADD); + a.storeLocal(offset); + } + } else { + // Offset adjust is one if returned object is null. + a.dup(); + Label notNull = a.createLabel(); + a.ifNullBranch(notNull, false); + a.loadConstant(1 + (offset == null ? constantOffset : 0)); + Label cont = a.createLabel(); + a.branch(cont); + notNull.setLocation(); + a.loadConstant(~amt + (offset == null ? constantOffset : 0)); + cont.setLocation(); + + if (offset == null) { + offset = a.createLocalVariable(null, TypeDesc.INT); + } else { + a.loadLocal(offset); + a.math(Opcode.IADD); + } + a.storeLocal(offset); + } + } + } else { + if (i + 1 >= properties.length) { + // Don't need to keep track of offset anymore. + a.pop(); + } else { + // Only adjust offset if there are more properties. + if (offset == null) { + if (constantOffset > 0) { + a.loadConstant(constantOffset); + a.math(Opcode.IADD); + } + offset = a.createLocalVariable(null, TypeDesc.INT); + } else { + a.loadLocal(offset); + a.math(Opcode.IADD); + } + a.storeLocal(offset); + } + + // Get the value out of the ref array so that it can be stored. + a.loadLocal(valueRefRef[0]); + a.loadConstant(0); + a.loadFromArray(valueRefRef[0].getType()); + } + + storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass); + } + } + + /** + * Generates code that calls a decoding method in DataDecoder or + * KeyDecoder. Parameters must already be on the stack. + * + * @return 0 if an int amount is pushed onto the stack, or a positive value + * if offset adjust amount is constant, or a negative value if offset + * adjust is constant or one more + */ + private int decodeProperty(CodeAssembler a, + GenericPropertyInfo info, TypeDesc storageType, + boolean forKey, boolean descending, + LocalVariable[] stringRefRef, LocalVariable[] byteArrayRefRef, + LocalVariable[] valueRefRef) + throws SupportException + { + TypeDesc primType = storageType.toPrimitiveType(); + + if (primType != null) { + String methodName; + TypeDesc returnType; + int adjust; + + if (primType != storageType && info.isNullable()) { + // Property type is a nullable boxed primitive. + returnType = storageType; + + switch (primType.getTypeCode()) { + case TypeDesc.BYTE_CODE: + methodName = "decodeByteObj"; + adjust = ~2; + break; + case TypeDesc.BOOLEAN_CODE: + methodName = "decodeBooleanObj"; + adjust = 1; + break; + case TypeDesc.SHORT_CODE: + methodName = "decodeShortObj"; + adjust = ~3; + break; + case TypeDesc.CHAR_CODE: + methodName = "decodeCharacterObj"; + adjust = ~3; + break; + default: + case TypeDesc.INT_CODE: + methodName = "decodeIntegerObj"; + adjust = ~5; + break; + case TypeDesc.FLOAT_CODE: + methodName = "decodeFloatObj"; + adjust = 4; + break; + case TypeDesc.LONG_CODE: + methodName = "decodeLongObj"; + adjust = ~9; + break; + case TypeDesc.DOUBLE_CODE: + methodName = "decodeDoubleObj"; + adjust = 8; + break; + } + } else { + // Property type is a primitive or a boxed primitive. + returnType = primType; + + switch (primType.getTypeCode()) { + case TypeDesc.BYTE_CODE: + methodName = "decodeByte"; + adjust = 1; + break; + case TypeDesc.BOOLEAN_CODE: + methodName = "decodeBoolean"; + adjust = 1; + break; + case TypeDesc.SHORT_CODE: + methodName = "decodeShort"; + adjust = 2; + break; + case TypeDesc.CHAR_CODE: + methodName = "decodeChar"; + adjust = 2; + break; + default: + case TypeDesc.INT_CODE: + methodName = "decodeInt"; + adjust = 4; + break; + case TypeDesc.FLOAT_CODE: + methodName = "decodeFloat"; + adjust = 4; + break; + case TypeDesc.LONG_CODE: + methodName = "decodeLong"; + adjust = 8; + break; + case TypeDesc.DOUBLE_CODE: + methodName = "decodeDouble"; + adjust = 8; + break; + } + } + + TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT}; + if (forKey && descending) { + a.invokeStatic + (KeyDecoder.class.getName(), methodName + "Desc", returnType, params); + } else { + a.invokeStatic + (DataDecoder.class.getName(), methodName, returnType, params); + } + + if (returnType.isPrimitive()) { + if (!storageType.isPrimitive()) { + // Wrap it. + a.convert(returnType, storageType); + } + } + + return adjust; + } else { + String className = (forKey ? KeyDecoder.class : DataDecoder.class).getName(); + String methodName; + TypeDesc refType; + + if (storageType == TypeDesc.STRING) { + methodName = (forKey && descending) ? "decodeStringDesc" : "decodeString"; + refType = TypeDesc.forClass(String[].class); + if (stringRefRef[0] == null) { + stringRefRef[0] = a.createLocalVariable(null, refType); + a.loadConstant(1); + a.newObject(refType); + a.storeLocal(stringRefRef[0]); + } + a.loadLocal(stringRefRef[0]); + valueRefRef[0] = stringRefRef[0]; + } else if (storageType.toClass() == byte[].class) { + methodName = (forKey && descending) ? "decodeDesc" : "decode"; + refType = TypeDesc.forClass(byte[][].class); + if (byteArrayRefRef[0] == null) { + byteArrayRefRef[0] = a.createLocalVariable(null, refType); + a.loadConstant(1); + a.newObject(refType); + a.storeLocal(byteArrayRefRef[0]); + } + a.loadLocal(byteArrayRefRef[0]); + valueRefRef[0] = byteArrayRefRef[0]; + } else { + throw notSupported(info.getPropertyName(), storageType.getFullName()); + } + + TypeDesc[] params = {TypeDesc.forClass(byte[].class), TypeDesc.INT, refType}; + a.invokeStatic(className, methodName, TypeDesc.INT, params); + + return 0; + } + } + + /** + * Push decoding instanceVar to stack in preparation to calling + * storePropertyValue. + * + * @param ordinal zero-based property ordinal, used only if instanceVar + * refers to an object array. + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are written to the runtime value of this array instead + * of a Storable instance. + * @see #storePropertyValue storePropertyValue + */ + protected void pushDecodingInstanceVar(CodeAssembler a, int ordinal, + LocalVariable instanceVar) { + if (instanceVar == null) { + // Push this to stack in preparation for storing a property. + a.loadThis(); + } else if (instanceVar.getType() != TypeDesc.forClass(Object[].class)) { + // Push reference to stack in preparation for storing a property. + a.loadLocal(instanceVar); + } else { + // Push array and index to stack in preparation for storing a property. + a.loadLocal(instanceVar); + a.loadConstant(ordinal); + } + } + + /** + * Generates code to store a property value into an instance which is + * already on the operand stack. If instance is an Object array, index into + * array must also be on the operand stack. + * + * @param info info for property to store to + * @param useWriteMethod when true, set property by public write method + * instead of protected field + * @param instanceVar local variable referencing Storable instance, + * defaults to "this" if null. If variable type is an Object array, then + * property values are written to the runtime value of this array instead + * of a Storable instance. + * @param adapterInstanceClass class containing static references to + * adapter instances - defaults to instanceVar + * @see #pushDecodingInstanceVar pushDecodingInstanceVar + */ + protected void storePropertyValue(CodeAssembler a, StorablePropertyInfo info, + boolean useWriteMethod, + LocalVariable instanceVar, + Class adapterInstanceClass) { + TypeDesc type = info.getPropertyType(); + TypeDesc storageType = info.getStorageType(); + + boolean isObjectArrayInstanceVar = instanceVar != null + && instanceVar.getType() == TypeDesc.forClass(Object[].class); + + boolean useAdapterInstance = adapterInstanceClass != null + && info.getToStorageAdapter() != null + && (useWriteMethod || isObjectArrayInstanceVar); + + if (useAdapterInstance) { + // Push adapter instance to adapt property value. It must be on the + // stack before the property value, so swap. + + // Store unadapted property to temp var in order to be swapped. + LocalVariable temp = a.createLocalVariable(null, storageType); + a.storeLocal(temp); + + String fieldName = + info.getPropertyName() + StorableGenerator.ADAPTER_FIELD_ELEMENT + 0; + TypeDesc adapterType = TypeDesc.forClass + (info.getToStorageAdapter().getDeclaringClass()); + a.loadStaticField + (TypeDesc.forClass(adapterInstanceClass), fieldName, adapterType); + + a.loadLocal(temp); + a.invoke(info.getFromStorageAdapter()); + + // Stack now contains property adapted to its publicly declared type. + } + + if (instanceVar == null) { + if (useWriteMethod) { + info.addInvokeWriteMethod(a); + } else { + // Set property value directly to protected field of instance. + if (info.getToStorageAdapter() == null) { + a.storeField(info.getPropertyName(), type); + } else { + // Invoke adapter method. + a.invokeVirtual(info.getWriteMethodName() + '$', + null, new TypeDesc[] {storageType}); + } + } + } else if (!isObjectArrayInstanceVar) { + TypeDesc instanceVarType = instanceVar.getType(); + + // Drop properties that are missing or whose types are incompatible. + doDrop: { + Class instanceVarClass = instanceVarType.toClass(); + if (instanceVarClass != null) { + Map props = + BeanIntrospector.getAllProperties(instanceVarClass); + BeanProperty prop = props.get(info.getPropertyName()); + if (prop != null) { + if (prop.getType() == type.toClass()) { + break doDrop; + } + // Types differ, but if primitive types, perform conversion. + TypeDesc primType = type.toPrimitiveType(); + if (primType != null) { + TypeDesc propType = TypeDesc.forClass(prop.getType()); + TypeDesc primPropType = propType.toPrimitiveType(); + if (primPropType != null) { + // Apply conversion and store property. + a.convert(type, propType); + type = propType; + break doDrop; + } + } + } + } + + // Drop missing or incompatible property. + if (storageType.isDoubleWord()) { + a.pop2(); + } else { + a.pop(); + } + return; + } + + if (useWriteMethod) { + info.addInvokeWriteMethod(a, instanceVarType); + } else { + // Set property value directly to protected field of referenced + // instance. Assumes code is being defined in the same package + // or a subclass. + if (info.getToStorageAdapter() == null) { + a.storeField(instanceVarType, info.getPropertyName(), type); + } else { + // Invoke adapter method. + a.invokeVirtual(instanceVarType, info.getWriteMethodName() + '$', + null, new TypeDesc[] {storageType}); + } + } + } else { + // Set property value to object array. No need to check if we + // should call a write method because arrays don't have write + // methods. + if (type.isPrimitive()) { + a.convert(type, type.toObjectType()); + } + a.storeToArray(TypeDesc.OBJECT); + } + } + + /** + * Generates code that ensures a matching generation value exists in the + * byte array referenced by the local variable, throwing a + * CorruptEncodingException otherwise. + * + * @param generation if less than zero, no code is generated + */ + private void decodeGeneration(CodeAssembler a, LocalVariable encodedVar, + int offset, int generation, Label altGenerationHandler) + { + if (offset < 0) { + throw new IllegalArgumentException(); + } + if (generation < 0) { + return; + } + + LocalVariable actualGeneration = a.createLocalVariable(null, TypeDesc.INT); + a.loadLocal(encodedVar); + a.loadConstant(offset); + a.loadFromArray(TypeDesc.BYTE); + a.storeLocal(actualGeneration); + a.loadLocal(actualGeneration); + Label compareGeneration = a.createLabel(); + a.ifZeroComparisonBranch(compareGeneration, ">="); + + // Decode four byte generation format. + a.loadLocal(actualGeneration); + a.loadConstant(24); + a.math(Opcode.ISHL); + a.loadConstant(0x7fffffff); + a.math(Opcode.IAND); + for (int i=1; i<4; i++) { + a.loadLocal(encodedVar); + a.loadConstant(offset + i); + a.loadFromArray(TypeDesc.BYTE); + a.loadConstant(0xff); + a.math(Opcode.IAND); + int shift = 8 * (3 - i); + if (shift > 0) { + a.loadConstant(shift); + a.math(Opcode.ISHL); + } + a.math(Opcode.IOR); + } + a.storeLocal(actualGeneration); + + compareGeneration.setLocation(); + + a.loadConstant(generation); + a.loadLocal(actualGeneration); + Label generationMatches = a.createLabel(); + a.ifComparisonBranch(generationMatches, "=="); + + if (altGenerationHandler != null) { + a.loadLocal(actualGeneration); + a.branch(altGenerationHandler); + } else { + // Throw CorruptEncodingException. + + TypeDesc corruptEncodingEx = TypeDesc.forClass(CorruptEncodingException.class); + a.newObject(corruptEncodingEx); + a.dup(); + a.loadConstant(generation); // expected generation + a.loadLocal(actualGeneration); // actual generation + a.invokeConstructor(corruptEncodingEx, new TypeDesc[] {TypeDesc.INT, TypeDesc.INT}); + a.throwObject(); + } + + generationMatches.setLocation(); + } +} -- cgit v1.2.3