summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/gen
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/amazon/carbonado/gen')
-rw-r--r--src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java536
-rw-r--r--src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java87
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterFeature.java59
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java835
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterSupport.java39
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableGenerator.java3671
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableSerializer.java337
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableSupport.java41
-rw-r--r--src/main/java/com/amazon/carbonado/gen/TriggerSupport.java50
-rw-r--r--src/main/java/com/amazon/carbonado/gen/WrappedSupport.java75
-rw-r--r--src/main/java/com/amazon/carbonado/gen/package-info.java22
11 files changed, 5752 insertions, 0 deletions
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<foo>, all
+ * classes and implemented interfaces for every superclass between foo (the leaf) and
+ * Object (the base).
+ * <P>A copy must be coercible into any of these types, and copy bridge methods must be
+ * provided to do so.
+ *
+ * <P>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<Class> gatherAllBridgeTypes(Set<Class> 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<Class>(), 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<params.length; i++) {
+ paramClasses[i] = params[i].toClass();
+ }
+ }
+ try {
+ Method existing = clazz.getMethod(name, paramClasses);
+ if (Modifier.isFinal(existing.getModifiers())) {
+ if (TypeDesc.forClass(existing.getReturnType()) == retType) {
+ // Method is already implemented and is final.
+ return true;
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Define a classfile appropriate for most Storables. Specifically:
+ * <ul>
+ * <li>implements Storable</li>
+ * <li>implements Cloneable
+ * <li>abstract if appropriate
+ * <li>marked synthetic
+ * <li>targetted for java version 1.5
+ * </ul>
+ * @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 <S extends Storable> ClassFile createStorableClassFile(
+ ClassInjector ci, Class<S> 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.
+ *
+ * <P>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<S extends Storable> {
+ // 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<Object, Class<? extends Storable>> 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:
+ *
+ * <pre>
+ * public &lt;init&gt;(MasterSupport);
+ * </pre>
+ *
+ * Subclasses must implement the following abstract protected methods,
+ * whose exact names are defined by constants in this class:
+ *
+ * <pre>
+ * // 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;
+ * </pre>
+ *
+ * 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 <S extends Storable> Class<? extends S>
+ getAbstractClass(Class<S> type, EnumSet<MasterFeature> features)
+ throws SupportException, IllegalArgumentException
+ {
+ StorableInfo<S> info = StorableIntrospector.examine(type);
+
+ anySequences:
+ if (features.contains(MasterFeature.INSERT_SEQUENCES)) {
+ for (StorableProperty<S> 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<? extends S> abstractClass = (Class<? extends S>) cCache.get(key);
+ if (abstractClass != null) {
+ return abstractClass;
+ }
+ abstractClass =
+ new MasterStorableGenerator<S>(type, features).generateAndInjectClass();
+ cCache.put(key, abstractClass);
+ return abstractClass;
+ }
+ }
+
+ private final EnumSet<MasterFeature> mFeatures;
+ private final StorableInfo<S> mInfo;
+ private final Map<String, ? extends StorableProperty<S>> mAllProperties;
+
+ private final ClassInjector mClassInjector;
+ private final ClassFile mClassFile;
+
+ private MasterStorableGenerator(Class<S> storableType, EnumSet<MasterFeature> features) {
+ mFeatures = features;
+ mInfo = StorableIntrospector.examine(storableType);
+ mAllProperties = mInfo.getAllProperties();
+
+ final Class<? extends S> 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<? extends S> generateAndInjectClass() throws SupportException {
+ generateClass();
+ Class abstractClass = mClassInjector.defineClass(mClassFile);
+ return (Class<? extends S>) 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<S> 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<Integer> stateAppendMethods = new HashSet<Integer>();
+
+ // Parameters are: StringBuilder, count, mask, property name
+ TypeDesc[] appendParams = {sbType, TypeDesc.INT, TypeDesc.INT, TypeDesc.STRING};
+
+ for (StorableProperty<S> 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<MasterFeature> 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<MasterFeature> 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<S extends Storable> extends TriggerSupport<S> {
+ /**
+ * Returns a sequence value producer by name, or throw PersistException if not found.
+ *
+ * <p>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<S extends Storable> {
+
+ // 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<Class, Reference<Class<? extends Storable>>> cAbstractCache;
+ // Cache of generated wrapped classes.
+ private static Map<Class, Reference<Class<? extends Storable>>> 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:
+ *
+ * <pre>
+ * /**
+ * * @param support Access to triggers
+ * *&#047;
+ * public &lt;init&gt;(TriggerSupport support);
+ * </pre>
+ *
+ * <p>Subclasses must implement the following abstract protected methods,
+ * whose exact names are defined by constants in this class:
+ *
+ * <pre>
+ * // 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;
+ * </pre>
+ *
+ * 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.
+ *
+ * <pre>
+ * // 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;
+ * </pre>
+ *
+ * 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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <pre>
+ * // 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();
+ * </pre>
+ *
+ * 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 <S extends Storable> Class<? extends S> getAbstractClass(Class<S> type)
+ throws IllegalArgumentException
+ {
+ synchronized (cAbstractCache) {
+ Class<? extends S> abstractClass;
+ Reference<Class<? extends Storable>> ref = cAbstractCache.get(type);
+ if (ref != null) {
+ abstractClass = (Class<? extends S>) ref.get();
+ if (abstractClass != null) {
+ return abstractClass;
+ }
+ }
+ abstractClass = new StorableGenerator<S>(type, GEN_ABSTRACT).generateAndInjectClass();
+ cAbstractCache.put(type, new SoftReference<Class<? extends Storable>>(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:
+ *
+ * <pre>
+ * /**
+ * * @param support Custom implementation for Storable CRUD operations
+ * * @param storable Storable being wrapped
+ * *&#047;
+ * public &lt;init&gt;(WrappedSupport support, Storable storable);
+ * </pre>
+ *
+ * <p>Instances of the wrapped Storable delegate to the WrappedSupport for
+ * all CRUD operations:
+ *
+ * <ul>
+ * <li>load and tryLoad
+ * <li>insert and tryInsert
+ * <li>update and tryUpdate
+ * <li>delete and tryDelete
+ * </ul>
+ *
+ * <p>Methods which delegate to wrapped Storable:
+ *
+ * <ul>
+ * <li>all ordinary user-defined properties
+ * <li>copyAllProperties
+ * <li>copyPrimaryKeyProperties
+ * <li>copyVersionProperty
+ * <li>copyUnequalProperties
+ * <li>copyDirtyProperties
+ * <li>hasDirtyProperties
+ * <li>markPropertiesClean
+ * <li>markAllPropertiesClean
+ * <li>markPropertiesDirty
+ * <li>markAllPropertiesDirty
+ * <li>hashCode
+ * <li>equalPrimaryKeys
+ * <li>equalProperties
+ * <li>toString
+ * <li>toStringKeyOnly
+ * </ul>
+ *
+ * <p>Methods with special implementation:
+ *
+ * <ul>
+ * <li>all user-defined join properties (join properties query using wrapper's Storage)
+ * <li>storage (returns Storage used by wrapper)
+ * <li>storableType (returns literal class)
+ * <li>copy (delegates to wrapped storable, but bridge methods must be defined as well)
+ * <li>equals (compares Storage instance and properties)
+ * </ul>
+ *
+ * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed
+ * @throws IllegalArgumentException if type is null
+ */
+ @SuppressWarnings("unchecked")
+ public static <S extends Storable> Class<? extends S> getWrappedClass(Class<S> type)
+ throws IllegalArgumentException
+ {
+ synchronized (cWrappedCache) {
+ Class<? extends S> wrappedClass;
+ Reference<Class<? extends Storable>> ref = cWrappedCache.get(type);
+ if (ref != null) {
+ wrappedClass = (Class<? extends S>) ref.get();
+ if (wrappedClass != null) {
+ return wrappedClass;
+ }
+ }
+ wrappedClass = new StorableGenerator<S>(type, GEN_WRAPPED).generateAndInjectClass();
+ cWrappedCache.put(type, new SoftReference<Class<? extends Storable>>(wrappedClass));
+ return wrappedClass;
+ }
+ }
+
+ private final Class<S> mStorableType;
+ private final int mGenMode;
+ private final TypeDesc mSupportType;
+ private final StorableInfo<S> mInfo;
+ private final Map<String, ? extends StorableProperty<S>> mAllProperties;
+ private final boolean mHasJoins;
+
+ private final ClassInjector mClassInjector;
+ private final ClassFile mClassFile;
+
+ private StorableGenerator(Class<S> 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<? extends S> generateAndInjectClass() {
+ generateClass();
+ Class abstractClass = mClassInjector.defineClass(mClassFile);
+ return (Class<? extends S>) 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 <user storable> 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<count; i++) {
+ StorablePropertyConstraint spc = property.getConstraint(i);
+ String fieldName = property.getName() + CONSTRAINT_FIELD_ELEMENT + i;
+ TypeDesc constraintType = TypeDesc.forClass
+ (spc.getConstraintConstructor().getDeclaringClass());
+ mClassFile.addField(fieldModifiers, fieldName, constraintType);
+
+ if (clinit == null) {
+ clinit = new CodeBuilder(mClassFile.addInitializer());
+ }
+
+ // Assign value to new field.
+ // admin$constraint$0 = new LengthConstraint.Constraint
+ // (UserInfo.class, "firstName", annotation);
+
+ clinit.newObject(constraintType);
+ clinit.dup();
+ clinit.loadConstant(TypeDesc.forClass(mStorableType));
+ clinit.loadConstant(property.getName());
+
+ // Generate code to load property annotation third parameter.
+ loadPropertyAnnotation(clinit, property, spc.getAnnotation());
+
+ clinit.invoke(spc.getConstraintConstructor());
+ clinit.storeStaticField(fieldName, constraintType);
+ }
+ }
+
+ if (clinit != null) {
+ // Must return else verifier complains.
+ clinit.returnVoid();
+ }
+ }
+
+ // Add property fields and methods.
+ // Also remember ordinal of optional version property for use later.
+ int versionOrdinal = -1;
+ {
+ int ordinal = -1;
+ int maxOrdinal = mAllProperties.size() - 1;
+ boolean requireStateField = false;
+
+ for (StorableProperty<S> 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<count; i++) {
+ StorableProperty internal = property.getInternalJoinElement(i);
+ StorableProperty external = property.getExternalJoinElement(i);
+ if (internal.isNullable() && !external.isNullable()) {
+ break nullPossible;
+ }
+ }
+ break buildShortCircuit;
+ }
+
+ for (int i=0; i<count; i++) {
+ StorableProperty internal = property.getInternalJoinElement(i);
+ StorableProperty external = property.getExternalJoinElement(i);
+ if (internal.isNullable() && !external.isNullable()) {
+ 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());
+ }
+
+ Label notNull = b.createLabel();
+ b.ifNullBranch(notNull, false);
+ b.loadNull();
+ b.storeLocal(join);
+ b.branch(shortCircuit);
+ notNull.setLocation();
+ }
+ }
+ }
+
+ // Get the storage for the join type.
+ loadStorageForFetch(b, TypeDesc.forClass(property.getJoinedType()));
+ TypeDesc storageType = TypeDesc.forClass(Storage.class);
+
+ // There are two ways that property can be loaded. The
+ // general form is to use a Query. Calling load on the
+ // property itself is preferred, but it is only
+ // possible if the join is against a key and all
+ // external properties have a write method.
+
+ boolean canUseDirectForm = !property.isQuery();
+
+ if (canUseDirectForm) {
+ int joinCount = property.getJoinElementCount();
+ for (int i=0; i<joinCount; i++) {
+ StorableProperty external = property.getExternalJoinElement(i);
+ if (external.getWriteMethod() == null) {
+ canUseDirectForm = false;
+ }
+ }
+ }
+
+ final TypeDesc storableDesc = TypeDesc.forClass(Storable.class);
+
+ if (canUseDirectForm) {
+ // Generate direct load form.
+
+ // Storage instance is already on the stack... replace it
+ // with an instance of the joined type.
+ b.invokeInterface
+ (storageType, PREPARE_METHOD_NAME, storableDesc, null);
+ b.checkCast(type);
+ b.storeLocal(join);
+
+ // Set the keys on the joined type.
+ int count = property.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ b.loadLocal(join);
+ StorableProperty internal = property.getInternalJoinElement(i);
+ StorableProperty external = property.getExternalJoinElement(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());
+ }
+ CodeBuilderUtil.convertValue
+ (b, internal.getType(), external.getType());
+ b.invoke(external.getWriteMethod());
+ }
+
+ // Now load the object.
+ b.loadLocal(join);
+ if (!property.isNullable()) {
+ b.invokeInterface(storableDesc, LOAD_METHOD_NAME, null, null);
+ } else {
+ b.invokeInterface
+ (storableDesc, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
+ Label wasLoaded = b.createLabel();
+ b.ifZeroComparisonBranch(wasLoaded, "!=");
+ // Not loaded, so replace joined object with null.
+ b.loadNull();
+ b.storeLocal(join);
+ wasLoaded.setLocation();
+ }
+ } else {
+ // Generate query load form.
+
+ // Storage instance is already on the stack... replace it
+ // with a Query. First, we need to define the query string.
+
+ StringBuilder queryBuilder = new StringBuilder();
+
+ // Set the keys on the joined type.
+ int count = property.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ queryBuilder.append(" & ");
+ }
+ queryBuilder.append(property.getExternalJoinElement(i).getName());
+ queryBuilder.append(" = ?");
+ }
+
+ b.loadConstant(queryBuilder.toString());
+ TypeDesc queryType = TypeDesc.forClass(Query.class);
+ b.invokeInterface(storageType, QUERY_METHOD_NAME, queryType,
+ new TypeDesc[]{TypeDesc.STRING});
+
+ // Now fill in the parameters of the query.
+ for (int i=0; i<count; i++) {
+ StorableProperty<S> 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<count; i++) {
+ StorableProperty internal = property.getInternalJoinElement(i);
+ StorableProperty external = property.getExternalJoinElement(i);
+
+ b.loadLocal(b.getParameter(0));
+ b.invoke(external.getReadMethod());
+ CodeBuilderUtil.convertValue
+ (b, external.getType(), internal.getType());
+
+ LocalVariable newInternalPropVar =
+ b.createLocalVariable(null, TypeDesc.forClass(internal.getType()));
+ b.storeLocal(newInternalPropVar);
+
+ // Since join properties may be pre-loaded, they
+ // are set via the public write method. If internal
+ // property is clean and equal to new value, then
+ // don't set internal property. Doing so would mark
+ // it as dirty, which is not the right behavior
+ // when pre-loading join properties. The internal
+ // properties should remain clean.
+
+ Label setInternalProp = b.createLabel();
+
+ if (mGenMode == GEN_ABSTRACT) {
+ // Access state of internal property directly.
+ int ord = findPropertyOrdinal(internal);
+ b.loadThis();
+ b.loadField(PROPERTY_STATE_FIELD_NAME + (ord >> 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<mInfo.getAlternateKeyCount(); i++) {
+ b.loadThis();
+ b.invokeVirtual(IS_ALT_KEY_INITIALIZED_PREFIX + i, TypeDesc.BOOLEAN, null);
+ Label noAltKey = b.createLabel();
+ b.ifZeroComparisonBranch(noAltKey, "==");
+
+ StorableKey<S> altKey = mInfo.getAlternateKey(i);
+
+ // Form query filter.
+ StringBuilder queryBuilder = new StringBuilder();
+ for (OrderedProperty<S> 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<S> op : altKey.getProperties()) {
+ StorableProperty<S> 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<mInfo.getAlternateKeyCount(); i++) {
+ Map<String, StorableProperty<S>> altProps =
+ new HashMap<String, StorableProperty<S>>();
+
+ StorableKey<S> altKey = mInfo.getAlternateKey(i);
+
+ for (OrderedProperty<S> op : altKey.getProperties()) {
+ ChainedProperty<S> cp = op.getChainedProperty();
+ if (cp.getChainCount() > 0) {
+ // This should not be possible.
+ continue addAltKeyMethods;
+ }
+ StorableProperty<S> property = cp.getPrimeProperty();
+ altProps.put(property.getName(), property);
+ }
+
+ addIsInitializedMethod(IS_ALT_KEY_INITIALIZED_PREFIX + i, altProps);
+ }
+
+ // Define protected isRequiredDataInitialized method.
+ defineIsRequiredDataInitialized: {
+ Map<String, StorableProperty<S>> requiredProperties =
+ new HashMap<String, StorableProperty<S>>();
+
+ 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<count; j++) {
+ b.loadLocal(b.getParameter(j));
+ }
+
+ Method method = lookupMethod(mStorableType, mi);
+ b.invoke(method);
+ if (method.getReturnType() == void.class) {
+ b.returnVoid();
+ } else {
+ b.returnValue(TypeDesc.forClass(method.getReturnType()));
+ }
+ }
+
+ private static Method lookupMethod(Class type, MethodInfo mi) {
+ MethodDesc desc = mi.getMethodDescriptor();
+ TypeDesc[] params = desc.getParameterTypes();
+ Class[] args;
+
+ if (params == null || params.length == 0) {
+ args = null;
+ } else {
+ args = new Class[params.length];
+ for (int i=0; i<args.length; i++) {
+ args[i] = params[i].toClass();
+ }
+ }
+
+ return lookupMethod(type, mi.getName(), args);
+ }
+
+ private static Method lookupMethod(Class type, String name, Class[] args) {
+ try {
+ return type.getMethod(name, args);
+ } catch (NoSuchMethodException e) {
+ Error error = new NoSuchMethodError();
+ error.initCause(e);
+ throw error;
+ }
+ }
+
+ /**
+ * Generates a copy properties method with several options to control its
+ * behavior. Although eight combinations can be defined, only four are
+ * required by Storable interface. Uninitialized properties are never
+ * copied.
+ *
+ * @param pkProperties when true, copy primary key properties
+ * @param dataProperties when true, copy data properties
+ * @param unequalOnly when true, only copy unequal properties
+ * @param dirtyOnly when true, only copy dirty properties
+ */
+ private void addCopyPropertiesMethod
+ (String methodName,
+ boolean pkProperties,
+ boolean versionProperty,
+ boolean dataProperties,
+ boolean unequalOnly,
+ boolean dirtyOnly)
+ {
+ TypeDesc[] param = { TypeDesc.forClass(Storable.class) };
+ TypeDesc storableTypeDesc = TypeDesc.forClass(mStorableType);
+
+ MethodInfo mi= addMethodIfNotFinal
+ (Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
+ methodName,
+ null,
+ param);
+
+ if (mi == null) {
+ return;
+ }
+
+ if (mGenMode == GEN_WRAPPED) {
+ callWrappedStorable(mi);
+ return;
+ }
+
+ CodeBuilder b = new CodeBuilder(mi);
+
+ LocalVariable target = CodeBuilderUtil.uneraseGenericParameter(b, storableTypeDesc, 0);
+
+ LocalVariable stateBits = null;
+ int ordinal = 0;
+ int mask = PROPERTY_STATE_DIRTY;
+
+ for (StorableProperty property : mAllProperties.values()) {
+ // Decide if property should be part of the copy.
+ boolean shouldCopy = !property.isJoin() &&
+ (property.isPrimaryKeyMember() && pkProperties ||
+ property.isVersion() && versionProperty ||
+ !property.isPrimaryKeyMember() && dataProperties);
+
+ if (shouldCopy) {
+ if (stateBits == null) {
+ // Load state bits into local for quick retrieval.
+ stateBits = b.createLocalVariable(null, TypeDesc.INT);
+ String stateFieldName =
+ StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 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<String, ? extends StorableProperty<S>> properties)
+ {
+ // Don't check Automatic properties.
+ {
+ boolean cloned = false;
+ for (StorableProperty<S> prop : properties.values()) {
+ if (prop.isAutomatic() || prop.isVersion()) {
+ if (!cloned) {
+ properties = new LinkedHashMap<String, StorableProperty<S>>(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<S> 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<caseCount; i++) {
+ cases[i] = i;
+ }
+
+ Label[] switchLabels = new Label[caseCount];
+ Label noMatch = b.createLabel();
+ List<StorableProperty<?>>[] caseMatches = caseMatches(caseCount);
+
+ for (int i=0; i<caseCount; i++) {
+ List<?> 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<StorableProperty<?>, Integer> ordinalMap = new HashMap<StorableProperty<?>, 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<caseCount; i++) {
+ List<StorableProperty<?>> matches = caseMatches[i];
+ if (matches == null || matches.size() == 0) {
+ continue;
+ }
+
+ switchLabels[i].setLocation();
+
+ int matchCount = matches.size();
+ for (int j=0; j<matchCount; j++) {
+ StorableProperty<?> 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<StorableProperty<?>>[] caseMatches(int caseCount) {
+ List<StorableProperty<?>>[] 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<StorableProperty<?>>();
+ }
+ 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<altKeyCount; i++) {
+ b.loadConstant(", {");
+ invokeAppend(b, stringParam);
+
+ StorableKey<S> key = mInfo.getAlternateKey(i);
+
+ ordinal = 0;
+ for (OrderedProperty<S> op : key.getProperties()) {
+ StorableProperty<S> 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.
+ * <p>
+ * 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<S extends Storable> {
+ 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<Class, Reference<StorableSerializer<?>>> cCache = new WeakIdentityMap();
+
+ /**
+ * @param type type of storable to serialize
+ */
+ @SuppressWarnings("unchecked")
+ public static <S extends Storable> StorableSerializer<S> forType(Class<S> type)
+ throws SupportException
+ {
+ synchronized (cCache) {
+ StorableSerializer<S> serializer;
+ Reference<StorableSerializer<?>> ref = cCache.get(type);
+ if (ref != null) {
+ serializer = (StorableSerializer<S>) ref.get();
+ if (serializer != null) {
+ return serializer;
+ }
+ }
+ serializer = generateSerializer(type);
+ cCache.put(type, new SoftReference<StorableSerializer<?>>(serializer));
+ return serializer;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <S extends Storable> StorableSerializer<S> generateSerializer(Class<S> type)
+ throws SupportException
+ {
+ Class<? extends S> 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<String, ? extends StorableProperty<S>> propertyMap =
+ StorableIntrospector.examine(type).getAllProperties();
+
+ StorableProperty<S>[] properties;
+ {
+ // Exclude joins.
+ List<StorableProperty<S>> list =
+ new ArrayList<StorableProperty<S>>(propertyMap.size());
+ for (StorableProperty<S> property : propertyMap.values()) {
+ if (!property.isJoin()) {
+ list.add(property);
+ }
+ }
+ properties = new StorableProperty[list.size()];
+ list.toArray(properties);
+ }
+
+ GenericEncodingStrategy<S> ges = new GenericEncodingStrategy<S>(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<StorableSerializer> clazz = (Class<StorableSerializer>) 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<S> storage, DataInput in) throws IOException, EOFException;
+
+ public abstract S read(Storage<S> 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<S extends Storable> {
+ /**
+ * 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<S extends Storable> extends StorableSupport<S> {
+ /**
+ * Returns a trigger which must be run for all insert operations.
+ *
+ * @return null if no trigger
+ */
+ Trigger<? super S> getInsertTrigger();
+
+ /**
+ * Returns a trigger which must be run for all update operations.
+ *
+ * @return null if no trigger
+ */
+ Trigger<? super S> getUpdateTrigger();
+
+ /**
+ * Returns a trigger which must be run for all delete operations.
+ *
+ * @return null if no trigger
+ */
+ Trigger<? super S> 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<S extends Storable> extends TriggerSupport<S> {
+ /**
+ * @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<S> 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;