summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2007-04-01 00:00:07 +0000
committerBrian S. O'Neill <bronee@gmail.com>2007-04-01 00:00:07 +0000
commit641a7812d439daca3045e7471b604beb807c1891 (patch)
tree8d4c9535d122b96131739d9ac168969235a54c1c /src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java
parentc7f8a6ef89b43a908e677df76f35cc425b9c7f91 (diff)
Move Storable code generation support to separate package.
Diffstat (limited to 'src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java')
-rw-r--r--src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java536
1 files changed, 536 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);
+ }
+}