summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/spi/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/spi/CodeBuilderUtil.java
parentc7f8a6ef89b43a908e677df76f35cc425b9c7f91 (diff)
Move Storable code generation support to separate package.
Diffstat (limited to 'src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java')
-rw-r--r--src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java574
1 files changed, 0 insertions, 574 deletions
diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
deleted file mode 100644
index 0623f94..0000000
--- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.amazon.carbonado.spi;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.Map;
-import java.util.HashMap;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-import org.cojen.classfile.ClassFile;
-import org.cojen.classfile.Modifiers;
-import org.cojen.classfile.CodeBuilder;
-import org.cojen.classfile.MethodInfo;
-import org.cojen.classfile.Label;
-import org.cojen.classfile.TypeDesc;
-import org.cojen.classfile.LocalVariable;
-import org.cojen.classfile.MethodDesc;
-import org.cojen.classfile.Opcode;
-import org.cojen.util.ClassInjector;
-
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
-
-import static com.amazon.carbonado.spi.CommonMethodNames.*;
-
-/**
- * Collection of useful utilities for generating Carbonado code.
- *
- * @author Don Schneider
- * @author Brian S O'Neill
- */
-public class CodeBuilderUtil {
-
- /**
- * Generate code to throw an exception if a parameter is null
- * @param b CodeBuilder into which to append the code
- * @param paramIndex index of the parameter to check
- */
- public static void assertParameterNotNull(CodeBuilder b, int paramIndex) {
- b.loadLocal(b.getParameter(paramIndex));
- Label notNull = b.createLabel();
- b.ifNullBranch(notNull, false);
- throwException(b, IllegalArgumentException.class, null);
- notNull.setLocation();
- }
-
- /**
- * Generate code to create a local variable containing the specified parameter coerced
- * to the specified type. This is useful for re-interpreting erased generics into
- * the more specific genericized type.
- *
- * @param b CodeBuilder into which to append the code
- * @param paramType the more specific type which was erased during compilation
- * @param paramIndex index of the parameter to unerase
- * @return a local variable referencing the type-cast parameter
- */
- public static LocalVariable uneraseGenericParameter(
- CodeBuilder b, TypeDesc paramType, final int paramIndex)
- {
- b.loadLocal(b.getParameter(paramIndex));
- b.checkCast(paramType);
- LocalVariable result = b.createLocalVariable(null, paramType);
- b.storeLocal(result);
- return result;
- }
-
- /**
- * Generate code to throw an exception with an optional message.
- * @param b {@link CodeBuilder} to which to add code
- * @param type type of the object to throw
- * @param message optional message to provide to the constructor
- */
- public static void throwException(CodeBuilder b, Class type, String message) {
- TypeDesc desc = TypeDesc.forClass(type);
- b.newObject(desc);
- b.dup();
- if (message == null) {
- b.invokeConstructor(desc, null);
- } else {
- b.loadConstant(message);
- b.invokeConstructor(desc, new TypeDesc[] {TypeDesc.STRING});
- }
- b.throwObject();
- }
-
- /**
- * Collect a set of all the interfaces and recursively all superclasses for the leaf
- * (genericised class) and root (genericised base class). Eg, for Object<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 a new modifiable mapping of method signatures to methods.
- *
- * @return map of {@link #createSig signatures} to methods
- */
- public static Map<String, Method> gatherAllDeclaredMethods(Class clazz) {
- Map<String, Method> methods = new HashMap<String, Method>();
- gatherAllDeclaredMethods(methods, clazz);
- return methods;
- }
-
- private static void gatherAllDeclaredMethods(Map<String, Method> methods, Class clazz) {
- for (Method m : clazz.getDeclaredMethods()) {
- String desc = createSig(m);
- if (!methods.containsKey(desc)) {
- methods.put(desc, m);
- }
- }
-
- Class superclass = clazz.getSuperclass();
- if (superclass != null) {
- gatherAllDeclaredMethods(methods, superclass);
- }
- for (Class c : clazz.getInterfaces()) {
- gatherAllDeclaredMethods(methods, c);
- }
- }
-
- /**
- * Returns true if a public final method exists which matches the given
- * specification.
- */
- public static boolean isPublicMethodFinal(Class clazz, String name,
- TypeDesc retType, TypeDesc[] params)
- {
- if (!clazz.isInterface()) {
- Class[] paramClasses;
- if (params == null || params.length == 0) {
- paramClasses = null;
- } else {
- paramClasses = new Class[params.length];
- for (int i=0; i<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);
- }
- }
- }
-
- /**
- * Create a representation of the signature which includes the method name.
- * This uniquely identifies the method.
- *
- * @param m method to describe
- */
- public static String createSig(Method m) {
- return m.getName() + ':' + MethodDesc.forMethod(m).getDescriptor();
- }
-
- /**
- * Converts a value on the stack. If "to" type is a String, then conversion
- * may call the String.valueOf(from).
- */
- public static void convertValue(CodeBuilder b, Class from, Class to) {
- if (from == to) {
- return;
- }
-
- TypeDesc fromType = TypeDesc.forClass(from);
- TypeDesc toType = TypeDesc.forClass(to);
-
- // Let CodeBuilder have a crack at the conversion first.
- try {
- b.convert(fromType, toType);
- return;
- } catch (IllegalArgumentException e) {
- if (to != String.class && to != Object.class && to != CharSequence.class) {
- throw e;
- }
- }
-
- // Fallback case is to convert to a String.
-
- if (fromType.isPrimitive()) {
- b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING, new TypeDesc[]{fromType});
- } else {
- // If object on stack is null, then just leave it alone.
- b.dup();
- Label isNull = b.createLabel();
- b.ifNullBranch(isNull, true);
- b.invokeStatic(TypeDesc.STRING, "valueOf", TypeDesc.STRING,
- new TypeDesc[]{TypeDesc.OBJECT});
- isNull.setLocation();
- }
- }
-
- /**
- * Generates code to push an initial version property value on the stack.
- *
- * @throws SupportException if version type is not supported
- */
- public static void initialVersion(CodeBuilder b, TypeDesc type, int value)
- throws SupportException
- {
- adjustVersion(b, type, value, false);
- }
-
- /**
- * Generates code to increment a version property value, already on the stack.
- *
- * @throws SupportException if version type is not supported
- */
- public static void incrementVersion(CodeBuilder b, TypeDesc type)
- throws SupportException
- {
- adjustVersion(b, type, 0, true);
- }
-
- private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment)
- throws SupportException
- {
- TypeDesc primitiveType = type.toPrimitiveType();
- supportCheck: {
- if (primitiveType != null) {
- switch (primitiveType.getTypeCode()) {
- case TypeDesc.INT_CODE:
- case TypeDesc.LONG_CODE:
- break supportCheck;
- }
- }
- throw new SupportException("Unsupported version type: " + type.getFullName());
- }
-
- if (!increment) {
- if (primitiveType == TypeDesc.LONG) {
- b.loadConstant((long) value);
- } else {
- b.loadConstant(value);
- }
- } else {
- Label setVersion = b.createLabel();
- if (!type.isPrimitive()) {
- b.dup();
- Label versionNotNull = b.createLabel();
- b.ifNullBranch(versionNotNull, false);
- b.pop();
- if (primitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- } else {
- b.loadConstant(1);
- }
- b.branch(setVersion);
- versionNotNull.setLocation();
- b.convert(type, primitiveType);
- }
- if (primitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- b.math(Opcode.LADD);
- } else {
- b.loadConstant(1);
- b.math(Opcode.IADD);
- }
- setVersion.setLocation();
- }
-
- b.convert(primitiveType, type);
- }
-
- /**
- * Determines which overloaded "with" method on Query should be bound to.
- */
- public static TypeDesc bindQueryParam(Class clazz) {
- // This method is a bit vestigial. Once upon a time the Query class did
- // not support all primitive types.
- if (clazz.isPrimitive()) {
- TypeDesc type = TypeDesc.forClass(clazz);
- switch (type.getTypeCode()) {
- case TypeDesc.INT_CODE:
- case TypeDesc.LONG_CODE:
- case TypeDesc.FLOAT_CODE:
- case TypeDesc.DOUBLE_CODE:
- case TypeDesc.BOOLEAN_CODE:
- case TypeDesc.CHAR_CODE:
- case TypeDesc.BYTE_CODE:
- case TypeDesc.SHORT_CODE:
- return type;
- }
- }
- return TypeDesc.OBJECT;
- }
-
- /**
- * Appends a String to a StringBuilder. A StringBuilder and String must be
- * on the stack, and a StringBuilder is left on the stack after the call.
- */
- public static void callStringBuilderAppendString(CodeBuilder b) {
- // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
- // cannot use reflection to get method signature.
- TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
- b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING});
- }
-
- /**
- * Appends a char to a StringBuilder. A StringBuilder and char must be on
- * the stack, and a StringBuilder is left on the stack after the call.
- */
- public static void callStringBuilderAppendChar(CodeBuilder b) {
- // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
- // cannot use reflection to get method signature.
- TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
- b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR});
- }
-
- /**
- * Calls length on a StringBuilder on the stack, leaving an int on the stack.
- */
- public static void callStringBuilderLength(CodeBuilder b) {
- // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
- // cannot use reflection to get method signature.
- TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
- b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null);
- }
-
- /**
- * Calls setLength on a StringBuilder. A StringBuilder and int must be on
- * the stack, and both are consumed after the call.
- */
- public static void callStringBuilderSetLength(CodeBuilder b) {
- // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
- // cannot use reflection to get method signature.
- TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
- b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT});
- }
-
- /**
- * Calls toString on a StringBuilder. A StringBuilder must be on the stack,
- * and a String is left on the stack after the call.
- */
- public static void callStringBuilderToString(CodeBuilder b) {
- // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
- // cannot use reflection to get method signature.
- TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
- b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null);
- }
-}