From 641a7812d439daca3045e7471b604beb807c1891 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 1 Apr 2007 00:00:07 +0000 Subject: Move Storable code generation support to separate package. --- .../amazon/carbonado/cursor/MergeSortBuffer.java | 3 +- .../com/amazon/carbonado/gen/CodeBuilderUtil.java | 536 +++ .../amazon/carbonado/gen/CommonMethodNames.java | 87 + .../com/amazon/carbonado/gen/MasterFeature.java | 59 + .../carbonado/gen/MasterStorableGenerator.java | 835 +++++ .../com/amazon/carbonado/gen/MasterSupport.java | 39 + .../amazon/carbonado/gen/StorableGenerator.java | 3671 ++++++++++++++++++++ .../amazon/carbonado/gen/StorableSerializer.java | 337 ++ .../com/amazon/carbonado/gen/StorableSupport.java | 41 + .../com/amazon/carbonado/gen/TriggerSupport.java | 50 + .../com/amazon/carbonado/gen/WrappedSupport.java | 75 + .../com/amazon/carbonado/gen/package-info.java | 22 + .../carbonado/info/ConversionComparator.java | 212 ++ .../carbonado/info/StorableIntrospector.java | 48 +- .../amazon/carbonado/qe/JoinedQueryExecutor.java | 2 +- .../carbonado/raw/GenericEncodingStrategy.java | 4 +- .../amazon/carbonado/raw/GenericStorableCodec.java | 2 +- .../amazon/carbonado/raw/RawStorableGenerator.java | 13 +- .../java/com/amazon/carbonado/raw/RawSupport.java | 2 +- .../carbonado/repo/jdbc/JDBCStorableGenerator.java | 15 +- .../amazon/carbonado/repo/jdbc/JDBCSupport.java | 2 +- .../com/amazon/carbonado/spi/CodeBuilderUtil.java | 574 --- .../amazon/carbonado/spi/CommonMethodNames.java | 87 - .../amazon/carbonado/spi/ConversionComparator.java | 212 -- .../com/amazon/carbonado/spi/MasterFeature.java | 59 - .../carbonado/spi/MasterStorableGenerator.java | 835 ----- .../com/amazon/carbonado/spi/MasterSupport.java | 39 - .../amazon/carbonado/spi/StorableGenerator.java | 3671 -------------------- .../amazon/carbonado/spi/StorableSerializer.java | 337 -- .../com/amazon/carbonado/spi/StorableSupport.java | 41 - .../com/amazon/carbonado/spi/TriggerSupport.java | 50 - .../com/amazon/carbonado/spi/WrappedStorage.java | 3 + .../com/amazon/carbonado/spi/WrappedSupport.java | 75 - .../com/amazon/carbonado/spi/package-info.java | 6 +- .../SyntheticStorableReferenceBuilder.java | 2 +- 35 files changed, 6034 insertions(+), 6012 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java create mode 100644 src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java create mode 100644 src/main/java/com/amazon/carbonado/gen/MasterFeature.java create mode 100644 src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java create mode 100644 src/main/java/com/amazon/carbonado/gen/MasterSupport.java create mode 100644 src/main/java/com/amazon/carbonado/gen/StorableGenerator.java create mode 100644 src/main/java/com/amazon/carbonado/gen/StorableSerializer.java create mode 100644 src/main/java/com/amazon/carbonado/gen/StorableSupport.java create mode 100644 src/main/java/com/amazon/carbonado/gen/TriggerSupport.java create mode 100644 src/main/java/com/amazon/carbonado/gen/WrappedSupport.java create mode 100644 src/main/java/com/amazon/carbonado/gen/package-info.java create mode 100644 src/main/java/com/amazon/carbonado/info/ConversionComparator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/CommonMethodNames.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/ConversionComparator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/MasterFeature.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/MasterSupport.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/StorableGenerator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/StorableSerializer.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/StorableSupport.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/TriggerSupport.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/WrappedSupport.java (limited to 'src/main/java/com/amazon/carbonado') diff --git a/src/main/java/com/amazon/carbonado/cursor/MergeSortBuffer.java b/src/main/java/com/amazon/carbonado/cursor/MergeSortBuffer.java index adaff14..3279487 100644 --- a/src/main/java/com/amazon/carbonado/cursor/MergeSortBuffer.java +++ b/src/main/java/com/amazon/carbonado/cursor/MergeSortBuffer.java @@ -43,9 +43,10 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.Storage; import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.gen.StorableSerializer; + import com.amazon.carbonado.spi.RAFInputStream; import com.amazon.carbonado.spi.RAFOutputStream; -import com.amazon.carbonado.spi.StorableSerializer; /** * Sort buffer implemented via a merge sort algorithm. If there are too many diff --git a/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java new file mode 100644 index 0000000..c1f26a0 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java @@ -0,0 +1,536 @@ +/* + * 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.gen; + +import java.util.HashSet; +import java.util.Set; +import java.util.Map; +import java.util.HashMap; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Label; +import org.cojen.classfile.TypeDesc; +import org.cojen.classfile.LocalVariable; +import org.cojen.classfile.MethodDesc; +import org.cojen.classfile.Opcode; +import org.cojen.util.ClassInjector; + +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; + +import static com.amazon.carbonado.gen.CommonMethodNames.*; + +/** + * Collection of useful utilities for generating Carbonado code. + * + * @author Don Schneider + * @author Brian S O'Neill + */ +public class CodeBuilderUtil { + + /** + * Generate code to throw an exception if a parameter is null + * @param b CodeBuilder into which to append the code + * @param paramIndex index of the parameter to check + */ + public static void assertParameterNotNull(CodeBuilder b, int paramIndex) { + b.loadLocal(b.getParameter(paramIndex)); + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + throwException(b, IllegalArgumentException.class, null); + notNull.setLocation(); + } + + /** + * Generate code to create a local variable containing the specified parameter coerced + * to the specified type. This is useful for re-interpreting erased generics into + * the more specific genericized type. + * + * @param b CodeBuilder into which to append the code + * @param paramType the more specific type which was erased during compilation + * @param paramIndex index of the parameter to unerase + * @return a local variable referencing the type-cast parameter + */ + public static LocalVariable uneraseGenericParameter( + CodeBuilder b, TypeDesc paramType, final int paramIndex) + { + b.loadLocal(b.getParameter(paramIndex)); + b.checkCast(paramType); + LocalVariable result = b.createLocalVariable(null, paramType); + b.storeLocal(result); + return result; + } + + /** + * Generate code to throw an exception with an optional message. + * @param b {@link CodeBuilder} to which to add code + * @param type type of the object to throw + * @param message optional message to provide to the constructor + */ + public static void throwException(CodeBuilder b, Class type, String message) { + TypeDesc desc = TypeDesc.forClass(type); + b.newObject(desc); + b.dup(); + if (message == null) { + b.invokeConstructor(desc, null); + } else { + b.loadConstant(message); + b.invokeConstructor(desc, new TypeDesc[] {TypeDesc.STRING}); + } + b.throwObject(); + } + + /** + * Collect a set of all the interfaces and recursively all superclasses for the leaf + * (genericised class) and root (genericised base class). Eg, for Object, all + * classes and implemented interfaces for every superclass between foo (the leaf) and + * Object (the base). + *

A copy must be coercible into any of these types, and copy bridge methods must be + * provided to do so. + * + *

Note that the official documentation for this is in draft form, and you have to be + * psychic to have figured out the necessity in the first place. + * + * @param set set into which the class types will be collected + * @param leaf leaf class + * @return same set as was passed in + */ + public static Set gatherAllBridgeTypes(Set set, Class leaf) { + set.add(leaf); + for (Class c : leaf.getInterfaces()) { + gatherAllBridgeTypes(set, c); + } + if ((leaf = leaf.getSuperclass()) != null) { + gatherAllBridgeTypes(set, leaf); + } + return set; + } + + /** + * Add copy bridge methods for all classes/interfaces between the leaf (genericised class) + * and the root (genericised baseclass). + * + * @param cf file to which to add the copy bridge + * @param leaf leaf class + */ + public static void defineCopyBridges(ClassFile cf, Class leaf) { + for (Class c : gatherAllBridgeTypes(new HashSet(), leaf)) { + if (c != Object.class) { + defineCopyBridge(cf, leaf, c); + } + } + } + + /** + * Add a copy bridge method to the classfile for the given type. This is needed to allow + * the genericised class make a copy itself -- which will be erased to the base type -- and + * return it as the correct type. + * + * @param cf file to which to add the copy bridge + * @param leaf leaf class + * @param returnClass type returned from generated bridge method + */ + private static void defineCopyBridge(ClassFile cf, Class leaf, Class returnClass) { + TypeDesc returnType = TypeDesc.forClass(returnClass); + + if (isPublicMethodFinal(leaf, COPY_METHOD_NAME, returnType, null)) { + // Cannot override. + return; + } + + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toBridge(true), + COPY_METHOD_NAME, returnType, null); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.invokeVirtual(COPY_METHOD_NAME, cf.getType(), null); + b.returnValue(returnType); + } + + /** + * Returns true if a public final method exists which matches the given + * specification. + */ + public static boolean isPublicMethodFinal(Class clazz, String name, + TypeDesc retType, TypeDesc[] params) + { + if (!clazz.isInterface()) { + Class[] paramClasses; + if (params == null || params.length == 0) { + paramClasses = null; + } else { + paramClasses = new Class[params.length]; + for (int i=0; i + *

  • implements Storable
  • + *
  • implements Cloneable + *
  • abstract if appropriate + *
  • marked synthetic + *
  • targetted for java version 1.5 + * + * @param ci ClassInjector for the storable + * @param type specific Storable implementation to generate + * @param isAbstract true if the class should be abstract + * @param aSourcefileName identifier for the classfile, typically the factory class name + * @return ClassFile object ready to have methods added. + */ + public static ClassFile createStorableClassFile( + ClassInjector ci, Class type, boolean isAbstract, String aSourcefileName) + { + ClassFile cf; + if (type.isInterface()) { + cf = new ClassFile(ci.getClassName()); + cf.addInterface(type); + } else { + cf = new ClassFile(ci.getClassName(), type); + } + + if (isAbstract) { + Modifiers modifiers = cf.getModifiers().toAbstract(true); + cf.setModifiers(modifiers); + } + cf.addInterface(Storable.class); + cf.addInterface(Cloneable.class); + cf.markSynthetic(); + cf.setSourceFile(aSourcefileName); + cf.setTarget("1.5"); + return cf; + } + + /** + * Generates code to compare a field in this object against the same one in a + * different instance. Branch to the provided Label if they are not equal. + * + * @param b {@link CodeBuilder} to which to add the code + * @param fieldName the name of the field + * @param fieldType the type of the field + * @param testForNull if true and the values are references, they will be considered + * unequal unless neither or both are null. If false, assume neither is null. + * @param fail the label to branch to + * @param other the other instance to test + */ + public static void addEqualsCall(CodeBuilder b, + String fieldName, + TypeDesc fieldType, + boolean testForNull, + Label fail, + LocalVariable other) + { + b.loadThis(); + b.loadField(fieldName, fieldType); + + b.loadLocal(other); + b.loadField(fieldName, fieldType); + + addValuesEqualCall(b, fieldType, testForNull, fail, false); + } + + /** + * Generates code to compare two values on the stack, and branch to the + * provided Label if they are not equal. Both values must be of the same type. + * + *

    The generated instruction consumes both values on the stack. + * + * @param b {@link CodeBuilder} to which to add the code + * @param valueType the type of the values + * @param testForNull if true and the values are references, they will be considered + * unequal unless neither or both are null. If false, assume neither is null. + * @param label the label to branch to + * @param choice when true, branch to label if values are equal, else + * branch to label if values are unequal. + */ + public static void addValuesEqualCall(final CodeBuilder b, + final TypeDesc valueType, + final boolean testForNull, + final Label label, + final boolean choice) + { + if (valueType.getTypeCode() != TypeDesc.OBJECT_CODE) { + b.ifComparisonBranch(label, choice ? "==" : "!=", valueType); + return; + } + + // Equals method returns zero for false, so if choice is true, branch + // if not zero. Note that operator selection is opposite when invoking + // a direct ifComparisonBranch method. + String equalsBranchOp = choice ? "!=" : "=="; + + if (!testForNull) { + addEqualsCallTo(b, valueType); + b.ifZeroComparisonBranch(label, equalsBranchOp); + return; + } + + Label isNotNull = b.createLabel(); + LocalVariable value = b.createLocalVariable(null, valueType); + b.storeLocal(value); + b.loadLocal(value); + b.ifNullBranch(isNotNull, false); + + // First value popped off stack is null. Just test remaining one for null. + b.ifNullBranch(label, choice); + Label cont = b.createLabel(); + b.branch(cont); + + // First value popped off stack is not null, but second one might + // be. Call equals method, but swap values so that the second value is + // an argument into the equals method. + isNotNull.setLocation(); + b.loadLocal(value); + b.swap(); + addEqualsCallTo(b, valueType); + b.ifZeroComparisonBranch(label, equalsBranchOp); + + cont.setLocation(); + } + + public static void addEqualsCallTo(CodeBuilder b, TypeDesc fieldType) { + if (fieldType.isArray()) { + if (!fieldType.getComponentType().isPrimitive()) { + TypeDesc type = TypeDesc.forClass(Object[].class); + b.invokeStatic("java.util.Arrays", "deepEquals", + TypeDesc.BOOLEAN, new TypeDesc[] {type, type}); + } else { + b.invokeStatic("java.util.Arrays", "equals", + TypeDesc.BOOLEAN, new TypeDesc[] {fieldType, fieldType}); + } + } else { + TypeDesc[] params = {TypeDesc.OBJECT}; + if (fieldType.toClass() != null) { + if (fieldType.toClass().isInterface()) { + b.invokeInterface(fieldType, "equals", TypeDesc.BOOLEAN, params); + } else { + b.invokeVirtual(fieldType, "equals", TypeDesc.BOOLEAN, params); + } + } else { + b.invokeVirtual(TypeDesc.OBJECT, "equals", TypeDesc.BOOLEAN, params); + } + } + } + + /** + * Converts a value on the stack. If "to" type is a String, then conversion + * may call the String.valueOf(from). + */ + public static void convertValue(CodeBuilder b, Class from, Class to) { + if (from == to) { + return; + } + + TypeDesc fromType = TypeDesc.forClass(from); + TypeDesc toType = TypeDesc.forClass(to); + + // Let CodeBuilder have a crack at the conversion first. + try { + b.convert(fromType, toType); + return; + } catch (IllegalArgumentException e) { + if (to != String.class && to != Object.class && to != CharSequence.class) { + throw e; + } + } + + // Fallback case is to convert to a String. + + if (fromType.isPrimitive()) { + b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING, new TypeDesc[]{fromType}); + } else { + // If object on stack is null, then just leave it alone. + b.dup(); + Label isNull = b.createLabel(); + b.ifNullBranch(isNull, true); + b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING, + new TypeDesc[]{TypeDesc.OBJECT}); + isNull.setLocation(); + } + } + + /** + * Generates code to push an initial version property value on the stack. + * + * @throws SupportException if version type is not supported + */ + public static void initialVersion(CodeBuilder b, TypeDesc type, int value) + throws SupportException + { + adjustVersion(b, type, value, false); + } + + /** + * Generates code to increment a version property value, already on the stack. + * + * @throws SupportException if version type is not supported + */ + public static void incrementVersion(CodeBuilder b, TypeDesc type) + throws SupportException + { + adjustVersion(b, type, 0, true); + } + + private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment) + throws SupportException + { + TypeDesc primitiveType = type.toPrimitiveType(); + supportCheck: { + if (primitiveType != null) { + switch (primitiveType.getTypeCode()) { + case TypeDesc.INT_CODE: + case TypeDesc.LONG_CODE: + break supportCheck; + } + } + throw new SupportException("Unsupported version type: " + type.getFullName()); + } + + if (!increment) { + if (primitiveType == TypeDesc.LONG) { + b.loadConstant((long) value); + } else { + b.loadConstant(value); + } + } else { + Label setVersion = b.createLabel(); + if (!type.isPrimitive()) { + b.dup(); + Label versionNotNull = b.createLabel(); + b.ifNullBranch(versionNotNull, false); + b.pop(); + if (primitiveType == TypeDesc.LONG) { + b.loadConstant(1L); + } else { + b.loadConstant(1); + } + b.branch(setVersion); + versionNotNull.setLocation(); + b.convert(type, primitiveType); + } + if (primitiveType == TypeDesc.LONG) { + b.loadConstant(1L); + b.math(Opcode.LADD); + } else { + b.loadConstant(1); + b.math(Opcode.IADD); + } + setVersion.setLocation(); + } + + b.convert(primitiveType, type); + } + + /** + * Determines which overloaded "with" method on Query should be bound to. + */ + public static TypeDesc bindQueryParam(Class clazz) { + // This method is a bit vestigial. Once upon a time the Query class did + // not support all primitive types. + if (clazz.isPrimitive()) { + TypeDesc type = TypeDesc.forClass(clazz); + switch (type.getTypeCode()) { + case TypeDesc.INT_CODE: + case TypeDesc.LONG_CODE: + case TypeDesc.FLOAT_CODE: + case TypeDesc.DOUBLE_CODE: + case TypeDesc.BOOLEAN_CODE: + case TypeDesc.CHAR_CODE: + case TypeDesc.BYTE_CODE: + case TypeDesc.SHORT_CODE: + return type; + } + } + return TypeDesc.OBJECT; + } + + /** + * Appends a String to a StringBuilder. A StringBuilder and String must be + * on the stack, and a StringBuilder is left on the stack after the call. + */ + public static void callStringBuilderAppendString(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING}); + } + + /** + * Appends a char to a StringBuilder. A StringBuilder and char must be on + * the stack, and a StringBuilder is left on the stack after the call. + */ + public static void callStringBuilderAppendChar(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR}); + } + + /** + * Calls length on a StringBuilder on the stack, leaving an int on the stack. + */ + public static void callStringBuilderLength(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null); + } + + /** + * Calls setLength on a StringBuilder. A StringBuilder and int must be on + * the stack, and both are consumed after the call. + */ + public static void callStringBuilderSetLength(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT}); + } + + /** + * Calls toString on a StringBuilder. A StringBuilder must be on the stack, + * and a String is left on the stack after the call. + */ + public static void callStringBuilderToString(CodeBuilder b) { + // Because of JDK1.5 bug which exposes AbstractStringBuilder class, + // cannot use reflection to get method signature. + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null); + } +} diff --git a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java new file mode 100644 index 0000000..4e56e0a --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java @@ -0,0 +1,87 @@ +/* + * 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.gen; + +/** + * Collection of constant method names for the public API. + * + * @author Brian S O'Neill + */ +public class CommonMethodNames { + /** Storable API method name */ + public static final String + LOAD_METHOD_NAME = "load", + INSERT_METHOD_NAME = "insert", + UPDATE_METHOD_NAME = "update", + DELETE_METHOD_NAME = "delete", + TRY_LOAD_METHOD_NAME = "tryLoad", + TRY_INSERT_METHOD_NAME = "tryInsert", + TRY_UPDATE_METHOD_NAME = "tryUpdate", + TRY_DELETE_METHOD_NAME = "tryDelete", + STORABLE_TYPE_METHOD_NAME = "storableType", + COPY_METHOD_NAME = "copy", + CLONE_METHOD_NAME = "clone", + COPY_ALL_PROPERTIES = "copyAllProperties", + COPY_PRIMARY_KEY_PROPERTIES = "copyPrimaryKeyProperties", + COPY_VERSION_PROPERTY = "copyVersionProperty", + COPY_UNEQUAL_PROPERTIES = "copyUnequalProperties", + COPY_DIRTY_PROPERTIES = "copyDirtyProperties", + HAS_DIRTY_PROPERTIES = "hasDirtyProperties", + MARK_PROPERTIES_CLEAN = "markPropertiesClean", + MARK_ALL_PROPERTIES_CLEAN = "markAllPropertiesClean", + MARK_PROPERTIES_DIRTY = "markPropertiesDirty", + MARK_ALL_PROPERTIES_DIRTY = "markAllPropertiesDirty", + IS_PROPERTY_UNINITIALIZED = "isPropertyUninitialized", + IS_PROPERTY_DIRTY = "isPropertyDirty", + IS_PROPERTY_CLEAN = "isPropertyClean", + IS_PROPERTY_SUPPORTED = "isPropertySupported", + TO_STRING_KEY_ONLY_METHOD_NAME = "toStringKeyOnly", + TO_STRING_METHOD_NAME = "toString", + HASHCODE_METHOD_NAME = "hashCode", + EQUALS_METHOD_NAME = "equals", + EQUAL_PRIMARY_KEYS_METHOD_NAME = "equalPrimaryKeys", + EQUAL_PROPERTIES_METHOD_NAME = "equalProperties"; + + /** Storage API method name */ + public static final String + QUERY_METHOD_NAME = "query", + PREPARE_METHOD_NAME = "prepare"; + + /** Query API method name */ + public static final String + LOAD_ONE_METHOD_NAME = "loadOne", + TRY_LOAD_ONE_METHOD_NAME = "tryLoadOne", + WITH_METHOD_NAME = "with", + FETCH_METHOD_NAME = "fetch"; + + /** Repository API method name */ + public static final String + STORAGE_FOR_METHOD_NAME = "storageFor", + ENTER_TRANSACTION_METHOD_NAME = "enterTransaction", + GET_TRANSACTION_ISOLATION_LEVEL_METHOD_NAME = "getTransactionIsolationLevel"; + + /** Transaction API method name */ + public static final String + SET_FOR_UPDATE_METHOD_NAME = "setForUpdate", + COMMIT_METHOD_NAME = "commit", + EXIT_METHOD_NAME = "exit"; + + /** WrappedStorage.Support API method name */ + public static final String CREATE_WRAPPED_SUPPORT_METHOD_NAME = "createSupport"; +} diff --git a/src/main/java/com/amazon/carbonado/gen/MasterFeature.java b/src/main/java/com/amazon/carbonado/gen/MasterFeature.java new file mode 100644 index 0000000..7875d49 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/MasterFeature.java @@ -0,0 +1,59 @@ +/* + * 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.gen; + +/** + * Master feature to enable when using {@link MasterStorableGenerator}. + * + * @author Brian S O'Neill + */ +public enum MasterFeature { + /** Insert and update operations implement record versioning, if version property exists */ + VERSIONING, + + /** Update operations load clean copy first, to prevent destructive update */ + UPDATE_FULL, + + /** Ensure update operation always is in a transaction */ + UPDATE_TXN, + + /** Ensure update operation always is in a transaction, "for update" */ + UPDATE_TXN_FOR_UPDATE, + + /** Insert operation applies any sequences to unset properties */ + INSERT_SEQUENCES, + + /** + * Insert operation checks that all required data properties have been set, + * excluding automatic properties and version property. + */ + INSERT_CHECK_REQUIRED, + + /** Ensure insert operation always is in a transaction */ + INSERT_TXN, + + /** Ensure insert operation always is in a transaction, "for update" */ + INSERT_TXN_FOR_UPDATE, + + /** Ensure delete operation always is in a transaction */ + DELETE_TXN, + + /** Ensure delete operation always is in a transaction, "for update" */ + DELETE_TXN_FOR_UPDATE, +} diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java new file mode 100644 index 0000000..17efb70 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java @@ -0,0 +1,835 @@ +/* + * 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.gen; + +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Map; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.Label; +import org.cojen.classfile.LocalVariable; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.Opcode; +import org.cojen.classfile.TypeDesc; +import org.cojen.util.ClassInjector; +import org.cojen.util.KeyFactory; +import org.cojen.util.SoftValuedHashMap; + +import com.amazon.carbonado.ConstraintException; +import com.amazon.carbonado.OptimisticLockException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Transaction; + +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.sequence.SequenceValueProducer; + +import static com.amazon.carbonado.gen.CommonMethodNames.*; + +/** + * Generates and caches abstract implementations of {@link Storable} types + * suitable for use by master repositories. The generated classes extend those + * generated by {@link StorableGenerator}. Subclasses need not worry about + * transactions since this class takes care of that. + * + * @author Brian S O'Neill + */ +public final class MasterStorableGenerator { + // Note: All generated fields/methods have a "$" character in them to + // prevent name collisions with any inherited fields/methods. User storable + // properties are defined as fields which exactly match the property + // name. We don't want collisions with those either. Legal bean properties + // cannot have "$" in them, so there's nothing to worry about. + + /** Name of protected abstract method in generated storable */ + public static final String + DO_TRY_LOAD_MASTER_METHOD_NAME = StorableGenerator.DO_TRY_LOAD_METHOD_NAME, + DO_TRY_INSERT_MASTER_METHOD_NAME = "doTryInsert$master", + DO_TRY_UPDATE_MASTER_METHOD_NAME = "doTryUpdate$master", + DO_TRY_DELETE_MASTER_METHOD_NAME = "doTryDelete$master"; + + private static final String APPEND_UNINIT_PROPERTY = "appendUninitializedPropertyName$"; + + private static final String INSERT_OP = "Insert"; + private static final String UPDATE_OP = "Update"; + private static final String DELETE_OP = "Delete"; + + // Cache of generated abstract classes. + private static Map> cCache = new SoftValuedHashMap(); + + /** + * Returns an abstract implementation of the given Storable type, which + * is fully thread-safe. The Storable type itself may be an interface or + * a class. If it is a class, then it must not be final, and it must have a + * public, no-arg constructor. The constructor for the returned abstract + * class looks like this: + * + *

    +     * public <init>(MasterSupport);
    +     * 
    + * + * Subclasses must implement the following abstract protected methods, + * whose exact names are defined by constants in this class: + * + *
    +     * // Load the object by examining the primary key.
    +     * protected abstract boolean doTryLoad() throws FetchException;
    +     *
    +     * // Insert the object into the storage layer.
    +     * protected abstract boolean doTryInsert_master() throws PersistException;
    +     *
    +     * // Update the object in the storage.
    +     * protected abstract boolean doTryUpdate_master() throws PersistException;
    +     *
    +     * // Delete the object from the storage layer by the primary key.
    +     * protected abstract boolean doTryDelete_master() throws PersistException;
    +     * 
    + * + * Subclasses can access the MasterSupport instance via the protected field + * named by {@link StorableGenerator#SUPPORT_FIELD_NAME SUPPORT_FIELD_NAME}. + * + * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed + * @throws IllegalArgumentException if type is null + * @see MasterSupport + */ + public static Class + getAbstractClass(Class type, EnumSet features) + throws SupportException, IllegalArgumentException + { + StorableInfo info = StorableIntrospector.examine(type); + + anySequences: + if (features.contains(MasterFeature.INSERT_SEQUENCES)) { + for (StorableProperty property : info.getAllProperties().values()) { + if (property.getSequenceName() != null) { + break anySequences; + } + } + features.remove(MasterFeature.INSERT_SEQUENCES); + } + + if (info.getVersionProperty() == null) { + features.remove(MasterFeature.VERSIONING); + } + + if (features.contains(MasterFeature.VERSIONING)) { + // Implied feature. + features.add(MasterFeature.UPDATE_FULL); + } + + if (alwaysHasTxn(INSERT_OP, features)) { + // Implied feature. + features.add(MasterFeature.INSERT_TXN); + } + if (alwaysHasTxn(UPDATE_OP, features)) { + // Implied feature. + features.add(MasterFeature.UPDATE_TXN); + } + if (alwaysHasTxn(DELETE_OP, features)) { + // Implied feature. + features.add(MasterFeature.DELETE_TXN); + } + + if (requiresTxnForUpdate(INSERT_OP, features)) { + // Implied feature. + features.add(MasterFeature.INSERT_TXN_FOR_UPDATE); + } + if (requiresTxnForUpdate(UPDATE_OP, features)) { + // Implied feature. + features.add(MasterFeature.UPDATE_TXN_FOR_UPDATE); + } + if (requiresTxnForUpdate(DELETE_OP, features)) { + // Implied feature. + features.add(MasterFeature.DELETE_TXN_FOR_UPDATE); + } + + Object key = KeyFactory.createKey(new Object[] {type, features}); + + synchronized (cCache) { + Class abstractClass = (Class) cCache.get(key); + if (abstractClass != null) { + return abstractClass; + } + abstractClass = + new MasterStorableGenerator(type, features).generateAndInjectClass(); + cCache.put(key, abstractClass); + return abstractClass; + } + } + + private final EnumSet mFeatures; + private final StorableInfo mInfo; + private final Map> mAllProperties; + + private final ClassInjector mClassInjector; + private final ClassFile mClassFile; + + private MasterStorableGenerator(Class storableType, EnumSet features) { + mFeatures = features; + mInfo = StorableIntrospector.examine(storableType); + mAllProperties = mInfo.getAllProperties(); + + final Class abstractClass = StorableGenerator.getAbstractClass(storableType); + + mClassInjector = ClassInjector.create + (storableType.getName(), abstractClass.getClassLoader()); + + mClassFile = new ClassFile(mClassInjector.getClassName(), abstractClass); + mClassFile.setModifiers(mClassFile.getModifiers().toAbstract(true)); + mClassFile.markSynthetic(); + mClassFile.setSourceFile(MasterStorableGenerator.class.getName()); + mClassFile.setTarget("1.5"); + } + + private Class generateAndInjectClass() throws SupportException { + generateClass(); + Class abstractClass = mClassInjector.defineClass(mClassFile); + return (Class) abstractClass; + } + + private void generateClass() throws SupportException { + // Declare some types. + final TypeDesc storableType = TypeDesc.forClass(Storable.class); + final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); + final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); + final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + final TypeDesc optimisticLockType = TypeDesc.forClass(OptimisticLockException.class); + final TypeDesc persistExceptionType = TypeDesc.forClass(PersistException.class); + + // Add constructor that accepts a MasterSupport. + { + TypeDesc[] params = {masterSupportType}; + MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.loadLocal(b.getParameter(0)); + b.invokeSuperConstructor(new TypeDesc[] {triggerSupportType}); + + b.returnVoid(); + } + + // Declare protected abstract methods. + { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + } + + // Add required protected doTryInsert method. + { + // If sequence support requested, implement special insert hook to + // call sequences for properties which are UNINITIALIZED. User may + // provide explicit values for properties with sequences. + + if (mFeatures.contains(MasterFeature.INSERT_SEQUENCES)) { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED, + StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, + null, null); + CodeBuilder b = new CodeBuilder(mi); + + int ordinal = 0; + for (StorableProperty property : mAllProperties.values()) { + if (property.getSequenceName() != null) { + // Check the state of this property, to see if it is + // uninitialized. Uninitialized state has value zero. + + String stateFieldName = + StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); + + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + int shift = (ordinal & 0xf) * 2; + b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift); + b.math(Opcode.IAND); + + Label isInitialized = b.createLabel(); + b.ifZeroComparisonBranch(isInitialized, "!="); + + // Load this in preparation for storing value to property. + b.loadThis(); + + // Call MasterSupport.getSequenceValueProducer(String). + TypeDesc seqValueProdType = TypeDesc.forClass(SequenceValueProducer.class); + b.loadThis(); + b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); + b.checkCast(masterSupportType); + b.loadConstant(property.getSequenceName()); + b.invokeInterface + (masterSupportType, "getSequenceValueProducer", + seqValueProdType, new TypeDesc[] {TypeDesc.STRING}); + + // Find appropriate method to call for getting next sequence value. + TypeDesc propertyType = TypeDesc.forClass(property.getType()); + TypeDesc propertyObjType = propertyType.toObjectType(); + Method method; + + try { + if (propertyObjType == TypeDesc.LONG.toObjectType()) { + method = SequenceValueProducer.class + .getMethod("nextLongValue", (Class[]) null); + } else if (propertyObjType == TypeDesc.INT.toObjectType()) { + method = SequenceValueProducer.class + .getMethod("nextIntValue", (Class[]) null); + } else if (propertyObjType == TypeDesc.STRING) { + method = SequenceValueProducer.class + .getMethod("nextDecimalValue", (Class[]) null); + } else { + throw new SupportException + ("Unable to support sequence of type \"" + + property.getType().getName() + "\" for property: " + + property.getName()); + } + } catch (NoSuchMethodException e) { + Error err = new NoSuchMethodError(); + err.initCause(e); + throw err; + } + + b.invoke(method); + b.convert(TypeDesc.forClass(method.getReturnType()), propertyType); + + // Store property + b.storeField(property.getName(), propertyType); + + // Set state to dirty. + b.loadThis(); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift); + b.math(Opcode.IOR); + b.storeField(stateFieldName, TypeDesc.INT); + + isInitialized.setLocation(); + } + + ordinal++; + } + + // We've tried our best to fill in missing values, now run the + // original check method. + b.loadThis(); + b.invokeSuper(mClassFile.getSuperClassName(), + StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, + null, null); + b.returnVoid(); + } + + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED.toFinal(true), + StorableGenerator.DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + CodeBuilder b = new CodeBuilder(mi); + + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + + Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar); + + if (mFeatures.contains(MasterFeature.VERSIONING)) { + // Only set if uninitialized. + b.loadThis(); + b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME, + TypeDesc.BOOLEAN, null); + Label isInitialized = b.createLabel(); + b.ifZeroComparisonBranch(isInitialized, "!="); + addAdjustVersionProperty(b, null, 1); + isInitialized.setLocation(); + } + + if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) { + // Ensure that required properties have been set. + b.loadThis(); + b.invokeVirtual(StorableGenerator.IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, + TypeDesc.BOOLEAN, null); + Label isInitialized = b.createLabel(); + b.ifZeroComparisonBranch(isInitialized, "!="); + + // Throw a ConstraintException. + TypeDesc exType = TypeDesc.forClass(ConstraintException.class); + b.newObject(exType); + b.dup(); + + // Append all the uninitialized property names to the exception message. + + LocalVariable countVar = b.createLocalVariable(null, TypeDesc.INT); + b.loadConstant(0); + b.storeLocal(countVar); + + TypeDesc sbType = TypeDesc.forClass(StringBuilder.class); + b.newObject(sbType); + b.dup(); + b.loadConstant("Not all required properties have been set: "); + TypeDesc[] stringParam = {TypeDesc.STRING}; + b.invokeConstructor(sbType, stringParam); + LocalVariable sbVar = b.createLocalVariable(null, sbType); + b.storeLocal(sbVar); + + int ordinal = -1; + + HashSet stateAppendMethods = new HashSet(); + + // Parameters are: StringBuilder, count, mask, property name + TypeDesc[] appendParams = {sbType, TypeDesc.INT, TypeDesc.INT, TypeDesc.STRING}; + + for (StorableProperty property : mAllProperties.values()) { + ordinal++; + + if (property.isJoin() || property.isPrimaryKeyMember() + || property.isNullable() + || property.isAutomatic() || property.isVersion()) + { + continue; + } + + int stateField = ordinal >> 4; + + String stateAppendMethodName = APPEND_UNINIT_PROPERTY + stateField; + + if (!stateAppendMethods.contains(stateField)) { + stateAppendMethods.add(stateField); + + MethodInfo mi2 = mClassFile.addMethod + (Modifiers.PRIVATE, stateAppendMethodName, TypeDesc.INT, appendParams); + + CodeBuilder b2 = new CodeBuilder(mi2); + + // Load the StringBuilder parameter. + b2.loadLocal(b2.getParameter(0)); + + String stateFieldName = + StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); + + b2.loadThis(); + b2.loadField(stateFieldName, TypeDesc.INT); + // Load the mask parameter. + b2.loadLocal(b2.getParameter(2)); + b2.math(Opcode.IAND); + + Label propIsInitialized = b2.createLabel(); + b2.ifZeroComparisonBranch(propIsInitialized, "!="); + + // Load the count parameter. + b2.loadLocal(b2.getParameter(1)); + Label noComma = b2.createLabel(); + b2.ifZeroComparisonBranch(noComma, "=="); + b2.loadConstant(", "); + b2.invokeVirtual(sbType, "append", sbType, stringParam); + noComma.setLocation(); + + // Load the property name parameter. + b2.loadLocal(b2.getParameter(3)); + b2.invokeVirtual(sbType, "append", sbType, stringParam); + + // Increment the count parameter. + b2.integerIncrement(b2.getParameter(1), 1); + + propIsInitialized.setLocation(); + + // Return the possibly updated count. + b2.loadLocal(b2.getParameter(1)); + b2.returnValue(TypeDesc.INT); + } + + b.loadThis(); + // Parameters are: StringBuilder, count, mask, property name + b.loadLocal(sbVar); + b.loadLocal(countVar); + b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); + b.loadConstant(property.getName()); + b.invokePrivate(stateAppendMethodName, TypeDesc.INT, appendParams); + b.storeLocal(countVar); + } + + b.loadLocal(sbVar); + b.invokeVirtual(sbType, "toString", TypeDesc.STRING, null); + b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING}); + b.throwObject(); + + isInitialized.setLocation(); + } + + b.loadThis(); + b.invokeVirtual(DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (tryStart == null) { + b.returnValue(TypeDesc.BOOLEAN); + } else { + Label failed = b.createLabel(); + b.ifZeroComparisonBranch(failed, "=="); + + addCommitAndExitTransaction(b, INSERT_OP, txnVar); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + failed.setLocation(); + addExitTransaction(b, INSERT_OP, txnVar); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addExitTransaction(b, INSERT_OP, txnVar, tryStart); + } + } + + // Add required protected doTryUpdate method. + addDoTryUpdate: { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED.toFinal(true), + StorableGenerator.DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + CodeBuilder b = new CodeBuilder(mi); + + if ((!mFeatures.contains(MasterFeature.VERSIONING)) && + (!mFeatures.contains(MasterFeature.UPDATE_FULL)) && + (!mFeatures.contains(MasterFeature.UPDATE_TXN))) + { + // Nothing special needs to be done, so just delegate and return. + b.loadThis(); + b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + b.returnValue(TypeDesc.BOOLEAN); + break addDoTryUpdate; + } + + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + LocalVariable savedVar = null; + + Label tryStart = addEnterTransaction(b, UPDATE_OP, txnVar); + + Label failed = b.createLabel(); + + if (mFeatures.contains(MasterFeature.UPDATE_FULL)) { + // Storable saved = copy(); + b.loadThis(); + b.invokeVirtual(COPY_METHOD_NAME, storableType, null); + b.checkCast(mClassFile.getType()); + savedVar = b.createLocalVariable(null, mClassFile.getType()); + b.storeLocal(savedVar); + + // if (!saved.tryLoad()) { + // goto failed; + // } + b.loadLocal(savedVar); + b.invokeInterface(storableType, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + b.ifZeroComparisonBranch(failed, "=="); + + // if (version support enabled) { + // if (this.getVersionNumber() != saved.getVersionNumber()) { + // throw new OptimisticLockException + // (this.getVersionNumber(), saved.getVersionNumber(), this); + // } + // } + if (mFeatures.contains(MasterFeature.VERSIONING)) { + TypeDesc versionType = TypeDesc.forClass(mInfo.getVersionProperty().getType()); + b.loadThis(); + b.invoke(mInfo.getVersionProperty().getReadMethod()); + b.loadLocal(savedVar); + b.invoke(mInfo.getVersionProperty().getReadMethod()); + Label sameVersion = b.createLabel(); + CodeBuilderUtil.addValuesEqualCall(b, versionType, true, sameVersion, true); + b.newObject(optimisticLockType); + b.dup(); + b.loadThis(); + b.invoke(mInfo.getVersionProperty().getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.loadLocal(savedVar); + b.invoke(mInfo.getVersionProperty().getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.loadThis(); + b.invokeConstructor + (optimisticLockType, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType}); + b.throwObject(); + sameVersion.setLocation(); + } + + // this.copyDirtyProperties(saved); + // if (version support enabled) { + // saved.setVersionNumber(saved.getVersionNumber() + 1); + // } + b.loadThis(); + b.loadLocal(savedVar); + b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType}); + if (mFeatures.contains(MasterFeature.VERSIONING)) { + addAdjustVersionProperty(b, savedVar, -1); + } + + // if (!saved.doTryUpdateMaster()) { + // goto failed; + // } + b.loadLocal(savedVar); + b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + b.ifZeroComparisonBranch(failed, "=="); + + // saved.copyUnequalProperties(this); + b.loadLocal(savedVar); + b.loadThis(); + b.invokeInterface + (storableType, COPY_UNEQUAL_PROPERTIES, null, new TypeDesc[] {storableType}); + } else { + // if (!this.doTryUpdateMaster()) { + // goto failed; + // } + b.loadThis(); + b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + b.ifZeroComparisonBranch(failed, "=="); + } + + // txn.commit(); + // txn.exit(); + // return true; + addCommitAndExitTransaction(b, UPDATE_OP, txnVar); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + // failed: + // txn.exit(); + failed.setLocation(); + addExitTransaction(b, UPDATE_OP, txnVar); + // return false; + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addExitTransaction(b, UPDATE_OP, txnVar, tryStart); + } + + // Add required protected doTryDelete method. + { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED.toFinal(true), + StorableGenerator.DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(persistExceptionType); + CodeBuilder b = new CodeBuilder(mi); + + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + + Label tryStart = addEnterTransaction(b, DELETE_OP, txnVar); + + b.loadThis(); + b.invokeVirtual(DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (tryStart == null) { + b.returnValue(TypeDesc.BOOLEAN); + } else { + Label failed = b.createLabel(); + b.ifZeroComparisonBranch(failed, "=="); + addCommitAndExitTransaction(b, DELETE_OP, txnVar); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + failed.setLocation(); + addExitTransaction(b, DELETE_OP, txnVar); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addExitTransaction(b, DELETE_OP, txnVar, tryStart); + } + } + } + + /** + * Generates code to enter a transaction, if required. + * + * @param opType type of operation, Insert, Update, or Delete + * @param txnVar required variable of type Transaction for storing transaction + * @return optional try start label for transaction + */ + private Label addEnterTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { + if (!alwaysHasTxn(opType)) { + return null; + } + + // txn = masterSupport.getRootRepository().enterTransaction(); + + TypeDesc repositoryType = TypeDesc.forClass(Repository.class); + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); + TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); + + b.loadThis(); + b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); + b.invokeInterface(masterSupportType, "getRootRepository", + repositoryType, null); + b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, + transactionType, null); + b.storeLocal(txnVar); + if (requiresTxnForUpdate(opType)) { + // txn.setForUpdate(true); + b.loadLocal(txnVar); + b.loadConstant(true); + b.invokeInterface(transactionType, SET_FOR_UPDATE_METHOD_NAME, null, + new TypeDesc[] {TypeDesc.BOOLEAN}); + } + + return b.createLabel().setLocation(); + } + + private boolean alwaysHasTxn(String opType) { + return alwaysHasTxn(opType, mFeatures); + } + + private static boolean alwaysHasTxn(String opType, EnumSet features) { + if (opType == UPDATE_OP) { + return + features.contains(MasterFeature.UPDATE_TXN) || + features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || + features.contains(MasterFeature.VERSIONING) || + features.contains(MasterFeature.UPDATE_FULL); + } else if (opType == INSERT_OP) { + return + features.contains(MasterFeature.INSERT_TXN) || + features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); + } else if (opType == DELETE_OP) { + return + features.contains(MasterFeature.DELETE_TXN) || + features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); + } + return false; + } + + private boolean requiresTxnForUpdate(String opType) { + return requiresTxnForUpdate(opType, mFeatures); + } + + private static boolean requiresTxnForUpdate(String opType, EnumSet features) { + if (opType == UPDATE_OP) { + return + features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || + features.contains(MasterFeature.VERSIONING) || + features.contains(MasterFeature.UPDATE_FULL); + } else if (opType == INSERT_OP) { + return features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); + } else if (opType == DELETE_OP) { + return features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); + } + return false; + } + + private void addCommitAndExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { + if (!alwaysHasTxn(opType)) { + return; + } + + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + + // txn.commit(); + // txn.exit(); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + } + + /** + * + * @param opType type of operation, Insert, Update, or Delete + */ + private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { + if (!alwaysHasTxn(opType)) { + return; + } + + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + + // txn.exit(); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + } + + /** + * + * @param opType type of operation, Insert, Update, or Delete + */ + private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar, + Label tryStart) + { + if (tryStart == null) { + addExitTransaction(b, opType, txnVar); + return; + } + + // } catch (... e) { + // txn.exit(); + // throw e; + // } + + Label tryEnd = b.createLabel().setLocation(); + b.exceptionHandler(tryStart, tryEnd, null); + addExitTransaction(b, opType, txnVar); + b.throwObject(); + } + + /* + * Generates code to adjust the version property. If value parameter is negative, then + * version is incremented as follows: + * + * storable.setVersionNumber(storable.getVersionNumber() + 1); + * + * Otherwise, the version is set: + * + * storable.setVersionNumber(value); + * + * @param storableVar references storable instance, or null if this + * @param value if negative, increment version, else, set version to this value + */ + private void addAdjustVersionProperty(CodeBuilder b, + LocalVariable storableVar, + int value) + throws SupportException + { + // Push storable to stack in preparation for calling set method below. + if (storableVar == null) { + b.loadThis(); + } else { + b.loadLocal(storableVar); + } + + StorableProperty versionProperty = mInfo.getVersionProperty(); + TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); + + if (value >= 0) { + CodeBuilderUtil.initialVersion(b, versionType, value); + } else { + // Load current property value. + b.dup(); + b.invoke(versionProperty.getReadMethod()); + CodeBuilderUtil.incrementVersion(b, versionType); + } + + b.invoke(versionProperty.getWriteMethod()); + } +} diff --git a/src/main/java/com/amazon/carbonado/gen/MasterSupport.java b/src/main/java/com/amazon/carbonado/gen/MasterSupport.java new file mode 100644 index 0000000..cd1a183 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/MasterSupport.java @@ -0,0 +1,39 @@ +/* + * 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.gen; + +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.sequence.SequenceValueProducer; + +/** + * Provides runtime support for Storable classes generated by {@link MasterStorableGenerator}. + * + * @author Brian S O'Neill + */ +public interface MasterSupport extends TriggerSupport { + /** + * Returns a sequence value producer by name, or throw PersistException if not found. + * + *

    Note: this method throws PersistException even for fetch failures + * since this method is called by insert operations. Insert operations can + * only throw a PersistException. + */ + SequenceValueProducer getSequenceValueProducer(String name) throws PersistException; +} diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java new file mode 100644 index 0000000..cbfeab9 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java @@ -0,0 +1,3671 @@ +/* + * 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.gen; + +import java.lang.annotation.Annotation; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.math.BigInteger; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.Label; +import org.cojen.classfile.LocalVariable; +import org.cojen.classfile.MethodDesc; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.Opcode; +import org.cojen.classfile.TypeDesc; +import org.cojen.util.ClassInjector; +import org.cojen.util.WeakIdentityMap; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.FetchNoneException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.PersistNoneException; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.Transaction; +import com.amazon.carbonado.Trigger; +import com.amazon.carbonado.UniqueConstraintException; + +import com.amazon.carbonado.lob.Lob; + +import com.amazon.carbonado.info.ChainedProperty; +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableKey; +import com.amazon.carbonado.info.StorableProperty; +import com.amazon.carbonado.info.StorablePropertyAdapter; +import com.amazon.carbonado.info.StorablePropertyAnnotation; +import com.amazon.carbonado.info.StorablePropertyConstraint; + +import static com.amazon.carbonado.gen.CommonMethodNames.*; + +/** + * Generates and caches abstract and wrapped implementations of {@link + * Storable} types. This greatly simplifies the process of defining new kinds + * of {@link Repository Repositories}, since most of the mundane code + * generation is taken care of. + * + * @author Brian S O'Neill + * @author Don Schneider + * @see MasterStorableGenerator + */ +public final class StorableGenerator { + + // Note: All generated fields/methods have a "$" character in them to + // prevent name collisions with any inherited fields/methods. User storable + // properties are defined as fields which exactly match the property + // name. We don't want collisions with those either. Legal bean properties + // cannot have "$" in them, so there's nothing to worry about. + + /** Name of protected abstract method in generated storable */ + public static final String + DO_TRY_LOAD_METHOD_NAME = "doTryLoad$", + DO_TRY_INSERT_METHOD_NAME = "doTryInsert$", + DO_TRY_UPDATE_METHOD_NAME = "doTryUpdate$", + DO_TRY_DELETE_METHOD_NAME = "doTryDelete$"; + + /** + * Name of protected method in generated storable which checks that + * primary keys are initialized, throwing an exception otherwise. + */ + public static final String + CHECK_PK_FOR_INSERT_METHOD_NAME = "checkPkForInsert$", + CHECK_PK_FOR_UPDATE_METHOD_NAME = "checkPkForUpdate$", + CHECK_PK_FOR_DELETE_METHOD_NAME = "checkPkForDelete$"; + + /** + * Name of protected method in generated storable that returns false if any + * primary keys are uninitialized. + */ + public static final String IS_PK_INITIALIZED_METHOD_NAME = "isPkInitialized$"; + + /** + * Name prefix of protected method in generated storable that returns false + * if a specific alternate key is uninitialized. The complete name is + * formed by the prefix appended with the zero-based alternate key ordinal. + */ + public static final String IS_ALT_KEY_INITIALIZED_PREFIX = "isAltKeyInitialized$"; + + /** + * Name of protected method in generated storable that returns false if any + * non-nullable, non-pk properties are uninitialized. + */ + public static final String IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME = + "isRequiredDataInitialized$"; + + /** + * Name of protected method in generated storable that returns false if + * version property is uninitialized. If no version property exists, then + * this method is not defined. + */ + public static final String IS_VERSION_INITIALIZED_METHOD_NAME = "isVersionInitialized$"; + + /** + * Prefix of protected field in generated storable that holds property + * states. Each property consumes two bits to hold its state, and so each + * 32-bit field holds states for up to 16 properties. + */ + public static final String PROPERTY_STATE_FIELD_NAME = "propertyState$"; + + /** Adapter field names are propertyName + "$adapter$" + ordinal */ + public static final String ADAPTER_FIELD_ELEMENT = "$adapter$"; + + /** Constraint field names are propertyName + "$constraint$" + ordinal */ + public static final String CONSTRAINT_FIELD_ELEMENT = "$constraint$"; + + /** Reference to TriggerSupport or WrappedSupport instance */ + public static final String SUPPORT_FIELD_NAME = "support$"; + + /** Property state indicating that property has never been set, loaded, or saved */ + public static final int PROPERTY_STATE_UNINITIALIZED = 0; + /** Property state indicating that property has been set, but not saved */ + public static final int PROPERTY_STATE_DIRTY = 3; + /** Property state indicating that property value reflects a clean value */ + public static final int PROPERTY_STATE_CLEAN = 1; + /** Property state mask is 3, to cover the two bits used by a property state */ + public static final int PROPERTY_STATE_MASK = 3; + + // Private method which returns a property's state. + private static final String PROPERTY_STATE_EXTRACT_METHOD_NAME = "extractState$"; + + private static final String PRIVATE_INSERT_METHOD_NAME = "insert$"; + private static final String PRIVATE_UPDATE_METHOD_NAME = "update$"; + private static final String PRIVATE_DELETE_METHOD_NAME = "delete$"; + + // Cache of generated abstract classes. + private static Map>> cAbstractCache; + // Cache of generated wrapped classes. + private static Map>> cWrappedCache; + + static { + cAbstractCache = new WeakIdentityMap(); + cWrappedCache = new WeakIdentityMap(); + } + + // There are three flavors of equals methods, used by addEqualsMethod. + private static final int EQUAL_KEYS = 0; + private static final int EQUAL_PROPERTIES = 1; + private static final int EQUAL_FULL = 2; + + // Operation mode for generating Storable. + private static final int GEN_ABSTRACT = 1; + private static final int GEN_WRAPPED = 2; + + private static final String WRAPPED_STORABLE_FIELD_NAME = "wrappedStorable$"; + + private static final String UNCAUGHT_METHOD_NAME = "uncaught$"; + + private static final String INSERT_OP = "Insert"; + private static final String UPDATE_OP = "Update"; + private static final String DELETE_OP = "Delete"; + + /** + * Returns an abstract implementation of the given Storable type, which is + * fully thread-safe. The Storable type itself may be an interface or a + * class. If it is a class, then it must not be final, and it must have a + * public, no-arg constructor. The constructor signature for the returned + * abstract is defined as follows: + * + *

    +     * /**
    +     *  * @param support  Access to triggers
    +     *  */
    +     * public <init>(TriggerSupport support);
    +     * 
    + * + *

    Subclasses must implement the following abstract protected methods, + * whose exact names are defined by constants in this class: + * + *

    +     * // Load the object by examining the primary key.
    +     * protected abstract boolean doTryLoad() throws FetchException;
    +     *
    +     * // Insert the object into the storage layer.
    +     * protected abstract boolean doTryInsert() throws PersistException;
    +     *
    +     * // Update the object in the storage.
    +     * protected abstract boolean doTryUpdate() throws PersistException;
    +     *
    +     * // Delete the object from the storage layer by the primary key.
    +     * protected abstract boolean doTryDelete() throws PersistException;
    +     * 
    + * + * A set of protected hook methods are provided which ensure that all + * primary keys are initialized before performing a repository + * operation. Subclasses may override them, if they are capable of filling + * in unspecified primary keys. One such example is applying a sequence on + * insert. + * + *
    +     * // Throws exception if any primary keys are uninitialized.
    +     * // Actual method name defined by CHECK_PK_FOR_INSERT_METHOD_NAME.
    +     * protected void checkPkForInsert() throws IllegalStateException;
    +     *
    +     * // Throws exception if any primary keys are uninitialized.
    +     * // Actual method name defined by CHECK_PK_FOR_UPDATE_METHOD_NAME.
    +     * protected void checkPkForUpdate() throws IllegalStateException;
    +     *
    +     * // Throws exception if any primary keys are uninitialized.
    +     * // Actual method name defined by CHECK_PK_FOR_DELETE_METHOD_NAME.
    +     * protected void checkPkForDelete() throws IllegalStateException;
    +     * 
    + * + * Each property value is defined as a protected field whose name and type + * matches the property. Subclasses should access these fields directly + * during loading and storing. For loading, it bypasses constraint + * checks. For both, it provides better performance. + * + *

    Subclasses also have access to a set of property state bits stored + * in protected int fields. Subclasses are not responsible for updating + * these values. The intention is that these states may be used by + * subclasses to support partial updates. They may otherwise be ignored. + * + *

    As a convenience, protected methods are provided to test and alter + * the property state bits. Subclass constructors that fill all properties + * with loaded values must call markAllPropertiesClean to ensure all + * properties are identified as being valid. + * + *

    +     * // Returns true if all primary key properties have been set.
    +     * protected boolean isPkInitialized();
    +     *
    +     * // Returns true if all required data properties are set.
    +     * // A required data property is a non-nullable, non-primary key.
    +     * protected boolean isRequiredDataInitialized();
    +     *
    +     * // Returns true if a version property has been set.
    +     * // Note: This method is not generated if there is no version property.
    +     * protected boolean isVersionInitialized();
    +     * 
    + * + * Property state field names are defined by the concatenation of + * {@code PROPERTY_STATE_FIELD_NAME} and a zero-based decimal + * ordinal. To determine which field holds a particular property's state, + * the field ordinal is computed as the property ordinal divided by 16. The + * specific two-bit state position is the remainder of this division times 2. + * + * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed + * @throws IllegalArgumentException if type is null + */ + @SuppressWarnings("unchecked") + public static Class getAbstractClass(Class type) + throws IllegalArgumentException + { + synchronized (cAbstractCache) { + Class abstractClass; + Reference> ref = cAbstractCache.get(type); + if (ref != null) { + abstractClass = (Class) ref.get(); + if (abstractClass != null) { + return abstractClass; + } + } + abstractClass = new StorableGenerator(type, GEN_ABSTRACT).generateAndInjectClass(); + cAbstractCache.put(type, new SoftReference>(abstractClass)); + return abstractClass; + } + } + + /** + * Returns a concrete Storable implementation of the given type which wraps + * another Storable. The Storable type itself may be an interface or a + * class. If it is a class, then it must not be final, and it must have a + * public, no-arg constructor. The constructor signature for the returned + * class is defined as follows: + * + *
    +     * /**
    +     *  * @param support  Custom implementation for Storable CRUD operations
    +     *  * @param storable Storable being wrapped
    +     *  */
    +     * public <init>(WrappedSupport support, Storable storable);
    +     * 
    + * + *

    Instances of the wrapped Storable delegate to the WrappedSupport for + * all CRUD operations: + * + *

      + *
    • load and tryLoad + *
    • insert and tryInsert + *
    • update and tryUpdate + *
    • delete and tryDelete + *
    + * + *

    Methods which delegate to wrapped Storable: + * + *

      + *
    • all ordinary user-defined properties + *
    • copyAllProperties + *
    • copyPrimaryKeyProperties + *
    • copyVersionProperty + *
    • copyUnequalProperties + *
    • copyDirtyProperties + *
    • hasDirtyProperties + *
    • markPropertiesClean + *
    • markAllPropertiesClean + *
    • markPropertiesDirty + *
    • markAllPropertiesDirty + *
    • hashCode + *
    • equalPrimaryKeys + *
    • equalProperties + *
    • toString + *
    • toStringKeyOnly + *
    + * + *

    Methods with special implementation: + * + *

      + *
    • all user-defined join properties (join properties query using wrapper's Storage) + *
    • storage (returns Storage used by wrapper) + *
    • storableType (returns literal class) + *
    • copy (delegates to wrapped storable, but bridge methods must be defined as well) + *
    • equals (compares Storage instance and properties) + *
    + * + * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed + * @throws IllegalArgumentException if type is null + */ + @SuppressWarnings("unchecked") + public static Class getWrappedClass(Class type) + throws IllegalArgumentException + { + synchronized (cWrappedCache) { + Class wrappedClass; + Reference> ref = cWrappedCache.get(type); + if (ref != null) { + wrappedClass = (Class) ref.get(); + if (wrappedClass != null) { + return wrappedClass; + } + } + wrappedClass = new StorableGenerator(type, GEN_WRAPPED).generateAndInjectClass(); + cWrappedCache.put(type, new SoftReference>(wrappedClass)); + return wrappedClass; + } + } + + private final Class mStorableType; + private final int mGenMode; + private final TypeDesc mSupportType; + private final StorableInfo mInfo; + private final Map> mAllProperties; + private final boolean mHasJoins; + + private final ClassInjector mClassInjector; + private final ClassFile mClassFile; + + private StorableGenerator(Class storableType, int genMode) { + mStorableType = storableType; + mGenMode = genMode; + if (genMode == GEN_WRAPPED) { + mSupportType = TypeDesc.forClass(WrappedSupport.class); + } else { + mSupportType = TypeDesc.forClass(TriggerSupport.class); + } + mInfo = StorableIntrospector.examine(storableType); + mAllProperties = mInfo.getAllProperties(); + + boolean hasJoins = false; + for (StorableProperty property : mAllProperties.values()) { + if (property.isJoin()) { + hasJoins = true; + break; + } + } + mHasJoins = hasJoins; + + mClassInjector = ClassInjector.create + (storableType.getName(), storableType.getClassLoader()); + mClassFile = CodeBuilderUtil.createStorableClassFile + (mClassInjector, storableType, genMode == GEN_ABSTRACT, + StorableGenerator.class.getName()); + } + + private Class generateAndInjectClass() { + generateClass(); + Class abstractClass = mClassInjector.defineClass(mClassFile); + return (Class) abstractClass; + } + + private void generateClass() { + // Use this static method for passing uncaught exceptions. + defineUncaughtExceptionHandler(); + + // private final TriggerSupport support; + // Field is not final for GEN_WRAPPED, so that copy method can + // change WrappedSupport after calling clone. + mClassFile.addField(Modifiers.PROTECTED.toFinal(mGenMode == GEN_ABSTRACT), + SUPPORT_FIELD_NAME, + mSupportType); + + if (mGenMode == GEN_WRAPPED) { + // Add a few more fields to hold arguments passed from constructor. + + // private final wrappedStorable; + // Field is not final for GEN_WRAPPED, so that copy method can + // change wrapped Storable after calling clone. + mClassFile.addField(Modifiers.PRIVATE.toFinal(false), + WRAPPED_STORABLE_FIELD_NAME, + TypeDesc.forClass(mStorableType)); + } + + if (mGenMode == GEN_ABSTRACT) { + // Add protected constructor. + TypeDesc[] params = {mSupportType}; + + final int supportParam = 0; + MethodInfo mi = mClassFile.addConstructor(Modifiers.PROTECTED, params); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.invokeSuperConstructor(null); + + //// this.support = support + b.loadThis(); + b.loadLocal(b.getParameter(supportParam)); + b.storeField(SUPPORT_FIELD_NAME, mSupportType); + + b.returnVoid(); + } else if (mGenMode == GEN_WRAPPED) { + // Add public constructor. + TypeDesc[] params = {mSupportType, TypeDesc.forClass(Storable.class)}; + + final int wrappedSupportParam = 0; + final int wrappedStorableParam = 1; + MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.invokeSuperConstructor(null); + + //// this.wrappedSupport = wrappedSupport + b.loadThis(); + b.loadLocal(b.getParameter(wrappedSupportParam)); + b.storeField(SUPPORT_FIELD_NAME, mSupportType); + + //// this.wrappedStorable = wrappedStorable + b.loadThis(); + b.loadLocal(b.getParameter(wrappedStorableParam)); + b.checkCast(TypeDesc.forClass(mStorableType)); + b.storeField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + + b.returnVoid(); + } + + // Add static fields for adapters and constraints, and create static + // initializer to populate fields. + if (mGenMode == GEN_ABSTRACT) { + // CodeBuilder for static initializer, defined only if there's + // something to put in it. + CodeBuilder clinit = null; + + // Adapter and constraint fields are protected static. + final Modifiers fieldModifiers = Modifiers.PROTECTED.toStatic(true).toFinal(true); + + // Add adapter field. + for (StorableProperty property : mAllProperties.values()) { + StorablePropertyAdapter spa = property.getAdapter(); + if (spa == null) { + continue; + } + + String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; + TypeDesc adapterType = TypeDesc.forClass + (spa.getAdapterConstructor().getDeclaringClass()); + mClassFile.addField(fieldModifiers, fieldName, adapterType); + + if (clinit == null) { + clinit = new CodeBuilder(mClassFile.addInitializer()); + } + + // Assign value to new field. + // admin$adapter$0 = new YesNoAdapter.Adapter + // (UserInfo.class, "admin", annotation); + + clinit.newObject(adapterType); + clinit.dup(); + clinit.loadConstant(TypeDesc.forClass(mStorableType)); + clinit.loadConstant(property.getName()); + + // Generate code to load property annotation third parameter. + loadPropertyAnnotation(clinit, property, spa.getAnnotation()); + + clinit.invoke(spa.getAdapterConstructor()); + clinit.storeStaticField(fieldName, adapterType); + } + + // Add contraint fields. + for (StorableProperty property : mAllProperties.values()) { + int count = property.getConstraintCount(); + for (int i=0; i property : mAllProperties.values()) { + ordinal++; + + if (property.isVersion()) { + versionOrdinal = ordinal; + } + + final String name = property.getName(); + final TypeDesc type = TypeDesc.forClass(property.getType()); + + if (property.isJoin()) { + // If generating wrapper, property access is not guarded by + // synchronization. Mark as volatile instead. + mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED), + name, type); + requireStateField = true; + } else if (mGenMode == GEN_ABSTRACT) { + // Only define regular property fields if abstract + // class. Wrapped class doesn't reference them. Double + // words are volatile to prevent word tearing without + // explicit synchronization. + mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()), + name, type); + requireStateField = true; + } + + final String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); + if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) { + if (requireStateField) { + // If generating wrapper, property state access is not guarded by + // synchronization. Mark as volatile instead. + mClassFile.addField + (Modifiers.PROTECTED.toVolatile(mGenMode == GEN_WRAPPED), + stateFieldName, TypeDesc.INT); + } + requireStateField = false; + } + + // Add read method. + buildReadMethod: { + Method readMethod = property.getReadMethod(); + + MethodInfo mi; + if (readMethod != null) { + mi = mClassFile.addMethod(readMethod); + } else { + // Add a synthetic protected read method. + String readName = property.getReadMethodName(); + mi = mClassFile.addMethod(Modifiers.PROTECTED, readName, type, null); + mi.markSynthetic(); + if (property.isJoin()) { + mi.addException(TypeDesc.forClass(FetchException.class)); + } + } + + if (mGenMode == GEN_ABSTRACT && property.isJoin()) { + // Synchronization is required for join property + // accessors, as they may alter bit masks. + mi.setModifiers(mi.getModifiers().toSynchronized(true)); + } + + // Now add code that actually gets the property value. + CodeBuilder b = new CodeBuilder(mi); + + if (property.isJoin()) { + // Join properties support on-demand loading. + + // Check if property has been loaded. + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); + b.math(Opcode.IAND); + Label isLoaded = b.createLabel(); + b.ifZeroComparisonBranch(isLoaded, "!="); + + // Store loaded join result here. + LocalVariable join = b.createLocalVariable(name, type); + + // Check if any internal properties are nullable, but + // the matching external property is not. If so, load + // each of these special internal values and check if + // null. If null, short-circuit the load and use null + // as the join result. + + Label shortCircuit = b.createLabel(); + buildShortCircuit: { + int count = property.getJoinElementCount(); + nullPossible: { + for (int i=0; i internal = property.getInternalJoinElement(i); + if (mGenMode == GEN_ABSTRACT) { + b.loadThis(); + b.loadField(internal.getName(), + TypeDesc.forClass(internal.getType())); + } else { + b.loadThis(); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, + TypeDesc.forClass(mStorableType)); + b.invoke(internal.getReadMethod()); + } + TypeDesc bindType = + CodeBuilderUtil.bindQueryParam(internal.getType()); + CodeBuilderUtil.convertValue + (b, internal.getType(), bindType.toClass()); + b.invokeInterface(queryType, WITH_METHOD_NAME, queryType, + new TypeDesc[]{bindType}); + } + + // Now run the query. + if (property.isQuery()) { + // Just save and return the query. + b.storeLocal(join); + } else { + String loadMethod = + property.isNullable() ? + TRY_LOAD_ONE_METHOD_NAME : + LOAD_ONE_METHOD_NAME; + b.invokeInterface(queryType, loadMethod, storableDesc, null); + b.checkCast(type); + b.storeLocal(join); + } + } + + // Store loaded property. + shortCircuit.setLocation(); + b.loadThis(); + b.loadLocal(join); + b.storeField(property.getName(), type); + + // Add code to identify this property as being loaded. + b.loadThis(); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); + b.math(Opcode.IOR); + b.storeField(stateFieldName, TypeDesc.INT); + + isLoaded.setLocation(); + } + + // Load property value and return it. + + if (mGenMode == GEN_ABSTRACT || property.isJoin()) { + b.loadThis(); + b.loadField(property.getName(), type); + } else { + b.loadThis(); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + b.invoke(readMethod); + } + + b.returnValue(type); + } + + // Add write method. + if (!property.isQuery()) { + Method writeMethod = property.getWriteMethod(); + + MethodInfo mi; + if (writeMethod != null) { + mi = mClassFile.addMethod(writeMethod); + } else { + // Add a synthetic protected write method. + String writeName = property.getWriteMethodName(); + mi = mClassFile.addMethod(Modifiers.PROTECTED, writeName, null, + new TypeDesc[]{type}); + mi.markSynthetic(); + } + + if (mGenMode == GEN_ABSTRACT) { + mi.setModifiers(mi.getModifiers().toSynchronized(true)); + } + CodeBuilder b = new CodeBuilder(mi); + + // Primary keys cannot be altered if state is "clean". + if (mGenMode == GEN_ABSTRACT && property.isPrimaryKeyMember()) { + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); + b.math(Opcode.IAND); + b.loadConstant(PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2)); + Label isMutable = b.createLabel(); + b.ifComparisonBranch(isMutable, "!="); + CodeBuilderUtil.throwException + (b, IllegalStateException.class, "Cannot alter primary key"); + isMutable.setLocation(); + } + + int spcCount = property.getConstraintCount(); + + boolean nullNotAllowed = + !property.getType().isPrimitive() && + !property.isJoin() && !property.isNullable(); + + if (mGenMode == GEN_ABSTRACT && (nullNotAllowed || spcCount > 0)) { + // Add constraint checks. + Label skipConstraints = b.createLabel(); + + if (nullNotAllowed) { + // Don't allow null value to be set. + b.loadLocal(b.getParameter(0)); + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + CodeBuilderUtil.throwException + (b, IllegalArgumentException.class, + "Cannot set property \"" + property.getName() + + "\" to null"); + notNull.setLocation(); + } else { + // Don't invoke constraints if value is null. + if (!property.getType().isPrimitive()) { + b.loadLocal(b.getParameter(0)); + b.ifNullBranch(skipConstraints, true); + } + } + + // Add code to invoke constraints. + + for (int spcIndex = 0; spcIndex < spcCount; spcIndex++) { + StorablePropertyConstraint spc = property.getConstraint(spcIndex); + String fieldName = + property.getName() + CONSTRAINT_FIELD_ELEMENT + spcIndex; + TypeDesc constraintType = TypeDesc.forClass + (spc.getConstraintConstructor().getDeclaringClass()); + b.loadStaticField(fieldName, constraintType); + b.loadLocal(b.getParameter(0)); + b.convert + (b.getParameter(0).getType(), TypeDesc.forClass + (spc.getConstrainMethod().getParameterTypes()[0])); + b.invoke(spc.getConstrainMethod()); + } + + skipConstraints.setLocation(); + } + + Label setValue = b.createLabel(); + + if (!property.isJoin() || Lob.class.isAssignableFrom(property.getType())) { + if (mGenMode == GEN_ABSTRACT) { + if (Lob.class.isAssignableFrom(property.getType())) { + // Contrary to how standard properties are managed, + // only mark dirty if value changed. + b.loadThis(); + b.loadField(property.getName(), type); + b.loadLocal(b.getParameter(0)); + CodeBuilderUtil.addValuesEqualCall(b, type, true, setValue, true); + } + } + + markOrdinaryPropertyDirty(b, property); + } else { + // If passed value is null, throw an + // IllegalArgumentException. Passing in null could also + // indicate that the property should be unloaded, but + // that is non-intuitive. + + b.loadLocal(b.getParameter(0)); + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null); + notNull.setLocation(); + + // Copy internal properties from joined object. + int count = property.getJoinElementCount(); + for (int i=0; i> 4), TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_MASK << ((ord & 0xf) * 2)); + b.math(Opcode.IAND); + b.loadConstant(PROPERTY_STATE_CLEAN << ((ord & 0xf) * 2)); + // If not clean, skip equal check. + b.ifComparisonBranch(setInternalProp, "!="); + } else { + // Call the public isPropertyClean method since + // the raw state bits are hidden. + b.loadThis(); + b.loadConstant(internal.getName()); + b.invokeVirtual(IS_PROPERTY_CLEAN, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.STRING}); + // If not clean, skip equal check. + b.ifZeroComparisonBranch(setInternalProp, "=="); + } + + // If new internal property value is equal to + // existing value, skip setting it. + b.loadThis(); + b.invoke(internal.getReadMethod()); + b.loadLocal(newInternalPropVar); + Label skipSetInternalProp = b.createLabel(); + CodeBuilderUtil.addValuesEqualCall + (b, TypeDesc.forClass(internal.getType()), + true, skipSetInternalProp, true); + + setInternalProp.setLocation(); + + // Call set method to ensure that state bits are + // properly adjusted. + b.loadThis(); + b.loadLocal(newInternalPropVar); + b.invoke(internal.getWriteMethod()); + + skipSetInternalProp.setLocation(); + } + + // Add code to identify this property as being loaded. + b.loadThis(); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); + b.math(Opcode.IOR); + b.storeField(stateFieldName, TypeDesc.INT); + } + + // Now add code that actually sets the property value. + + setValue.setLocation(); + + if (mGenMode == GEN_ABSTRACT || property.isJoin()) { + b.loadThis(); + b.loadLocal(b.getParameter(0)); + b.storeField(property.getName(), type); + } else { + b.loadThis(); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + b.loadLocal(b.getParameter(0)); + b.invoke(writeMethod); + } + + b.returnVoid(); + } + + // Add optional protected adapted read methods. + if (mGenMode == GEN_ABSTRACT && property.getAdapter() != null) { + // End name with '$' to prevent any possible collisions. + String readName = property.getReadMethodName() + '$'; + + StorablePropertyAdapter adapter = property.getAdapter(); + + for (Method adaptMethod : adapter.findAdaptMethodsFrom(type.toClass())) { + TypeDesc toType = TypeDesc.forClass(adaptMethod.getReturnType()); + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED, readName, toType, null); + mi.markSynthetic(); + + // Now add code that actually gets the property value and + // then invokes adapt method. + CodeBuilder b = new CodeBuilder(mi); + + // Push adapter class to stack. + String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; + TypeDesc adapterType = TypeDesc.forClass + (adapter.getAdapterConstructor().getDeclaringClass()); + b.loadStaticField(fieldName, adapterType); + + // Load property value. + b.loadThis(); + b.loadField(property.getName(), type); + + b.invoke(adaptMethod); + b.returnValue(toType); + } + } + + // Add optional protected adapted write methods. + + // Note: Calling these methods does not affect any state bits. + // They are only intended to be used by subclasses during loading. + + if (mGenMode == GEN_ABSTRACT && property.getAdapter() != null) { + // End name with '$' to prevent any possible collisions. + String writeName = property.getWriteMethodName() + '$'; + + StorablePropertyAdapter adapter = property.getAdapter(); + + for (Method adaptMethod : adapter.findAdaptMethodsTo(type.toClass())) { + TypeDesc fromType = TypeDesc.forClass(adaptMethod.getParameterTypes()[0]); + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED, writeName, null, new TypeDesc[] {fromType}); + mi.markSynthetic(); + mi.setModifiers(mi.getModifiers().toSynchronized(true)); + + // Now add code that actually adapts parameter and then + // stores the property value. + CodeBuilder b = new CodeBuilder(mi); + + // Push this in preparation for storing a field. + b.loadThis(); + + // Push adapter class to stack. + String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; + TypeDesc adapterType = TypeDesc.forClass + (adapter.getAdapterConstructor().getDeclaringClass()); + b.loadStaticField(fieldName, adapterType); + + b.loadLocal(b.getParameter(0)); + b.invoke(adaptMethod); + b.storeField(property.getName(), type); + + b.returnVoid(); + } + } + } + } + + // Add tryLoad method which delegates to abstract doTryLoad method. + addTryLoad: { + // Define the tryLoad method. + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), + TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryLoad; + } + + mi.addException(TypeDesc.forClass(FetchException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, null, false, null); + break addTryLoad; + } + + CodeBuilder b = new CodeBuilder(mi); + + // Check that primary key is initialized. + b.loadThis(); + b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); + Label pkInitialized = b.createLabel(); + b.ifZeroComparisonBranch(pkInitialized, "!="); + + Label loaded = b.createLabel(); + Label notLoaded = b.createLabel(); + + if (mInfo.getAlternateKeyCount() == 0) { + CodeBuilderUtil.throwException(b, IllegalStateException.class, + "Primary key not fully specified"); + } else { + // If any alternate keys, check them too. + + // Load our Storage, in preparation for query against it. + loadStorageForFetch(b, TypeDesc.forClass(mStorableType)); + + Label runQuery = b.createLabel(); + TypeDesc queryType = TypeDesc.forClass(Query.class); + + for (int i=0; i altKey = mInfo.getAlternateKey(i); + + // Form query filter. + StringBuilder queryBuilder = new StringBuilder(); + for (OrderedProperty op : altKey.getProperties()) { + if (queryBuilder.length() > 0) { + queryBuilder.append(" & "); + } + queryBuilder.append(op.getChainedProperty().toString()); + queryBuilder.append(" = ?"); + } + + // Get query instance from Storage already loaded on stack. + b.loadConstant(queryBuilder.toString()); + b.invokeInterface(TypeDesc.forClass(Storage.class), + QUERY_METHOD_NAME, queryType, + new TypeDesc[]{TypeDesc.STRING}); + + // Now fill in the parameters of the query. + for (OrderedProperty op : altKey.getProperties()) { + StorableProperty prop = op.getChainedProperty().getPrimeProperty(); + b.loadThis(); + TypeDesc propType = TypeDesc.forClass(prop.getType()); + b.loadField(prop.getName(), propType); + TypeDesc bindType = CodeBuilderUtil.bindQueryParam(prop.getType()); + CodeBuilderUtil.convertValue(b, prop.getType(), bindType.toClass()); + b.invokeInterface(queryType, WITH_METHOD_NAME, queryType, + new TypeDesc[]{bindType}); + } + + b.branch(runQuery); + + noAltKey.setLocation(); + } + + CodeBuilderUtil.throwException(b, IllegalStateException.class, + "Primary or alternate key not fully specified"); + + // Run query sitting on the stack. + runQuery.setLocation(); + + b.invokeInterface(queryType, TRY_LOAD_ONE_METHOD_NAME, + TypeDesc.forClass(Storable.class), null); + LocalVariable fetchedVar = b.createLocalVariable(null, TypeDesc.OBJECT); + b.storeLocal(fetchedVar); + + // If query fetch is null, then object not found. Return false. + b.loadLocal(fetchedVar); + b.ifNullBranch(notLoaded, true); + + // Copy all properties from fetched object into this one. + + // Allow copy to destroy everything, including primary key. + b.loadThis(); + b.invokeVirtual(MARK_ALL_PROPERTIES_DIRTY, null, null); + + b.loadLocal(fetchedVar); + b.checkCast(TypeDesc.forClass(mStorableType)); + b.loadThis(); + b.invokeInterface(TypeDesc.forClass(Storable.class), + COPY_ALL_PROPERTIES, null, + new TypeDesc[] {TypeDesc.forClass(Storable.class)}); + + b.branch(loaded); + } + + pkInitialized.setLocation(); + + // Call doTryLoad and mark all properties as clean if load succeeded. + b.loadThis(); + b.invokeVirtual(DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + + b.ifZeroComparisonBranch(notLoaded, "=="); + + loaded.setLocation(); + // Only mark properties clean if doTryLoad returned true. + b.loadThis(); + b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + notLoaded.setLocation(); + // Mark properties dirty, to be consistent with a delete side-effect. + b.loadThis(); + b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + if (mGenMode == GEN_ABSTRACT) { + // Define the abstract method. + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(TypeDesc.forClass(FetchException.class)); + } + } + + // Add load method which calls tryLoad. + addLoad: { + // Define the load method. + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), + LOAD_METHOD_NAME, null, null); + + if (mi == null) { + break addLoad; + } + + mi.addException(TypeDesc.forClass(FetchException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, null, false, FetchNoneException.class); + break addLoad; + } + + CodeBuilder b = new CodeBuilder(mi); + + // Call tryLoad and throw an exception if false returned. + b.loadThis(); + b.invokeVirtual(TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + + Label wasNotLoaded = b.createLabel(); + b.ifZeroComparisonBranch(wasNotLoaded, "=="); + b.returnVoid(); + + wasNotLoaded.setLocation(); + + TypeDesc noMatchesType = TypeDesc.forClass(FetchNoneException.class); + b.newObject(noMatchesType); + b.dup(); + b.loadThis(); + b.invokeVirtual(TO_STRING_KEY_ONLY_METHOD_NAME, TypeDesc.STRING, null); + b.invokeConstructor(noMatchesType, new TypeDesc[] {TypeDesc.STRING}); + b.throwObject(); + } + + final TypeDesc triggerType = TypeDesc.forClass(Trigger.class); + final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + + // Add insert(boolean forTry) method which delegates to abstract doTryInsert method. + if (mGenMode == GEN_ABSTRACT) { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PRIVATE.toSynchronized(true), + PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); + mi.addException(TypeDesc.forClass(PersistException.class)); + + CodeBuilder b = new CodeBuilder(mi); + + LocalVariable forTryVar = b.getParameter(0); + LocalVariable triggerVar = b.createLocalVariable(null, triggerType); + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); + + Label tryStart = addGetTriggerAndEnterTxn + (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar); + + // Perform pk check after trigger has run, to allow it to define pk. + requirePkInitialized(b, CHECK_PK_FOR_INSERT_METHOD_NAME); + + // Call doTryInsert. + b.loadThis(); + b.invokeVirtual(DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); + + Label notInserted = b.createLabel(); + b.ifZeroComparisonBranch(notInserted, "=="); + + addTriggerAfterAndExitTxn + (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar); + + // Only mark properties clean if doTryInsert returned true. + b.loadThis(); + b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + notInserted.setLocation(); + addTriggerFailedAndExitTxn(b, INSERT_OP, triggerVar, txnVar, stateVar); + + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + b.ifZeroComparisonBranch(isForTry, "!="); + + TypeDesc constraintType = TypeDesc.forClass(UniqueConstraintException.class); + b.newObject(constraintType); + b.dup(); + b.loadThis(); + b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); + b.invokeConstructor(constraintType, new TypeDesc[] {TypeDesc.STRING}); + b.throwObject(); + + isForTry.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addTriggerFailedAndExitTxn + (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); + + // Define the abstract method. + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(TypeDesc.forClass(PersistException.class)); + } + + // Add insert method which calls insert(forTry = false) + addInsert: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null); + + if (mi == null) { + break addInsert; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, INSERT_OP, false, UniqueConstraintException.class); + break addInsert; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(false); + b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.pop(); + b.returnVoid(); + } + + // Add tryInsert method which calls insert(forTry = true) + addTryInsert: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryInsert; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, INSERT_OP, true, null); + break addTryInsert; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(true); + b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.returnValue(TypeDesc.BOOLEAN); + } + + // Add update(boolean forTry) method which delegates to abstract doTryUpdate method. + if (mGenMode == GEN_ABSTRACT) { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PRIVATE.toSynchronized(true), + PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); + mi.addException(TypeDesc.forClass(PersistException.class)); + + CodeBuilder b = new CodeBuilder(mi); + + requirePkInitialized(b, CHECK_PK_FOR_UPDATE_METHOD_NAME); + + // If version property is present, it too must be initialized. The + // versionOrdinal variable was set earlier, when properties were defined. + if (versionOrdinal >= 0) { + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2)); + b.math(Opcode.IAND); + Label versionIsSet = b.createLabel(); + b.ifZeroComparisonBranch(versionIsSet, "!="); + CodeBuilderUtil.throwException + (b, IllegalStateException.class, "Version not set"); + versionIsSet.setLocation(); + } + + LocalVariable forTryVar = b.getParameter(0); + LocalVariable triggerVar = b.createLocalVariable(null, triggerType); + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); + + Label tryStart = addGetTriggerAndEnterTxn + (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar); + + // If no properties are dirty, then don't update. + Label doUpdate = b.createLabel(); + branchIfDirty(b, true, doUpdate); + + // Even though there was no update, still need tryLoad side-effect. + { + Label tryStart2 = b.createLabel().setLocation(); + b.loadThis(); + b.invokeVirtual(DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); + + Label notUpdated = b.createLabel(); + b.ifZeroComparisonBranch(notUpdated, "=="); + + // Only mark properties clean if doTryLoad returned true. + b.loadThis(); + b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + notUpdated.setLocation(); + + // Mark properties dirty, to be consistent with a delete side-effect. + b.loadThis(); + b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + Label tryEnd = b.createLabel().setLocation(); + b.exceptionHandler(tryStart2, tryEnd, FetchException.class.getName()); + b.invokeVirtual(FetchException.class.getName(), "toPersistException", + TypeDesc.forClass(PersistException.class), null); + b.throwObject(); + } + + doUpdate.setLocation(); + + // Call doTryUpdate. + b.loadThis(); + b.invokeVirtual(DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + Label notUpdated = b.createLabel(); + b.ifZeroComparisonBranch(notUpdated, "=="); + + addTriggerAfterAndExitTxn + (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar); + + // Only mark properties clean if doUpdate returned true. + b.loadThis(); + // Note: all properties marked clean because doUpdate should have + // loaded values for all properties. + b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + notUpdated.setLocation(); + addTriggerFailedAndExitTxn(b, UPDATE_OP, triggerVar, txnVar, stateVar); + + // Mark properties dirty, to be consistent with a delete side-effect. + b.loadThis(); + b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); + + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + b.ifZeroComparisonBranch(isForTry, "!="); + + TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class); + b.newObject(persistNoneType); + b.dup(); + b.loadConstant("Cannot update missing object: "); + b.loadThis(); + b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); + b.invokeVirtual(TypeDesc.STRING, "concat", + TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING}); + b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING}); + b.throwObject(); + + isForTry.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addTriggerFailedAndExitTxn + (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); + + // Define the abstract method. + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(TypeDesc.forClass(PersistException.class)); + } + + // Add update method which calls update(forTry = false) + addUpdate: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null); + + if (mi == null) { + break addUpdate; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, UPDATE_OP, false, PersistNoneException.class); + break addUpdate; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(false); + b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.pop(); + b.returnVoid(); + } + + // Add tryUpdate method which calls update(forTry = true) + addTryUpdate: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryUpdate; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, UPDATE_OP, true, null); + break addTryUpdate; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(true); + b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.returnValue(TypeDesc.BOOLEAN); + } + + // Add delete(boolean forTry) method which delegates to abstract doTryDelete method. + if (mGenMode == GEN_ABSTRACT) { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PRIVATE.toSynchronized(true), + PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); + mi.addException(TypeDesc.forClass(PersistException.class)); + + CodeBuilder b = new CodeBuilder(mi); + + requirePkInitialized(b, CHECK_PK_FOR_DELETE_METHOD_NAME); + + LocalVariable forTryVar = b.getParameter(0); + LocalVariable triggerVar = b.createLocalVariable(null, triggerType); + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); + + Label tryStart = addGetTriggerAndEnterTxn + (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar); + + // Call doTryDelete. + b.loadThis(); + b.invokeVirtual(DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + b.loadThis(); + b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); + + Label notDeleted = b.createLabel(); + b.ifZeroComparisonBranch(notDeleted, "=="); + + addTriggerAfterAndExitTxn + (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + notDeleted.setLocation(); + addTriggerFailedAndExitTxn(b, DELETE_OP, triggerVar, txnVar, stateVar); + + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + b.ifZeroComparisonBranch(isForTry, "!="); + + TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class); + b.newObject(persistNoneType); + b.dup(); + b.loadConstant("Cannot delete missing object: "); + b.loadThis(); + b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); + b.invokeVirtual(TypeDesc.STRING, "concat", + TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING}); + b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING}); + b.throwObject(); + + isForTry.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + + addTriggerFailedAndExitTxn + (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); + + // Define the abstract method. + mi = mClassFile.addMethod + (Modifiers.PROTECTED.toAbstract(true), + DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); + mi.addException(TypeDesc.forClass(PersistException.class)); + } + + // Add delete method which calls delete(forTry = false) + addDelete: { + // Define the delete method. + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null); + + if (mi == null) { + break addDelete; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, DELETE_OP, false, PersistNoneException.class); + break addDelete; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(false); + b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.pop(); + b.returnVoid(); + } + + // Add tryDelete method which calls delete(forTry = true) + addTryDelete: { + // Define the delete method. + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addTryDelete; + } + + mi.addException(TypeDesc.forClass(PersistException.class)); + + if (mGenMode == GEN_WRAPPED) { + callWrappedSupport(mi, DELETE_OP, true, null); + break addTryDelete; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadConstant(true); + b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.BOOLEAN}); + b.returnValue(TypeDesc.BOOLEAN); + } + + // Add storableType method + addStorableType: { + final TypeDesc type = TypeDesc.forClass(mStorableType); + final TypeDesc storableClassType = TypeDesc.forClass(Class.class); + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null); + + if (mi == null) { + break addStorableType; + } + + CodeBuilder b = new CodeBuilder(mi); + b.loadConstant(type); + b.returnValue(storableClassType); + } + + // Add copy method. + addCopy: { + TypeDesc type = TypeDesc.forClass(mInfo.getStorableType()); + + // Add copy method. + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), + COPY_METHOD_NAME, mClassFile.getType(), null); + + if (mi == null) { + break addCopy; + } + + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null); + b.checkCast(mClassFile.getType()); + + if (mGenMode == GEN_WRAPPED) { + // Need to do a deeper copy. + + LocalVariable copiedVar = b.createLocalVariable(null, mClassFile.getType()); + b.storeLocal(copiedVar); + + // First copy the wrapped Storable. + b.loadLocal(copiedVar); // storeField later + b.loadThis(); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + b.invoke(lookupMethod(mStorableType, COPY_METHOD_NAME, null)); + b.checkCast(TypeDesc.forClass(mStorableType)); + b.storeField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + + // Replace the WrappedSupport, passing in copy of wrapped Storable. + b.loadLocal(copiedVar); // storeField later + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + b.loadLocal(copiedVar); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + + b.invokeInterface(WrappedSupport.class.getName(), + CREATE_WRAPPED_SUPPORT_METHOD_NAME, + mSupportType, + new TypeDesc[] {TypeDesc.forClass(Storable.class)}); + + // Store new WrappedSupport in copy. + b.storeField(SUPPORT_FIELD_NAME, mSupportType); + + b.loadLocal(copiedVar); + } + + b.returnValue(type); + } + + // Part of properly defining copy method, except needs to be added even + // if copy method was not added because it is inherited and final. + CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType()); + + // Create all the property copier methods. + // Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly + addCopyPropertiesMethod(COPY_ALL_PROPERTIES, + true, true, true, false, false); + addCopyPropertiesMethod(COPY_PRIMARY_KEY_PROPERTIES, + true, false, false, false, false); + addCopyPropertiesMethod(COPY_VERSION_PROPERTY, + false, true, false, false, false); + addCopyPropertiesMethod(COPY_UNEQUAL_PROPERTIES, + false, true, true, true, false); + addCopyPropertiesMethod(COPY_DIRTY_PROPERTIES, + false, true, true, false, true); + + // Define hasDirtyProperties method. + addHasDirtyProps: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null); + + if (mi == null) { + break addHasDirtyProps; + } + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi); + break addHasDirtyProps; + } + + CodeBuilder b = new CodeBuilder(mi); + Label isDirty = b.createLabel(); + branchIfDirty(b, false, isDirty); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + isDirty.setLocation(); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + } + + // Define isPropertyUninitialized, isPropertyDirty, and isPropertyClean methods. + addPropertyStateExtractMethod(); + addPropertyStateCheckMethod(IS_PROPERTY_UNINITIALIZED, PROPERTY_STATE_UNINITIALIZED); + addPropertyStateCheckMethod(IS_PROPERTY_DIRTY, PROPERTY_STATE_DIRTY); + addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN); + + // Define isPropertySupported method. + addIsPropertySupported: { + MethodInfo mi = addMethodIfNotFinal + (Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED, + TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); + + if (mi == null) { + break addIsPropertySupported; + } + + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + b.loadLocal(b.getParameter(0)); + b.invokeInterface(mSupportType, "isPropertySupported", TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.STRING}); + b.returnValue(TypeDesc.BOOLEAN); + } + + // Define standard object methods. + addHashCodeMethod(); + addEqualsMethod(EQUAL_FULL); + addEqualsMethod(EQUAL_KEYS); + addEqualsMethod(EQUAL_PROPERTIES); + addToStringMethod(false); + addToStringMethod(true); + + addMarkCleanMethod(MARK_PROPERTIES_CLEAN); + addMarkCleanMethod(MARK_ALL_PROPERTIES_CLEAN); + addMarkDirtyMethod(MARK_PROPERTIES_DIRTY); + addMarkDirtyMethod(MARK_ALL_PROPERTIES_DIRTY); + + if (mGenMode == GEN_ABSTRACT) { + // Define protected isPkInitialized method. + addIsInitializedMethod + (IS_PK_INITIALIZED_METHOD_NAME, mInfo.getPrimaryKeyProperties()); + + // Define protected methods to check if alternate key is initialized. + addAltKeyMethods: + for (int i=0; i> altProps = + new HashMap>(); + + StorableKey altKey = mInfo.getAlternateKey(i); + + for (OrderedProperty op : altKey.getProperties()) { + ChainedProperty cp = op.getChainedProperty(); + if (cp.getChainCount() > 0) { + // This should not be possible. + continue addAltKeyMethods; + } + StorableProperty property = cp.getPrimeProperty(); + altProps.put(property.getName(), property); + } + + addIsInitializedMethod(IS_ALT_KEY_INITIALIZED_PREFIX + i, altProps); + } + + // Define protected isRequiredDataInitialized method. + defineIsRequiredDataInitialized: { + Map> requiredProperties = + new HashMap>(); + + for (StorableProperty property : mAllProperties.values()) { + if (!property.isPrimaryKeyMember() && + !property.isJoin() && + !property.isNullable()) { + + requiredProperties.put(property.getName(), property); + } + } + + addIsInitializedMethod + (IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, requiredProperties); + } + + // Define optional protected isVersionInitialized method. The + // versionOrdinal variable was set earlier, when properties were defined. + if (versionOrdinal >= 0) { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED, IS_VERSION_INITIALIZED_METHOD_NAME, + TypeDesc.BOOLEAN, null); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2)); + b.math(Opcode.IAND); + // zero == false, not zero == true + b.returnValue(TypeDesc.BOOLEAN); + } + } + } + + /** + * If GEN_WRAPPED, generates a method implementation which delgates to the + * WrappedSupport. Also clears join property state if called method + * returns normally. + * + * @param opType optional, is one of INSERT_OP, UPDATE_OP, or DELETE_OP, for trigger support + * @param forTry used for INSERT_OP, UPDATE_OP, or DELETE_OP + * @param exceptionType optional - if called method throws this exception, + * join property state is still cleared. + */ + private void callWrappedSupport(MethodInfo mi, + String opType, + boolean forTry, + Class exceptionType) + { + if (mGenMode == GEN_ABSTRACT || !mHasJoins) { + // Don't need to clear state bits. + exceptionType = null; + } + + CodeBuilder b = new CodeBuilder(mi); + + final TypeDesc triggerType = TypeDesc.forClass(Trigger.class); + final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + + LocalVariable triggerVar = b.createLocalVariable(null, triggerType); + LocalVariable txnVar = b.createLocalVariable(null, transactionType); + LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); + + Label tryStart; + if (opType == null) { + tryStart = b.createLabel().setLocation(); + } else { + tryStart = addGetTriggerAndEnterTxn + (b, opType, null, forTry, triggerVar, txnVar, stateVar); + } + + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + Method method = lookupMethod(WrappedSupport.class, mi); + b.invoke(method); + + Label tryEnd = b.createLabel().setLocation(); + + clearState(b); + + if (method.getReturnType() == void.class) { + if (opType != null) { + addTriggerAfterAndExitTxn(b, opType, null, forTry, triggerVar, txnVar, stateVar); + } + b.returnVoid(); + } else { + if (opType != null) { + Label notDone = b.createLabel(); + b.ifZeroComparisonBranch(notDone, "=="); + addTriggerAfterAndExitTxn(b, opType, null, forTry, triggerVar, txnVar, stateVar); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + notDone.setLocation(); + addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); + b.loadConstant(false); + } + b.returnValue(TypeDesc.forClass(method.getReturnType())); + } + + if (opType != null) { + addTriggerFailedAndExitTxn + (b, opType, null, forTry, triggerVar, txnVar, stateVar, tryStart); + } + + if (exceptionType != null) { + b.exceptionHandler(tryStart, tryEnd, exceptionType.getName()); + clearState(b); + b.throwObject(); + } + } + + /** + * If GEN_WRAPPED, generates a method implementation which delgates to the + * wrapped Storable. + */ + private void callWrappedStorable(MethodInfo mi) { + callWrappedStorable(mi, new CodeBuilder(mi)); + } + + /** + * If GEN_WRAPPED, generates a method implementation which delgates to the + * wrapped Storable. + */ + private void callWrappedStorable(MethodInfo mi, CodeBuilder b) { + b.loadThis(); + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + + int count = mi.getMethodDescriptor().getParameterCount(); + for (int j=0; j> 4); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.storeLocal(stateBits); + } + + Label skipCopy = b.createLabel(); + + // Check if independent property is supported, and skip if not. + if (property.isIndependent()) { + addSkipIndependent(b, target, property, skipCopy); + } + + // Skip property if uninitialized. + b.loadLocal(stateBits); + b.loadConstant(mask); + b.math(Opcode.IAND); + b.ifZeroComparisonBranch(skipCopy, "=="); + + if (dirtyOnly) { + // Add code to find out if property has been dirty. + b.loadLocal(stateBits); + b.loadConstant(mask); + b.math(Opcode.IAND); + b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); + b.ifComparisonBranch(skipCopy, "!="); + } + + TypeDesc type = TypeDesc.forClass(property.getType()); + + if (unequalOnly) { + // Add code to find out if they're equal. + b.loadThis(); + b.loadField(property.getName(), type); // [this.propValue + b.loadLocal(target); // [this.propValue, target + b.invoke(property.getReadMethod()); // [this.propValue, target.propValue + CodeBuilderUtil.addValuesEqualCall + (b, TypeDesc.forClass(property.getType()), true, skipCopy, true); + } + + b.loadLocal(target); // [target + b.loadThis(); // [target, this + b.loadField(property.getName(), type); // [target, this.propValue + mutateProperty(b, property, type); + + skipCopy.setLocation(); + } + + ordinal++; + if ((mask <<= 2) == 0) { + mask = 3; + stateBits = null; + } + } + + b.returnVoid(); + } + + private void addSkipIndependent(CodeBuilder b, + LocalVariable target, + StorableProperty property, + Label skipCopy) + { + TypeDesc storableTypeDesc = TypeDesc.forClass(Storable.class); + + if (target != null) { + b.loadLocal(target); + b.loadConstant(property.getName()); + b.invokeInterface(storableTypeDesc, + "isPropertySupported", + TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.STRING}); + b.ifZeroComparisonBranch(skipCopy, "=="); + } + + b.loadThis(); + b.loadConstant(property.getName()); + b.invokeInterface(storableTypeDesc, + "isPropertySupported", + TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.STRING}); + b.ifZeroComparisonBranch(skipCopy, "=="); + } + + /** + * Puts the value on the stack into the specified storable. If a write method is defined + * uses it, otherwise just shoves the value into the appropriate field. + * + * entry stack: [storable, value + * exit stack: [ + * + * @param b - {@link CodeBuilder} to which to add the mutation code + * @param property - property to mutate + * @param type - type of the property + */ + private void mutateProperty(CodeBuilder b, StorableProperty property, TypeDesc type) { + if (property.getWriteMethod() == null) { + b.storeField(property.getName(), type); + } else { + b.invoke(property.getWriteMethod()); + } + } + + /** + * Generates code that loads a property annotation to the stack. + */ + private void loadPropertyAnnotation(CodeBuilder b, + StorableProperty property, + StorablePropertyAnnotation annotation) { + /* Example + UserInfo.class.getMethod("setFirstName", new Class[] {String.class}) + .getAnnotation(LengthConstraint.class) + */ + + String methodName = annotation.getAnnotatedMethod().getName(); + boolean isAccessor = !methodName.startsWith("set"); + + b.loadConstant(TypeDesc.forClass(property.getEnclosingType())); + b.loadConstant(methodName); + if (isAccessor) { + // Accessor method has no parameters. + b.loadNull(); + } else { + // Mutator method has one parameter. + b.loadConstant(1); + b.newObject(TypeDesc.forClass(Class[].class)); + b.dup(); + b.loadConstant(0); + b.loadConstant(TypeDesc.forClass(property.getType())); + b.storeToArray(TypeDesc.forClass(Class[].class)); + } + b.invokeVirtual(Class.class.getName(), "getMethod", + TypeDesc.forClass(Method.class), new TypeDesc[] { + TypeDesc.STRING, TypeDesc.forClass(Class[].class) + }); + b.loadConstant(TypeDesc.forClass(annotation.getAnnotationType())); + b.invokeVirtual(Method.class.getName(), "getAnnotation", + TypeDesc.forClass(Annotation.class), new TypeDesc[] { + TypeDesc.forClass(Class.class) + }); + b.checkCast(TypeDesc.forClass(annotation.getAnnotationType())); + } + + /** + * Generates code that loads a Storage instance on the stack, throwing a + * FetchException if Storage request fails. + * + * @param type type of Storage to request + */ + private void loadStorageForFetch(CodeBuilder b, TypeDesc type) { + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + TypeDesc storageType = TypeDesc.forClass(Storage.class); + + TypeDesc repositoryType = TypeDesc.forClass(Repository.class); + b.invokeInterface + (mSupportType, "getRootRepository", repositoryType, null); + b.loadConstant(type); + + // This may throw a RepositoryException. + Label tryStart = b.createLabel().setLocation(); + b.invokeInterface(repositoryType, STORAGE_FOR_METHOD_NAME, storageType, + new TypeDesc[]{TypeDesc.forClass(Class.class)}); + Label tryEnd = b.createLabel().setLocation(); + Label noException = b.createLabel(); + b.branch(noException); + + b.exceptionHandler(tryStart, tryEnd, + RepositoryException.class.getName()); + b.invokeVirtual + (RepositoryException.class.getName(), "toFetchException", + TypeDesc.forClass(FetchException.class), null); + b.throwObject(); + + noException.setLocation(); + } + + /** + * For the given join property, marks all of its dependent internal join + * element properties as dirty. + */ + /* + private void markInternalJoinElementsDirty(CodeBuilder b, StorableProperty joinProperty) { + int count = mAllProperties.size(); + + int ordinal = 0; + int mask = 0; + for (StorableProperty property : mAllProperties.values()) { + if (property != joinProperty && !property.isJoin()) { + // Check to see if property is an internal member of joinProperty. + for (int i=joinProperty.getJoinElementCount(); --i>=0; ) { + if (property == joinProperty.getInternalJoinElement(i)) { + mask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); + } + } + } + ordinal++; + if (((ordinal & 0xf) == 0 || ordinal >= count) && mask != 0) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); + b.loadThis(); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(mask); + b.math(Opcode.IOR); + b.storeField(stateFieldName, TypeDesc.INT); + mask = 0; + } + } + } + */ + + /** + * Generates code to set all state properties to zero. + */ + private void clearState(CodeBuilder b) { + int ordinal = -1; + int maxOrdinal = mAllProperties.size() - 1; + boolean requireStateField = false; + + for (StorableProperty property : mAllProperties.values()) { + ordinal++; + + if (property.isJoin() || mGenMode == GEN_ABSTRACT) { + requireStateField = true; + } + + if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) { + if (requireStateField) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); + + b.loadThis(); + b.loadConstant(0); + b.storeField(stateFieldName, TypeDesc.INT); + } + requireStateField = false; + } + } + } + + private void addMarkCleanMethod(String name) { + MethodInfo mi = + addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(true), name, null, null); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + if (mGenMode == GEN_WRAPPED) { + clearState(b); + callWrappedStorable(mi, b); + return; + } + + final int count = mAllProperties.size(); + int ordinal = 0; + int andMask = 0; + int orMask = 0; + + for (StorableProperty property : mAllProperties.values()) { + if (property.isQuery()) { + // Don't erase cached query. + andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); + } else if (!property.isJoin()) { + if (name == MARK_ALL_PROPERTIES_CLEAN) { + // Force clean state (1) always. + orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2); + } else if (name == MARK_PROPERTIES_CLEAN) { + // Mask will convert dirty (3) to clean (1). State 2, which + // is illegal, is converted to 0. + andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2); + } + } + + ordinal++; + if ((ordinal & 0xf) == 0 || ordinal >= count) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); + b.loadThis(); + if (andMask == 0) { + b.loadConstant(orMask); + } else { + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(andMask); + b.math(Opcode.IAND); + if (orMask != 0) { + b.loadConstant(orMask); + b.math(Opcode.IOR); + } + } + b.storeField(stateFieldName, TypeDesc.INT); + andMask = 0; + orMask = 0; + } + } + + b.returnVoid(); + } + + private void addMarkDirtyMethod(String name) { + MethodInfo mi = + addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + if (mGenMode == GEN_WRAPPED) { + clearState(b); + callWrappedStorable(mi, b); + return; + } + + final int count = mAllProperties.size(); + int ordinal = 0; + int andMask = 0; + int orMask = 0; + + for (StorableProperty property : mAllProperties.values()) { + if (property.isJoin()) { + // Erase cached join properties, but don't erase cached query. + if (!property.isQuery()) { + andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); + } + } else if (name == MARK_ALL_PROPERTIES_DIRTY) { + // Force dirty state (3). + orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); + } + + ordinal++; + if ((ordinal & 0xf) == 0 || ordinal >= count) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); + if (name == MARK_ALL_PROPERTIES_DIRTY) { + if (orMask != 0 || andMask != 0) { + b.loadThis(); // [this + b.loadThis(); // [this, this + b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField + if (andMask != 0) { + b.loadConstant(~andMask); + b.math(Opcode.IAND); + } + if (orMask != 0) { + b.loadConstant(orMask); + b.math(Opcode.IOR); + } + b.storeField(stateFieldName, TypeDesc.INT); + } + } else { + // This is a great trick to convert all states of value 1 + // (clean) into value 3 (dirty). States 0, 2, and 3 stay the + // same. Since joins cannot have state 1, they aren't affected. + // stateField |= ((stateField & 0x55555555) << 1); + + b.loadThis(); // [this + b.loadThis(); // [this, this + b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField + if (andMask != 0) { + b.loadConstant(~andMask); + b.math(Opcode.IAND); + } + b.dup(); // [this, this.stateField, this.stateField + b.loadConstant(0x55555555); + b.math(Opcode.IAND); // [this, this.stateField, this.stateField & 0x55555555 + b.loadConstant(1); + b.math(Opcode.ISHL); // [this, this.stateField, orMaskValue + b.math(Opcode.IOR); // [this, newStateFieldValue + b.storeField(stateFieldName, TypeDesc.INT); + } + + andMask = 0; + orMask = 0; + } + } + + b.returnVoid(); + } + + /** + * For the given ordinary key property, marks all of its dependent join + * element properties as uninitialized, and marks given property as dirty. + */ + private void markOrdinaryPropertyDirty + (CodeBuilder b, StorableProperty ordinaryProperty) + { + int count = mAllProperties.size(); + + int ordinal = 0; + int andMask = 0xffffffff; + int orMask = 0; + for (StorableProperty property : mAllProperties.values()) { + if (property == ordinaryProperty) { + if (mGenMode == GEN_ABSTRACT) { + // Only GEN_ABSTRACT mode uses these state bits. + orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); + } + } else if (property.isJoin()) { + // Check to see if ordinary is an internal member of join property. + for (int i=property.getJoinElementCount(); --i>=0; ) { + if (ordinaryProperty == property.getInternalJoinElement(i)) { + andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); + } + } + } + ordinal++; + if ((ordinal & 0xf) == 0 || ordinal >= count) { + if (andMask != 0xffffffff || orMask != 0) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); + b.loadThis(); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + if (andMask != 0xffffffff) { + b.loadConstant(andMask); + b.math(Opcode.IAND); + } + if (orMask != 0) { + b.loadConstant(orMask); + b.math(Opcode.IOR); + } + b.storeField(stateFieldName, TypeDesc.INT); + } + andMask = 0xffffffff; + orMask = 0; + } + } + } + + // Generates code that branches to the given label if any properties are dirty. + private void branchIfDirty(CodeBuilder b, boolean includePk, Label label) { + int count = mAllProperties.size(); + int ordinal = 0; + int andMask = 0; + for (StorableProperty property : mAllProperties.values()) { + if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) { + // Logical 'and' will convert state 1 (clean) to state 0, so + // that it will be ignored. State 3 (dirty) is what we're + // looking for, and it turns into 2. Essentially, we leave the + // high order bit on, since there is no state which has the + // high order bit on unless the low order bit is also on. + andMask |= 2 << ((ordinal & 0xf) * 2); + } + ordinal++; + if ((ordinal & 0xf) == 0 || ordinal >= count) { + String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); + b.loadThis(); + b.loadField(stateFieldName, TypeDesc.INT); + b.loadConstant(andMask); + b.math(Opcode.IAND); + // At least one property is dirty, so short circuit. + b.ifZeroComparisonBranch(label, "!="); + andMask = 0; + } + } + } + + private void addIsInitializedMethod + (String name, Map> properties) + { + // Don't check Automatic properties. + { + boolean cloned = false; + for (StorableProperty prop : properties.values()) { + if (prop.isAutomatic() || prop.isVersion()) { + if (!cloned) { + properties = new LinkedHashMap>(properties); + cloned = true; + } + // This isn't concurrent modification since the loop is + // still operating on the original properties map. + properties.remove(prop.getName()); + } + } + } + + MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null); + CodeBuilder b = new CodeBuilder(mi); + + if (properties.size() == 0) { + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + return; + } + + if (properties.size() == 1) { + int ordinal = findPropertyOrdinal(properties.values().iterator().next()); + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); + b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); + b.math(Opcode.IAND); + // zero == false, not zero == true + b.returnValue(TypeDesc.BOOLEAN); + return; + } + + // Multiple properties is a bit more tricky. The goal here is to + // minimize the amount of work that needs to be done at runtime. + + int ordinal = 0; + int mask = 0; + for (StorableProperty property : mAllProperties.values()) { + if (properties.containsKey(property.getName())) { + mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); + } + ordinal++; + if (((ordinal & 0xf) == 0 || ordinal >= mAllProperties.size()) && mask != 0) { + // This is a great trick to convert all states of value 1 + // (clean) into value 3 (dirty). States 0, 2, and 3 stay the + // same. Since joins cannot have state 1, they aren't affected. + // stateField | ((stateField & 0x55555555) << 1); + + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4), TypeDesc.INT); + b.dup(); // [this.stateField, this.stateField + b.loadConstant(0x55555555); + b.math(Opcode.IAND); // [this.stateField, this.stateField & 0x55555555 + b.loadConstant(1); + b.math(Opcode.ISHL); // [this.stateField, orMaskValue + b.math(Opcode.IOR); // [newStateFieldValue + + // Flip all bits for property states. If final result is + // non-zero, then there were uninitialized properties. + + b.loadConstant(mask); + b.math(Opcode.IXOR); + if (mask != 0xffffffff) { + b.loadConstant(mask); + b.math(Opcode.IAND); + } + + Label cont = b.createLabel(); + b.ifZeroComparisonBranch(cont, "=="); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + cont.setLocation(); + + mask = 0; + } + } + + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + } + + private int findPropertyOrdinal(StorableProperty property) { + int ordinal = 0; + for (StorableProperty p : mAllProperties.values()) { + if (p == property) { + return ordinal; + } + ordinal++; + } + throw new IllegalArgumentException(); + } + + /** + * Generates code that verifies that all primary keys are initialized. + * + * @param b builder that will invoke generated method + * @param methodName name to give to generated method + */ + private void requirePkInitialized(CodeBuilder b, String methodName) { + // Add code to call method which we are about to define. + b.loadThis(); + b.invokeVirtual(methodName, null, null); + + // Now define new method, discarding original builder object. + b = new CodeBuilder(mClassFile.addMethod(Modifiers.PROTECTED, methodName, null, null)); + b.loadThis(); + b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); + Label pkInitialized = b.createLabel(); + b.ifZeroComparisonBranch(pkInitialized, "!="); + CodeBuilderUtil.throwException + (b, IllegalStateException.class, "Primary key not fully specified"); + pkInitialized.setLocation(); + b.returnVoid(); + } + + /** + * Generates a private method which accepts a property name and returns + * PROPERTY_STATE_UNINITIALIZED, PROPERTY_STATE_DIRTY, or + * PROPERTY_STATE_CLEAN. + */ + private void addPropertyStateExtractMethod() { + if (mGenMode == GEN_WRAPPED) { + return; + } + + MethodInfo mi = mClassFile.addMethod(Modifiers.PRIVATE, PROPERTY_STATE_EXTRACT_METHOD_NAME, + TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); + CodeBuilder b = new CodeBuilder(mi); + + // Generate big switch statement that operates on Strings. See also + // org.cojen.util.BeanPropertyAccessor, which also generates this kind of + // switch. + + // For switch case count, obtain a prime number, at least twice as + // large as needed. This should minimize hash collisions. Since all the + // hash keys are known up front, the capacity could be tweaked until + // there are no collisions, but this technique is easier and + // deterministic. + + int caseCount; + { + BigInteger capacity = BigInteger.valueOf(mAllProperties.size() * 2 + 1); + while (!capacity.isProbablePrime(100)) { + capacity = capacity.add(BigInteger.valueOf(2)); + } + caseCount = capacity.intValue(); + } + + int[] cases = new int[caseCount]; + for (int i=0; i>[] caseMatches = caseMatches(caseCount); + + for (int i=0; i matches = caseMatches[i]; + if (matches == null || matches.size() == 0) { + switchLabels[i] = noMatch; + } else { + switchLabels[i] = b.createLabel(); + } + } + + b.loadLocal(b.getParameter(0)); + b.invokeVirtual(String.class.getName(), "hashCode", TypeDesc.INT, null); + b.loadConstant(0x7fffffff); + b.math(Opcode.IAND); + b.loadConstant(caseCount); + b.math(Opcode.IREM); + + b.switchBranch(cases, switchLabels, noMatch); + + // Gather property ordinals. + Map, Integer> ordinalMap = new HashMap, Integer>(); + { + int ordinal = 0; + for (StorableProperty prop : mAllProperties.values()) { + ordinalMap.put(prop, ordinal++); + } + } + + // Params to invoke String.equals. + TypeDesc[] params = {TypeDesc.OBJECT}; + + Label joinMatch = null; + + for (int i=0; i> matches = caseMatches[i]; + if (matches == null || matches.size() == 0) { + continue; + } + + switchLabels[i].setLocation(); + + int matchCount = matches.size(); + for (int j=0; j prop = matches.get(j); + + // Test against name to find exact match. + + b.loadConstant(prop.getName()); + b.loadLocal(b.getParameter(0)); + b.invokeVirtual(String.class.getName(), "equals", TypeDesc.BOOLEAN, params); + + Label notEqual; + + if (j == matchCount - 1) { + notEqual = null; + b.ifZeroComparisonBranch(noMatch, "=="); + } else { + notEqual = b.createLabel(); + b.ifZeroComparisonBranch(notEqual, "=="); + } + + if (prop.isJoin()) { + if (joinMatch == null) { + joinMatch = b.createLabel(); + } + b.branch(joinMatch); + } else { + int ordinal = ordinalMap.get(prop); + + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); + int shift = (ordinal & 0xf) * 2; + if (shift != 0) { + b.loadConstant(shift); + b.math(Opcode.ISHR); + } + b.loadConstant(PROPERTY_STATE_MASK); + b.math(Opcode.IAND); + b.returnValue(TypeDesc.INT); + } + + if (notEqual != null) { + notEqual.setLocation(); + } + } + } + + TypeDesc exceptionType = TypeDesc.forClass(IllegalArgumentException.class); + params = new TypeDesc[] {TypeDesc.STRING}; + + noMatch.setLocation(); + + b.newObject(exceptionType); + b.dup(); + b.loadConstant("Unknown property: "); + b.loadLocal(b.getParameter(0)); + b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); + b.invokeConstructor(exceptionType, params); + b.throwObject(); + + if (joinMatch != null) { + joinMatch.setLocation(); + + b.newObject(exceptionType); + b.dup(); + b.loadConstant("Cannot get state for join property: "); + b.loadLocal(b.getParameter(0)); + b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); + b.invokeConstructor(exceptionType, params); + b.throwObject(); + } + } + + /** + * Returns the properties that match on a given case. The array length is + * the same as the case count. Each list represents the matches. The lists + * themselves may be null if no matches for that case. + */ + private List>[] caseMatches(int caseCount) { + List>[] cases = new List[caseCount]; + + for (StorableProperty prop : mAllProperties.values()) { + int hashCode = prop.getName().hashCode(); + int caseValue = (hashCode & 0x7fffffff) % caseCount; + List matches = cases[caseValue]; + if (matches == null) { + matches = cases[caseValue] = new ArrayList>(); + } + matches.add(prop); + } + + return cases; + } + + /** + * Generates public method which accepts a property name and returns a + * boolean true, if the given state matches the property's actual state. + * + * @param name name of method + * @param state property state to check + */ + private void addPropertyStateCheckMethod(String name, int state) { + MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name, + TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi, b); + return; + } + + // Call private method to extract state and compare. + b.loadThis(); + b.loadLocal(b.getParameter(0)); + b.invokePrivate(PROPERTY_STATE_EXTRACT_METHOD_NAME, + TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); + Label isFalse = b.createLabel(); + if (state == 0) { + b.ifZeroComparisonBranch(isFalse, "!="); + } else { + b.loadConstant(state); + b.ifComparisonBranch(isFalse, "!="); + } + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + isFalse.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + } + + /** + * Defines a hashCode method. + */ + private void addHashCodeMethod() { + Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); + MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null); + + if (mi == null) { + return; + } + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi); + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + boolean mixIn = false; + for (StorableProperty property : mAllProperties.values()) { + if (property.isJoin()) { + continue; + } + addHashCodeCall(b, property.getName(), + TypeDesc.forClass(property.getType()), true, mixIn); + mixIn = true; + } + + b.returnValue(TypeDesc.INT); + } + + private void addHashCodeCall(CodeBuilder b, String fieldName, + TypeDesc fieldType, boolean testForNull, + boolean mixIn) + { + if (mixIn) { + // Multiply current hashcode by 31 before adding more to it. + b.loadConstant(5); + b.math(Opcode.ISHL); + b.loadConstant(1); + b.math(Opcode.ISUB); + } + + b.loadThis(); + b.loadField(fieldName, fieldType); + + switch (fieldType.getTypeCode()) { + case TypeDesc.FLOAT_CODE: + b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "floatToIntBits", + TypeDesc.INT, new TypeDesc[]{TypeDesc.FLOAT}); + // Fall through + case TypeDesc.INT_CODE: + case TypeDesc.CHAR_CODE: + case TypeDesc.SHORT_CODE: + case TypeDesc.BYTE_CODE: + case TypeDesc.BOOLEAN_CODE: + if (mixIn) { + b.math(Opcode.IADD); + } + break; + + case TypeDesc.DOUBLE_CODE: + b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "doubleToLongBits", + TypeDesc.LONG, new TypeDesc[]{TypeDesc.DOUBLE}); + // Fall through + case TypeDesc.LONG_CODE: + b.dup2(); + b.loadConstant(32); + b.math(Opcode.LUSHR); + b.math(Opcode.LXOR); + b.convert(TypeDesc.LONG, TypeDesc.INT); + if (mixIn) { + b.math(Opcode.IADD); + } + break; + + case TypeDesc.OBJECT_CODE: + default: + LocalVariable value = null; + if (testForNull) { + value = b.createLocalVariable(null, fieldType); + b.storeLocal(value); + b.loadLocal(value); + } + if (mixIn) { + Label isNull = b.createLabel(); + if (testForNull) { + b.ifNullBranch(isNull, true); + b.loadLocal(value); + } + addHashCodeCallTo(b, fieldType); + b.math(Opcode.IADD); + if (testForNull) { + isNull.setLocation(); + } + } else { + Label cont = b.createLabel(); + if (testForNull) { + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + b.loadConstant(0); + b.branch(cont); + notNull.setLocation(); + b.loadLocal(value); + } + addHashCodeCallTo(b, fieldType); + if (testForNull) { + cont.setLocation(); + } + } + break; + } + } + + private void addHashCodeCallTo(CodeBuilder b, TypeDesc fieldType) { + if (fieldType.isArray()) { + if (!fieldType.getComponentType().isPrimitive()) { + b.invokeStatic("java.util.Arrays", "deepHashCode", + TypeDesc.INT, new TypeDesc[] {TypeDesc.forClass(Object[].class)}); + } else { + b.invokeStatic("java.util.Arrays", "hashCode", + TypeDesc.INT, new TypeDesc[] {fieldType}); + } + } else { + b.invokeVirtual(TypeDesc.OBJECT, "hashCode", TypeDesc.INT, null); + } + } + + /** + * Defines an equals method. + * + * @param equalityType Type of equality to define - {@link EQUAL_KEYS} for "equalKeys", + * {@link EQUAL_PROPERTIES} for "equalProperties", and {@link EQUAL_FULL} for "equals" + */ + private void addEqualsMethod(int equalityType) { + TypeDesc[] objectParam = {TypeDesc.OBJECT}; + + String equalsMethodName; + switch (equalityType) { + default: + throw new IllegalArgumentException(); + case EQUAL_KEYS: + equalsMethodName = EQUAL_PRIMARY_KEYS_METHOD_NAME; + break; + case EQUAL_PROPERTIES: + equalsMethodName = EQUAL_PROPERTIES_METHOD_NAME; + break; + case EQUAL_FULL: + equalsMethodName = EQUALS_METHOD_NAME; + } + + Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); + MethodInfo mi = addMethodIfNotFinal + (modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam); + + if (mi == null) { + return; + } + + if (mGenMode == GEN_WRAPPED && equalityType != EQUAL_FULL) { + callWrappedStorable(mi); + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + // if (this == target) return true; + b.loadThis(); + b.loadLocal(b.getParameter(0)); + Label notEqual = b.createLabel(); + b.ifEqualBranch(notEqual, false); + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + notEqual.setLocation(); + + // if (! target instanceof this) return false; + TypeDesc userStorableTypeDesc = TypeDesc.forClass(mStorableType); + b.loadLocal(b.getParameter(0)); + b.instanceOf(userStorableTypeDesc); + Label fail = b.createLabel(); + b.ifZeroComparisonBranch(fail, "=="); + + // this.class other = (this.class)target; + LocalVariable other = b.createLocalVariable(null, userStorableTypeDesc); + b.loadLocal(b.getParameter(0)); + b.checkCast(userStorableTypeDesc); + b.storeLocal(other); + + for (StorableProperty property : mAllProperties.values()) { + if (property.isJoin()) { + continue; + } + // If we're only comparing keys, and this isn't a key, skip it + if ((equalityType == EQUAL_KEYS) && !property.isPrimaryKeyMember()) { + continue; + } + + // Check if independent property is supported, and skip if not. + Label skipCheck = b.createLabel(); + if (equalityType != EQUAL_KEYS && property.isIndependent()) { + addSkipIndependent(b, other, property, skipCheck); + } + + TypeDesc fieldType = TypeDesc.forClass(property.getType()); + b.loadThis(); + if (mGenMode == GEN_ABSTRACT) { + b.loadField(property.getName(), fieldType); + } else { + b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); + b.invoke(property.getReadMethod()); + } + + b.loadLocal(other); + b.invoke(property.getReadMethod()); + CodeBuilderUtil.addValuesEqualCall(b, fieldType, true, fail, false); + + skipCheck.setLocation(); + } + + b.loadConstant(true); + b.returnValue(TypeDesc.BOOLEAN); + + fail.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + } + + /** + * Defines a toString method, which assumes that the ClassFile is targeting + * version 1.5 of Java. + * + * @param keyOnly when true, generate a toStringKeyOnly method instead + */ + private void addToStringMethod(boolean keyOnly) { + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + TypeDesc[] stringParam = {TypeDesc.STRING}; + TypeDesc[] charParam = {TypeDesc.CHAR}; + + Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); + MethodInfo mi = addMethodIfNotFinal(modifiers, + keyOnly ? + TO_STRING_KEY_ONLY_METHOD_NAME : + TO_STRING_METHOD_NAME, + TypeDesc.STRING, null); + + if (mi == null) { + return; + } + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi); + return; + } + + CodeBuilder b = new CodeBuilder(mi); + b.newObject(stringBuilder); + b.dup(); + b.invokeConstructor(stringBuilder, null); + b.loadConstant(mStorableType.getName()); + invokeAppend(b, stringParam); + + String detail; + if (keyOnly) { + detail = " (key only) {"; + } else { + detail = " {"; + } + + b.loadConstant(detail); + invokeAppend(b, stringParam); + + // First pass, just print primary keys. + int ordinal = 0; + for (StorableProperty property : mAllProperties.values()) { + if (property.isPrimaryKeyMember()) { + Label skipPrint = b.createLabel(); + + // Check if independent property is supported, and skip if not. + if (property.isIndependent()) { + addSkipIndependent(b, null, property, skipPrint); + } + + if (ordinal++ > 0) { + b.loadConstant(", "); + invokeAppend(b, stringParam); + } + addPropertyAppendCall(b, property, stringParam, charParam); + + skipPrint.setLocation(); + } + } + + // Second pass, print non-primary keys. + if (!keyOnly) { + for (StorableProperty property : mAllProperties.values()) { + // Don't print join properties if they may throw an exception. + if (!property.isPrimaryKeyMember() && (!property.isJoin())) { + Label skipPrint = b.createLabel(); + + // Check if independent property is supported, and skip if not. + if (property.isIndependent()) { + addSkipIndependent(b, null, property, skipPrint); + } + + b.loadConstant(", "); + invokeAppend(b, stringParam); + addPropertyAppendCall(b, property, stringParam, charParam); + + skipPrint.setLocation(); + } + } + } + + b.loadConstant('}'); + invokeAppend(b, charParam); + + // For key string, also show all the alternate keys. This makes the + // FetchNoneException message more helpful. + if (keyOnly) { + int altKeyCount = mInfo.getAlternateKeyCount(); + for (int i=0; i key = mInfo.getAlternateKey(i); + + ordinal = 0; + for (OrderedProperty op : key.getProperties()) { + StorableProperty property = op.getChainedProperty().getPrimeProperty(); + + Label skipPrint = b.createLabel(); + + // Check if independent property is supported, and skip if not. + if (property.isIndependent()) { + addSkipIndependent(b, null, property, skipPrint); + } + + if (ordinal++ > 0) { + b.loadConstant(", "); + invokeAppend(b, stringParam); + } + addPropertyAppendCall(b, property, stringParam, charParam); + + skipPrint.setLocation(); + } + + b.loadConstant('}'); + invokeAppend(b, charParam); + } + } + + b.invokeVirtual(stringBuilder, TO_STRING_METHOD_NAME, TypeDesc.STRING, null); + b.returnValue(TypeDesc.STRING); + } + + private void invokeAppend(CodeBuilder b, TypeDesc[] paramType) { + TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); + b.invokeVirtual(stringBuilder, "append", stringBuilder, paramType); + } + + private void addPropertyAppendCall(CodeBuilder b, + StorableProperty property, + TypeDesc[] stringParam, + TypeDesc[] charParam) + { + b.loadConstant(property.getName()); + invokeAppend(b, stringParam); + b.loadConstant('='); + invokeAppend(b, charParam); + b.loadThis(); + TypeDesc type = TypeDesc.forClass(property.getType()); + b.loadField(property.getName(), type); + if (type.isPrimitive()) { + if (type == TypeDesc.BYTE || type == TypeDesc.SHORT) { + type = TypeDesc.INT; + } + } else { + if (type != TypeDesc.STRING) { + if (type.isArray()) { + if (!type.getComponentType().isPrimitive()) { + b.invokeStatic("java.util.Arrays", "deepToString", + TypeDesc.STRING, + new TypeDesc[] {TypeDesc.OBJECT.toArrayType()}); + } else { + b.invokeStatic("java.util.Arrays", TO_STRING_METHOD_NAME, + TypeDesc.STRING, new TypeDesc[] {type}); + } + } + type = TypeDesc.OBJECT; + } + } + invokeAppend(b, new TypeDesc[]{type}); + } + + /** + * Generates code to get a trigger, forcing a transaction if trigger is not + * null. Also, if there is a trigger, the "before" method is called. + * + * @param opType type of operation, Insert, Update, or Delete + * @param forTryVar optional boolean variable for selecting whether to call + * "before" or "beforeTry" method + * @param forTry used if forTryVar is null + * @param triggerVar required variable of type Trigger for storing trigger + * @param txnVar required variable of type Transaction for storing transaction + * @param stateVar variable of type Object for storing state + * @return try start label for transaction + */ + private Label addGetTriggerAndEnterTxn(CodeBuilder b, + String opType, + LocalVariable forTryVar, + boolean forTry, + LocalVariable triggerVar, + LocalVariable txnVar, + LocalVariable stateVar) + { + // trigger = support$.getXxxTrigger(); + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + Method m = lookupMethod(mSupportType.toClass(), "get" + opType + "Trigger", null); + b.invoke(m); + b.storeLocal(triggerVar); + // state = null; + b.loadNull(); + b.storeLocal(stateVar); + + // if (trigger == null) { + // txn = null; + // } else { + // txn = support.getRootRepository().enterTransaction(); + // tryStart: + // if (forTry) { + // state = trigger.beforeTryXxx(this); + // } else { + // state = trigger.beforeXxx(this); + // } + // } + b.loadLocal(triggerVar); + Label hasTrigger = b.createLabel(); + b.ifNullBranch(hasTrigger, false); + + // txn = null + b.loadNull(); + b.storeLocal(txnVar); + Label cont = b.createLabel(); + b.branch(cont); + + hasTrigger.setLocation(); + + // txn = support.getRootRepository().enterTransaction(); + TypeDesc repositoryType = TypeDesc.forClass(Repository.class); + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + b.loadThis(); + b.loadField(SUPPORT_FIELD_NAME, mSupportType); + b.invokeInterface(mSupportType, "getRootRepository", repositoryType, null); + b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, transactionType, null); + b.storeLocal(txnVar); + + Label tryStart = b.createLabel().setLocation(); + + // if (forTry) { + // state = trigger.beforeTryXxx(this); + // } else { + // state = trigger.beforeXxx(this); + // } + b.loadLocal(triggerVar); + b.loadThis(); + + if (forTryVar == null) { + if (forTry) { + b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType, + TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); + } else { + b.invokeVirtual(triggerVar.getType(), "before" + opType, + TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); + } + b.storeLocal(stateVar); + } else { + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + + b.ifZeroComparisonBranch(isForTry, "!="); + b.invokeVirtual(triggerVar.getType(), "before" + opType, + TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); + b.storeLocal(stateVar); + b.branch(cont); + + isForTry.setLocation(); + b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType, + TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); + b.storeLocal(stateVar); + } + + cont.setLocation(); + + return tryStart; + } + + /** + * Generates code to call a trigger after the persistence operation has + * been invoked. + * + * @param opType type of operation, Insert, Update, or Delete + * @param forTryVar optional boolean variable for selecting whether to call + * "after" or "afterTry" method + * @param forTry used if forTryVar is null + * @param triggerVar required variable of type Trigger for retrieving trigger + * @param txnVar required variable of type Transaction for storing transaction + * @param stateVar required variable of type Object for retrieving state + */ + private void addTriggerAfterAndExitTxn(CodeBuilder b, + String opType, + LocalVariable forTryVar, + boolean forTry, + LocalVariable triggerVar, + LocalVariable txnVar, + LocalVariable stateVar) + { + // if (trigger != null) { + b.loadLocal(triggerVar); + Label cont = b.createLabel(); + b.ifNullBranch(cont, true); + + // if (forTry) { + // trigger.afterTryXxx(this, state); + // } else { + // trigger.afterXxx(this, state); + // } + b.loadLocal(triggerVar); + b.loadThis(); + b.loadLocal(stateVar); + + if (forTryVar == null) { + if (forTry) { + b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); + } else { + b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); + } + } else { + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + + b.ifZeroComparisonBranch(isForTry, "!="); + b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); + Label commitAndExit = b.createLabel(); + b.branch(commitAndExit); + + isForTry.setLocation(); + b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); + commitAndExit.setLocation(); + } + + // txn.commit(); + // txn.exit(); + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + + cont.setLocation(); + } + + /** + * Generates code to call a trigger after the persistence operation has + * failed. + * + * @param opType type of operation, Insert, Update, or Delete + * @param triggerVar required variable of type Trigger for retrieving trigger + * @param txnVar required variable of type Transaction for storing transaction + * @param stateVar required variable of type Object for retrieving state + */ + private void addTriggerFailedAndExitTxn(CodeBuilder b, + String opType, + LocalVariable triggerVar, + LocalVariable txnVar, + LocalVariable stateVar) + { + TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + + // if (trigger != null) { + b.loadLocal(triggerVar); + Label isNull = b.createLabel(); + b.ifNullBranch(isNull, true); + + // try { + // trigger.failedXxx(this, state); + // } catch (Throwable e) { + // uncaught(e); + // } + Label tryStart = b.createLabel().setLocation(); + b.loadLocal(triggerVar); + b.loadThis(); + b.loadLocal(stateVar); + b.invokeVirtual(TypeDesc.forClass(Trigger.class), "failed" + opType, null, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); + Label tryEnd = b.createLabel().setLocation(); + Label cont = b.createLabel(); + b.branch(cont); + b.exceptionHandler(tryStart, tryEnd, Throwable.class.getName()); + b.invokeStatic(UNCAUGHT_METHOD_NAME, null, + new TypeDesc[] {TypeDesc.forClass(Throwable.class)}); + cont.setLocation(); + + // txn.exit(); + b.loadLocal(txnVar); + b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + + isNull.setLocation(); + } + + /** + * Generates exception handler code to call a trigger after the persistence + * operation has failed. + * + * @param opType type of operation, Insert, Update, or Delete + * @param forTryVar optional boolean variable for selecting whether to + * throw or catch Trigger.Abort. + * @param forTry used if forTryVar is null + * @param triggerVar required variable of type Trigger for retrieving trigger + * @param txnVar required variable of type Transaction for storing transaction + * @param stateVar required variable of type Object for retrieving state + * @param tryStart start of exception handler around transaction + */ + private void addTriggerFailedAndExitTxn(CodeBuilder b, + String opType, + LocalVariable forTryVar, + boolean forTry, + LocalVariable triggerVar, + LocalVariable txnVar, + LocalVariable stateVar, + Label tryStart) + { + if (tryStart == null) { + addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); + return; + } + + // } catch (... e) { + // if (trigger != null) { + // try { + // trigger.failedXxx(this, state); + // } catch (Throwable e) { + // uncaught(e); + // } + // } + // txn.exit(); + // if (e instanceof Trigger.Abort) { + // if (forTryVar) { + // return false; + // } else { + // // Try to add some trace for context + // throw ((Trigger.Abort) e).withStackTrace(); + // } + // } + // if (e instanceof RepositoryException) { + // throw ((RepositoryException) e).toPersistException(); + // } + // throw e; + // } + + Label tryEnd = b.createLabel().setLocation(); + b.exceptionHandler(tryStart, tryEnd, null); + LocalVariable exceptionVar = b.createLocalVariable(null, TypeDesc.OBJECT); + b.storeLocal(exceptionVar); + + addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); + + b.loadLocal(exceptionVar); + TypeDesc abortException = TypeDesc.forClass(Trigger.Abort.class); + b.instanceOf(abortException); + Label nextCheck = b.createLabel(); + b.ifZeroComparisonBranch(nextCheck, "=="); + if (forTryVar == null) { + if (forTry) { + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + } else { + b.loadLocal(exceptionVar); + b.checkCast(abortException); + b.invokeVirtual(abortException, "withStackTrace", abortException, null); + b.throwObject(); + } + } else { + b.loadLocal(forTryVar); + Label isForTry = b.createLabel(); + b.ifZeroComparisonBranch(isForTry, "!="); + b.loadLocal(exceptionVar); + b.checkCast(abortException); + b.invokeVirtual(abortException, "withStackTrace", abortException, null); + b.throwObject(); + isForTry.setLocation(); + b.loadConstant(false); + b.returnValue(TypeDesc.BOOLEAN); + } + + nextCheck.setLocation(); + b.loadLocal(exceptionVar); + TypeDesc repException = TypeDesc.forClass(RepositoryException.class); + b.instanceOf(repException); + Label throwAny = b.createLabel(); + b.ifZeroComparisonBranch(throwAny, "=="); + b.loadLocal(exceptionVar); + b.checkCast(repException); + b.invokeVirtual(repException, "toPersistException", + TypeDesc.forClass(PersistException.class), null); + b.throwObject(); + + throwAny.setLocation(); + b.loadLocal(exceptionVar); + b.throwObject(); + } + + /** + * Generates method which passes exception to uncaught exception handler. + */ + private void defineUncaughtExceptionHandler() { + MethodInfo mi = mClassFile.addMethod + (Modifiers.PRIVATE.toStatic(true), UNCAUGHT_METHOD_NAME, null, + new TypeDesc[] {TypeDesc.forClass(Throwable.class)}); + CodeBuilder b = new CodeBuilder(mi); + + // Thread t = Thread.currentThread(); + // t.getUncaughtExceptionHandler().uncaughtException(t, e); + TypeDesc threadType = TypeDesc.forClass(Thread.class); + b.invokeStatic(Thread.class.getName(), "currentThread", threadType, null); + LocalVariable threadVar = b.createLocalVariable(null, threadType); + b.storeLocal(threadVar); + b.loadLocal(threadVar); + TypeDesc handlerType = TypeDesc.forClass(Thread.UncaughtExceptionHandler.class); + b.invokeVirtual(threadType, "getUncaughtExceptionHandler", handlerType, null); + b.loadLocal(threadVar); + b.loadLocal(b.getParameter(0)); + b.invokeInterface(handlerType, "uncaughtException", null, + new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)}); + b.returnVoid(); + } + + /** + * @return MethodInfo for completing definition or null if superclass + * already implements method as final. + */ + private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name, + TypeDesc retType, TypeDesc[] params) + { + if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) { + return null; + } + + return mClassFile.addMethod(modifiers, name, retType, params); + } +} diff --git a/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java new file mode 100644 index 0000000..5115854 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java @@ -0,0 +1,337 @@ +/* + * 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.gen; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; + +import java.lang.reflect.UndeclaredThrowableException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.LocalVariable; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.TypeDesc; +import org.cojen.util.ClassInjector; +import org.cojen.util.WeakIdentityMap; + +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; + +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import static com.amazon.carbonado.gen.CommonMethodNames.*; + +import com.amazon.carbonado.raw.GenericEncodingStrategy; + +/** + * Support for general-purpose serialization of storables. + *

    + * TODO: This class is unable to determine state of properties, and so they are + * lost during serialization. Upon deserialization, all properties are assumed + * dirty. To fix this, serialization might need to be supported directly by + * Storables. When that happens, this class will be deprecated. + * + * @author Brian S O'Neill + */ +public abstract class StorableSerializer { + private static final String ENCODE_METHOD_NAME = "encode"; + private static final String DECODE_METHOD_NAME = "decode"; + private static final String WRITE_METHOD_NAME = "write"; + private static final String READ_METHOD_NAME = "read"; + + @SuppressWarnings("unchecked") + private static Map>> cCache = new WeakIdentityMap(); + + /** + * @param type type of storable to serialize + */ + @SuppressWarnings("unchecked") + public static StorableSerializer forType(Class type) + throws SupportException + { + synchronized (cCache) { + StorableSerializer serializer; + Reference> ref = cCache.get(type); + if (ref != null) { + serializer = (StorableSerializer) ref.get(); + if (serializer != null) { + return serializer; + } + } + serializer = generateSerializer(type); + cCache.put(type, new SoftReference>(serializer)); + return serializer; + } + } + + @SuppressWarnings("unchecked") + private static StorableSerializer generateSerializer(Class type) + throws SupportException + { + Class abstractClass = StorableGenerator.getAbstractClass(type); + + // Use abstract class ClassLoader in order to access adapter instances. + ClassInjector ci = ClassInjector.create + (type.getName(), abstractClass.getClassLoader()); + ClassFile cf = new ClassFile(ci.getClassName(), StorableSerializer.class); + cf.markSynthetic(); + cf.setSourceFile(StorableSerializer.class.getName()); + cf.setTarget("1.5"); + + cf.addDefaultConstructor(); + + Map> propertyMap = + StorableIntrospector.examine(type).getAllProperties(); + + StorableProperty[] properties; + { + // Exclude joins. + List> list = + new ArrayList>(propertyMap.size()); + for (StorableProperty property : propertyMap.values()) { + if (!property.isJoin()) { + list.add(property); + } + } + properties = new StorableProperty[list.size()]; + list.toArray(properties); + } + + GenericEncodingStrategy ges = new GenericEncodingStrategy(type, null); + + TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); + TypeDesc storableType = TypeDesc.forClass(Storable.class); + TypeDesc userStorableType = TypeDesc.forClass(type); + TypeDesc storageType = TypeDesc.forClass(Storage.class); + + // Build method to encode storable into a byte array. + { + MethodInfo mi = cf.addMethod + (Modifiers.PRIVATE.toStatic(true), ENCODE_METHOD_NAME, byteArrayType, + new TypeDesc[] {userStorableType}); + CodeBuilder b = new CodeBuilder(mi); + LocalVariable encodedVar = + ges.buildDataEncoding(b, properties, b.getParameter(0), abstractClass, true, -1); + b.loadLocal(encodedVar); + b.returnValue(byteArrayType); + } + + // Build method to decode storable from a byte array. + { + MethodInfo mi = cf.addMethod + (Modifiers.PRIVATE.toStatic(true), DECODE_METHOD_NAME, userStorableType, + new TypeDesc[] {storageType, byteArrayType}); + CodeBuilder b = new CodeBuilder(mi); + LocalVariable instanceVar = b.createLocalVariable(null, userStorableType); + b.loadLocal(b.getParameter(0)); + b.invokeInterface(storageType, PREPARE_METHOD_NAME, + storableType, null); + b.checkCast(userStorableType); + b.storeLocal(instanceVar); + LocalVariable encodedVar = b.getParameter(1); + ges.buildDataDecoding + (b, properties, instanceVar, abstractClass, true, -1, null, encodedVar); + b.loadLocal(instanceVar); + b.returnValue(storableType); + } + + // Build write method for DataOutput. + { + TypeDesc dataOutputType = TypeDesc.forClass(DataOutput.class); + + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, WRITE_METHOD_NAME, null, + new TypeDesc[] {storableType, dataOutputType}); + + CodeBuilder b = new CodeBuilder(mi); + LocalVariable storableVar = b.getParameter(0); + LocalVariable doutVar = b.getParameter(1); + + b.loadLocal(storableVar); + b.checkCast(userStorableType); + b.invokeStatic(ENCODE_METHOD_NAME, byteArrayType, new TypeDesc[] {userStorableType}); + LocalVariable encodedVar = b.createLocalVariable(null, byteArrayType); + b.storeLocal(encodedVar); + + b.loadLocal(doutVar); + b.loadLocal(encodedVar); + b.arrayLength(); + b.invokeInterface(dataOutputType, "writeInt", null, new TypeDesc[] {TypeDesc.INT}); + + b.loadLocal(doutVar); + b.loadLocal(encodedVar); + b.invokeInterface(dataOutputType, "write", null, new TypeDesc[] {byteArrayType}); + b.returnVoid(); + } + + final TypeDesc storableSerializerType = TypeDesc.forClass(StorableSerializer.class); + + // Build write method for OutputStream. + { + TypeDesc outputStreamType = TypeDesc.forClass(OutputStream.class); + + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, WRITE_METHOD_NAME, null, + new TypeDesc[] {storableType, outputStreamType}); + + CodeBuilder b = new CodeBuilder(mi); + LocalVariable storableVar = b.getParameter(0); + LocalVariable outVar = b.getParameter(1); + + b.loadLocal(storableVar); + b.checkCast(userStorableType); + b.invokeStatic(ENCODE_METHOD_NAME, byteArrayType, new TypeDesc[] {userStorableType}); + LocalVariable encodedVar = b.createLocalVariable(null, byteArrayType); + b.storeLocal(encodedVar); + + b.loadLocal(outVar); + b.loadLocal(encodedVar); + b.arrayLength(); + b.invokeStatic(storableSerializerType, "writeInt", null, + new TypeDesc[] {outputStreamType, TypeDesc.INT}); + + b.loadLocal(outVar); + b.loadLocal(encodedVar); + b.invokeVirtual(outputStreamType, "write", null, new TypeDesc[] {byteArrayType}); + b.returnVoid(); + } + + // Build read method for DataInput. + { + TypeDesc dataInputType = TypeDesc.forClass(DataInput.class); + + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, READ_METHOD_NAME, storableType, + new TypeDesc[] {storageType, dataInputType}); + + CodeBuilder b = new CodeBuilder(mi); + LocalVariable storageVar = b.getParameter(0); + LocalVariable dinVar = b.getParameter(1); + + b.loadLocal(dinVar); + b.invokeInterface(dataInputType, "readInt", TypeDesc.INT, null); + b.newObject(byteArrayType); + LocalVariable byteArrayVar = b.createLocalVariable(null, byteArrayType); + b.storeLocal(byteArrayVar); + + b.loadLocal(dinVar); + b.loadLocal(byteArrayVar); + b.invokeInterface(dataInputType, "readFully", null, new TypeDesc[] {byteArrayType}); + + b.loadLocal(storageVar); + b.loadLocal(byteArrayVar); + b.invokeStatic(DECODE_METHOD_NAME, userStorableType, + new TypeDesc[] {storageType, byteArrayType}); + b.returnValue(storableType); + } + + // Build read method for InputStream. + { + TypeDesc inputStreamType = TypeDesc.forClass(InputStream.class); + + MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, READ_METHOD_NAME, storableType, + new TypeDesc[] {storageType, inputStreamType}); + + CodeBuilder b = new CodeBuilder(mi); + LocalVariable storageVar = b.getParameter(0); + LocalVariable inVar = b.getParameter(1); + + b.loadLocal(inVar); + b.invokeStatic(storableSerializerType, "readInt", TypeDesc.INT, + new TypeDesc[] {inputStreamType}); + b.newObject(byteArrayType); + LocalVariable byteArrayVar = b.createLocalVariable(null, byteArrayType); + b.storeLocal(byteArrayVar); + + b.loadLocal(inVar); + b.loadLocal(byteArrayVar); + b.invokeStatic(storableSerializerType, "readFully", null, + new TypeDesc[] {inputStreamType, byteArrayType}); + + b.loadLocal(storageVar); + b.loadLocal(byteArrayVar); + b.invokeStatic(DECODE_METHOD_NAME, userStorableType, + new TypeDesc[] {storageType, byteArrayType}); + b.returnValue(storableType); + } + + Class clazz = (Class) ci.defineClass(cf); + + try { + return clazz.newInstance(); + } catch (InstantiationException e) { + throw new UndeclaredThrowableException(e); + } catch (IllegalAccessException e) { + throw new UndeclaredThrowableException(e); + } + } + + protected StorableSerializer() { + } + + public abstract void write(S storable, DataOutput out) throws IOException; + + public abstract void write(S storable, OutputStream out) throws IOException; + + public abstract S read(Storage storage, DataInput in) throws IOException, EOFException; + + public abstract S read(Storage storage, InputStream in) throws IOException, EOFException; + + public static void writeInt(OutputStream out, int v) throws IOException { + out.write((v >>> 24) & 0xff); + out.write((v >>> 16) & 0xff); + out.write((v >>> 8) & 0xff); + out.write(v & 0xff); + } + + public static int readInt(InputStream in) throws IOException { + int a = in.read(); + int b = in.read(); + int c = in.read(); + int d = in.read(); + if ((a | b | c | d) < 0) { + throw new EOFException(); + } + return (a << 24) | (b << 16) | (c << 8) | d; + } + + public static void readFully(InputStream in, byte[] b) throws IOException { + int length = b.length; + int n = 0; + while (n < length) { + int count = in.read(b, n, length - n); + if (count < 0) { + throw new EOFException(); + } + n += count; + } + } +} diff --git a/src/main/java/com/amazon/carbonado/gen/StorableSupport.java b/src/main/java/com/amazon/carbonado/gen/StorableSupport.java new file mode 100644 index 0000000..16fed67 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/StorableSupport.java @@ -0,0 +1,41 @@ +/* + * 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.gen; + +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.Storable; + +/** + * Provides runtime support for Storable classes generated by {@link StorableGenerator}. + * + * @author Brian S O'Neill + */ +public interface StorableSupport { + /** + * Returns the root parent Repository that the Storable came from. + */ + Repository getRootRepository(); + + /** + * Returns true if the given property exists and is supported. + * + * @param propertyName name of property to check + */ + boolean isPropertySupported(String propertyName); +} diff --git a/src/main/java/com/amazon/carbonado/gen/TriggerSupport.java b/src/main/java/com/amazon/carbonado/gen/TriggerSupport.java new file mode 100644 index 0000000..39b7806 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/TriggerSupport.java @@ -0,0 +1,50 @@ +/* + * 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.gen; + +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Trigger; + +/** + * Provides runtime support for Storable classes generated by {@link StorableGenerator}. + * + * @author Brian S O'Neill + */ +public interface TriggerSupport extends StorableSupport { + /** + * Returns a trigger which must be run for all insert operations. + * + * @return null if no trigger + */ + Trigger getInsertTrigger(); + + /** + * Returns a trigger which must be run for all update operations. + * + * @return null if no trigger + */ + Trigger getUpdateTrigger(); + + /** + * Returns a trigger which must be run for all delete operations. + * + * @return null if no trigger + */ + Trigger getDeleteTrigger(); +} diff --git a/src/main/java/com/amazon/carbonado/gen/WrappedSupport.java b/src/main/java/com/amazon/carbonado/gen/WrappedSupport.java new file mode 100644 index 0000000..78f58b3 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/WrappedSupport.java @@ -0,0 +1,75 @@ +/* + * 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.gen; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Storable; + +/** + * Provides runtime support for Storable classes generated by {@link StorableGenerator}. + * + * @author Brian S O'Neill + */ +public interface WrappedSupport extends TriggerSupport { + /** + * @see Storable#load + */ + void load() throws FetchException; + + /** + * @see Storable#tryLoad + */ + boolean tryLoad() throws FetchException; + + /** + * @see Storable#insert + */ + void insert() throws PersistException; + + /** + * @see Storable#tryInsert + */ + boolean tryInsert() throws PersistException; + + /** + * @see Storable#update + */ + void update() throws PersistException; + + /** + * @see Storable#tryUpdate + */ + boolean tryUpdate() throws PersistException; + + /** + * @see Storable#delete + */ + void delete() throws PersistException; + + /** + * @see Storable#tryDelete + */ + boolean tryDelete() throws PersistException; + + /** + * Return another Support instance for the given Storable. + */ + WrappedSupport createSupport(S storable); +} diff --git a/src/main/java/com/amazon/carbonado/gen/package-info.java b/src/main/java/com/amazon/carbonado/gen/package-info.java new file mode 100644 index 0000000..007ee0b --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Storable code generation support intended for use by repository implementations. + */ +package com.amazon.carbonado.gen; diff --git a/src/main/java/com/amazon/carbonado/info/ConversionComparator.java b/src/main/java/com/amazon/carbonado/info/ConversionComparator.java new file mode 100644 index 0000000..dc88f95 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/info/ConversionComparator.java @@ -0,0 +1,212 @@ +/* + * 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.info; + +import java.util.Comparator; + +import org.cojen.classfile.TypeDesc; + +/** + * Compares type conversions, finding the one that is nearest. + * + * @author Brian S O'Neill + */ +class ConversionComparator implements Comparator { + private final TypeDesc mFrom; + + public ConversionComparator(Class fromType) { + mFrom = TypeDesc.forClass(fromType); + } + + /** + * Returns true if a coversion is possible to the given type. + */ + public boolean isConversionPossible(Class toType) { + return isConversionPossible(mFrom, TypeDesc.forClass(toType)); + } + + @SuppressWarnings("unchecked") + private static boolean isConversionPossible(TypeDesc from, TypeDesc to) { + if (from == to) { + return true; + } + + if (from.toPrimitiveType() != null && to.toPrimitiveType() != null) { + from = from.toPrimitiveType(); + to = to.toPrimitiveType(); + } else { + from = from.toObjectType(); + to = to.toObjectType(); + } + + switch (from.getTypeCode()) { + case TypeDesc.OBJECT_CODE: default: + return to.toClass().isAssignableFrom(from.toClass()); + case TypeDesc.BOOLEAN_CODE: + return to == TypeDesc.BOOLEAN; + case TypeDesc.BYTE_CODE: + return to == TypeDesc.BYTE || to == TypeDesc.SHORT + || to == TypeDesc.INT || to == TypeDesc.LONG + || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.SHORT_CODE: + return to == TypeDesc.SHORT + || to == TypeDesc.INT || to == TypeDesc.LONG + || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.CHAR_CODE: + return to == TypeDesc.CHAR; + case TypeDesc.INT_CODE: + return to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.DOUBLE; + case TypeDesc.FLOAT_CODE: + return to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.LONG_CODE: + return to == TypeDesc.LONG; + case TypeDesc.DOUBLE_CODE: + return to == TypeDesc.DOUBLE; + } + } + + /** + * Evaluates two types, to see which one is nearest to the from type. + * Return <0 if "a" is nearest, 0 if both are equally good, >0 if "b" is + * nearest. + */ + public int compare(Class toType_a, Class toType_b) { + TypeDesc from = mFrom; + TypeDesc a = TypeDesc.forClass(toType_a); + TypeDesc b = TypeDesc.forClass(toType_b); + + if (from == a) { + if (from == b) { + return 0; + } + return -1; + } else if (from == b) { + return 1; + } + + int result = compare(from, a, b); + if (result != 0) { + return result; + } + + if (from.isPrimitive()) { + // Try boxing. + if (from.toObjectType() != null) { + from = from.toObjectType(); + return compare(from, a, b); + } + } else { + // Try unboxing. + if (from.toPrimitiveType() != null) { + from = from.toPrimitiveType(); + result = compare(from, a, b); + if (result != 0) { + return result; + } + // Try boxing back up. Test by unboxing 'to' types. + if (!toType_a.isPrimitive() && a.toPrimitiveType() != null) { + a = a.toPrimitiveType(); + } + if (!toType_b.isPrimitive() && b.toPrimitiveType() != null) { + b = b.toPrimitiveType(); + } + return compare(from, a, b); + } + } + + return 0; + } + + private static int compare(TypeDesc from, TypeDesc a, TypeDesc b) { + if (isConversionPossible(from, a)) { + if (isConversionPossible(from, b)) { + if (from.isPrimitive()) { + if (a.isPrimitive()) { + if (b.isPrimitive()) { + // Choose the one with the least amount of widening. + return primitiveWidth(a) - primitiveWidth(b); + } else { + return -1; + } + } else if (b.isPrimitive()) { + return 1; + } + } else { + // Choose the one with the shortest distance up the class + // hierarchy. + Class fromClass = from.toClass(); + if (!fromClass.isInterface()) { + if (a.toClass().isInterface()) { + if (!b.toClass().isInterface()) { + return -1; + } + } else if (b.toClass().isInterface()) { + return 1; + } else { + return distance(fromClass, a.toClass()) + - distance(fromClass, b.toClass()); + } + } + } + } else { + return -1; + } + } else if (isConversionPossible(from, b)) { + return 1; + } + + return 0; + } + + // 1 = boolean, 2 = byte, 3 = short, 4 = char, 5 = int, 6 = float, 7 = long, 8 = double + private static int primitiveWidth(TypeDesc type) { + switch (type.getTypeCode()) { + default: + return 0; + case TypeDesc.BOOLEAN_CODE: + return 1; + case TypeDesc.BYTE_CODE: + return 2; + case TypeDesc.SHORT_CODE: + return 3; + case TypeDesc.CHAR_CODE: + return 4; + case TypeDesc.INT_CODE: + return 5; + case TypeDesc.FLOAT_CODE: + return 6; + case TypeDesc.LONG_CODE: + return 7; + case TypeDesc.DOUBLE_CODE: + return 8; + } + } + + private static int distance(Class from, Class to) { + int distance = 0; + while (from != to) { + from = from.getSuperclass(); + if (from == null) { + return Integer.MAX_VALUE; + } + distance++; + } + return distance; + } +} diff --git a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java index c527371..f318c33 100644 --- a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.cojen.classfile.MethodDesc; import org.cojen.classfile.TypeDesc; import org.cojen.util.BeanComparator; import org.cojen.util.BeanProperty; @@ -67,9 +68,6 @@ import com.amazon.carbonado.adapter.AdapterDefinition; import com.amazon.carbonado.constraint.ConstraintDefinition; import com.amazon.carbonado.lob.Lob; -import com.amazon.carbonado.spi.CodeBuilderUtil; -import com.amazon.carbonado.spi.ConversionComparator; - /** * Supports examination of {@link Storable} types, returning all metadata * associated with it. As part of the examination, all annotations are gathered @@ -470,7 +468,7 @@ public class StorableIntrospector { // Gather all methods. We'll be removing them as we implement them, // and if there are any abstract ones left over at the end, why, // that would be bad. - Map methods = CodeBuilderUtil.gatherAllDeclaredMethods(type); + Map methods = gatherAllDeclaredMethods(type); // Remove methods not abstract or defined explicitly in // Storable. Storable methods still must be implemented, but not as @@ -541,7 +539,7 @@ public class StorableIntrospector { } if (readMethod != null) { - String sig = CodeBuilderUtil.createSig(readMethod); + String sig = createSig(readMethod); if (methods.containsKey(sig)) { methods.remove(sig); properties.put(property.getName(), storableProp); @@ -551,7 +549,7 @@ public class StorableIntrospector { } if (writeMethod != null) { - String sig = CodeBuilderUtil.createSig(writeMethod); + String sig = createSig(writeMethod); if (methods.containsKey(sig)) { methods.remove(sig); properties.put(property.getName(), storableProp); @@ -1129,6 +1127,44 @@ public class StorableIntrospector { return (StorablePropertyAdapter[]) list.toArray(new StorablePropertyAdapter[list.size()]); } + /** + * Returns a new modifiable mapping of method signatures to methods. + * + * @return map of {@link #createSig signatures} to methods + */ + private static Map gatherAllDeclaredMethods(Class clazz) { + Map methods = new HashMap(); + gatherAllDeclaredMethods(methods, clazz); + return methods; + } + + private static void gatherAllDeclaredMethods(Map methods, Class clazz) { + for (Method m : clazz.getDeclaredMethods()) { + String desc = createSig(m); + if (!methods.containsKey(desc)) { + methods.put(desc, m); + } + } + + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + gatherAllDeclaredMethods(methods, superclass); + } + for (Class c : clazz.getInterfaces()) { + gatherAllDeclaredMethods(methods, c); + } + } + + /** + * Create a representation of the signature which includes the method name. + * This uniquely identifies the method. + * + * @param m method to describe + */ + private static String createSig(Method m) { + return m.getName() + ':' + MethodDesc.forMethod(m).getDescriptor(); + } + private static final class Info implements StorableInfo { private final Class mType; private final String[] mAliases; diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java index 00ffda3..0f4e64d 100644 --- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java @@ -53,7 +53,7 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.util.QuickConstructorGenerator; -import com.amazon.carbonado.spi.CodeBuilderUtil; +import com.amazon.carbonado.gen.CodeBuilderUtil; /** * QueryExecutor which joins a source and target executor, diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java index c22881e..cba6776 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java @@ -38,8 +38,8 @@ 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.gen.StorableGenerator; +import com.amazon.carbonado.gen.TriggerSupport; import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.Direction; diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java index 0ef4846..43677f2 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java @@ -50,7 +50,7 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.layout.Layout; -import com.amazon.carbonado.spi.CodeBuilderUtil; +import com.amazon.carbonado.gen.CodeBuilderUtil; import com.amazon.carbonado.util.ThrowUnchecked; import com.amazon.carbonado.util.QuickConstructorGenerator; diff --git a/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java index 880bb7c..f475cab 100644 --- a/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java @@ -43,13 +43,12 @@ import com.amazon.carbonado.SupportException; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; -import com.amazon.carbonado.spi.MasterFeature; -import com.amazon.carbonado.spi.MasterStorableGenerator; -import com.amazon.carbonado.spi.MasterSupport; -import com.amazon.carbonado.spi.StorableGenerator; -import com.amazon.carbonado.spi.TriggerSupport; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; +import com.amazon.carbonado.gen.MasterFeature; +import com.amazon.carbonado.gen.MasterStorableGenerator; +import com.amazon.carbonado.gen.MasterSupport; +import com.amazon.carbonado.gen.StorableGenerator; +import com.amazon.carbonado.gen.TriggerSupport; +import static com.amazon.carbonado.gen.CommonMethodNames.*; /** * Generates and caches abstract implementations of {@link Storable} types diff --git a/src/main/java/com/amazon/carbonado/raw/RawSupport.java b/src/main/java/com/amazon/carbonado/raw/RawSupport.java index f6253c9..63577d7 100644 --- a/src/main/java/com/amazon/carbonado/raw/RawSupport.java +++ b/src/main/java/com/amazon/carbonado/raw/RawSupport.java @@ -25,7 +25,7 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.lob.Blob; import com.amazon.carbonado.lob.Clob; -import com.amazon.carbonado.spi.MasterSupport; +import com.amazon.carbonado.gen.MasterSupport; /** * Provides runtime support for Storable classes generated by {@link RawStorableGenerator}. diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java index 6a6a3b6..2528cbf 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java @@ -53,14 +53,13 @@ import com.amazon.carbonado.lob.Lob; import com.amazon.carbonado.info.StorablePropertyAdapter; -import com.amazon.carbonado.spi.CodeBuilderUtil; -import com.amazon.carbonado.spi.MasterFeature; -import com.amazon.carbonado.spi.MasterStorableGenerator; -import com.amazon.carbonado.spi.MasterSupport; -import com.amazon.carbonado.spi.StorableGenerator; -import com.amazon.carbonado.spi.TriggerSupport; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; +import com.amazon.carbonado.gen.CodeBuilderUtil; +import com.amazon.carbonado.gen.MasterFeature; +import com.amazon.carbonado.gen.MasterStorableGenerator; +import com.amazon.carbonado.gen.MasterSupport; +import com.amazon.carbonado.gen.StorableGenerator; +import com.amazon.carbonado.gen.TriggerSupport; +import static com.amazon.carbonado.gen.CommonMethodNames.*; /** * Generates concrete implementations of {@link Storable} types for diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupport.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupport.java index f178b7d..304c816 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupport.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupport.java @@ -24,7 +24,7 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.spi.MasterSupport; +import com.amazon.carbonado.gen.MasterSupport; /** * diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java deleted file mode 100644 index 0623f94..0000000 --- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * 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.spi; - -import java.util.HashSet; -import java.util.Set; -import java.util.Map; -import java.util.HashMap; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Label; -import org.cojen.classfile.TypeDesc; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodDesc; -import org.cojen.classfile.Opcode; -import org.cojen.util.ClassInjector; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; - -/** - * Collection of useful utilities for generating Carbonado code. - * - * @author Don Schneider - * @author Brian S O'Neill - */ -public class CodeBuilderUtil { - - /** - * Generate code to throw an exception if a parameter is null - * @param b CodeBuilder into which to append the code - * @param paramIndex index of the parameter to check - */ - public static void assertParameterNotNull(CodeBuilder b, int paramIndex) { - b.loadLocal(b.getParameter(paramIndex)); - Label notNull = b.createLabel(); - b.ifNullBranch(notNull, false); - throwException(b, IllegalArgumentException.class, null); - notNull.setLocation(); - } - - /** - * Generate code to create a local variable containing the specified parameter coerced - * to the specified type. This is useful for re-interpreting erased generics into - * the more specific genericized type. - * - * @param b CodeBuilder into which to append the code - * @param paramType the more specific type which was erased during compilation - * @param paramIndex index of the parameter to unerase - * @return a local variable referencing the type-cast parameter - */ - public static LocalVariable uneraseGenericParameter( - CodeBuilder b, TypeDesc paramType, final int paramIndex) - { - b.loadLocal(b.getParameter(paramIndex)); - b.checkCast(paramType); - LocalVariable result = b.createLocalVariable(null, paramType); - b.storeLocal(result); - return result; - } - - /** - * Generate code to throw an exception with an optional message. - * @param b {@link CodeBuilder} to which to add code - * @param type type of the object to throw - * @param message optional message to provide to the constructor - */ - public static void throwException(CodeBuilder b, Class type, String message) { - TypeDesc desc = TypeDesc.forClass(type); - b.newObject(desc); - b.dup(); - if (message == null) { - b.invokeConstructor(desc, null); - } else { - b.loadConstant(message); - b.invokeConstructor(desc, new TypeDesc[] {TypeDesc.STRING}); - } - b.throwObject(); - } - - /** - * Collect a set of all the interfaces and recursively all superclasses for the leaf - * (genericised class) and root (genericised base class). Eg, for Object, all - * classes and implemented interfaces for every superclass between foo (the leaf) and - * Object (the base). - *

    A copy must be coercible into any of these types, and copy bridge methods must be - * provided to do so. - * - *

    Note that the official documentation for this is in draft form, and you have to be - * psychic to have figured out the necessity in the first place. - * - * @param set set into which the class types will be collected - * @param leaf leaf class - * @return same set as was passed in - */ - public static Set gatherAllBridgeTypes(Set set, Class leaf) { - set.add(leaf); - for (Class c : leaf.getInterfaces()) { - gatherAllBridgeTypes(set, c); - } - if ((leaf = leaf.getSuperclass()) != null) { - gatherAllBridgeTypes(set, leaf); - } - return set; - } - - /** - * Add copy bridge methods for all classes/interfaces between the leaf (genericised class) - * and the root (genericised baseclass). - * - * @param cf file to which to add the copy bridge - * @param leaf leaf class - */ - public static void defineCopyBridges(ClassFile cf, Class leaf) { - for (Class c : gatherAllBridgeTypes(new HashSet(), leaf)) { - if (c != Object.class) { - defineCopyBridge(cf, leaf, c); - } - } - } - - /** - * Add a copy bridge method to the classfile for the given type. This is needed to allow - * the genericised class make a copy itself -- which will be erased to the base type -- and - * return it as the correct type. - * - * @param cf file to which to add the copy bridge - * @param leaf leaf class - * @param returnClass type returned from generated bridge method - */ - private static void defineCopyBridge(ClassFile cf, Class leaf, Class returnClass) { - TypeDesc returnType = TypeDesc.forClass(returnClass); - - if (isPublicMethodFinal(leaf, COPY_METHOD_NAME, returnType, null)) { - // Cannot override. - return; - } - - MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toBridge(true), - COPY_METHOD_NAME, returnType, null); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.invokeVirtual(COPY_METHOD_NAME, cf.getType(), null); - b.returnValue(returnType); - } - - /** - * Returns a new modifiable mapping of method signatures to methods. - * - * @return map of {@link #createSig signatures} to methods - */ - public static Map gatherAllDeclaredMethods(Class clazz) { - Map methods = new HashMap(); - gatherAllDeclaredMethods(methods, clazz); - return methods; - } - - private static void gatherAllDeclaredMethods(Map methods, Class clazz) { - for (Method m : clazz.getDeclaredMethods()) { - String desc = createSig(m); - if (!methods.containsKey(desc)) { - methods.put(desc, m); - } - } - - Class superclass = clazz.getSuperclass(); - if (superclass != null) { - gatherAllDeclaredMethods(methods, superclass); - } - for (Class c : clazz.getInterfaces()) { - gatherAllDeclaredMethods(methods, c); - } - } - - /** - * Returns true if a public final method exists which matches the given - * specification. - */ - public static boolean isPublicMethodFinal(Class clazz, String name, - TypeDesc retType, TypeDesc[] params) - { - if (!clazz.isInterface()) { - Class[] paramClasses; - if (params == null || params.length == 0) { - paramClasses = null; - } else { - paramClasses = new Class[params.length]; - for (int i=0; i - *

  • implements Storable
  • - *
  • implements Cloneable - *
  • abstract if appropriate - *
  • marked synthetic - *
  • targetted for java version 1.5 - * - * @param ci ClassInjector for the storable - * @param type specific Storable implementation to generate - * @param isAbstract true if the class should be abstract - * @param aSourcefileName identifier for the classfile, typically the factory class name - * @return ClassFile object ready to have methods added. - */ - public static ClassFile createStorableClassFile( - ClassInjector ci, Class type, boolean isAbstract, String aSourcefileName) - { - ClassFile cf; - if (type.isInterface()) { - cf = new ClassFile(ci.getClassName()); - cf.addInterface(type); - } else { - cf = new ClassFile(ci.getClassName(), type); - } - - if (isAbstract) { - Modifiers modifiers = cf.getModifiers().toAbstract(true); - cf.setModifiers(modifiers); - } - cf.addInterface(Storable.class); - cf.addInterface(Cloneable.class); - cf.markSynthetic(); - cf.setSourceFile(aSourcefileName); - cf.setTarget("1.5"); - return cf; - } - - /** - * Generates code to compare a field in this object against the same one in a - * different instance. Branch to the provided Label if they are not equal. - * - * @param b {@link CodeBuilder} to which to add the code - * @param fieldName the name of the field - * @param fieldType the type of the field - * @param testForNull if true and the values are references, they will be considered - * unequal unless neither or both are null. If false, assume neither is null. - * @param fail the label to branch to - * @param other the other instance to test - */ - public static void addEqualsCall(CodeBuilder b, - String fieldName, - TypeDesc fieldType, - boolean testForNull, - Label fail, - LocalVariable other) - { - b.loadThis(); - b.loadField(fieldName, fieldType); - - b.loadLocal(other); - b.loadField(fieldName, fieldType); - - addValuesEqualCall(b, fieldType, testForNull, fail, false); - } - - /** - * Generates code to compare two values on the stack, and branch to the - * provided Label if they are not equal. Both values must be of the same type. - * - *

    The generated instruction consumes both values on the stack. - * - * @param b {@link CodeBuilder} to which to add the code - * @param valueType the type of the values - * @param testForNull if true and the values are references, they will be considered - * unequal unless neither or both are null. If false, assume neither is null. - * @param label the label to branch to - * @param choice when true, branch to label if values are equal, else - * branch to label if values are unequal. - */ - public static void addValuesEqualCall(final CodeBuilder b, - final TypeDesc valueType, - final boolean testForNull, - final Label label, - final boolean choice) - { - if (valueType.getTypeCode() != TypeDesc.OBJECT_CODE) { - b.ifComparisonBranch(label, choice ? "==" : "!=", valueType); - return; - } - - // Equals method returns zero for false, so if choice is true, branch - // if not zero. Note that operator selection is opposite when invoking - // a direct ifComparisonBranch method. - String equalsBranchOp = choice ? "!=" : "=="; - - if (!testForNull) { - addEqualsCallTo(b, valueType); - b.ifZeroComparisonBranch(label, equalsBranchOp); - return; - } - - Label isNotNull = b.createLabel(); - LocalVariable value = b.createLocalVariable(null, valueType); - b.storeLocal(value); - b.loadLocal(value); - b.ifNullBranch(isNotNull, false); - - // First value popped off stack is null. Just test remaining one for null. - b.ifNullBranch(label, choice); - Label cont = b.createLabel(); - b.branch(cont); - - // First value popped off stack is not null, but second one might - // be. Call equals method, but swap values so that the second value is - // an argument into the equals method. - isNotNull.setLocation(); - b.loadLocal(value); - b.swap(); - addEqualsCallTo(b, valueType); - b.ifZeroComparisonBranch(label, equalsBranchOp); - - cont.setLocation(); - } - - public static void addEqualsCallTo(CodeBuilder b, TypeDesc fieldType) { - if (fieldType.isArray()) { - if (!fieldType.getComponentType().isPrimitive()) { - TypeDesc type = TypeDesc.forClass(Object[].class); - b.invokeStatic("java.util.Arrays", "deepEquals", - TypeDesc.BOOLEAN, new TypeDesc[] {type, type}); - } else { - b.invokeStatic("java.util.Arrays", "equals", - TypeDesc.BOOLEAN, new TypeDesc[] {fieldType, fieldType}); - } - } else { - TypeDesc[] params = {TypeDesc.OBJECT}; - if (fieldType.toClass() != null) { - if (fieldType.toClass().isInterface()) { - b.invokeInterface(fieldType, "equals", TypeDesc.BOOLEAN, params); - } else { - b.invokeVirtual(fieldType, "equals", TypeDesc.BOOLEAN, params); - } - } else { - b.invokeVirtual(TypeDesc.OBJECT, "equals", TypeDesc.BOOLEAN, params); - } - } - } - - /** - * Create a representation of the signature which includes the method name. - * This uniquely identifies the method. - * - * @param m method to describe - */ - public static String createSig(Method m) { - return m.getName() + ':' + MethodDesc.forMethod(m).getDescriptor(); - } - - /** - * Converts a value on the stack. If "to" type is a String, then conversion - * may call the String.valueOf(from). - */ - public static void convertValue(CodeBuilder b, Class from, Class to) { - if (from == to) { - return; - } - - TypeDesc fromType = TypeDesc.forClass(from); - TypeDesc toType = TypeDesc.forClass(to); - - // Let CodeBuilder have a crack at the conversion first. - try { - b.convert(fromType, toType); - return; - } catch (IllegalArgumentException e) { - if (to != String.class && to != Object.class && to != CharSequence.class) { - throw e; - } - } - - // Fallback case is to convert to a String. - - if (fromType.isPrimitive()) { - b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING, new TypeDesc[]{fromType}); - } else { - // If object on stack is null, then just leave it alone. - b.dup(); - Label isNull = b.createLabel(); - b.ifNullBranch(isNull, true); - b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING, - new TypeDesc[]{TypeDesc.OBJECT}); - isNull.setLocation(); - } - } - - /** - * Generates code to push an initial version property value on the stack. - * - * @throws SupportException if version type is not supported - */ - public static void initialVersion(CodeBuilder b, TypeDesc type, int value) - throws SupportException - { - adjustVersion(b, type, value, false); - } - - /** - * Generates code to increment a version property value, already on the stack. - * - * @throws SupportException if version type is not supported - */ - public static void incrementVersion(CodeBuilder b, TypeDesc type) - throws SupportException - { - adjustVersion(b, type, 0, true); - } - - private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment) - throws SupportException - { - TypeDesc primitiveType = type.toPrimitiveType(); - supportCheck: { - if (primitiveType != null) { - switch (primitiveType.getTypeCode()) { - case TypeDesc.INT_CODE: - case TypeDesc.LONG_CODE: - break supportCheck; - } - } - throw new SupportException("Unsupported version type: " + type.getFullName()); - } - - if (!increment) { - if (primitiveType == TypeDesc.LONG) { - b.loadConstant((long) value); - } else { - b.loadConstant(value); - } - } else { - Label setVersion = b.createLabel(); - if (!type.isPrimitive()) { - b.dup(); - Label versionNotNull = b.createLabel(); - b.ifNullBranch(versionNotNull, false); - b.pop(); - if (primitiveType == TypeDesc.LONG) { - b.loadConstant(1L); - } else { - b.loadConstant(1); - } - b.branch(setVersion); - versionNotNull.setLocation(); - b.convert(type, primitiveType); - } - if (primitiveType == TypeDesc.LONG) { - b.loadConstant(1L); - b.math(Opcode.LADD); - } else { - b.loadConstant(1); - b.math(Opcode.IADD); - } - setVersion.setLocation(); - } - - b.convert(primitiveType, type); - } - - /** - * Determines which overloaded "with" method on Query should be bound to. - */ - public static TypeDesc bindQueryParam(Class clazz) { - // This method is a bit vestigial. Once upon a time the Query class did - // not support all primitive types. - if (clazz.isPrimitive()) { - TypeDesc type = TypeDesc.forClass(clazz); - switch (type.getTypeCode()) { - case TypeDesc.INT_CODE: - case TypeDesc.LONG_CODE: - case TypeDesc.FLOAT_CODE: - case TypeDesc.DOUBLE_CODE: - case TypeDesc.BOOLEAN_CODE: - case TypeDesc.CHAR_CODE: - case TypeDesc.BYTE_CODE: - case TypeDesc.SHORT_CODE: - return type; - } - } - return TypeDesc.OBJECT; - } - - /** - * Appends a String to a StringBuilder. A StringBuilder and String must be - * on the stack, and a StringBuilder is left on the stack after the call. - */ - public static void callStringBuilderAppendString(CodeBuilder b) { - // Because of JDK1.5 bug which exposes AbstractStringBuilder class, - // cannot use reflection to get method signature. - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING}); - } - - /** - * Appends a char to a StringBuilder. A StringBuilder and char must be on - * the stack, and a StringBuilder is left on the stack after the call. - */ - public static void callStringBuilderAppendChar(CodeBuilder b) { - // Because of JDK1.5 bug which exposes AbstractStringBuilder class, - // cannot use reflection to get method signature. - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR}); - } - - /** - * Calls length on a StringBuilder on the stack, leaving an int on the stack. - */ - public static void callStringBuilderLength(CodeBuilder b) { - // Because of JDK1.5 bug which exposes AbstractStringBuilder class, - // cannot use reflection to get method signature. - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null); - } - - /** - * Calls setLength on a StringBuilder. A StringBuilder and int must be on - * the stack, and both are consumed after the call. - */ - public static void callStringBuilderSetLength(CodeBuilder b) { - // Because of JDK1.5 bug which exposes AbstractStringBuilder class, - // cannot use reflection to get method signature. - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT}); - } - - /** - * Calls toString on a StringBuilder. A StringBuilder must be on the stack, - * and a String is left on the stack after the call. - */ - public static void callStringBuilderToString(CodeBuilder b) { - // Because of JDK1.5 bug which exposes AbstractStringBuilder class, - // cannot use reflection to get method signature. - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/CommonMethodNames.java b/src/main/java/com/amazon/carbonado/spi/CommonMethodNames.java deleted file mode 100644 index 8a011c9..0000000 --- a/src/main/java/com/amazon/carbonado/spi/CommonMethodNames.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.spi; - -/** - * Collection of constant method names for the public API. - * - * @author Brian S O'Neill - */ -public class CommonMethodNames { - /** Storable API method name */ - public static final String - LOAD_METHOD_NAME = "load", - INSERT_METHOD_NAME = "insert", - UPDATE_METHOD_NAME = "update", - DELETE_METHOD_NAME = "delete", - TRY_LOAD_METHOD_NAME = "tryLoad", - TRY_INSERT_METHOD_NAME = "tryInsert", - TRY_UPDATE_METHOD_NAME = "tryUpdate", - TRY_DELETE_METHOD_NAME = "tryDelete", - STORABLE_TYPE_METHOD_NAME = "storableType", - COPY_METHOD_NAME = "copy", - CLONE_METHOD_NAME = "clone", - COPY_ALL_PROPERTIES = "copyAllProperties", - COPY_PRIMARY_KEY_PROPERTIES = "copyPrimaryKeyProperties", - COPY_VERSION_PROPERTY = "copyVersionProperty", - COPY_UNEQUAL_PROPERTIES = "copyUnequalProperties", - COPY_DIRTY_PROPERTIES = "copyDirtyProperties", - HAS_DIRTY_PROPERTIES = "hasDirtyProperties", - MARK_PROPERTIES_CLEAN = "markPropertiesClean", - MARK_ALL_PROPERTIES_CLEAN = "markAllPropertiesClean", - MARK_PROPERTIES_DIRTY = "markPropertiesDirty", - MARK_ALL_PROPERTIES_DIRTY = "markAllPropertiesDirty", - IS_PROPERTY_UNINITIALIZED = "isPropertyUninitialized", - IS_PROPERTY_DIRTY = "isPropertyDirty", - IS_PROPERTY_CLEAN = "isPropertyClean", - IS_PROPERTY_SUPPORTED = "isPropertySupported", - TO_STRING_KEY_ONLY_METHOD_NAME = "toStringKeyOnly", - TO_STRING_METHOD_NAME = "toString", - HASHCODE_METHOD_NAME = "hashCode", - EQUALS_METHOD_NAME = "equals", - EQUAL_PRIMARY_KEYS_METHOD_NAME = "equalPrimaryKeys", - EQUAL_PROPERTIES_METHOD_NAME = "equalProperties"; - - /** Storage API method name */ - public static final String - QUERY_METHOD_NAME = "query", - PREPARE_METHOD_NAME = "prepare"; - - /** Query API method name */ - public static final String - LOAD_ONE_METHOD_NAME = "loadOne", - TRY_LOAD_ONE_METHOD_NAME = "tryLoadOne", - WITH_METHOD_NAME = "with", - FETCH_METHOD_NAME = "fetch"; - - /** Repository API method name */ - public static final String - STORAGE_FOR_METHOD_NAME = "storageFor", - ENTER_TRANSACTION_METHOD_NAME = "enterTransaction", - GET_TRANSACTION_ISOLATION_LEVEL_METHOD_NAME = "getTransactionIsolationLevel"; - - /** Transaction API method name */ - public static final String - SET_FOR_UPDATE_METHOD_NAME = "setForUpdate", - COMMIT_METHOD_NAME = "commit", - EXIT_METHOD_NAME = "exit"; - - /** WrappedStorage.Support API method name */ - public static final String CREATE_WRAPPED_SUPPORT_METHOD_NAME = "createSupport"; -} diff --git a/src/main/java/com/amazon/carbonado/spi/ConversionComparator.java b/src/main/java/com/amazon/carbonado/spi/ConversionComparator.java deleted file mode 100644 index aed00b1..0000000 --- a/src/main/java/com/amazon/carbonado/spi/ConversionComparator.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.spi; - -import java.util.Comparator; - -import org.cojen.classfile.TypeDesc; - -/** - * Compares type conversions, finding the one that is nearest. - * - * @author Brian S O'Neill - */ -public class ConversionComparator implements Comparator { - private final TypeDesc mFrom; - - public ConversionComparator(Class fromType) { - mFrom = TypeDesc.forClass(fromType); - } - - /** - * Returns true if a coversion is possible to the given type. - */ - public boolean isConversionPossible(Class toType) { - return isConversionPossible(mFrom, TypeDesc.forClass(toType)); - } - - @SuppressWarnings("unchecked") - private static boolean isConversionPossible(TypeDesc from, TypeDesc to) { - if (from == to) { - return true; - } - - if (from.toPrimitiveType() != null && to.toPrimitiveType() != null) { - from = from.toPrimitiveType(); - to = to.toPrimitiveType(); - } else { - from = from.toObjectType(); - to = to.toObjectType(); - } - - switch (from.getTypeCode()) { - case TypeDesc.OBJECT_CODE: default: - return to.toClass().isAssignableFrom(from.toClass()); - case TypeDesc.BOOLEAN_CODE: - return to == TypeDesc.BOOLEAN; - case TypeDesc.BYTE_CODE: - return to == TypeDesc.BYTE || to == TypeDesc.SHORT - || to == TypeDesc.INT || to == TypeDesc.LONG - || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.SHORT_CODE: - return to == TypeDesc.SHORT - || to == TypeDesc.INT || to == TypeDesc.LONG - || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.CHAR_CODE: - return to == TypeDesc.CHAR; - case TypeDesc.INT_CODE: - return to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.DOUBLE; - case TypeDesc.FLOAT_CODE: - return to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.LONG_CODE: - return to == TypeDesc.LONG; - case TypeDesc.DOUBLE_CODE: - return to == TypeDesc.DOUBLE; - } - } - - /** - * Evaluates two types, to see which one is nearest to the from type. - * Return <0 if "a" is nearest, 0 if both are equally good, >0 if "b" is - * nearest. - */ - public int compare(Class toType_a, Class toType_b) { - TypeDesc from = mFrom; - TypeDesc a = TypeDesc.forClass(toType_a); - TypeDesc b = TypeDesc.forClass(toType_b); - - if (from == a) { - if (from == b) { - return 0; - } - return -1; - } else if (from == b) { - return 1; - } - - int result = compare(from, a, b); - if (result != 0) { - return result; - } - - if (from.isPrimitive()) { - // Try boxing. - if (from.toObjectType() != null) { - from = from.toObjectType(); - return compare(from, a, b); - } - } else { - // Try unboxing. - if (from.toPrimitiveType() != null) { - from = from.toPrimitiveType(); - result = compare(from, a, b); - if (result != 0) { - return result; - } - // Try boxing back up. Test by unboxing 'to' types. - if (!toType_a.isPrimitive() && a.toPrimitiveType() != null) { - a = a.toPrimitiveType(); - } - if (!toType_b.isPrimitive() && b.toPrimitiveType() != null) { - b = b.toPrimitiveType(); - } - return compare(from, a, b); - } - } - - return 0; - } - - private static int compare(TypeDesc from, TypeDesc a, TypeDesc b) { - if (isConversionPossible(from, a)) { - if (isConversionPossible(from, b)) { - if (from.isPrimitive()) { - if (a.isPrimitive()) { - if (b.isPrimitive()) { - // Choose the one with the least amount of widening. - return primitiveWidth(a) - primitiveWidth(b); - } else { - return -1; - } - } else if (b.isPrimitive()) { - return 1; - } - } else { - // Choose the one with the shortest distance up the class - // hierarchy. - Class fromClass = from.toClass(); - if (!fromClass.isInterface()) { - if (a.toClass().isInterface()) { - if (!b.toClass().isInterface()) { - return -1; - } - } else if (b.toClass().isInterface()) { - return 1; - } else { - return distance(fromClass, a.toClass()) - - distance(fromClass, b.toClass()); - } - } - } - } else { - return -1; - } - } else if (isConversionPossible(from, b)) { - return 1; - } - - return 0; - } - - // 1 = boolean, 2 = byte, 3 = short, 4 = char, 5 = int, 6 = float, 7 = long, 8 = double - private static int primitiveWidth(TypeDesc type) { - switch (type.getTypeCode()) { - default: - return 0; - case TypeDesc.BOOLEAN_CODE: - return 1; - case TypeDesc.BYTE_CODE: - return 2; - case TypeDesc.SHORT_CODE: - return 3; - case TypeDesc.CHAR_CODE: - return 4; - case TypeDesc.INT_CODE: - return 5; - case TypeDesc.FLOAT_CODE: - return 6; - case TypeDesc.LONG_CODE: - return 7; - case TypeDesc.DOUBLE_CODE: - return 8; - } - } - - private static int distance(Class from, Class to) { - int distance = 0; - while (from != to) { - from = from.getSuperclass(); - if (from == null) { - return Integer.MAX_VALUE; - } - distance++; - } - return distance; - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/MasterFeature.java b/src/main/java/com/amazon/carbonado/spi/MasterFeature.java deleted file mode 100644 index 1ec1fc3..0000000 --- a/src/main/java/com/amazon/carbonado/spi/MasterFeature.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.spi; - -/** - * Master feature to enable when using {@link MasterStorableGenerator}. - * - * @author Brian S O'Neill - */ -public enum MasterFeature { - /** Insert and update operations implement record versioning, if version property exists */ - VERSIONING, - - /** Update operations load clean copy first, to prevent destructive update */ - UPDATE_FULL, - - /** Ensure update operation always is in a transaction */ - UPDATE_TXN, - - /** Ensure update operation always is in a transaction, "for update" */ - UPDATE_TXN_FOR_UPDATE, - - /** Insert operation applies any sequences to unset properties */ - INSERT_SEQUENCES, - - /** - * Insert operation checks that all required data properties have been set, - * excluding automatic properties and version property. - */ - INSERT_CHECK_REQUIRED, - - /** Ensure insert operation always is in a transaction */ - INSERT_TXN, - - /** Ensure insert operation always is in a transaction, "for update" */ - INSERT_TXN_FOR_UPDATE, - - /** Ensure delete operation always is in a transaction */ - DELETE_TXN, - - /** Ensure delete operation always is in a transaction, "for update" */ - DELETE_TXN_FOR_UPDATE, -} diff --git a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java deleted file mode 100644 index 3de8fb5..0000000 --- a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java +++ /dev/null @@ -1,835 +0,0 @@ -/* - * 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.spi; - -import java.lang.reflect.Method; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Map; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.Label; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.Opcode; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; - -import com.amazon.carbonado.ConstraintException; -import com.amazon.carbonado.OptimisticLockException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Repository; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; -import com.amazon.carbonado.Transaction; - -import com.amazon.carbonado.info.StorableInfo; -import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.info.StorableProperty; - -import com.amazon.carbonado.sequence.SequenceValueProducer; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; - -/** - * Generates and caches abstract implementations of {@link Storable} types - * suitable for use by master repositories. The generated classes extend those - * generated by {@link StorableGenerator}. Subclasses need not worry about - * transactions since this class takes care of that. - * - * @author Brian S O'Neill - */ -public final class MasterStorableGenerator { - // Note: All generated fields/methods have a "$" character in them to - // prevent name collisions with any inherited fields/methods. User storable - // properties are defined as fields which exactly match the property - // name. We don't want collisions with those either. Legal bean properties - // cannot have "$" in them, so there's nothing to worry about. - - /** Name of protected abstract method in generated storable */ - public static final String - DO_TRY_LOAD_MASTER_METHOD_NAME = StorableGenerator.DO_TRY_LOAD_METHOD_NAME, - DO_TRY_INSERT_MASTER_METHOD_NAME = "doTryInsert$master", - DO_TRY_UPDATE_MASTER_METHOD_NAME = "doTryUpdate$master", - DO_TRY_DELETE_MASTER_METHOD_NAME = "doTryDelete$master"; - - private static final String APPEND_UNINIT_PROPERTY = "appendUninitializedPropertyName$"; - - private static final String INSERT_OP = "Insert"; - private static final String UPDATE_OP = "Update"; - private static final String DELETE_OP = "Delete"; - - // Cache of generated abstract classes. - private static Map> cCache = new SoftValuedHashMap(); - - /** - * Returns an abstract implementation of the given Storable type, which - * is fully thread-safe. The Storable type itself may be an interface or - * a class. If it is a class, then it must not be final, and it must have a - * public, no-arg constructor. The constructor for the returned abstract - * class looks like this: - * - *

    -     * public <init>(MasterSupport);
    -     * 
    - * - * Subclasses must implement the following abstract protected methods, - * whose exact names are defined by constants in this class: - * - *
    -     * // Load the object by examining the primary key.
    -     * protected abstract boolean doTryLoad() throws FetchException;
    -     *
    -     * // Insert the object into the storage layer.
    -     * protected abstract boolean doTryInsert_master() throws PersistException;
    -     *
    -     * // Update the object in the storage.
    -     * protected abstract boolean doTryUpdate_master() throws PersistException;
    -     *
    -     * // Delete the object from the storage layer by the primary key.
    -     * protected abstract boolean doTryDelete_master() throws PersistException;
    -     * 
    - * - * Subclasses can access the MasterSupport instance via the protected field - * named by {@link StorableGenerator#SUPPORT_FIELD_NAME SUPPORT_FIELD_NAME}. - * - * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed - * @throws IllegalArgumentException if type is null - * @see MasterSupport - */ - public static Class - getAbstractClass(Class type, EnumSet features) - throws SupportException, IllegalArgumentException - { - StorableInfo info = StorableIntrospector.examine(type); - - anySequences: - if (features.contains(MasterFeature.INSERT_SEQUENCES)) { - for (StorableProperty property : info.getAllProperties().values()) { - if (property.getSequenceName() != null) { - break anySequences; - } - } - features.remove(MasterFeature.INSERT_SEQUENCES); - } - - if (info.getVersionProperty() == null) { - features.remove(MasterFeature.VERSIONING); - } - - if (features.contains(MasterFeature.VERSIONING)) { - // Implied feature. - features.add(MasterFeature.UPDATE_FULL); - } - - if (alwaysHasTxn(INSERT_OP, features)) { - // Implied feature. - features.add(MasterFeature.INSERT_TXN); - } - if (alwaysHasTxn(UPDATE_OP, features)) { - // Implied feature. - features.add(MasterFeature.UPDATE_TXN); - } - if (alwaysHasTxn(DELETE_OP, features)) { - // Implied feature. - features.add(MasterFeature.DELETE_TXN); - } - - if (requiresTxnForUpdate(INSERT_OP, features)) { - // Implied feature. - features.add(MasterFeature.INSERT_TXN_FOR_UPDATE); - } - if (requiresTxnForUpdate(UPDATE_OP, features)) { - // Implied feature. - features.add(MasterFeature.UPDATE_TXN_FOR_UPDATE); - } - if (requiresTxnForUpdate(DELETE_OP, features)) { - // Implied feature. - features.add(MasterFeature.DELETE_TXN_FOR_UPDATE); - } - - Object key = KeyFactory.createKey(new Object[] {type, features}); - - synchronized (cCache) { - Class abstractClass = (Class) cCache.get(key); - if (abstractClass != null) { - return abstractClass; - } - abstractClass = - new MasterStorableGenerator(type, features).generateAndInjectClass(); - cCache.put(key, abstractClass); - return abstractClass; - } - } - - private final EnumSet mFeatures; - private final StorableInfo mInfo; - private final Map> mAllProperties; - - private final ClassInjector mClassInjector; - private final ClassFile mClassFile; - - private MasterStorableGenerator(Class storableType, EnumSet features) { - mFeatures = features; - mInfo = StorableIntrospector.examine(storableType); - mAllProperties = mInfo.getAllProperties(); - - final Class abstractClass = StorableGenerator.getAbstractClass(storableType); - - mClassInjector = ClassInjector.create - (storableType.getName(), abstractClass.getClassLoader()); - - mClassFile = new ClassFile(mClassInjector.getClassName(), abstractClass); - mClassFile.setModifiers(mClassFile.getModifiers().toAbstract(true)); - mClassFile.markSynthetic(); - mClassFile.setSourceFile(MasterStorableGenerator.class.getName()); - mClassFile.setTarget("1.5"); - } - - private Class generateAndInjectClass() throws SupportException { - generateClass(); - Class abstractClass = mClassInjector.defineClass(mClassFile); - return (Class) abstractClass; - } - - private void generateClass() throws SupportException { - // Declare some types. - final TypeDesc storableType = TypeDesc.forClass(Storable.class); - final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); - final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); - final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - final TypeDesc optimisticLockType = TypeDesc.forClass(OptimisticLockException.class); - final TypeDesc persistExceptionType = TypeDesc.forClass(PersistException.class); - - // Add constructor that accepts a MasterSupport. - { - TypeDesc[] params = {masterSupportType}; - MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.loadLocal(b.getParameter(0)); - b.invokeSuperConstructor(new TypeDesc[] {triggerSupportType}); - - b.returnVoid(); - } - - // Declare protected abstract methods. - { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - } - - // Add required protected doTryInsert method. - { - // If sequence support requested, implement special insert hook to - // call sequences for properties which are UNINITIALIZED. User may - // provide explicit values for properties with sequences. - - if (mFeatures.contains(MasterFeature.INSERT_SEQUENCES)) { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED, - StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, - null, null); - CodeBuilder b = new CodeBuilder(mi); - - int ordinal = 0; - for (StorableProperty property : mAllProperties.values()) { - if (property.getSequenceName() != null) { - // Check the state of this property, to see if it is - // uninitialized. Uninitialized state has value zero. - - String stateFieldName = - StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); - - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - int shift = (ordinal & 0xf) * 2; - b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift); - b.math(Opcode.IAND); - - Label isInitialized = b.createLabel(); - b.ifZeroComparisonBranch(isInitialized, "!="); - - // Load this in preparation for storing value to property. - b.loadThis(); - - // Call MasterSupport.getSequenceValueProducer(String). - TypeDesc seqValueProdType = TypeDesc.forClass(SequenceValueProducer.class); - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.checkCast(masterSupportType); - b.loadConstant(property.getSequenceName()); - b.invokeInterface - (masterSupportType, "getSequenceValueProducer", - seqValueProdType, new TypeDesc[] {TypeDesc.STRING}); - - // Find appropriate method to call for getting next sequence value. - TypeDesc propertyType = TypeDesc.forClass(property.getType()); - TypeDesc propertyObjType = propertyType.toObjectType(); - Method method; - - try { - if (propertyObjType == TypeDesc.LONG.toObjectType()) { - method = SequenceValueProducer.class - .getMethod("nextLongValue", (Class[]) null); - } else if (propertyObjType == TypeDesc.INT.toObjectType()) { - method = SequenceValueProducer.class - .getMethod("nextIntValue", (Class[]) null); - } else if (propertyObjType == TypeDesc.STRING) { - method = SequenceValueProducer.class - .getMethod("nextDecimalValue", (Class[]) null); - } else { - throw new SupportException - ("Unable to support sequence of type \"" + - property.getType().getName() + "\" for property: " + - property.getName()); - } - } catch (NoSuchMethodException e) { - Error err = new NoSuchMethodError(); - err.initCause(e); - throw err; - } - - b.invoke(method); - b.convert(TypeDesc.forClass(method.getReturnType()), propertyType); - - // Store property - b.storeField(property.getName(), propertyType); - - // Set state to dirty. - b.loadThis(); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift); - b.math(Opcode.IOR); - b.storeField(stateFieldName, TypeDesc.INT); - - isInitialized.setLocation(); - } - - ordinal++; - } - - // We've tried our best to fill in missing values, now run the - // original check method. - b.loadThis(); - b.invokeSuper(mClassFile.getSuperClassName(), - StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, - null, null); - b.returnVoid(); - } - - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED.toFinal(true), - StorableGenerator.DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - CodeBuilder b = new CodeBuilder(mi); - - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - - Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar); - - if (mFeatures.contains(MasterFeature.VERSIONING)) { - // Only set if uninitialized. - b.loadThis(); - b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME, - TypeDesc.BOOLEAN, null); - Label isInitialized = b.createLabel(); - b.ifZeroComparisonBranch(isInitialized, "!="); - addAdjustVersionProperty(b, null, 1); - isInitialized.setLocation(); - } - - if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) { - // Ensure that required properties have been set. - b.loadThis(); - b.invokeVirtual(StorableGenerator.IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, - TypeDesc.BOOLEAN, null); - Label isInitialized = b.createLabel(); - b.ifZeroComparisonBranch(isInitialized, "!="); - - // Throw a ConstraintException. - TypeDesc exType = TypeDesc.forClass(ConstraintException.class); - b.newObject(exType); - b.dup(); - - // Append all the uninitialized property names to the exception message. - - LocalVariable countVar = b.createLocalVariable(null, TypeDesc.INT); - b.loadConstant(0); - b.storeLocal(countVar); - - TypeDesc sbType = TypeDesc.forClass(StringBuilder.class); - b.newObject(sbType); - b.dup(); - b.loadConstant("Not all required properties have been set: "); - TypeDesc[] stringParam = {TypeDesc.STRING}; - b.invokeConstructor(sbType, stringParam); - LocalVariable sbVar = b.createLocalVariable(null, sbType); - b.storeLocal(sbVar); - - int ordinal = -1; - - HashSet stateAppendMethods = new HashSet(); - - // Parameters are: StringBuilder, count, mask, property name - TypeDesc[] appendParams = {sbType, TypeDesc.INT, TypeDesc.INT, TypeDesc.STRING}; - - for (StorableProperty property : mAllProperties.values()) { - ordinal++; - - if (property.isJoin() || property.isPrimaryKeyMember() - || property.isNullable() - || property.isAutomatic() || property.isVersion()) - { - continue; - } - - int stateField = ordinal >> 4; - - String stateAppendMethodName = APPEND_UNINIT_PROPERTY + stateField; - - if (!stateAppendMethods.contains(stateField)) { - stateAppendMethods.add(stateField); - - MethodInfo mi2 = mClassFile.addMethod - (Modifiers.PRIVATE, stateAppendMethodName, TypeDesc.INT, appendParams); - - CodeBuilder b2 = new CodeBuilder(mi2); - - // Load the StringBuilder parameter. - b2.loadLocal(b2.getParameter(0)); - - String stateFieldName = - StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); - - b2.loadThis(); - b2.loadField(stateFieldName, TypeDesc.INT); - // Load the mask parameter. - b2.loadLocal(b2.getParameter(2)); - b2.math(Opcode.IAND); - - Label propIsInitialized = b2.createLabel(); - b2.ifZeroComparisonBranch(propIsInitialized, "!="); - - // Load the count parameter. - b2.loadLocal(b2.getParameter(1)); - Label noComma = b2.createLabel(); - b2.ifZeroComparisonBranch(noComma, "=="); - b2.loadConstant(", "); - b2.invokeVirtual(sbType, "append", sbType, stringParam); - noComma.setLocation(); - - // Load the property name parameter. - b2.loadLocal(b2.getParameter(3)); - b2.invokeVirtual(sbType, "append", sbType, stringParam); - - // Increment the count parameter. - b2.integerIncrement(b2.getParameter(1), 1); - - propIsInitialized.setLocation(); - - // Return the possibly updated count. - b2.loadLocal(b2.getParameter(1)); - b2.returnValue(TypeDesc.INT); - } - - b.loadThis(); - // Parameters are: StringBuilder, count, mask, property name - b.loadLocal(sbVar); - b.loadLocal(countVar); - b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); - b.loadConstant(property.getName()); - b.invokePrivate(stateAppendMethodName, TypeDesc.INT, appendParams); - b.storeLocal(countVar); - } - - b.loadLocal(sbVar); - b.invokeVirtual(sbType, "toString", TypeDesc.STRING, null); - b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING}); - b.throwObject(); - - isInitialized.setLocation(); - } - - b.loadThis(); - b.invokeVirtual(DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (tryStart == null) { - b.returnValue(TypeDesc.BOOLEAN); - } else { - Label failed = b.createLabel(); - b.ifZeroComparisonBranch(failed, "=="); - - addCommitAndExitTransaction(b, INSERT_OP, txnVar); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - failed.setLocation(); - addExitTransaction(b, INSERT_OP, txnVar); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addExitTransaction(b, INSERT_OP, txnVar, tryStart); - } - } - - // Add required protected doTryUpdate method. - addDoTryUpdate: { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED.toFinal(true), - StorableGenerator.DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - CodeBuilder b = new CodeBuilder(mi); - - if ((!mFeatures.contains(MasterFeature.VERSIONING)) && - (!mFeatures.contains(MasterFeature.UPDATE_FULL)) && - (!mFeatures.contains(MasterFeature.UPDATE_TXN))) - { - // Nothing special needs to be done, so just delegate and return. - b.loadThis(); - b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - b.returnValue(TypeDesc.BOOLEAN); - break addDoTryUpdate; - } - - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - LocalVariable savedVar = null; - - Label tryStart = addEnterTransaction(b, UPDATE_OP, txnVar); - - Label failed = b.createLabel(); - - if (mFeatures.contains(MasterFeature.UPDATE_FULL)) { - // Storable saved = copy(); - b.loadThis(); - b.invokeVirtual(COPY_METHOD_NAME, storableType, null); - b.checkCast(mClassFile.getType()); - savedVar = b.createLocalVariable(null, mClassFile.getType()); - b.storeLocal(savedVar); - - // if (!saved.tryLoad()) { - // goto failed; - // } - b.loadLocal(savedVar); - b.invokeInterface(storableType, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - b.ifZeroComparisonBranch(failed, "=="); - - // if (version support enabled) { - // if (this.getVersionNumber() != saved.getVersionNumber()) { - // throw new OptimisticLockException - // (this.getVersionNumber(), saved.getVersionNumber(), this); - // } - // } - if (mFeatures.contains(MasterFeature.VERSIONING)) { - TypeDesc versionType = TypeDesc.forClass(mInfo.getVersionProperty().getType()); - b.loadThis(); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.loadLocal(savedVar); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - Label sameVersion = b.createLabel(); - CodeBuilderUtil.addValuesEqualCall(b, versionType, true, sameVersion, true); - b.newObject(optimisticLockType); - b.dup(); - b.loadThis(); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.convert(versionType, TypeDesc.OBJECT); - b.loadLocal(savedVar); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.convert(versionType, TypeDesc.OBJECT); - b.loadThis(); - b.invokeConstructor - (optimisticLockType, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType}); - b.throwObject(); - sameVersion.setLocation(); - } - - // this.copyDirtyProperties(saved); - // if (version support enabled) { - // saved.setVersionNumber(saved.getVersionNumber() + 1); - // } - b.loadThis(); - b.loadLocal(savedVar); - b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType}); - if (mFeatures.contains(MasterFeature.VERSIONING)) { - addAdjustVersionProperty(b, savedVar, -1); - } - - // if (!saved.doTryUpdateMaster()) { - // goto failed; - // } - b.loadLocal(savedVar); - b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - b.ifZeroComparisonBranch(failed, "=="); - - // saved.copyUnequalProperties(this); - b.loadLocal(savedVar); - b.loadThis(); - b.invokeInterface - (storableType, COPY_UNEQUAL_PROPERTIES, null, new TypeDesc[] {storableType}); - } else { - // if (!this.doTryUpdateMaster()) { - // goto failed; - // } - b.loadThis(); - b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - b.ifZeroComparisonBranch(failed, "=="); - } - - // txn.commit(); - // txn.exit(); - // return true; - addCommitAndExitTransaction(b, UPDATE_OP, txnVar); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - // failed: - // txn.exit(); - failed.setLocation(); - addExitTransaction(b, UPDATE_OP, txnVar); - // return false; - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addExitTransaction(b, UPDATE_OP, txnVar, tryStart); - } - - // Add required protected doTryDelete method. - { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED.toFinal(true), - StorableGenerator.DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(persistExceptionType); - CodeBuilder b = new CodeBuilder(mi); - - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - - Label tryStart = addEnterTransaction(b, DELETE_OP, txnVar); - - b.loadThis(); - b.invokeVirtual(DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (tryStart == null) { - b.returnValue(TypeDesc.BOOLEAN); - } else { - Label failed = b.createLabel(); - b.ifZeroComparisonBranch(failed, "=="); - addCommitAndExitTransaction(b, DELETE_OP, txnVar); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - failed.setLocation(); - addExitTransaction(b, DELETE_OP, txnVar); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addExitTransaction(b, DELETE_OP, txnVar, tryStart); - } - } - } - - /** - * Generates code to enter a transaction, if required. - * - * @param opType type of operation, Insert, Update, or Delete - * @param txnVar required variable of type Transaction for storing transaction - * @return optional try start label for transaction - */ - private Label addEnterTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { - if (!alwaysHasTxn(opType)) { - return null; - } - - // txn = masterSupport.getRootRepository().enterTransaction(); - - TypeDesc repositoryType = TypeDesc.forClass(Repository.class); - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); - TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); - - b.loadThis(); - b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); - b.invokeInterface(masterSupportType, "getRootRepository", - repositoryType, null); - b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, - transactionType, null); - b.storeLocal(txnVar); - if (requiresTxnForUpdate(opType)) { - // txn.setForUpdate(true); - b.loadLocal(txnVar); - b.loadConstant(true); - b.invokeInterface(transactionType, SET_FOR_UPDATE_METHOD_NAME, null, - new TypeDesc[] {TypeDesc.BOOLEAN}); - } - - return b.createLabel().setLocation(); - } - - private boolean alwaysHasTxn(String opType) { - return alwaysHasTxn(opType, mFeatures); - } - - private static boolean alwaysHasTxn(String opType, EnumSet features) { - if (opType == UPDATE_OP) { - return - features.contains(MasterFeature.UPDATE_TXN) || - features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || - features.contains(MasterFeature.VERSIONING) || - features.contains(MasterFeature.UPDATE_FULL); - } else if (opType == INSERT_OP) { - return - features.contains(MasterFeature.INSERT_TXN) || - features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); - } else if (opType == DELETE_OP) { - return - features.contains(MasterFeature.DELETE_TXN) || - features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); - } - return false; - } - - private boolean requiresTxnForUpdate(String opType) { - return requiresTxnForUpdate(opType, mFeatures); - } - - private static boolean requiresTxnForUpdate(String opType, EnumSet features) { - if (opType == UPDATE_OP) { - return - features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || - features.contains(MasterFeature.VERSIONING) || - features.contains(MasterFeature.UPDATE_FULL); - } else if (opType == INSERT_OP) { - return features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); - } else if (opType == DELETE_OP) { - return features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); - } - return false; - } - - private void addCommitAndExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { - if (!alwaysHasTxn(opType)) { - return; - } - - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - - // txn.commit(); - // txn.exit(); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); - } - - /** - * - * @param opType type of operation, Insert, Update, or Delete - */ - private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { - if (!alwaysHasTxn(opType)) { - return; - } - - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - - // txn.exit(); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); - } - - /** - * - * @param opType type of operation, Insert, Update, or Delete - */ - private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar, - Label tryStart) - { - if (tryStart == null) { - addExitTransaction(b, opType, txnVar); - return; - } - - // } catch (... e) { - // txn.exit(); - // throw e; - // } - - Label tryEnd = b.createLabel().setLocation(); - b.exceptionHandler(tryStart, tryEnd, null); - addExitTransaction(b, opType, txnVar); - b.throwObject(); - } - - /* - * Generates code to adjust the version property. If value parameter is negative, then - * version is incremented as follows: - * - * storable.setVersionNumber(storable.getVersionNumber() + 1); - * - * Otherwise, the version is set: - * - * storable.setVersionNumber(value); - * - * @param storableVar references storable instance, or null if this - * @param value if negative, increment version, else, set version to this value - */ - private void addAdjustVersionProperty(CodeBuilder b, - LocalVariable storableVar, - int value) - throws SupportException - { - // Push storable to stack in preparation for calling set method below. - if (storableVar == null) { - b.loadThis(); - } else { - b.loadLocal(storableVar); - } - - StorableProperty versionProperty = mInfo.getVersionProperty(); - TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); - - if (value >= 0) { - CodeBuilderUtil.initialVersion(b, versionType, value); - } else { - // Load current property value. - b.dup(); - b.invoke(versionProperty.getReadMethod()); - CodeBuilderUtil.incrementVersion(b, versionType); - } - - b.invoke(versionProperty.getWriteMethod()); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java deleted file mode 100644 index dd48194..0000000 --- a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.spi; - -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.sequence.SequenceValueProducer; - -/** - * Provides runtime support for Storable classes generated by {@link MasterStorableGenerator}. - * - * @author Brian S O'Neill - */ -public interface MasterSupport extends TriggerSupport { - /** - * Returns a sequence value producer by name, or throw PersistException if not found. - * - *

    Note: this method throws PersistException even for fetch failures - * since this method is called by insert operations. Insert operations can - * only throw a PersistException. - */ - SequenceValueProducer getSequenceValueProducer(String name) throws PersistException; -} diff --git a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java deleted file mode 100644 index 0c2d50d..0000000 --- a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java +++ /dev/null @@ -1,3671 +0,0 @@ -/* - * 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.spi; - -import java.lang.annotation.Annotation; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.math.BigInteger; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.Label; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodDesc; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.Opcode; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.WeakIdentityMap; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.FetchNoneException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.PersistNoneException; -import com.amazon.carbonado.Query; -import com.amazon.carbonado.Repository; -import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.Transaction; -import com.amazon.carbonado.Trigger; -import com.amazon.carbonado.UniqueConstraintException; - -import com.amazon.carbonado.lob.Lob; - -import com.amazon.carbonado.info.ChainedProperty; -import com.amazon.carbonado.info.OrderedProperty; -import com.amazon.carbonado.info.StorableInfo; -import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.info.StorableKey; -import com.amazon.carbonado.info.StorableProperty; -import com.amazon.carbonado.info.StorablePropertyAdapter; -import com.amazon.carbonado.info.StorablePropertyAnnotation; -import com.amazon.carbonado.info.StorablePropertyConstraint; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; - -/** - * Generates and caches abstract and wrapped implementations of {@link - * Storable} types. This greatly simplifies the process of defining new kinds - * of {@link Repository Repositories}, since most of the mundane code - * generation is taken care of. - * - * @author Brian S O'Neill - * @author Don Schneider - * @see MasterStorableGenerator - */ -public final class StorableGenerator { - - // Note: All generated fields/methods have a "$" character in them to - // prevent name collisions with any inherited fields/methods. User storable - // properties are defined as fields which exactly match the property - // name. We don't want collisions with those either. Legal bean properties - // cannot have "$" in them, so there's nothing to worry about. - - /** Name of protected abstract method in generated storable */ - public static final String - DO_TRY_LOAD_METHOD_NAME = "doTryLoad$", - DO_TRY_INSERT_METHOD_NAME = "doTryInsert$", - DO_TRY_UPDATE_METHOD_NAME = "doTryUpdate$", - DO_TRY_DELETE_METHOD_NAME = "doTryDelete$"; - - /** - * Name of protected method in generated storable which checks that - * primary keys are initialized, throwing an exception otherwise. - */ - public static final String - CHECK_PK_FOR_INSERT_METHOD_NAME = "checkPkForInsert$", - CHECK_PK_FOR_UPDATE_METHOD_NAME = "checkPkForUpdate$", - CHECK_PK_FOR_DELETE_METHOD_NAME = "checkPkForDelete$"; - - /** - * Name of protected method in generated storable that returns false if any - * primary keys are uninitialized. - */ - public static final String IS_PK_INITIALIZED_METHOD_NAME = "isPkInitialized$"; - - /** - * Name prefix of protected method in generated storable that returns false - * if a specific alternate key is uninitialized. The complete name is - * formed by the prefix appended with the zero-based alternate key ordinal. - */ - public static final String IS_ALT_KEY_INITIALIZED_PREFIX = "isAltKeyInitialized$"; - - /** - * Name of protected method in generated storable that returns false if any - * non-nullable, non-pk properties are uninitialized. - */ - public static final String IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME = - "isRequiredDataInitialized$"; - - /** - * Name of protected method in generated storable that returns false if - * version property is uninitialized. If no version property exists, then - * this method is not defined. - */ - public static final String IS_VERSION_INITIALIZED_METHOD_NAME = "isVersionInitialized$"; - - /** - * Prefix of protected field in generated storable that holds property - * states. Each property consumes two bits to hold its state, and so each - * 32-bit field holds states for up to 16 properties. - */ - public static final String PROPERTY_STATE_FIELD_NAME = "propertyState$"; - - /** Adapter field names are propertyName + "$adapter$" + ordinal */ - public static final String ADAPTER_FIELD_ELEMENT = "$adapter$"; - - /** Constraint field names are propertyName + "$constraint$" + ordinal */ - public static final String CONSTRAINT_FIELD_ELEMENT = "$constraint$"; - - /** Reference to TriggerSupport or WrappedSupport instance */ - public static final String SUPPORT_FIELD_NAME = "support$"; - - /** Property state indicating that property has never been set, loaded, or saved */ - public static final int PROPERTY_STATE_UNINITIALIZED = 0; - /** Property state indicating that property has been set, but not saved */ - public static final int PROPERTY_STATE_DIRTY = 3; - /** Property state indicating that property value reflects a clean value */ - public static final int PROPERTY_STATE_CLEAN = 1; - /** Property state mask is 3, to cover the two bits used by a property state */ - public static final int PROPERTY_STATE_MASK = 3; - - // Private method which returns a property's state. - private static final String PROPERTY_STATE_EXTRACT_METHOD_NAME = "extractState$"; - - private static final String PRIVATE_INSERT_METHOD_NAME = "insert$"; - private static final String PRIVATE_UPDATE_METHOD_NAME = "update$"; - private static final String PRIVATE_DELETE_METHOD_NAME = "delete$"; - - // Cache of generated abstract classes. - private static Map>> cAbstractCache; - // Cache of generated wrapped classes. - private static Map>> cWrappedCache; - - static { - cAbstractCache = new WeakIdentityMap(); - cWrappedCache = new WeakIdentityMap(); - } - - // There are three flavors of equals methods, used by addEqualsMethod. - private static final int EQUAL_KEYS = 0; - private static final int EQUAL_PROPERTIES = 1; - private static final int EQUAL_FULL = 2; - - // Operation mode for generating Storable. - private static final int GEN_ABSTRACT = 1; - private static final int GEN_WRAPPED = 2; - - private static final String WRAPPED_STORABLE_FIELD_NAME = "wrappedStorable$"; - - private static final String UNCAUGHT_METHOD_NAME = "uncaught$"; - - private static final String INSERT_OP = "Insert"; - private static final String UPDATE_OP = "Update"; - private static final String DELETE_OP = "Delete"; - - /** - * Returns an abstract implementation of the given Storable type, which is - * fully thread-safe. The Storable type itself may be an interface or a - * class. If it is a class, then it must not be final, and it must have a - * public, no-arg constructor. The constructor signature for the returned - * abstract is defined as follows: - * - *

    -     * /**
    -     *  * @param support  Access to triggers
    -     *  */
    -     * public <init>(TriggerSupport support);
    -     * 
    - * - *

    Subclasses must implement the following abstract protected methods, - * whose exact names are defined by constants in this class: - * - *

    -     * // Load the object by examining the primary key.
    -     * protected abstract boolean doTryLoad() throws FetchException;
    -     *
    -     * // Insert the object into the storage layer.
    -     * protected abstract boolean doTryInsert() throws PersistException;
    -     *
    -     * // Update the object in the storage.
    -     * protected abstract boolean doTryUpdate() throws PersistException;
    -     *
    -     * // Delete the object from the storage layer by the primary key.
    -     * protected abstract boolean doTryDelete() throws PersistException;
    -     * 
    - * - * A set of protected hook methods are provided which ensure that all - * primary keys are initialized before performing a repository - * operation. Subclasses may override them, if they are capable of filling - * in unspecified primary keys. One such example is applying a sequence on - * insert. - * - *
    -     * // Throws exception if any primary keys are uninitialized.
    -     * // Actual method name defined by CHECK_PK_FOR_INSERT_METHOD_NAME.
    -     * protected void checkPkForInsert() throws IllegalStateException;
    -     *
    -     * // Throws exception if any primary keys are uninitialized.
    -     * // Actual method name defined by CHECK_PK_FOR_UPDATE_METHOD_NAME.
    -     * protected void checkPkForUpdate() throws IllegalStateException;
    -     *
    -     * // Throws exception if any primary keys are uninitialized.
    -     * // Actual method name defined by CHECK_PK_FOR_DELETE_METHOD_NAME.
    -     * protected void checkPkForDelete() throws IllegalStateException;
    -     * 
    - * - * Each property value is defined as a protected field whose name and type - * matches the property. Subclasses should access these fields directly - * during loading and storing. For loading, it bypasses constraint - * checks. For both, it provides better performance. - * - *

    Subclasses also have access to a set of property state bits stored - * in protected int fields. Subclasses are not responsible for updating - * these values. The intention is that these states may be used by - * subclasses to support partial updates. They may otherwise be ignored. - * - *

    As a convenience, protected methods are provided to test and alter - * the property state bits. Subclass constructors that fill all properties - * with loaded values must call markAllPropertiesClean to ensure all - * properties are identified as being valid. - * - *

    -     * // Returns true if all primary key properties have been set.
    -     * protected boolean isPkInitialized();
    -     *
    -     * // Returns true if all required data properties are set.
    -     * // A required data property is a non-nullable, non-primary key.
    -     * protected boolean isRequiredDataInitialized();
    -     *
    -     * // Returns true if a version property has been set.
    -     * // Note: This method is not generated if there is no version property.
    -     * protected boolean isVersionInitialized();
    -     * 
    - * - * Property state field names are defined by the concatenation of - * {@code PROPERTY_STATE_FIELD_NAME} and a zero-based decimal - * ordinal. To determine which field holds a particular property's state, - * the field ordinal is computed as the property ordinal divided by 16. The - * specific two-bit state position is the remainder of this division times 2. - * - * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed - * @throws IllegalArgumentException if type is null - */ - @SuppressWarnings("unchecked") - public static Class getAbstractClass(Class type) - throws IllegalArgumentException - { - synchronized (cAbstractCache) { - Class abstractClass; - Reference> ref = cAbstractCache.get(type); - if (ref != null) { - abstractClass = (Class) ref.get(); - if (abstractClass != null) { - return abstractClass; - } - } - abstractClass = new StorableGenerator(type, GEN_ABSTRACT).generateAndInjectClass(); - cAbstractCache.put(type, new SoftReference>(abstractClass)); - return abstractClass; - } - } - - /** - * Returns a concrete Storable implementation of the given type which wraps - * another Storable. The Storable type itself may be an interface or a - * class. If it is a class, then it must not be final, and it must have a - * public, no-arg constructor. The constructor signature for the returned - * class is defined as follows: - * - *
    -     * /**
    -     *  * @param support  Custom implementation for Storable CRUD operations
    -     *  * @param storable Storable being wrapped
    -     *  */
    -     * public <init>(WrappedSupport support, Storable storable);
    -     * 
    - * - *

    Instances of the wrapped Storable delegate to the WrappedSupport for - * all CRUD operations: - * - *

      - *
    • load and tryLoad - *
    • insert and tryInsert - *
    • update and tryUpdate - *
    • delete and tryDelete - *
    - * - *

    Methods which delegate to wrapped Storable: - * - *

      - *
    • all ordinary user-defined properties - *
    • copyAllProperties - *
    • copyPrimaryKeyProperties - *
    • copyVersionProperty - *
    • copyUnequalProperties - *
    • copyDirtyProperties - *
    • hasDirtyProperties - *
    • markPropertiesClean - *
    • markAllPropertiesClean - *
    • markPropertiesDirty - *
    • markAllPropertiesDirty - *
    • hashCode - *
    • equalPrimaryKeys - *
    • equalProperties - *
    • toString - *
    • toStringKeyOnly - *
    - * - *

    Methods with special implementation: - * - *

      - *
    • all user-defined join properties (join properties query using wrapper's Storage) - *
    • storage (returns Storage used by wrapper) - *
    • storableType (returns literal class) - *
    • copy (delegates to wrapped storable, but bridge methods must be defined as well) - *
    • equals (compares Storage instance and properties) - *
    - * - * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed - * @throws IllegalArgumentException if type is null - */ - @SuppressWarnings("unchecked") - public static Class getWrappedClass(Class type) - throws IllegalArgumentException - { - synchronized (cWrappedCache) { - Class wrappedClass; - Reference> ref = cWrappedCache.get(type); - if (ref != null) { - wrappedClass = (Class) ref.get(); - if (wrappedClass != null) { - return wrappedClass; - } - } - wrappedClass = new StorableGenerator(type, GEN_WRAPPED).generateAndInjectClass(); - cWrappedCache.put(type, new SoftReference>(wrappedClass)); - return wrappedClass; - } - } - - private final Class mStorableType; - private final int mGenMode; - private final TypeDesc mSupportType; - private final StorableInfo mInfo; - private final Map> mAllProperties; - private final boolean mHasJoins; - - private final ClassInjector mClassInjector; - private final ClassFile mClassFile; - - private StorableGenerator(Class storableType, int genMode) { - mStorableType = storableType; - mGenMode = genMode; - if (genMode == GEN_WRAPPED) { - mSupportType = TypeDesc.forClass(WrappedSupport.class); - } else { - mSupportType = TypeDesc.forClass(TriggerSupport.class); - } - mInfo = StorableIntrospector.examine(storableType); - mAllProperties = mInfo.getAllProperties(); - - boolean hasJoins = false; - for (StorableProperty property : mAllProperties.values()) { - if (property.isJoin()) { - hasJoins = true; - break; - } - } - mHasJoins = hasJoins; - - mClassInjector = ClassInjector.create - (storableType.getName(), storableType.getClassLoader()); - mClassFile = CodeBuilderUtil.createStorableClassFile - (mClassInjector, storableType, genMode == GEN_ABSTRACT, - StorableGenerator.class.getName()); - } - - private Class generateAndInjectClass() { - generateClass(); - Class abstractClass = mClassInjector.defineClass(mClassFile); - return (Class) abstractClass; - } - - private void generateClass() { - // Use this static method for passing uncaught exceptions. - defineUncaughtExceptionHandler(); - - // private final TriggerSupport support; - // Field is not final for GEN_WRAPPED, so that copy method can - // change WrappedSupport after calling clone. - mClassFile.addField(Modifiers.PROTECTED.toFinal(mGenMode == GEN_ABSTRACT), - SUPPORT_FIELD_NAME, - mSupportType); - - if (mGenMode == GEN_WRAPPED) { - // Add a few more fields to hold arguments passed from constructor. - - // private final wrappedStorable; - // Field is not final for GEN_WRAPPED, so that copy method can - // change wrapped Storable after calling clone. - mClassFile.addField(Modifiers.PRIVATE.toFinal(false), - WRAPPED_STORABLE_FIELD_NAME, - TypeDesc.forClass(mStorableType)); - } - - if (mGenMode == GEN_ABSTRACT) { - // Add protected constructor. - TypeDesc[] params = {mSupportType}; - - final int supportParam = 0; - MethodInfo mi = mClassFile.addConstructor(Modifiers.PROTECTED, params); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.invokeSuperConstructor(null); - - //// this.support = support - b.loadThis(); - b.loadLocal(b.getParameter(supportParam)); - b.storeField(SUPPORT_FIELD_NAME, mSupportType); - - b.returnVoid(); - } else if (mGenMode == GEN_WRAPPED) { - // Add public constructor. - TypeDesc[] params = {mSupportType, TypeDesc.forClass(Storable.class)}; - - final int wrappedSupportParam = 0; - final int wrappedStorableParam = 1; - MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.invokeSuperConstructor(null); - - //// this.wrappedSupport = wrappedSupport - b.loadThis(); - b.loadLocal(b.getParameter(wrappedSupportParam)); - b.storeField(SUPPORT_FIELD_NAME, mSupportType); - - //// this.wrappedStorable = wrappedStorable - b.loadThis(); - b.loadLocal(b.getParameter(wrappedStorableParam)); - b.checkCast(TypeDesc.forClass(mStorableType)); - b.storeField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - - b.returnVoid(); - } - - // Add static fields for adapters and constraints, and create static - // initializer to populate fields. - if (mGenMode == GEN_ABSTRACT) { - // CodeBuilder for static initializer, defined only if there's - // something to put in it. - CodeBuilder clinit = null; - - // Adapter and constraint fields are protected static. - final Modifiers fieldModifiers = Modifiers.PROTECTED.toStatic(true).toFinal(true); - - // Add adapter field. - for (StorableProperty property : mAllProperties.values()) { - StorablePropertyAdapter spa = property.getAdapter(); - if (spa == null) { - continue; - } - - String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; - TypeDesc adapterType = TypeDesc.forClass - (spa.getAdapterConstructor().getDeclaringClass()); - mClassFile.addField(fieldModifiers, fieldName, adapterType); - - if (clinit == null) { - clinit = new CodeBuilder(mClassFile.addInitializer()); - } - - // Assign value to new field. - // admin$adapter$0 = new YesNoAdapter.Adapter - // (UserInfo.class, "admin", annotation); - - clinit.newObject(adapterType); - clinit.dup(); - clinit.loadConstant(TypeDesc.forClass(mStorableType)); - clinit.loadConstant(property.getName()); - - // Generate code to load property annotation third parameter. - loadPropertyAnnotation(clinit, property, spa.getAnnotation()); - - clinit.invoke(spa.getAdapterConstructor()); - clinit.storeStaticField(fieldName, adapterType); - } - - // Add contraint fields. - for (StorableProperty property : mAllProperties.values()) { - int count = property.getConstraintCount(); - for (int i=0; i property : mAllProperties.values()) { - ordinal++; - - if (property.isVersion()) { - versionOrdinal = ordinal; - } - - final String name = property.getName(); - final TypeDesc type = TypeDesc.forClass(property.getType()); - - if (property.isJoin()) { - // If generating wrapper, property access is not guarded by - // synchronization. Mark as volatile instead. - mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED), - name, type); - requireStateField = true; - } else if (mGenMode == GEN_ABSTRACT) { - // Only define regular property fields if abstract - // class. Wrapped class doesn't reference them. Double - // words are volatile to prevent word tearing without - // explicit synchronization. - mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()), - name, type); - requireStateField = true; - } - - final String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); - if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) { - if (requireStateField) { - // If generating wrapper, property state access is not guarded by - // synchronization. Mark as volatile instead. - mClassFile.addField - (Modifiers.PROTECTED.toVolatile(mGenMode == GEN_WRAPPED), - stateFieldName, TypeDesc.INT); - } - requireStateField = false; - } - - // Add read method. - buildReadMethod: { - Method readMethod = property.getReadMethod(); - - MethodInfo mi; - if (readMethod != null) { - mi = mClassFile.addMethod(readMethod); - } else { - // Add a synthetic protected read method. - String readName = property.getReadMethodName(); - mi = mClassFile.addMethod(Modifiers.PROTECTED, readName, type, null); - mi.markSynthetic(); - if (property.isJoin()) { - mi.addException(TypeDesc.forClass(FetchException.class)); - } - } - - if (mGenMode == GEN_ABSTRACT && property.isJoin()) { - // Synchronization is required for join property - // accessors, as they may alter bit masks. - mi.setModifiers(mi.getModifiers().toSynchronized(true)); - } - - // Now add code that actually gets the property value. - CodeBuilder b = new CodeBuilder(mi); - - if (property.isJoin()) { - // Join properties support on-demand loading. - - // Check if property has been loaded. - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); - b.math(Opcode.IAND); - Label isLoaded = b.createLabel(); - b.ifZeroComparisonBranch(isLoaded, "!="); - - // Store loaded join result here. - LocalVariable join = b.createLocalVariable(name, type); - - // Check if any internal properties are nullable, but - // the matching external property is not. If so, load - // each of these special internal values and check if - // null. If null, short-circuit the load and use null - // as the join result. - - Label shortCircuit = b.createLabel(); - buildShortCircuit: { - int count = property.getJoinElementCount(); - nullPossible: { - for (int i=0; i internal = property.getInternalJoinElement(i); - if (mGenMode == GEN_ABSTRACT) { - b.loadThis(); - b.loadField(internal.getName(), - TypeDesc.forClass(internal.getType())); - } else { - b.loadThis(); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, - TypeDesc.forClass(mStorableType)); - b.invoke(internal.getReadMethod()); - } - TypeDesc bindType = - CodeBuilderUtil.bindQueryParam(internal.getType()); - CodeBuilderUtil.convertValue - (b, internal.getType(), bindType.toClass()); - b.invokeInterface(queryType, WITH_METHOD_NAME, queryType, - new TypeDesc[]{bindType}); - } - - // Now run the query. - if (property.isQuery()) { - // Just save and return the query. - b.storeLocal(join); - } else { - String loadMethod = - property.isNullable() ? - TRY_LOAD_ONE_METHOD_NAME : - LOAD_ONE_METHOD_NAME; - b.invokeInterface(queryType, loadMethod, storableDesc, null); - b.checkCast(type); - b.storeLocal(join); - } - } - - // Store loaded property. - shortCircuit.setLocation(); - b.loadThis(); - b.loadLocal(join); - b.storeField(property.getName(), type); - - // Add code to identify this property as being loaded. - b.loadThis(); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); - b.math(Opcode.IOR); - b.storeField(stateFieldName, TypeDesc.INT); - - isLoaded.setLocation(); - } - - // Load property value and return it. - - if (mGenMode == GEN_ABSTRACT || property.isJoin()) { - b.loadThis(); - b.loadField(property.getName(), type); - } else { - b.loadThis(); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - b.invoke(readMethod); - } - - b.returnValue(type); - } - - // Add write method. - if (!property.isQuery()) { - Method writeMethod = property.getWriteMethod(); - - MethodInfo mi; - if (writeMethod != null) { - mi = mClassFile.addMethod(writeMethod); - } else { - // Add a synthetic protected write method. - String writeName = property.getWriteMethodName(); - mi = mClassFile.addMethod(Modifiers.PROTECTED, writeName, null, - new TypeDesc[]{type}); - mi.markSynthetic(); - } - - if (mGenMode == GEN_ABSTRACT) { - mi.setModifiers(mi.getModifiers().toSynchronized(true)); - } - CodeBuilder b = new CodeBuilder(mi); - - // Primary keys cannot be altered if state is "clean". - if (mGenMode == GEN_ABSTRACT && property.isPrimaryKeyMember()) { - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); - b.math(Opcode.IAND); - b.loadConstant(PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2)); - Label isMutable = b.createLabel(); - b.ifComparisonBranch(isMutable, "!="); - CodeBuilderUtil.throwException - (b, IllegalStateException.class, "Cannot alter primary key"); - isMutable.setLocation(); - } - - int spcCount = property.getConstraintCount(); - - boolean nullNotAllowed = - !property.getType().isPrimitive() && - !property.isJoin() && !property.isNullable(); - - if (mGenMode == GEN_ABSTRACT && (nullNotAllowed || spcCount > 0)) { - // Add constraint checks. - Label skipConstraints = b.createLabel(); - - if (nullNotAllowed) { - // Don't allow null value to be set. - b.loadLocal(b.getParameter(0)); - Label notNull = b.createLabel(); - b.ifNullBranch(notNull, false); - CodeBuilderUtil.throwException - (b, IllegalArgumentException.class, - "Cannot set property \"" + property.getName() + - "\" to null"); - notNull.setLocation(); - } else { - // Don't invoke constraints if value is null. - if (!property.getType().isPrimitive()) { - b.loadLocal(b.getParameter(0)); - b.ifNullBranch(skipConstraints, true); - } - } - - // Add code to invoke constraints. - - for (int spcIndex = 0; spcIndex < spcCount; spcIndex++) { - StorablePropertyConstraint spc = property.getConstraint(spcIndex); - String fieldName = - property.getName() + CONSTRAINT_FIELD_ELEMENT + spcIndex; - TypeDesc constraintType = TypeDesc.forClass - (spc.getConstraintConstructor().getDeclaringClass()); - b.loadStaticField(fieldName, constraintType); - b.loadLocal(b.getParameter(0)); - b.convert - (b.getParameter(0).getType(), TypeDesc.forClass - (spc.getConstrainMethod().getParameterTypes()[0])); - b.invoke(spc.getConstrainMethod()); - } - - skipConstraints.setLocation(); - } - - Label setValue = b.createLabel(); - - if (!property.isJoin() || Lob.class.isAssignableFrom(property.getType())) { - if (mGenMode == GEN_ABSTRACT) { - if (Lob.class.isAssignableFrom(property.getType())) { - // Contrary to how standard properties are managed, - // only mark dirty if value changed. - b.loadThis(); - b.loadField(property.getName(), type); - b.loadLocal(b.getParameter(0)); - CodeBuilderUtil.addValuesEqualCall(b, type, true, setValue, true); - } - } - - markOrdinaryPropertyDirty(b, property); - } else { - // If passed value is null, throw an - // IllegalArgumentException. Passing in null could also - // indicate that the property should be unloaded, but - // that is non-intuitive. - - b.loadLocal(b.getParameter(0)); - Label notNull = b.createLabel(); - b.ifNullBranch(notNull, false); - CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null); - notNull.setLocation(); - - // Copy internal properties from joined object. - int count = property.getJoinElementCount(); - for (int i=0; i> 4), TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_MASK << ((ord & 0xf) * 2)); - b.math(Opcode.IAND); - b.loadConstant(PROPERTY_STATE_CLEAN << ((ord & 0xf) * 2)); - // If not clean, skip equal check. - b.ifComparisonBranch(setInternalProp, "!="); - } else { - // Call the public isPropertyClean method since - // the raw state bits are hidden. - b.loadThis(); - b.loadConstant(internal.getName()); - b.invokeVirtual(IS_PROPERTY_CLEAN, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.STRING}); - // If not clean, skip equal check. - b.ifZeroComparisonBranch(setInternalProp, "=="); - } - - // If new internal property value is equal to - // existing value, skip setting it. - b.loadThis(); - b.invoke(internal.getReadMethod()); - b.loadLocal(newInternalPropVar); - Label skipSetInternalProp = b.createLabel(); - CodeBuilderUtil.addValuesEqualCall - (b, TypeDesc.forClass(internal.getType()), - true, skipSetInternalProp, true); - - setInternalProp.setLocation(); - - // Call set method to ensure that state bits are - // properly adjusted. - b.loadThis(); - b.loadLocal(newInternalPropVar); - b.invoke(internal.getWriteMethod()); - - skipSetInternalProp.setLocation(); - } - - // Add code to identify this property as being loaded. - b.loadThis(); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); - b.math(Opcode.IOR); - b.storeField(stateFieldName, TypeDesc.INT); - } - - // Now add code that actually sets the property value. - - setValue.setLocation(); - - if (mGenMode == GEN_ABSTRACT || property.isJoin()) { - b.loadThis(); - b.loadLocal(b.getParameter(0)); - b.storeField(property.getName(), type); - } else { - b.loadThis(); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - b.loadLocal(b.getParameter(0)); - b.invoke(writeMethod); - } - - b.returnVoid(); - } - - // Add optional protected adapted read methods. - if (mGenMode == GEN_ABSTRACT && property.getAdapter() != null) { - // End name with '$' to prevent any possible collisions. - String readName = property.getReadMethodName() + '$'; - - StorablePropertyAdapter adapter = property.getAdapter(); - - for (Method adaptMethod : adapter.findAdaptMethodsFrom(type.toClass())) { - TypeDesc toType = TypeDesc.forClass(adaptMethod.getReturnType()); - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED, readName, toType, null); - mi.markSynthetic(); - - // Now add code that actually gets the property value and - // then invokes adapt method. - CodeBuilder b = new CodeBuilder(mi); - - // Push adapter class to stack. - String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; - TypeDesc adapterType = TypeDesc.forClass - (adapter.getAdapterConstructor().getDeclaringClass()); - b.loadStaticField(fieldName, adapterType); - - // Load property value. - b.loadThis(); - b.loadField(property.getName(), type); - - b.invoke(adaptMethod); - b.returnValue(toType); - } - } - - // Add optional protected adapted write methods. - - // Note: Calling these methods does not affect any state bits. - // They are only intended to be used by subclasses during loading. - - if (mGenMode == GEN_ABSTRACT && property.getAdapter() != null) { - // End name with '$' to prevent any possible collisions. - String writeName = property.getWriteMethodName() + '$'; - - StorablePropertyAdapter adapter = property.getAdapter(); - - for (Method adaptMethod : adapter.findAdaptMethodsTo(type.toClass())) { - TypeDesc fromType = TypeDesc.forClass(adaptMethod.getParameterTypes()[0]); - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED, writeName, null, new TypeDesc[] {fromType}); - mi.markSynthetic(); - mi.setModifiers(mi.getModifiers().toSynchronized(true)); - - // Now add code that actually adapts parameter and then - // stores the property value. - CodeBuilder b = new CodeBuilder(mi); - - // Push this in preparation for storing a field. - b.loadThis(); - - // Push adapter class to stack. - String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0; - TypeDesc adapterType = TypeDesc.forClass - (adapter.getAdapterConstructor().getDeclaringClass()); - b.loadStaticField(fieldName, adapterType); - - b.loadLocal(b.getParameter(0)); - b.invoke(adaptMethod); - b.storeField(property.getName(), type); - - b.returnVoid(); - } - } - } - } - - // Add tryLoad method which delegates to abstract doTryLoad method. - addTryLoad: { - // Define the tryLoad method. - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), - TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (mi == null) { - break addTryLoad; - } - - mi.addException(TypeDesc.forClass(FetchException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, null, false, null); - break addTryLoad; - } - - CodeBuilder b = new CodeBuilder(mi); - - // Check that primary key is initialized. - b.loadThis(); - b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); - Label pkInitialized = b.createLabel(); - b.ifZeroComparisonBranch(pkInitialized, "!="); - - Label loaded = b.createLabel(); - Label notLoaded = b.createLabel(); - - if (mInfo.getAlternateKeyCount() == 0) { - CodeBuilderUtil.throwException(b, IllegalStateException.class, - "Primary key not fully specified"); - } else { - // If any alternate keys, check them too. - - // Load our Storage, in preparation for query against it. - loadStorageForFetch(b, TypeDesc.forClass(mStorableType)); - - Label runQuery = b.createLabel(); - TypeDesc queryType = TypeDesc.forClass(Query.class); - - for (int i=0; i altKey = mInfo.getAlternateKey(i); - - // Form query filter. - StringBuilder queryBuilder = new StringBuilder(); - for (OrderedProperty op : altKey.getProperties()) { - if (queryBuilder.length() > 0) { - queryBuilder.append(" & "); - } - queryBuilder.append(op.getChainedProperty().toString()); - queryBuilder.append(" = ?"); - } - - // Get query instance from Storage already loaded on stack. - b.loadConstant(queryBuilder.toString()); - b.invokeInterface(TypeDesc.forClass(Storage.class), - QUERY_METHOD_NAME, queryType, - new TypeDesc[]{TypeDesc.STRING}); - - // Now fill in the parameters of the query. - for (OrderedProperty op : altKey.getProperties()) { - StorableProperty prop = op.getChainedProperty().getPrimeProperty(); - b.loadThis(); - TypeDesc propType = TypeDesc.forClass(prop.getType()); - b.loadField(prop.getName(), propType); - TypeDesc bindType = CodeBuilderUtil.bindQueryParam(prop.getType()); - CodeBuilderUtil.convertValue(b, prop.getType(), bindType.toClass()); - b.invokeInterface(queryType, WITH_METHOD_NAME, queryType, - new TypeDesc[]{bindType}); - } - - b.branch(runQuery); - - noAltKey.setLocation(); - } - - CodeBuilderUtil.throwException(b, IllegalStateException.class, - "Primary or alternate key not fully specified"); - - // Run query sitting on the stack. - runQuery.setLocation(); - - b.invokeInterface(queryType, TRY_LOAD_ONE_METHOD_NAME, - TypeDesc.forClass(Storable.class), null); - LocalVariable fetchedVar = b.createLocalVariable(null, TypeDesc.OBJECT); - b.storeLocal(fetchedVar); - - // If query fetch is null, then object not found. Return false. - b.loadLocal(fetchedVar); - b.ifNullBranch(notLoaded, true); - - // Copy all properties from fetched object into this one. - - // Allow copy to destroy everything, including primary key. - b.loadThis(); - b.invokeVirtual(MARK_ALL_PROPERTIES_DIRTY, null, null); - - b.loadLocal(fetchedVar); - b.checkCast(TypeDesc.forClass(mStorableType)); - b.loadThis(); - b.invokeInterface(TypeDesc.forClass(Storable.class), - COPY_ALL_PROPERTIES, null, - new TypeDesc[] {TypeDesc.forClass(Storable.class)}); - - b.branch(loaded); - } - - pkInitialized.setLocation(); - - // Call doTryLoad and mark all properties as clean if load succeeded. - b.loadThis(); - b.invokeVirtual(DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - - b.ifZeroComparisonBranch(notLoaded, "=="); - - loaded.setLocation(); - // Only mark properties clean if doTryLoad returned true. - b.loadThis(); - b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - notLoaded.setLocation(); - // Mark properties dirty, to be consistent with a delete side-effect. - b.loadThis(); - b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - if (mGenMode == GEN_ABSTRACT) { - // Define the abstract method. - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(FetchException.class)); - } - } - - // Add load method which calls tryLoad. - addLoad: { - // Define the load method. - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), - LOAD_METHOD_NAME, null, null); - - if (mi == null) { - break addLoad; - } - - mi.addException(TypeDesc.forClass(FetchException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, null, false, FetchNoneException.class); - break addLoad; - } - - CodeBuilder b = new CodeBuilder(mi); - - // Call tryLoad and throw an exception if false returned. - b.loadThis(); - b.invokeVirtual(TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - - Label wasNotLoaded = b.createLabel(); - b.ifZeroComparisonBranch(wasNotLoaded, "=="); - b.returnVoid(); - - wasNotLoaded.setLocation(); - - TypeDesc noMatchesType = TypeDesc.forClass(FetchNoneException.class); - b.newObject(noMatchesType); - b.dup(); - b.loadThis(); - b.invokeVirtual(TO_STRING_KEY_ONLY_METHOD_NAME, TypeDesc.STRING, null); - b.invokeConstructor(noMatchesType, new TypeDesc[] {TypeDesc.STRING}); - b.throwObject(); - } - - final TypeDesc triggerType = TypeDesc.forClass(Trigger.class); - final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - - // Add insert(boolean forTry) method which delegates to abstract doTryInsert method. - if (mGenMode == GEN_ABSTRACT) { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PRIVATE.toSynchronized(true), - PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); - mi.addException(TypeDesc.forClass(PersistException.class)); - - CodeBuilder b = new CodeBuilder(mi); - - LocalVariable forTryVar = b.getParameter(0); - LocalVariable triggerVar = b.createLocalVariable(null, triggerType); - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); - - Label tryStart = addGetTriggerAndEnterTxn - (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar); - - // Perform pk check after trigger has run, to allow it to define pk. - requirePkInitialized(b, CHECK_PK_FOR_INSERT_METHOD_NAME); - - // Call doTryInsert. - b.loadThis(); - b.invokeVirtual(DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); - - Label notInserted = b.createLabel(); - b.ifZeroComparisonBranch(notInserted, "=="); - - addTriggerAfterAndExitTxn - (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar); - - // Only mark properties clean if doTryInsert returned true. - b.loadThis(); - b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - notInserted.setLocation(); - addTriggerFailedAndExitTxn(b, INSERT_OP, triggerVar, txnVar, stateVar); - - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - b.ifZeroComparisonBranch(isForTry, "!="); - - TypeDesc constraintType = TypeDesc.forClass(UniqueConstraintException.class); - b.newObject(constraintType); - b.dup(); - b.loadThis(); - b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); - b.invokeConstructor(constraintType, new TypeDesc[] {TypeDesc.STRING}); - b.throwObject(); - - isForTry.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addTriggerFailedAndExitTxn - (b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); - - // Define the abstract method. - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - } - - // Add insert method which calls insert(forTry = false) - addInsert: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null); - - if (mi == null) { - break addInsert; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, INSERT_OP, false, UniqueConstraintException.class); - break addInsert; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(false); - b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.pop(); - b.returnVoid(); - } - - // Add tryInsert method which calls insert(forTry = true) - addTryInsert: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (mi == null) { - break addTryInsert; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, INSERT_OP, true, null); - break addTryInsert; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(true); - b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add update(boolean forTry) method which delegates to abstract doTryUpdate method. - if (mGenMode == GEN_ABSTRACT) { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PRIVATE.toSynchronized(true), - PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); - mi.addException(TypeDesc.forClass(PersistException.class)); - - CodeBuilder b = new CodeBuilder(mi); - - requirePkInitialized(b, CHECK_PK_FOR_UPDATE_METHOD_NAME); - - // If version property is present, it too must be initialized. The - // versionOrdinal variable was set earlier, when properties were defined. - if (versionOrdinal >= 0) { - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2)); - b.math(Opcode.IAND); - Label versionIsSet = b.createLabel(); - b.ifZeroComparisonBranch(versionIsSet, "!="); - CodeBuilderUtil.throwException - (b, IllegalStateException.class, "Version not set"); - versionIsSet.setLocation(); - } - - LocalVariable forTryVar = b.getParameter(0); - LocalVariable triggerVar = b.createLocalVariable(null, triggerType); - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); - - Label tryStart = addGetTriggerAndEnterTxn - (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar); - - // If no properties are dirty, then don't update. - Label doUpdate = b.createLabel(); - branchIfDirty(b, true, doUpdate); - - // Even though there was no update, still need tryLoad side-effect. - { - Label tryStart2 = b.createLabel().setLocation(); - b.loadThis(); - b.invokeVirtual(DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); - - Label notUpdated = b.createLabel(); - b.ifZeroComparisonBranch(notUpdated, "=="); - - // Only mark properties clean if doTryLoad returned true. - b.loadThis(); - b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - notUpdated.setLocation(); - - // Mark properties dirty, to be consistent with a delete side-effect. - b.loadThis(); - b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - Label tryEnd = b.createLabel().setLocation(); - b.exceptionHandler(tryStart2, tryEnd, FetchException.class.getName()); - b.invokeVirtual(FetchException.class.getName(), "toPersistException", - TypeDesc.forClass(PersistException.class), null); - b.throwObject(); - } - - doUpdate.setLocation(); - - // Call doTryUpdate. - b.loadThis(); - b.invokeVirtual(DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); - - Label notUpdated = b.createLabel(); - b.ifZeroComparisonBranch(notUpdated, "=="); - - addTriggerAfterAndExitTxn - (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar); - - // Only mark properties clean if doUpdate returned true. - b.loadThis(); - // Note: all properties marked clean because doUpdate should have - // loaded values for all properties. - b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - notUpdated.setLocation(); - addTriggerFailedAndExitTxn(b, UPDATE_OP, triggerVar, txnVar, stateVar); - - // Mark properties dirty, to be consistent with a delete side-effect. - b.loadThis(); - b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); - - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - b.ifZeroComparisonBranch(isForTry, "!="); - - TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class); - b.newObject(persistNoneType); - b.dup(); - b.loadConstant("Cannot update missing object: "); - b.loadThis(); - b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); - b.invokeVirtual(TypeDesc.STRING, "concat", - TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING}); - b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING}); - b.throwObject(); - - isForTry.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addTriggerFailedAndExitTxn - (b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); - - // Define the abstract method. - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - } - - // Add update method which calls update(forTry = false) - addUpdate: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null); - - if (mi == null) { - break addUpdate; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, UPDATE_OP, false, PersistNoneException.class); - break addUpdate; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(false); - b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.pop(); - b.returnVoid(); - } - - // Add tryUpdate method which calls update(forTry = true) - addTryUpdate: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (mi == null) { - break addTryUpdate; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, UPDATE_OP, true, null); - break addTryUpdate; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(true); - b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add delete(boolean forTry) method which delegates to abstract doTryDelete method. - if (mGenMode == GEN_ABSTRACT) { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PRIVATE.toSynchronized(true), - PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN}); - mi.addException(TypeDesc.forClass(PersistException.class)); - - CodeBuilder b = new CodeBuilder(mi); - - requirePkInitialized(b, CHECK_PK_FOR_DELETE_METHOD_NAME); - - LocalVariable forTryVar = b.getParameter(0); - LocalVariable triggerVar = b.createLocalVariable(null, triggerType); - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); - - Label tryStart = addGetTriggerAndEnterTxn - (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar); - - // Call doTryDelete. - b.loadThis(); - b.invokeVirtual(DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); - - b.loadThis(); - b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null); - - Label notDeleted = b.createLabel(); - b.ifZeroComparisonBranch(notDeleted, "=="); - - addTriggerAfterAndExitTxn - (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - notDeleted.setLocation(); - addTriggerFailedAndExitTxn(b, DELETE_OP, triggerVar, txnVar, stateVar); - - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - b.ifZeroComparisonBranch(isForTry, "!="); - - TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class); - b.newObject(persistNoneType); - b.dup(); - b.loadConstant("Cannot delete missing object: "); - b.loadThis(); - b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null); - b.invokeVirtual(TypeDesc.STRING, "concat", - TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING}); - b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING}); - b.throwObject(); - - isForTry.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - - addTriggerFailedAndExitTxn - (b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart); - - // Define the abstract method. - mi = mClassFile.addMethod - (Modifiers.PROTECTED.toAbstract(true), - DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); - mi.addException(TypeDesc.forClass(PersistException.class)); - } - - // Add delete method which calls delete(forTry = false) - addDelete: { - // Define the delete method. - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null); - - if (mi == null) { - break addDelete; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, DELETE_OP, false, PersistNoneException.class); - break addDelete; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(false); - b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.pop(); - b.returnVoid(); - } - - // Add tryDelete method which calls delete(forTry = true) - addTryDelete: { - // Define the delete method. - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); - - if (mi == null) { - break addTryDelete; - } - - mi.addException(TypeDesc.forClass(PersistException.class)); - - if (mGenMode == GEN_WRAPPED) { - callWrappedSupport(mi, DELETE_OP, true, null); - break addTryDelete; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadConstant(true); - b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.BOOLEAN}); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Add storableType method - addStorableType: { - final TypeDesc type = TypeDesc.forClass(mStorableType); - final TypeDesc storableClassType = TypeDesc.forClass(Class.class); - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null); - - if (mi == null) { - break addStorableType; - } - - CodeBuilder b = new CodeBuilder(mi); - b.loadConstant(type); - b.returnValue(storableClassType); - } - - // Add copy method. - addCopy: { - TypeDesc type = TypeDesc.forClass(mInfo.getStorableType()); - - // Add copy method. - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT), - COPY_METHOD_NAME, mClassFile.getType(), null); - - if (mi == null) { - break addCopy; - } - - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null); - b.checkCast(mClassFile.getType()); - - if (mGenMode == GEN_WRAPPED) { - // Need to do a deeper copy. - - LocalVariable copiedVar = b.createLocalVariable(null, mClassFile.getType()); - b.storeLocal(copiedVar); - - // First copy the wrapped Storable. - b.loadLocal(copiedVar); // storeField later - b.loadThis(); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - b.invoke(lookupMethod(mStorableType, COPY_METHOD_NAME, null)); - b.checkCast(TypeDesc.forClass(mStorableType)); - b.storeField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - - // Replace the WrappedSupport, passing in copy of wrapped Storable. - b.loadLocal(copiedVar); // storeField later - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - b.loadLocal(copiedVar); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - - b.invokeInterface(WrappedSupport.class.getName(), - CREATE_WRAPPED_SUPPORT_METHOD_NAME, - mSupportType, - new TypeDesc[] {TypeDesc.forClass(Storable.class)}); - - // Store new WrappedSupport in copy. - b.storeField(SUPPORT_FIELD_NAME, mSupportType); - - b.loadLocal(copiedVar); - } - - b.returnValue(type); - } - - // Part of properly defining copy method, except needs to be added even - // if copy method was not added because it is inherited and final. - CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType()); - - // Create all the property copier methods. - // Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly - addCopyPropertiesMethod(COPY_ALL_PROPERTIES, - true, true, true, false, false); - addCopyPropertiesMethod(COPY_PRIMARY_KEY_PROPERTIES, - true, false, false, false, false); - addCopyPropertiesMethod(COPY_VERSION_PROPERTY, - false, true, false, false, false); - addCopyPropertiesMethod(COPY_UNEQUAL_PROPERTIES, - false, true, true, true, false); - addCopyPropertiesMethod(COPY_DIRTY_PROPERTIES, - false, true, true, false, true); - - // Define hasDirtyProperties method. - addHasDirtyProps: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null); - - if (mi == null) { - break addHasDirtyProps; - } - - if (mGenMode == GEN_WRAPPED) { - callWrappedStorable(mi); - break addHasDirtyProps; - } - - CodeBuilder b = new CodeBuilder(mi); - Label isDirty = b.createLabel(); - branchIfDirty(b, false, isDirty); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - isDirty.setLocation(); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Define isPropertyUninitialized, isPropertyDirty, and isPropertyClean methods. - addPropertyStateExtractMethod(); - addPropertyStateCheckMethod(IS_PROPERTY_UNINITIALIZED, PROPERTY_STATE_UNINITIALIZED); - addPropertyStateCheckMethod(IS_PROPERTY_DIRTY, PROPERTY_STATE_DIRTY); - addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN); - - // Define isPropertySupported method. - addIsPropertySupported: { - MethodInfo mi = addMethodIfNotFinal - (Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED, - TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); - - if (mi == null) { - break addIsPropertySupported; - } - - CodeBuilder b = new CodeBuilder(mi); - - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - b.loadLocal(b.getParameter(0)); - b.invokeInterface(mSupportType, "isPropertySupported", TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.STRING}); - b.returnValue(TypeDesc.BOOLEAN); - } - - // Define standard object methods. - addHashCodeMethod(); - addEqualsMethod(EQUAL_FULL); - addEqualsMethod(EQUAL_KEYS); - addEqualsMethod(EQUAL_PROPERTIES); - addToStringMethod(false); - addToStringMethod(true); - - addMarkCleanMethod(MARK_PROPERTIES_CLEAN); - addMarkCleanMethod(MARK_ALL_PROPERTIES_CLEAN); - addMarkDirtyMethod(MARK_PROPERTIES_DIRTY); - addMarkDirtyMethod(MARK_ALL_PROPERTIES_DIRTY); - - if (mGenMode == GEN_ABSTRACT) { - // Define protected isPkInitialized method. - addIsInitializedMethod - (IS_PK_INITIALIZED_METHOD_NAME, mInfo.getPrimaryKeyProperties()); - - // Define protected methods to check if alternate key is initialized. - addAltKeyMethods: - for (int i=0; i> altProps = - new HashMap>(); - - StorableKey altKey = mInfo.getAlternateKey(i); - - for (OrderedProperty op : altKey.getProperties()) { - ChainedProperty cp = op.getChainedProperty(); - if (cp.getChainCount() > 0) { - // This should not be possible. - continue addAltKeyMethods; - } - StorableProperty property = cp.getPrimeProperty(); - altProps.put(property.getName(), property); - } - - addIsInitializedMethod(IS_ALT_KEY_INITIALIZED_PREFIX + i, altProps); - } - - // Define protected isRequiredDataInitialized method. - defineIsRequiredDataInitialized: { - Map> requiredProperties = - new HashMap>(); - - for (StorableProperty property : mAllProperties.values()) { - if (!property.isPrimaryKeyMember() && - !property.isJoin() && - !property.isNullable()) { - - requiredProperties.put(property.getName(), property); - } - } - - addIsInitializedMethod - (IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, requiredProperties); - } - - // Define optional protected isVersionInitialized method. The - // versionOrdinal variable was set earlier, when properties were defined. - if (versionOrdinal >= 0) { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PROTECTED, IS_VERSION_INITIALIZED_METHOD_NAME, - TypeDesc.BOOLEAN, null); - CodeBuilder b = new CodeBuilder(mi); - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2)); - b.math(Opcode.IAND); - // zero == false, not zero == true - b.returnValue(TypeDesc.BOOLEAN); - } - } - } - - /** - * If GEN_WRAPPED, generates a method implementation which delgates to the - * WrappedSupport. Also clears join property state if called method - * returns normally. - * - * @param opType optional, is one of INSERT_OP, UPDATE_OP, or DELETE_OP, for trigger support - * @param forTry used for INSERT_OP, UPDATE_OP, or DELETE_OP - * @param exceptionType optional - if called method throws this exception, - * join property state is still cleared. - */ - private void callWrappedSupport(MethodInfo mi, - String opType, - boolean forTry, - Class exceptionType) - { - if (mGenMode == GEN_ABSTRACT || !mHasJoins) { - // Don't need to clear state bits. - exceptionType = null; - } - - CodeBuilder b = new CodeBuilder(mi); - - final TypeDesc triggerType = TypeDesc.forClass(Trigger.class); - final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - - LocalVariable triggerVar = b.createLocalVariable(null, triggerType); - LocalVariable txnVar = b.createLocalVariable(null, transactionType); - LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT); - - Label tryStart; - if (opType == null) { - tryStart = b.createLabel().setLocation(); - } else { - tryStart = addGetTriggerAndEnterTxn - (b, opType, null, forTry, triggerVar, txnVar, stateVar); - } - - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - Method method = lookupMethod(WrappedSupport.class, mi); - b.invoke(method); - - Label tryEnd = b.createLabel().setLocation(); - - clearState(b); - - if (method.getReturnType() == void.class) { - if (opType != null) { - addTriggerAfterAndExitTxn(b, opType, null, forTry, triggerVar, txnVar, stateVar); - } - b.returnVoid(); - } else { - if (opType != null) { - Label notDone = b.createLabel(); - b.ifZeroComparisonBranch(notDone, "=="); - addTriggerAfterAndExitTxn(b, opType, null, forTry, triggerVar, txnVar, stateVar); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - notDone.setLocation(); - addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); - b.loadConstant(false); - } - b.returnValue(TypeDesc.forClass(method.getReturnType())); - } - - if (opType != null) { - addTriggerFailedAndExitTxn - (b, opType, null, forTry, triggerVar, txnVar, stateVar, tryStart); - } - - if (exceptionType != null) { - b.exceptionHandler(tryStart, tryEnd, exceptionType.getName()); - clearState(b); - b.throwObject(); - } - } - - /** - * If GEN_WRAPPED, generates a method implementation which delgates to the - * wrapped Storable. - */ - private void callWrappedStorable(MethodInfo mi) { - callWrappedStorable(mi, new CodeBuilder(mi)); - } - - /** - * If GEN_WRAPPED, generates a method implementation which delgates to the - * wrapped Storable. - */ - private void callWrappedStorable(MethodInfo mi, CodeBuilder b) { - b.loadThis(); - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - - int count = mi.getMethodDescriptor().getParameterCount(); - for (int j=0; j> 4); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.storeLocal(stateBits); - } - - Label skipCopy = b.createLabel(); - - // Check if independent property is supported, and skip if not. - if (property.isIndependent()) { - addSkipIndependent(b, target, property, skipCopy); - } - - // Skip property if uninitialized. - b.loadLocal(stateBits); - b.loadConstant(mask); - b.math(Opcode.IAND); - b.ifZeroComparisonBranch(skipCopy, "=="); - - if (dirtyOnly) { - // Add code to find out if property has been dirty. - b.loadLocal(stateBits); - b.loadConstant(mask); - b.math(Opcode.IAND); - b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); - b.ifComparisonBranch(skipCopy, "!="); - } - - TypeDesc type = TypeDesc.forClass(property.getType()); - - if (unequalOnly) { - // Add code to find out if they're equal. - b.loadThis(); - b.loadField(property.getName(), type); // [this.propValue - b.loadLocal(target); // [this.propValue, target - b.invoke(property.getReadMethod()); // [this.propValue, target.propValue - CodeBuilderUtil.addValuesEqualCall - (b, TypeDesc.forClass(property.getType()), true, skipCopy, true); - } - - b.loadLocal(target); // [target - b.loadThis(); // [target, this - b.loadField(property.getName(), type); // [target, this.propValue - mutateProperty(b, property, type); - - skipCopy.setLocation(); - } - - ordinal++; - if ((mask <<= 2) == 0) { - mask = 3; - stateBits = null; - } - } - - b.returnVoid(); - } - - private void addSkipIndependent(CodeBuilder b, - LocalVariable target, - StorableProperty property, - Label skipCopy) - { - TypeDesc storableTypeDesc = TypeDesc.forClass(Storable.class); - - if (target != null) { - b.loadLocal(target); - b.loadConstant(property.getName()); - b.invokeInterface(storableTypeDesc, - "isPropertySupported", - TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.STRING}); - b.ifZeroComparisonBranch(skipCopy, "=="); - } - - b.loadThis(); - b.loadConstant(property.getName()); - b.invokeInterface(storableTypeDesc, - "isPropertySupported", - TypeDesc.BOOLEAN, - new TypeDesc[] {TypeDesc.STRING}); - b.ifZeroComparisonBranch(skipCopy, "=="); - } - - /** - * Puts the value on the stack into the specified storable. If a write method is defined - * uses it, otherwise just shoves the value into the appropriate field. - * - * entry stack: [storable, value - * exit stack: [ - * - * @param b - {@link CodeBuilder} to which to add the mutation code - * @param property - property to mutate - * @param type - type of the property - */ - private void mutateProperty(CodeBuilder b, StorableProperty property, TypeDesc type) { - if (property.getWriteMethod() == null) { - b.storeField(property.getName(), type); - } else { - b.invoke(property.getWriteMethod()); - } - } - - /** - * Generates code that loads a property annotation to the stack. - */ - private void loadPropertyAnnotation(CodeBuilder b, - StorableProperty property, - StorablePropertyAnnotation annotation) { - /* Example - UserInfo.class.getMethod("setFirstName", new Class[] {String.class}) - .getAnnotation(LengthConstraint.class) - */ - - String methodName = annotation.getAnnotatedMethod().getName(); - boolean isAccessor = !methodName.startsWith("set"); - - b.loadConstant(TypeDesc.forClass(property.getEnclosingType())); - b.loadConstant(methodName); - if (isAccessor) { - // Accessor method has no parameters. - b.loadNull(); - } else { - // Mutator method has one parameter. - b.loadConstant(1); - b.newObject(TypeDesc.forClass(Class[].class)); - b.dup(); - b.loadConstant(0); - b.loadConstant(TypeDesc.forClass(property.getType())); - b.storeToArray(TypeDesc.forClass(Class[].class)); - } - b.invokeVirtual(Class.class.getName(), "getMethod", - TypeDesc.forClass(Method.class), new TypeDesc[] { - TypeDesc.STRING, TypeDesc.forClass(Class[].class) - }); - b.loadConstant(TypeDesc.forClass(annotation.getAnnotationType())); - b.invokeVirtual(Method.class.getName(), "getAnnotation", - TypeDesc.forClass(Annotation.class), new TypeDesc[] { - TypeDesc.forClass(Class.class) - }); - b.checkCast(TypeDesc.forClass(annotation.getAnnotationType())); - } - - /** - * Generates code that loads a Storage instance on the stack, throwing a - * FetchException if Storage request fails. - * - * @param type type of Storage to request - */ - private void loadStorageForFetch(CodeBuilder b, TypeDesc type) { - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - TypeDesc storageType = TypeDesc.forClass(Storage.class); - - TypeDesc repositoryType = TypeDesc.forClass(Repository.class); - b.invokeInterface - (mSupportType, "getRootRepository", repositoryType, null); - b.loadConstant(type); - - // This may throw a RepositoryException. - Label tryStart = b.createLabel().setLocation(); - b.invokeInterface(repositoryType, STORAGE_FOR_METHOD_NAME, storageType, - new TypeDesc[]{TypeDesc.forClass(Class.class)}); - Label tryEnd = b.createLabel().setLocation(); - Label noException = b.createLabel(); - b.branch(noException); - - b.exceptionHandler(tryStart, tryEnd, - RepositoryException.class.getName()); - b.invokeVirtual - (RepositoryException.class.getName(), "toFetchException", - TypeDesc.forClass(FetchException.class), null); - b.throwObject(); - - noException.setLocation(); - } - - /** - * For the given join property, marks all of its dependent internal join - * element properties as dirty. - */ - /* - private void markInternalJoinElementsDirty(CodeBuilder b, StorableProperty joinProperty) { - int count = mAllProperties.size(); - - int ordinal = 0; - int mask = 0; - for (StorableProperty property : mAllProperties.values()) { - if (property != joinProperty && !property.isJoin()) { - // Check to see if property is an internal member of joinProperty. - for (int i=joinProperty.getJoinElementCount(); --i>=0; ) { - if (property == joinProperty.getInternalJoinElement(i)) { - mask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); - } - } - } - ordinal++; - if (((ordinal & 0xf) == 0 || ordinal >= count) && mask != 0) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); - b.loadThis(); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(mask); - b.math(Opcode.IOR); - b.storeField(stateFieldName, TypeDesc.INT); - mask = 0; - } - } - } - */ - - /** - * Generates code to set all state properties to zero. - */ - private void clearState(CodeBuilder b) { - int ordinal = -1; - int maxOrdinal = mAllProperties.size() - 1; - boolean requireStateField = false; - - for (StorableProperty property : mAllProperties.values()) { - ordinal++; - - if (property.isJoin() || mGenMode == GEN_ABSTRACT) { - requireStateField = true; - } - - if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) { - if (requireStateField) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); - - b.loadThis(); - b.loadConstant(0); - b.storeField(stateFieldName, TypeDesc.INT); - } - requireStateField = false; - } - } - } - - private void addMarkCleanMethod(String name) { - MethodInfo mi = - addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(true), name, null, null); - - if (mi == null) { - return; - } - - CodeBuilder b = new CodeBuilder(mi); - - if (mGenMode == GEN_WRAPPED) { - clearState(b); - callWrappedStorable(mi, b); - return; - } - - final int count = mAllProperties.size(); - int ordinal = 0; - int andMask = 0; - int orMask = 0; - - for (StorableProperty property : mAllProperties.values()) { - if (property.isQuery()) { - // Don't erase cached query. - andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); - } else if (!property.isJoin()) { - if (name == MARK_ALL_PROPERTIES_CLEAN) { - // Force clean state (1) always. - orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2); - } else if (name == MARK_PROPERTIES_CLEAN) { - // Mask will convert dirty (3) to clean (1). State 2, which - // is illegal, is converted to 0. - andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2); - } - } - - ordinal++; - if ((ordinal & 0xf) == 0 || ordinal >= count) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); - b.loadThis(); - if (andMask == 0) { - b.loadConstant(orMask); - } else { - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(andMask); - b.math(Opcode.IAND); - if (orMask != 0) { - b.loadConstant(orMask); - b.math(Opcode.IOR); - } - } - b.storeField(stateFieldName, TypeDesc.INT); - andMask = 0; - orMask = 0; - } - } - - b.returnVoid(); - } - - private void addMarkDirtyMethod(String name) { - MethodInfo mi = - addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null); - - if (mi == null) { - return; - } - - CodeBuilder b = new CodeBuilder(mi); - - if (mGenMode == GEN_WRAPPED) { - clearState(b); - callWrappedStorable(mi, b); - return; - } - - final int count = mAllProperties.size(); - int ordinal = 0; - int andMask = 0; - int orMask = 0; - - for (StorableProperty property : mAllProperties.values()) { - if (property.isJoin()) { - // Erase cached join properties, but don't erase cached query. - if (!property.isQuery()) { - andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); - } - } else if (name == MARK_ALL_PROPERTIES_DIRTY) { - // Force dirty state (3). - orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); - } - - ordinal++; - if ((ordinal & 0xf) == 0 || ordinal >= count) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); - if (name == MARK_ALL_PROPERTIES_DIRTY) { - if (orMask != 0 || andMask != 0) { - b.loadThis(); // [this - b.loadThis(); // [this, this - b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField - if (andMask != 0) { - b.loadConstant(~andMask); - b.math(Opcode.IAND); - } - if (orMask != 0) { - b.loadConstant(orMask); - b.math(Opcode.IOR); - } - b.storeField(stateFieldName, TypeDesc.INT); - } - } else { - // This is a great trick to convert all states of value 1 - // (clean) into value 3 (dirty). States 0, 2, and 3 stay the - // same. Since joins cannot have state 1, they aren't affected. - // stateField |= ((stateField & 0x55555555) << 1); - - b.loadThis(); // [this - b.loadThis(); // [this, this - b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField - if (andMask != 0) { - b.loadConstant(~andMask); - b.math(Opcode.IAND); - } - b.dup(); // [this, this.stateField, this.stateField - b.loadConstant(0x55555555); - b.math(Opcode.IAND); // [this, this.stateField, this.stateField & 0x55555555 - b.loadConstant(1); - b.math(Opcode.ISHL); // [this, this.stateField, orMaskValue - b.math(Opcode.IOR); // [this, newStateFieldValue - b.storeField(stateFieldName, TypeDesc.INT); - } - - andMask = 0; - orMask = 0; - } - } - - b.returnVoid(); - } - - /** - * For the given ordinary key property, marks all of its dependent join - * element properties as uninitialized, and marks given property as dirty. - */ - private void markOrdinaryPropertyDirty - (CodeBuilder b, StorableProperty ordinaryProperty) - { - int count = mAllProperties.size(); - - int ordinal = 0; - int andMask = 0xffffffff; - int orMask = 0; - for (StorableProperty property : mAllProperties.values()) { - if (property == ordinaryProperty) { - if (mGenMode == GEN_ABSTRACT) { - // Only GEN_ABSTRACT mode uses these state bits. - orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2); - } - } else if (property.isJoin()) { - // Check to see if ordinary is an internal member of join property. - for (int i=property.getJoinElementCount(); --i>=0; ) { - if (ordinaryProperty == property.getInternalJoinElement(i)) { - andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2)); - } - } - } - ordinal++; - if ((ordinal & 0xf) == 0 || ordinal >= count) { - if (andMask != 0xffffffff || orMask != 0) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); - b.loadThis(); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - if (andMask != 0xffffffff) { - b.loadConstant(andMask); - b.math(Opcode.IAND); - } - if (orMask != 0) { - b.loadConstant(orMask); - b.math(Opcode.IOR); - } - b.storeField(stateFieldName, TypeDesc.INT); - } - andMask = 0xffffffff; - orMask = 0; - } - } - } - - // Generates code that branches to the given label if any properties are dirty. - private void branchIfDirty(CodeBuilder b, boolean includePk, Label label) { - int count = mAllProperties.size(); - int ordinal = 0; - int andMask = 0; - for (StorableProperty property : mAllProperties.values()) { - if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) { - // Logical 'and' will convert state 1 (clean) to state 0, so - // that it will be ignored. State 3 (dirty) is what we're - // looking for, and it turns into 2. Essentially, we leave the - // high order bit on, since there is no state which has the - // high order bit on unless the low order bit is also on. - andMask |= 2 << ((ordinal & 0xf) * 2); - } - ordinal++; - if ((ordinal & 0xf) == 0 || ordinal >= count) { - String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4); - b.loadThis(); - b.loadField(stateFieldName, TypeDesc.INT); - b.loadConstant(andMask); - b.math(Opcode.IAND); - // At least one property is dirty, so short circuit. - b.ifZeroComparisonBranch(label, "!="); - andMask = 0; - } - } - } - - private void addIsInitializedMethod - (String name, Map> properties) - { - // Don't check Automatic properties. - { - boolean cloned = false; - for (StorableProperty prop : properties.values()) { - if (prop.isAutomatic() || prop.isVersion()) { - if (!cloned) { - properties = new LinkedHashMap>(properties); - cloned = true; - } - // This isn't concurrent modification since the loop is - // still operating on the original properties map. - properties.remove(prop.getName()); - } - } - } - - MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null); - CodeBuilder b = new CodeBuilder(mi); - - if (properties.size() == 0) { - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - return; - } - - if (properties.size() == 1) { - int ordinal = findPropertyOrdinal(properties.values().iterator().next()); - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); - b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); - b.math(Opcode.IAND); - // zero == false, not zero == true - b.returnValue(TypeDesc.BOOLEAN); - return; - } - - // Multiple properties is a bit more tricky. The goal here is to - // minimize the amount of work that needs to be done at runtime. - - int ordinal = 0; - int mask = 0; - for (StorableProperty property : mAllProperties.values()) { - if (properties.containsKey(property.getName())) { - mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2); - } - ordinal++; - if (((ordinal & 0xf) == 0 || ordinal >= mAllProperties.size()) && mask != 0) { - // This is a great trick to convert all states of value 1 - // (clean) into value 3 (dirty). States 0, 2, and 3 stay the - // same. Since joins cannot have state 1, they aren't affected. - // stateField | ((stateField & 0x55555555) << 1); - - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4), TypeDesc.INT); - b.dup(); // [this.stateField, this.stateField - b.loadConstant(0x55555555); - b.math(Opcode.IAND); // [this.stateField, this.stateField & 0x55555555 - b.loadConstant(1); - b.math(Opcode.ISHL); // [this.stateField, orMaskValue - b.math(Opcode.IOR); // [newStateFieldValue - - // Flip all bits for property states. If final result is - // non-zero, then there were uninitialized properties. - - b.loadConstant(mask); - b.math(Opcode.IXOR); - if (mask != 0xffffffff) { - b.loadConstant(mask); - b.math(Opcode.IAND); - } - - Label cont = b.createLabel(); - b.ifZeroComparisonBranch(cont, "=="); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - cont.setLocation(); - - mask = 0; - } - } - - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - } - - private int findPropertyOrdinal(StorableProperty property) { - int ordinal = 0; - for (StorableProperty p : mAllProperties.values()) { - if (p == property) { - return ordinal; - } - ordinal++; - } - throw new IllegalArgumentException(); - } - - /** - * Generates code that verifies that all primary keys are initialized. - * - * @param b builder that will invoke generated method - * @param methodName name to give to generated method - */ - private void requirePkInitialized(CodeBuilder b, String methodName) { - // Add code to call method which we are about to define. - b.loadThis(); - b.invokeVirtual(methodName, null, null); - - // Now define new method, discarding original builder object. - b = new CodeBuilder(mClassFile.addMethod(Modifiers.PROTECTED, methodName, null, null)); - b.loadThis(); - b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); - Label pkInitialized = b.createLabel(); - b.ifZeroComparisonBranch(pkInitialized, "!="); - CodeBuilderUtil.throwException - (b, IllegalStateException.class, "Primary key not fully specified"); - pkInitialized.setLocation(); - b.returnVoid(); - } - - /** - * Generates a private method which accepts a property name and returns - * PROPERTY_STATE_UNINITIALIZED, PROPERTY_STATE_DIRTY, or - * PROPERTY_STATE_CLEAN. - */ - private void addPropertyStateExtractMethod() { - if (mGenMode == GEN_WRAPPED) { - return; - } - - MethodInfo mi = mClassFile.addMethod(Modifiers.PRIVATE, PROPERTY_STATE_EXTRACT_METHOD_NAME, - TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); - CodeBuilder b = new CodeBuilder(mi); - - // Generate big switch statement that operates on Strings. See also - // org.cojen.util.BeanPropertyAccessor, which also generates this kind of - // switch. - - // For switch case count, obtain a prime number, at least twice as - // large as needed. This should minimize hash collisions. Since all the - // hash keys are known up front, the capacity could be tweaked until - // there are no collisions, but this technique is easier and - // deterministic. - - int caseCount; - { - BigInteger capacity = BigInteger.valueOf(mAllProperties.size() * 2 + 1); - while (!capacity.isProbablePrime(100)) { - capacity = capacity.add(BigInteger.valueOf(2)); - } - caseCount = capacity.intValue(); - } - - int[] cases = new int[caseCount]; - for (int i=0; i>[] caseMatches = caseMatches(caseCount); - - for (int i=0; i matches = caseMatches[i]; - if (matches == null || matches.size() == 0) { - switchLabels[i] = noMatch; - } else { - switchLabels[i] = b.createLabel(); - } - } - - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(String.class.getName(), "hashCode", TypeDesc.INT, null); - b.loadConstant(0x7fffffff); - b.math(Opcode.IAND); - b.loadConstant(caseCount); - b.math(Opcode.IREM); - - b.switchBranch(cases, switchLabels, noMatch); - - // Gather property ordinals. - Map, Integer> ordinalMap = new HashMap, Integer>(); - { - int ordinal = 0; - for (StorableProperty prop : mAllProperties.values()) { - ordinalMap.put(prop, ordinal++); - } - } - - // Params to invoke String.equals. - TypeDesc[] params = {TypeDesc.OBJECT}; - - Label joinMatch = null; - - for (int i=0; i> matches = caseMatches[i]; - if (matches == null || matches.size() == 0) { - continue; - } - - switchLabels[i].setLocation(); - - int matchCount = matches.size(); - for (int j=0; j prop = matches.get(j); - - // Test against name to find exact match. - - b.loadConstant(prop.getName()); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(String.class.getName(), "equals", TypeDesc.BOOLEAN, params); - - Label notEqual; - - if (j == matchCount - 1) { - notEqual = null; - b.ifZeroComparisonBranch(noMatch, "=="); - } else { - notEqual = b.createLabel(); - b.ifZeroComparisonBranch(notEqual, "=="); - } - - if (prop.isJoin()) { - if (joinMatch == null) { - joinMatch = b.createLabel(); - } - b.branch(joinMatch); - } else { - int ordinal = ordinalMap.get(prop); - - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); - int shift = (ordinal & 0xf) * 2; - if (shift != 0) { - b.loadConstant(shift); - b.math(Opcode.ISHR); - } - b.loadConstant(PROPERTY_STATE_MASK); - b.math(Opcode.IAND); - b.returnValue(TypeDesc.INT); - } - - if (notEqual != null) { - notEqual.setLocation(); - } - } - } - - TypeDesc exceptionType = TypeDesc.forClass(IllegalArgumentException.class); - params = new TypeDesc[] {TypeDesc.STRING}; - - noMatch.setLocation(); - - b.newObject(exceptionType); - b.dup(); - b.loadConstant("Unknown property: "); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); - b.invokeConstructor(exceptionType, params); - b.throwObject(); - - if (joinMatch != null) { - joinMatch.setLocation(); - - b.newObject(exceptionType); - b.dup(); - b.loadConstant("Cannot get state for join property: "); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); - b.invokeConstructor(exceptionType, params); - b.throwObject(); - } - } - - /** - * Returns the properties that match on a given case. The array length is - * the same as the case count. Each list represents the matches. The lists - * themselves may be null if no matches for that case. - */ - private List>[] caseMatches(int caseCount) { - List>[] cases = new List[caseCount]; - - for (StorableProperty prop : mAllProperties.values()) { - int hashCode = prop.getName().hashCode(); - int caseValue = (hashCode & 0x7fffffff) % caseCount; - List matches = cases[caseValue]; - if (matches == null) { - matches = cases[caseValue] = new ArrayList>(); - } - matches.add(prop); - } - - return cases; - } - - /** - * Generates public method which accepts a property name and returns a - * boolean true, if the given state matches the property's actual state. - * - * @param name name of method - * @param state property state to check - */ - private void addPropertyStateCheckMethod(String name, int state) { - MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name, - TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); - - if (mi == null) { - return; - } - - CodeBuilder b = new CodeBuilder(mi); - - if (mGenMode == GEN_WRAPPED) { - callWrappedStorable(mi, b); - return; - } - - // Call private method to extract state and compare. - b.loadThis(); - b.loadLocal(b.getParameter(0)); - b.invokePrivate(PROPERTY_STATE_EXTRACT_METHOD_NAME, - TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); - Label isFalse = b.createLabel(); - if (state == 0) { - b.ifZeroComparisonBranch(isFalse, "!="); - } else { - b.loadConstant(state); - b.ifComparisonBranch(isFalse, "!="); - } - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - isFalse.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - } - - /** - * Defines a hashCode method. - */ - private void addHashCodeMethod() { - Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null); - - if (mi == null) { - return; - } - - if (mGenMode == GEN_WRAPPED) { - callWrappedStorable(mi); - return; - } - - CodeBuilder b = new CodeBuilder(mi); - - boolean mixIn = false; - for (StorableProperty property : mAllProperties.values()) { - if (property.isJoin()) { - continue; - } - addHashCodeCall(b, property.getName(), - TypeDesc.forClass(property.getType()), true, mixIn); - mixIn = true; - } - - b.returnValue(TypeDesc.INT); - } - - private void addHashCodeCall(CodeBuilder b, String fieldName, - TypeDesc fieldType, boolean testForNull, - boolean mixIn) - { - if (mixIn) { - // Multiply current hashcode by 31 before adding more to it. - b.loadConstant(5); - b.math(Opcode.ISHL); - b.loadConstant(1); - b.math(Opcode.ISUB); - } - - b.loadThis(); - b.loadField(fieldName, fieldType); - - switch (fieldType.getTypeCode()) { - case TypeDesc.FLOAT_CODE: - b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "floatToIntBits", - TypeDesc.INT, new TypeDesc[]{TypeDesc.FLOAT}); - // Fall through - case TypeDesc.INT_CODE: - case TypeDesc.CHAR_CODE: - case TypeDesc.SHORT_CODE: - case TypeDesc.BYTE_CODE: - case TypeDesc.BOOLEAN_CODE: - if (mixIn) { - b.math(Opcode.IADD); - } - break; - - case TypeDesc.DOUBLE_CODE: - b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "doubleToLongBits", - TypeDesc.LONG, new TypeDesc[]{TypeDesc.DOUBLE}); - // Fall through - case TypeDesc.LONG_CODE: - b.dup2(); - b.loadConstant(32); - b.math(Opcode.LUSHR); - b.math(Opcode.LXOR); - b.convert(TypeDesc.LONG, TypeDesc.INT); - if (mixIn) { - b.math(Opcode.IADD); - } - break; - - case TypeDesc.OBJECT_CODE: - default: - LocalVariable value = null; - if (testForNull) { - value = b.createLocalVariable(null, fieldType); - b.storeLocal(value); - b.loadLocal(value); - } - if (mixIn) { - Label isNull = b.createLabel(); - if (testForNull) { - b.ifNullBranch(isNull, true); - b.loadLocal(value); - } - addHashCodeCallTo(b, fieldType); - b.math(Opcode.IADD); - if (testForNull) { - isNull.setLocation(); - } - } else { - Label cont = b.createLabel(); - if (testForNull) { - Label notNull = b.createLabel(); - b.ifNullBranch(notNull, false); - b.loadConstant(0); - b.branch(cont); - notNull.setLocation(); - b.loadLocal(value); - } - addHashCodeCallTo(b, fieldType); - if (testForNull) { - cont.setLocation(); - } - } - break; - } - } - - private void addHashCodeCallTo(CodeBuilder b, TypeDesc fieldType) { - if (fieldType.isArray()) { - if (!fieldType.getComponentType().isPrimitive()) { - b.invokeStatic("java.util.Arrays", "deepHashCode", - TypeDesc.INT, new TypeDesc[] {TypeDesc.forClass(Object[].class)}); - } else { - b.invokeStatic("java.util.Arrays", "hashCode", - TypeDesc.INT, new TypeDesc[] {fieldType}); - } - } else { - b.invokeVirtual(TypeDesc.OBJECT, "hashCode", TypeDesc.INT, null); - } - } - - /** - * Defines an equals method. - * - * @param equalityType Type of equality to define - {@link EQUAL_KEYS} for "equalKeys", - * {@link EQUAL_PROPERTIES} for "equalProperties", and {@link EQUAL_FULL} for "equals" - */ - private void addEqualsMethod(int equalityType) { - TypeDesc[] objectParam = {TypeDesc.OBJECT}; - - String equalsMethodName; - switch (equalityType) { - default: - throw new IllegalArgumentException(); - case EQUAL_KEYS: - equalsMethodName = EQUAL_PRIMARY_KEYS_METHOD_NAME; - break; - case EQUAL_PROPERTIES: - equalsMethodName = EQUAL_PROPERTIES_METHOD_NAME; - break; - case EQUAL_FULL: - equalsMethodName = EQUALS_METHOD_NAME; - } - - Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = addMethodIfNotFinal - (modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam); - - if (mi == null) { - return; - } - - if (mGenMode == GEN_WRAPPED && equalityType != EQUAL_FULL) { - callWrappedStorable(mi); - return; - } - - CodeBuilder b = new CodeBuilder(mi); - - // if (this == target) return true; - b.loadThis(); - b.loadLocal(b.getParameter(0)); - Label notEqual = b.createLabel(); - b.ifEqualBranch(notEqual, false); - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - notEqual.setLocation(); - - // if (! target instanceof this) return false; - TypeDesc userStorableTypeDesc = TypeDesc.forClass(mStorableType); - b.loadLocal(b.getParameter(0)); - b.instanceOf(userStorableTypeDesc); - Label fail = b.createLabel(); - b.ifZeroComparisonBranch(fail, "=="); - - // this.class other = (this.class)target; - LocalVariable other = b.createLocalVariable(null, userStorableTypeDesc); - b.loadLocal(b.getParameter(0)); - b.checkCast(userStorableTypeDesc); - b.storeLocal(other); - - for (StorableProperty property : mAllProperties.values()) { - if (property.isJoin()) { - continue; - } - // If we're only comparing keys, and this isn't a key, skip it - if ((equalityType == EQUAL_KEYS) && !property.isPrimaryKeyMember()) { - continue; - } - - // Check if independent property is supported, and skip if not. - Label skipCheck = b.createLabel(); - if (equalityType != EQUAL_KEYS && property.isIndependent()) { - addSkipIndependent(b, other, property, skipCheck); - } - - TypeDesc fieldType = TypeDesc.forClass(property.getType()); - b.loadThis(); - if (mGenMode == GEN_ABSTRACT) { - b.loadField(property.getName(), fieldType); - } else { - b.loadField(WRAPPED_STORABLE_FIELD_NAME, TypeDesc.forClass(mStorableType)); - b.invoke(property.getReadMethod()); - } - - b.loadLocal(other); - b.invoke(property.getReadMethod()); - CodeBuilderUtil.addValuesEqualCall(b, fieldType, true, fail, false); - - skipCheck.setLocation(); - } - - b.loadConstant(true); - b.returnValue(TypeDesc.BOOLEAN); - - fail.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - } - - /** - * Defines a toString method, which assumes that the ClassFile is targeting - * version 1.5 of Java. - * - * @param keyOnly when true, generate a toStringKeyOnly method instead - */ - private void addToStringMethod(boolean keyOnly) { - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - TypeDesc[] stringParam = {TypeDesc.STRING}; - TypeDesc[] charParam = {TypeDesc.CHAR}; - - Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT); - MethodInfo mi = addMethodIfNotFinal(modifiers, - keyOnly ? - TO_STRING_KEY_ONLY_METHOD_NAME : - TO_STRING_METHOD_NAME, - TypeDesc.STRING, null); - - if (mi == null) { - return; - } - - if (mGenMode == GEN_WRAPPED) { - callWrappedStorable(mi); - return; - } - - CodeBuilder b = new CodeBuilder(mi); - b.newObject(stringBuilder); - b.dup(); - b.invokeConstructor(stringBuilder, null); - b.loadConstant(mStorableType.getName()); - invokeAppend(b, stringParam); - - String detail; - if (keyOnly) { - detail = " (key only) {"; - } else { - detail = " {"; - } - - b.loadConstant(detail); - invokeAppend(b, stringParam); - - // First pass, just print primary keys. - int ordinal = 0; - for (StorableProperty property : mAllProperties.values()) { - if (property.isPrimaryKeyMember()) { - Label skipPrint = b.createLabel(); - - // Check if independent property is supported, and skip if not. - if (property.isIndependent()) { - addSkipIndependent(b, null, property, skipPrint); - } - - if (ordinal++ > 0) { - b.loadConstant(", "); - invokeAppend(b, stringParam); - } - addPropertyAppendCall(b, property, stringParam, charParam); - - skipPrint.setLocation(); - } - } - - // Second pass, print non-primary keys. - if (!keyOnly) { - for (StorableProperty property : mAllProperties.values()) { - // Don't print join properties if they may throw an exception. - if (!property.isPrimaryKeyMember() && (!property.isJoin())) { - Label skipPrint = b.createLabel(); - - // Check if independent property is supported, and skip if not. - if (property.isIndependent()) { - addSkipIndependent(b, null, property, skipPrint); - } - - b.loadConstant(", "); - invokeAppend(b, stringParam); - addPropertyAppendCall(b, property, stringParam, charParam); - - skipPrint.setLocation(); - } - } - } - - b.loadConstant('}'); - invokeAppend(b, charParam); - - // For key string, also show all the alternate keys. This makes the - // FetchNoneException message more helpful. - if (keyOnly) { - int altKeyCount = mInfo.getAlternateKeyCount(); - for (int i=0; i key = mInfo.getAlternateKey(i); - - ordinal = 0; - for (OrderedProperty op : key.getProperties()) { - StorableProperty property = op.getChainedProperty().getPrimeProperty(); - - Label skipPrint = b.createLabel(); - - // Check if independent property is supported, and skip if not. - if (property.isIndependent()) { - addSkipIndependent(b, null, property, skipPrint); - } - - if (ordinal++ > 0) { - b.loadConstant(", "); - invokeAppend(b, stringParam); - } - addPropertyAppendCall(b, property, stringParam, charParam); - - skipPrint.setLocation(); - } - - b.loadConstant('}'); - invokeAppend(b, charParam); - } - } - - b.invokeVirtual(stringBuilder, TO_STRING_METHOD_NAME, TypeDesc.STRING, null); - b.returnValue(TypeDesc.STRING); - } - - private void invokeAppend(CodeBuilder b, TypeDesc[] paramType) { - TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class); - b.invokeVirtual(stringBuilder, "append", stringBuilder, paramType); - } - - private void addPropertyAppendCall(CodeBuilder b, - StorableProperty property, - TypeDesc[] stringParam, - TypeDesc[] charParam) - { - b.loadConstant(property.getName()); - invokeAppend(b, stringParam); - b.loadConstant('='); - invokeAppend(b, charParam); - b.loadThis(); - TypeDesc type = TypeDesc.forClass(property.getType()); - b.loadField(property.getName(), type); - if (type.isPrimitive()) { - if (type == TypeDesc.BYTE || type == TypeDesc.SHORT) { - type = TypeDesc.INT; - } - } else { - if (type != TypeDesc.STRING) { - if (type.isArray()) { - if (!type.getComponentType().isPrimitive()) { - b.invokeStatic("java.util.Arrays", "deepToString", - TypeDesc.STRING, - new TypeDesc[] {TypeDesc.OBJECT.toArrayType()}); - } else { - b.invokeStatic("java.util.Arrays", TO_STRING_METHOD_NAME, - TypeDesc.STRING, new TypeDesc[] {type}); - } - } - type = TypeDesc.OBJECT; - } - } - invokeAppend(b, new TypeDesc[]{type}); - } - - /** - * Generates code to get a trigger, forcing a transaction if trigger is not - * null. Also, if there is a trigger, the "before" method is called. - * - * @param opType type of operation, Insert, Update, or Delete - * @param forTryVar optional boolean variable for selecting whether to call - * "before" or "beforeTry" method - * @param forTry used if forTryVar is null - * @param triggerVar required variable of type Trigger for storing trigger - * @param txnVar required variable of type Transaction for storing transaction - * @param stateVar variable of type Object for storing state - * @return try start label for transaction - */ - private Label addGetTriggerAndEnterTxn(CodeBuilder b, - String opType, - LocalVariable forTryVar, - boolean forTry, - LocalVariable triggerVar, - LocalVariable txnVar, - LocalVariable stateVar) - { - // trigger = support$.getXxxTrigger(); - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - Method m = lookupMethod(mSupportType.toClass(), "get" + opType + "Trigger", null); - b.invoke(m); - b.storeLocal(triggerVar); - // state = null; - b.loadNull(); - b.storeLocal(stateVar); - - // if (trigger == null) { - // txn = null; - // } else { - // txn = support.getRootRepository().enterTransaction(); - // tryStart: - // if (forTry) { - // state = trigger.beforeTryXxx(this); - // } else { - // state = trigger.beforeXxx(this); - // } - // } - b.loadLocal(triggerVar); - Label hasTrigger = b.createLabel(); - b.ifNullBranch(hasTrigger, false); - - // txn = null - b.loadNull(); - b.storeLocal(txnVar); - Label cont = b.createLabel(); - b.branch(cont); - - hasTrigger.setLocation(); - - // txn = support.getRootRepository().enterTransaction(); - TypeDesc repositoryType = TypeDesc.forClass(Repository.class); - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - b.loadThis(); - b.loadField(SUPPORT_FIELD_NAME, mSupportType); - b.invokeInterface(mSupportType, "getRootRepository", repositoryType, null); - b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, transactionType, null); - b.storeLocal(txnVar); - - Label tryStart = b.createLabel().setLocation(); - - // if (forTry) { - // state = trigger.beforeTryXxx(this); - // } else { - // state = trigger.beforeXxx(this); - // } - b.loadLocal(triggerVar); - b.loadThis(); - - if (forTryVar == null) { - if (forTry) { - b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType, - TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); - } else { - b.invokeVirtual(triggerVar.getType(), "before" + opType, - TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); - } - b.storeLocal(stateVar); - } else { - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - - b.ifZeroComparisonBranch(isForTry, "!="); - b.invokeVirtual(triggerVar.getType(), "before" + opType, - TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); - b.storeLocal(stateVar); - b.branch(cont); - - isForTry.setLocation(); - b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType, - TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.OBJECT}); - b.storeLocal(stateVar); - } - - cont.setLocation(); - - return tryStart; - } - - /** - * Generates code to call a trigger after the persistence operation has - * been invoked. - * - * @param opType type of operation, Insert, Update, or Delete - * @param forTryVar optional boolean variable for selecting whether to call - * "after" or "afterTry" method - * @param forTry used if forTryVar is null - * @param triggerVar required variable of type Trigger for retrieving trigger - * @param txnVar required variable of type Transaction for storing transaction - * @param stateVar required variable of type Object for retrieving state - */ - private void addTriggerAfterAndExitTxn(CodeBuilder b, - String opType, - LocalVariable forTryVar, - boolean forTry, - LocalVariable triggerVar, - LocalVariable txnVar, - LocalVariable stateVar) - { - // if (trigger != null) { - b.loadLocal(triggerVar); - Label cont = b.createLabel(); - b.ifNullBranch(cont, true); - - // if (forTry) { - // trigger.afterTryXxx(this, state); - // } else { - // trigger.afterXxx(this, state); - // } - b.loadLocal(triggerVar); - b.loadThis(); - b.loadLocal(stateVar); - - if (forTryVar == null) { - if (forTry) { - b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); - } else { - b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); - } - } else { - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - - b.ifZeroComparisonBranch(isForTry, "!="); - b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); - Label commitAndExit = b.createLabel(); - b.branch(commitAndExit); - - isForTry.setLocation(); - b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); - commitAndExit.setLocation(); - } - - // txn.commit(); - // txn.exit(); - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); - - cont.setLocation(); - } - - /** - * Generates code to call a trigger after the persistence operation has - * failed. - * - * @param opType type of operation, Insert, Update, or Delete - * @param triggerVar required variable of type Trigger for retrieving trigger - * @param txnVar required variable of type Transaction for storing transaction - * @param stateVar required variable of type Object for retrieving state - */ - private void addTriggerFailedAndExitTxn(CodeBuilder b, - String opType, - LocalVariable triggerVar, - LocalVariable txnVar, - LocalVariable stateVar) - { - TypeDesc transactionType = TypeDesc.forClass(Transaction.class); - - // if (trigger != null) { - b.loadLocal(triggerVar); - Label isNull = b.createLabel(); - b.ifNullBranch(isNull, true); - - // try { - // trigger.failedXxx(this, state); - // } catch (Throwable e) { - // uncaught(e); - // } - Label tryStart = b.createLabel().setLocation(); - b.loadLocal(triggerVar); - b.loadThis(); - b.loadLocal(stateVar); - b.invokeVirtual(TypeDesc.forClass(Trigger.class), "failed" + opType, null, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT}); - Label tryEnd = b.createLabel().setLocation(); - Label cont = b.createLabel(); - b.branch(cont); - b.exceptionHandler(tryStart, tryEnd, Throwable.class.getName()); - b.invokeStatic(UNCAUGHT_METHOD_NAME, null, - new TypeDesc[] {TypeDesc.forClass(Throwable.class)}); - cont.setLocation(); - - // txn.exit(); - b.loadLocal(txnVar); - b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); - - isNull.setLocation(); - } - - /** - * Generates exception handler code to call a trigger after the persistence - * operation has failed. - * - * @param opType type of operation, Insert, Update, or Delete - * @param forTryVar optional boolean variable for selecting whether to - * throw or catch Trigger.Abort. - * @param forTry used if forTryVar is null - * @param triggerVar required variable of type Trigger for retrieving trigger - * @param txnVar required variable of type Transaction for storing transaction - * @param stateVar required variable of type Object for retrieving state - * @param tryStart start of exception handler around transaction - */ - private void addTriggerFailedAndExitTxn(CodeBuilder b, - String opType, - LocalVariable forTryVar, - boolean forTry, - LocalVariable triggerVar, - LocalVariable txnVar, - LocalVariable stateVar, - Label tryStart) - { - if (tryStart == null) { - addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); - return; - } - - // } catch (... e) { - // if (trigger != null) { - // try { - // trigger.failedXxx(this, state); - // } catch (Throwable e) { - // uncaught(e); - // } - // } - // txn.exit(); - // if (e instanceof Trigger.Abort) { - // if (forTryVar) { - // return false; - // } else { - // // Try to add some trace for context - // throw ((Trigger.Abort) e).withStackTrace(); - // } - // } - // if (e instanceof RepositoryException) { - // throw ((RepositoryException) e).toPersistException(); - // } - // throw e; - // } - - Label tryEnd = b.createLabel().setLocation(); - b.exceptionHandler(tryStart, tryEnd, null); - LocalVariable exceptionVar = b.createLocalVariable(null, TypeDesc.OBJECT); - b.storeLocal(exceptionVar); - - addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar); - - b.loadLocal(exceptionVar); - TypeDesc abortException = TypeDesc.forClass(Trigger.Abort.class); - b.instanceOf(abortException); - Label nextCheck = b.createLabel(); - b.ifZeroComparisonBranch(nextCheck, "=="); - if (forTryVar == null) { - if (forTry) { - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - } else { - b.loadLocal(exceptionVar); - b.checkCast(abortException); - b.invokeVirtual(abortException, "withStackTrace", abortException, null); - b.throwObject(); - } - } else { - b.loadLocal(forTryVar); - Label isForTry = b.createLabel(); - b.ifZeroComparisonBranch(isForTry, "!="); - b.loadLocal(exceptionVar); - b.checkCast(abortException); - b.invokeVirtual(abortException, "withStackTrace", abortException, null); - b.throwObject(); - isForTry.setLocation(); - b.loadConstant(false); - b.returnValue(TypeDesc.BOOLEAN); - } - - nextCheck.setLocation(); - b.loadLocal(exceptionVar); - TypeDesc repException = TypeDesc.forClass(RepositoryException.class); - b.instanceOf(repException); - Label throwAny = b.createLabel(); - b.ifZeroComparisonBranch(throwAny, "=="); - b.loadLocal(exceptionVar); - b.checkCast(repException); - b.invokeVirtual(repException, "toPersistException", - TypeDesc.forClass(PersistException.class), null); - b.throwObject(); - - throwAny.setLocation(); - b.loadLocal(exceptionVar); - b.throwObject(); - } - - /** - * Generates method which passes exception to uncaught exception handler. - */ - private void defineUncaughtExceptionHandler() { - MethodInfo mi = mClassFile.addMethod - (Modifiers.PRIVATE.toStatic(true), UNCAUGHT_METHOD_NAME, null, - new TypeDesc[] {TypeDesc.forClass(Throwable.class)}); - CodeBuilder b = new CodeBuilder(mi); - - // Thread t = Thread.currentThread(); - // t.getUncaughtExceptionHandler().uncaughtException(t, e); - TypeDesc threadType = TypeDesc.forClass(Thread.class); - b.invokeStatic(Thread.class.getName(), "currentThread", threadType, null); - LocalVariable threadVar = b.createLocalVariable(null, threadType); - b.storeLocal(threadVar); - b.loadLocal(threadVar); - TypeDesc handlerType = TypeDesc.forClass(Thread.UncaughtExceptionHandler.class); - b.invokeVirtual(threadType, "getUncaughtExceptionHandler", handlerType, null); - b.loadLocal(threadVar); - b.loadLocal(b.getParameter(0)); - b.invokeInterface(handlerType, "uncaughtException", null, - new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)}); - b.returnVoid(); - } - - /** - * @return MethodInfo for completing definition or null if superclass - * already implements method as final. - */ - private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name, - TypeDesc retType, TypeDesc[] params) - { - if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) { - return null; - } - - return mClassFile.addMethod(modifiers, name, retType, params); - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java b/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java deleted file mode 100644 index 83621e9..0000000 --- a/src/main/java/com/amazon/carbonado/spi/StorableSerializer.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * 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.spi; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; - -import java.lang.reflect.UndeclaredThrowableException; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.cojen.classfile.ClassFile; -import org.cojen.classfile.CodeBuilder; -import org.cojen.classfile.LocalVariable; -import org.cojen.classfile.MethodInfo; -import org.cojen.classfile.Modifiers; -import org.cojen.classfile.TypeDesc; -import org.cojen.util.ClassInjector; -import org.cojen.util.WeakIdentityMap; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.SupportException; - -import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.info.StorableProperty; - -import static com.amazon.carbonado.spi.CommonMethodNames.*; - -import com.amazon.carbonado.raw.GenericEncodingStrategy; - -/** - * Support for general-purpose serialization of storables. - *

    - * TODO: This class is unable to determine state of properties, and so they are - * lost during serialization. Upon deserialization, all properties are assumed - * dirty. To fix this, serialization might need to be supported directly by - * Storables. When that happens, this class will be deprecated. - * - * @author Brian S O'Neill - */ -public abstract class StorableSerializer { - private static final String ENCODE_METHOD_NAME = "encode"; - private static final String DECODE_METHOD_NAME = "decode"; - private static final String WRITE_METHOD_NAME = "write"; - private static final String READ_METHOD_NAME = "read"; - - @SuppressWarnings("unchecked") - private static Map>> cCache = new WeakIdentityMap(); - - /** - * @param type type of storable to serialize - */ - @SuppressWarnings("unchecked") - public static StorableSerializer forType(Class type) - throws SupportException - { - synchronized (cCache) { - StorableSerializer serializer; - Reference> ref = cCache.get(type); - if (ref != null) { - serializer = (StorableSerializer) ref.get(); - if (serializer != null) { - return serializer; - } - } - serializer = generateSerializer(type); - cCache.put(type, new SoftReference>(serializer)); - return serializer; - } - } - - @SuppressWarnings("unchecked") - private static StorableSerializer generateSerializer(Class type) - throws SupportException - { - Class abstractClass = StorableGenerator.getAbstractClass(type); - - // Use abstract class ClassLoader in order to access adapter instances. - ClassInjector ci = ClassInjector.create - (type.getName(), abstractClass.getClassLoader()); - ClassFile cf = new ClassFile(ci.getClassName(), StorableSerializer.class); - cf.markSynthetic(); - cf.setSourceFile(StorableSerializer.class.getName()); - cf.setTarget("1.5"); - - cf.addDefaultConstructor(); - - Map> propertyMap = - StorableIntrospector.examine(type).getAllProperties(); - - StorableProperty[] properties; - { - // Exclude joins. - List> list = - new ArrayList>(propertyMap.size()); - for (StorableProperty property : propertyMap.values()) { - if (!property.isJoin()) { - list.add(property); - } - } - properties = new StorableProperty[list.size()]; - list.toArray(properties); - } - - GenericEncodingStrategy ges = new GenericEncodingStrategy(type, null); - - TypeDesc byteArrayType = TypeDesc.forClass(byte[].class); - TypeDesc storableType = TypeDesc.forClass(Storable.class); - TypeDesc userStorableType = TypeDesc.forClass(type); - TypeDesc storageType = TypeDesc.forClass(Storage.class); - - // Build method to encode storable into a byte array. - { - MethodInfo mi = cf.addMethod - (Modifiers.PRIVATE.toStatic(true), ENCODE_METHOD_NAME, byteArrayType, - new TypeDesc[] {userStorableType}); - CodeBuilder b = new CodeBuilder(mi); - LocalVariable encodedVar = - ges.buildDataEncoding(b, properties, b.getParameter(0), abstractClass, true, -1); - b.loadLocal(encodedVar); - b.returnValue(byteArrayType); - } - - // Build method to decode storable from a byte array. - { - MethodInfo mi = cf.addMethod - (Modifiers.PRIVATE.toStatic(true), DECODE_METHOD_NAME, userStorableType, - new TypeDesc[] {storageType, byteArrayType}); - CodeBuilder b = new CodeBuilder(mi); - LocalVariable instanceVar = b.createLocalVariable(null, userStorableType); - b.loadLocal(b.getParameter(0)); - b.invokeInterface(storageType, PREPARE_METHOD_NAME, - storableType, null); - b.checkCast(userStorableType); - b.storeLocal(instanceVar); - LocalVariable encodedVar = b.getParameter(1); - ges.buildDataDecoding - (b, properties, instanceVar, abstractClass, true, -1, null, encodedVar); - b.loadLocal(instanceVar); - b.returnValue(storableType); - } - - // Build write method for DataOutput. - { - TypeDesc dataOutputType = TypeDesc.forClass(DataOutput.class); - - MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, WRITE_METHOD_NAME, null, - new TypeDesc[] {storableType, dataOutputType}); - - CodeBuilder b = new CodeBuilder(mi); - LocalVariable storableVar = b.getParameter(0); - LocalVariable doutVar = b.getParameter(1); - - b.loadLocal(storableVar); - b.checkCast(userStorableType); - b.invokeStatic(ENCODE_METHOD_NAME, byteArrayType, new TypeDesc[] {userStorableType}); - LocalVariable encodedVar = b.createLocalVariable(null, byteArrayType); - b.storeLocal(encodedVar); - - b.loadLocal(doutVar); - b.loadLocal(encodedVar); - b.arrayLength(); - b.invokeInterface(dataOutputType, "writeInt", null, new TypeDesc[] {TypeDesc.INT}); - - b.loadLocal(doutVar); - b.loadLocal(encodedVar); - b.invokeInterface(dataOutputType, "write", null, new TypeDesc[] {byteArrayType}); - b.returnVoid(); - } - - final TypeDesc storableSerializerType = TypeDesc.forClass(StorableSerializer.class); - - // Build write method for OutputStream. - { - TypeDesc outputStreamType = TypeDesc.forClass(OutputStream.class); - - MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, WRITE_METHOD_NAME, null, - new TypeDesc[] {storableType, outputStreamType}); - - CodeBuilder b = new CodeBuilder(mi); - LocalVariable storableVar = b.getParameter(0); - LocalVariable outVar = b.getParameter(1); - - b.loadLocal(storableVar); - b.checkCast(userStorableType); - b.invokeStatic(ENCODE_METHOD_NAME, byteArrayType, new TypeDesc[] {userStorableType}); - LocalVariable encodedVar = b.createLocalVariable(null, byteArrayType); - b.storeLocal(encodedVar); - - b.loadLocal(outVar); - b.loadLocal(encodedVar); - b.arrayLength(); - b.invokeStatic(storableSerializerType, "writeInt", null, - new TypeDesc[] {outputStreamType, TypeDesc.INT}); - - b.loadLocal(outVar); - b.loadLocal(encodedVar); - b.invokeVirtual(outputStreamType, "write", null, new TypeDesc[] {byteArrayType}); - b.returnVoid(); - } - - // Build read method for DataInput. - { - TypeDesc dataInputType = TypeDesc.forClass(DataInput.class); - - MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, READ_METHOD_NAME, storableType, - new TypeDesc[] {storageType, dataInputType}); - - CodeBuilder b = new CodeBuilder(mi); - LocalVariable storageVar = b.getParameter(0); - LocalVariable dinVar = b.getParameter(1); - - b.loadLocal(dinVar); - b.invokeInterface(dataInputType, "readInt", TypeDesc.INT, null); - b.newObject(byteArrayType); - LocalVariable byteArrayVar = b.createLocalVariable(null, byteArrayType); - b.storeLocal(byteArrayVar); - - b.loadLocal(dinVar); - b.loadLocal(byteArrayVar); - b.invokeInterface(dataInputType, "readFully", null, new TypeDesc[] {byteArrayType}); - - b.loadLocal(storageVar); - b.loadLocal(byteArrayVar); - b.invokeStatic(DECODE_METHOD_NAME, userStorableType, - new TypeDesc[] {storageType, byteArrayType}); - b.returnValue(storableType); - } - - // Build read method for InputStream. - { - TypeDesc inputStreamType = TypeDesc.forClass(InputStream.class); - - MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, READ_METHOD_NAME, storableType, - new TypeDesc[] {storageType, inputStreamType}); - - CodeBuilder b = new CodeBuilder(mi); - LocalVariable storageVar = b.getParameter(0); - LocalVariable inVar = b.getParameter(1); - - b.loadLocal(inVar); - b.invokeStatic(storableSerializerType, "readInt", TypeDesc.INT, - new TypeDesc[] {inputStreamType}); - b.newObject(byteArrayType); - LocalVariable byteArrayVar = b.createLocalVariable(null, byteArrayType); - b.storeLocal(byteArrayVar); - - b.loadLocal(inVar); - b.loadLocal(byteArrayVar); - b.invokeStatic(storableSerializerType, "readFully", null, - new TypeDesc[] {inputStreamType, byteArrayType}); - - b.loadLocal(storageVar); - b.loadLocal(byteArrayVar); - b.invokeStatic(DECODE_METHOD_NAME, userStorableType, - new TypeDesc[] {storageType, byteArrayType}); - b.returnValue(storableType); - } - - Class clazz = (Class) ci.defineClass(cf); - - try { - return clazz.newInstance(); - } catch (InstantiationException e) { - throw new UndeclaredThrowableException(e); - } catch (IllegalAccessException e) { - throw new UndeclaredThrowableException(e); - } - } - - protected StorableSerializer() { - } - - public abstract void write(S storable, DataOutput out) throws IOException; - - public abstract void write(S storable, OutputStream out) throws IOException; - - public abstract S read(Storage storage, DataInput in) throws IOException, EOFException; - - public abstract S read(Storage storage, InputStream in) throws IOException, EOFException; - - public static void writeInt(OutputStream out, int v) throws IOException { - out.write((v >>> 24) & 0xff); - out.write((v >>> 16) & 0xff); - out.write((v >>> 8) & 0xff); - out.write(v & 0xff); - } - - public static int readInt(InputStream in) throws IOException { - int a = in.read(); - int b = in.read(); - int c = in.read(); - int d = in.read(); - if ((a | b | c | d) < 0) { - throw new EOFException(); - } - return (a << 24) | (b << 16) | (c << 8) | d; - } - - public static void readFully(InputStream in, byte[] b) throws IOException { - int length = b.length; - int n = 0; - while (n < length) { - int count = in.read(b, n, length - n); - if (count < 0) { - throw new EOFException(); - } - n += count; - } - } -} diff --git a/src/main/java/com/amazon/carbonado/spi/StorableSupport.java b/src/main/java/com/amazon/carbonado/spi/StorableSupport.java deleted file mode 100644 index bf2609c..0000000 --- a/src/main/java/com/amazon/carbonado/spi/StorableSupport.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.spi; - -import com.amazon.carbonado.Repository; -import com.amazon.carbonado.Storable; - -/** - * - * - * @author Brian S O'Neill - */ -public interface StorableSupport { - /** - * Returns the root parent Repository that the Storable came from. - */ - Repository getRootRepository(); - - /** - * Returns true if the given property exists and is supported. - * - * @param propertyName name of property to check - */ - boolean isPropertySupported(String propertyName); -} diff --git a/src/main/java/com/amazon/carbonado/spi/TriggerSupport.java b/src/main/java/com/amazon/carbonado/spi/TriggerSupport.java deleted file mode 100644 index 0e40c38..0000000 --- a/src/main/java/com/amazon/carbonado/spi/TriggerSupport.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.spi; - -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.Trigger; - -/** - * - * - * @author Brian S O'Neill - */ -public interface TriggerSupport extends StorableSupport { - /** - * Returns a trigger which must be run for all insert operations. - * - * @return null if no trigger - */ - Trigger getInsertTrigger(); - - /** - * Returns a trigger which must be run for all update operations. - * - * @return null if no trigger - */ - Trigger getUpdateTrigger(); - - /** - * Returns a trigger which must be run for all delete operations. - * - * @return null if no trigger - */ - Trigger getDeleteTrigger(); -} diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java index 71f6fbc..ea12223 100644 --- a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java +++ b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java @@ -31,6 +31,9 @@ import com.amazon.carbonado.TriggerFactory; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.util.QuickConstructorGenerator; +import com.amazon.carbonado.gen.StorableGenerator; +import com.amazon.carbonado.gen.WrappedSupport; + /** * Abstract storage that wraps all returned Storables and Queries, including * those returned from joins. Property access methods (get and set) are diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedSupport.java b/src/main/java/com/amazon/carbonado/spi/WrappedSupport.java deleted file mode 100644 index 24e2c02..0000000 --- a/src/main/java/com/amazon/carbonado/spi/WrappedSupport.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.spi; - -import com.amazon.carbonado.FetchException; -import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Storable; - -/** - * - * - * @author Brian S O'Neill - */ -public interface WrappedSupport extends TriggerSupport { - /** - * @see Storable#load - */ - void load() throws FetchException; - - /** - * @see Storable#tryLoad - */ - boolean tryLoad() throws FetchException; - - /** - * @see Storable#insert - */ - void insert() throws PersistException; - - /** - * @see Storable#tryInsert - */ - boolean tryInsert() throws PersistException; - - /** - * @see Storable#update - */ - void update() throws PersistException; - - /** - * @see Storable#tryUpdate - */ - boolean tryUpdate() throws PersistException; - - /** - * @see Storable#delete - */ - void delete() throws PersistException; - - /** - * @see Storable#tryDelete - */ - boolean tryDelete() throws PersistException; - - /** - * Return another Support instance for the given Storable. - */ - WrappedSupport createSupport(S storable); -} diff --git a/src/main/java/com/amazon/carbonado/spi/package-info.java b/src/main/java/com/amazon/carbonado/spi/package-info.java index 2a6abad..b50105b 100644 --- a/src/main/java/com/amazon/carbonado/spi/package-info.java +++ b/src/main/java/com/amazon/carbonado/spi/package-info.java @@ -17,8 +17,8 @@ */ /** - * Core Service Provider Interface for Carbonado. Repositories are free to use - * this package to aid in their implementation. User-level applications have no - * need to use this package. + * Service Provider Interface for Carbonado. Repositories are free to use this + * package to aid in their implementation. User-level applications have no need + * to use this package. */ package com.amazon.carbonado.spi; diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java index fb8b1fc..2c9c0dc 100644 --- a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java @@ -33,7 +33,7 @@ import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; -import com.amazon.carbonado.spi.CodeBuilderUtil; +import com.amazon.carbonado.gen.CodeBuilderUtil; import com.amazon.carbonado.util.ThrowUnchecked; import org.cojen.classfile.ClassFile; -- cgit v1.2.3