/* * Copyright 2006-2012 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.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; 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.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 * @since 1.2 */ 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(); } /** * Generate code to throw an exception with a message concatenated at runtime. * * @param b {@link CodeBuilder} to which to add code * @param type type of the object to throw * @param messages messages to concat at runtime */ public static void throwConcatException(CodeBuilder b, Class type, String... messages) { if (messages == null || messages.length == 0) { throwException(b, type, null); return; } if (messages.length == 1) { throwException(b, type, messages[0]); return; } TypeDesc desc = TypeDesc.forClass(type); b.newObject(desc); b.dup(); TypeDesc[] params = new TypeDesc[] {TypeDesc.STRING}; for (int i=0; i 0) { b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); } } b.invokeConstructor(desc, params); b.throwObject(); } /** * Collect a set of all the interfaces and recursively all superclasses for the leaf * (genericised class) and root (genericised base class). Eg, for Object, all * classes and implemented interfaces for every superclass between foo (the leaf) and * Object (the base). *

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

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

  • implements Storable
  • *
  • implements Cloneable *
  • abstract if appropriate *
  • marked synthetic *
  • targetted for java version 1.5 * * @param ci ClassInjector for the storable * @param type specific Storable implementation to generate * @param isAbstract true if the class should be abstract * @param aSourcefileName identifier for the classfile, typically the factory class name * @return ClassFile object ready to have methods added. */ public static ClassFile createStorableClassFile( ClassInjector ci, Class type, boolean isAbstract, String aSourcefileName) { ClassFile cf; if (type.isInterface()) { cf = new ClassFile(ci.getClassName()); cf.addInterface(type); } else { cf = new ClassFile(ci.getClassName(), type); } if (isAbstract) { Modifiers modifiers = cf.getModifiers().toAbstract(true); cf.setModifiers(modifiers); } cf.addInterface(Storable.class); cf.addInterface(Cloneable.class); cf.markSynthetic(); cf.setSourceFile(aSourcefileName); cf.setTarget("1.5"); return cf; } /** * Generates code to compute a hashcode for a value on the stack, consuming * the value. After the code executes, the stack contains an int hashcode. * * @param b {@link CodeBuilder} to which to add the code * @param valueType the type of the value * @param testForNull if true and the value is a reference and might be null * @param mixIn if true, stack has an existing hashcode followed by a value * @since 1.2.2 */ public static void addValueHashCodeCall(CodeBuilder b, TypeDesc valueType, boolean testForNull, boolean mixIn) { LocalVariable value = null; if (mixIn) { value = b.createLocalVariable(null, valueType); b.storeLocal(value); // Multiply current hashcode by 31 before adding more to it. b.loadConstant(31); b.math(Opcode.IMUL); b.loadLocal(value); } switch (valueType.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: if (testForNull) { if (value == null) { value = b.createLocalVariable(null, valueType); b.storeLocal(value); b.loadLocal(value); } } if (mixIn) { Label isNull = b.createLabel(); if (testForNull) { b.ifNullBranch(isNull, true); b.loadLocal(value); } addValueHashCodeCallTo(b, valueType); 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); } addValueHashCodeCallTo(b, valueType); if (testForNull) { cont.setLocation(); } } break; } } private static void addValueHashCodeCallTo(CodeBuilder b, TypeDesc valueType) { if (valueType.isArray()) { if (!valueType.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[] {valueType}); } } else { b.invokeVirtual(TypeDesc.OBJECT, "hashCode", TypeDesc.INT, null); } } /** * 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. If they are floating point values, NaN is considered equal to NaN, * which is inconsistent with the usual treatment for NaN. * *

    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) { if (valueType.getTypeCode() == TypeDesc.FLOAT_CODE) { // Special treatment to handle NaN. b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "compare", TypeDesc.INT, new TypeDesc[] {TypeDesc.FLOAT, TypeDesc.FLOAT}); b.ifZeroComparisonBranch(label, choice ? "==" : "!="); } else if (valueType.getTypeCode() == TypeDesc.DOUBLE_CODE) { // Special treatment to handle NaN. b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "compare", TypeDesc.INT, new TypeDesc[] {TypeDesc.DOUBLE, TypeDesc.DOUBLE}); b.ifZeroComparisonBranch(label, choice ? "==" : "!="); } else { b.ifComparisonBranch(label, choice ? "==" : "!=", valueType); } return; } if (!testForNull) { String op = addEqualsCallTo(b, valueType, choice); b.ifZeroComparisonBranch(label, op); 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. isNotNull.setLocation(); if (compareToType(valueType) == null) { // Call equals method, but swap values so that the second value is // an argument into the equals method. b.loadLocal(value); b.swap(); } else { // Need to test for second argument too, since compareTo method // cannot cope with null. LocalVariable value2 = b.createLocalVariable(null, valueType); b.storeLocal(value2); b.loadLocal(value2); b.ifNullBranch(label, !choice); // Load both values in preparation for calling compareTo method. b.loadLocal(value); b.loadLocal(value2); } String op = addEqualsCallTo(b, valueType, choice); b.ifZeroComparisonBranch(label, op); cont.setLocation(); } /** * @param fieldType must be an object type * @return null if compareTo should not be called */ private static TypeDesc compareToType(TypeDesc fieldType) { if (fieldType.toPrimitiveType() == TypeDesc.FLOAT) { // Special treatment to handle NaN. return TypeDesc.FLOAT.toObjectType(); } else if (fieldType.toPrimitiveType() == TypeDesc.DOUBLE) { // Special treatment to handle NaN. return TypeDesc.DOUBLE.toObjectType(); } else if (BigDecimal.class.isAssignableFrom(fieldType.toClass())) { // Call compareTo to disregard scale. return TypeDesc.forClass(BigDecimal.class); } else { return null; } } /** * @param fieldType must be an object type * @return zero comparison branch operator */ private static String addEqualsCallTo(CodeBuilder b, TypeDesc fieldType, boolean choice) { if (fieldType.isArray()) { // FIXME: Array comparisons don't handle desired comparison of NaN. 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}); } return choice ? "!=" : "=="; } TypeDesc compareToType = compareToType(fieldType); if (compareToType != null) { b.invokeVirtual(compareToType, "compareTo", TypeDesc.INT, new TypeDesc[] {compareToType}); return choice ? "==" : "!="; } 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); } return choice ? "!=" : "=="; } } /** * 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); } /** * Generates code to push a blank value to the stack. For objects, it is * null, and for primitive types it is zero or false. */ public static void blankValue(CodeBuilder b, TypeDesc type) { switch (type.getTypeCode()) { default: b.loadNull(); break; case TypeDesc.BYTE_CODE: case TypeDesc.CHAR_CODE: case TypeDesc.SHORT_CODE: case TypeDesc.INT_CODE: b.loadConstant(0); break; case TypeDesc.BOOLEAN_CODE: b.loadConstant(false); break; case TypeDesc.LONG_CODE: b.loadConstant(0L); break; case TypeDesc.FLOAT_CODE: b.loadConstant(0.0f); break; case TypeDesc.DOUBLE_CODE: b.loadConstant(0.0); break; } } /** * 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); } }