/* * Copyright (c) 2009-2010 Clark & Parsia, LLC. <http://www.clarkparsia.com> * * 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.clarkparsia.empire.codegen; import com.google.common.collect.Sets; import com.google.common.base.Predicate; import javassist.ClassPool; import javassist.CtClass; import javassist.CtNewConstructor; import javassist.CtField; import javassist.CtNewMethod; import javassist.NotFoundException; import javassist.Modifier; import javassist.CannotCompileException; import javassist.CtMethod; import javassist.CtPrimitiveType; import javassist.LoaderClassPath; import javassist.bytecode.ConstPool; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.SignatureAttribute; import javassist.bytecode.annotation.Annotation; import java.util.Map; import java.util.HashMap; import java.util.Arrays; import java.util.Collection; import java.lang.reflect.Method; import java.lang.reflect.Type; import org.openrdf.model.Graph; import com.clarkparsia.empire.EmpireGenerated; import com.clarkparsia.empire.SupportsRdfId; import com.clarkparsia.empire.EmpireOptions; import com.clarkparsia.empire.util.BeanReflectUtil; import com.complexible.common.collect.Iterables2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl; /** * <p>Generate implementations of interfaces at runtime via bytecode manipulation.</p> * * @author Michael Grove * @since 0.5.1 * @version 0.7.3 */ public final class InstanceGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(BeanGenerator.class); private static final Collection<Method> processedMethods = Sets.newHashSet(); /** * No instances */ private InstanceGenerator() { } /** * <p>Given a bean-style interface, generate an instance of the interface by implementing getters and setters for each * property. It will also add implementations to support the {@link SupportsRdfId} interface and generate simple, * default equals, toString and hashCode methods.</p> * * <p>If there are other non-bean style (getter and/or setter's for properties) methods on the interface, this will * likely fail to generate the instance.</p> * @param theInterface the interface to build an instance of * @param <T> the type of the interface * @return New dynamically generated bytecode of a class that implements the given interface. * @throws Exception if there is an error while generating the bytecode of the new class. */ public synchronized static <T> Class<T> generateInstanceClass(Class<T> theInterface) throws Exception { processedMethods.clear(); // TODO: can we use some sort of template language for this? ClassPool aPool = ClassPool.getDefault(); aPool.appendClassPath(new LoaderClassPath(theInterface.getClassLoader())); CtClass aInterface = aPool.get(theInterface.getName()); CtClass aSupportsRdfIdInterface = aPool.get(SupportsRdfId.class.getName()); CtClass aEmpireGeneratedInterface = aPool.get(EmpireGenerated.class.getName()); if (!Arrays.asList(aInterface.getInterfaces()).contains(aSupportsRdfIdInterface) && !SupportsRdfId.class.isAssignableFrom(theInterface)) { throw new IllegalArgumentException("Class '" + theInterface.getName() + "' does not implement SupportsRdfId, cannot generate Empire suitable implementation."); } String aName = aInterface.getPackageName()+ ".impl." + aInterface.getSimpleName() + "Impl"; CtClass aClass = null; try { // i had a good reason for doing this, but i dont remember what it is. when i do, i'll explain it here =) aClass = aPool.get(aName); return (Class<T>) BeanReflectUtil.loadClass(aName); } catch (NotFoundException e) { aClass = aPool.makeClass(aInterface.getPackageName()+ ".impl." + aInterface.getSimpleName() + "Impl"); } catch (ClassNotFoundException e) { throw new Exception("Previously created class cannot be loaded.", e); } if (aClass.isFrozen()) { aClass.defrost(); } if (aInterface.isInterface()) { aClass.addInterface(aInterface); } else { aClass.setSuperclass(aInterface); } aClass.addInterface(aSupportsRdfIdInterface); aClass.addInterface(aEmpireGeneratedInterface); CtField aInterfaceField = new CtField(aPool.get(Class.class.getName()), "mInterfaceClass", aClass); aClass.addField(aInterfaceField, CtField.Initializer.byExpr(theInterface.getName() + ".class;")); CtField aAllTriplesField = new CtField(aPool.get(Graph.class.getName()), "mAllTriples", aClass); aClass.addField(aAllTriplesField, CtField.Initializer.byExpr("new com.complexible.common.openrdf.model.SetGraph();")); CtField aInstanceTriplesField = new CtField(aPool.get(Graph.class.getName()), "mInstanceTriples", aClass); aClass.addField(aInstanceTriplesField, CtField.Initializer.byExpr("new com.complexible.common.openrdf.model.SetGraph();")); aClass.addConstructor(CtNewConstructor.defaultConstructor(aClass)); generateMethods(theInterface, aPool, aClass); generateMethodsForSuperInterfaces(theInterface, aPool, aClass); CtField aIdField = new CtField(aPool.get(SupportsRdfId.class.getName()), "supportsId", aClass); aClass.addField(aIdField, CtField.Initializer.byExpr("new com.clarkparsia.empire.annotation.SupportsRdfIdImpl();")); if (!hasMethod(aClass, "getRdfId")) { aClass.addMethod(CtNewMethod.make("public com.clarkparsia.empire.SupportsRdfId.RdfKey getRdfId() { return supportsId.getRdfId(); } ", aClass)); } if (!hasMethod(aClass, "setRdfId")) { aClass.addMethod(CtNewMethod.make("public void setRdfId(com.clarkparsia.empire.SupportsRdfId.RdfKey theURI) { supportsId.setRdfId(theURI); } ", aClass)); } if (!hasMethod(aClass, "getAllTriples")) { aClass.addMethod(CtNewMethod.make("public org.openrdf.model.Graph getAllTriples() { return mAllTriples; } ", aClass)); } if (!hasMethod(aClass, "setAllTriples")) { aClass.addMethod(CtNewMethod.make("public void setAllTriples(org.openrdf.model.Graph theGraph) { mAllTriples = theGraph; } ", aClass)); } if (!hasMethod(aClass, "getInstanceTriples")) { aClass.addMethod(CtNewMethod.make("public org.openrdf.model.Graph getInstanceTriples() { return mInstanceTriples; } ", aClass)); } if (!hasMethod(aClass, "setInstanceTriples")) { aClass.addMethod(CtNewMethod.make("public void setInstanceTriples(org.openrdf.model.Graph theGraph) { mInstanceTriples = theGraph; } ", aClass)); } if (!hasMethod(aClass, "getInterfaceClass")) { aClass.addMethod(CtNewMethod.make("public java.lang.Class getInterfaceClass() { return mInterfaceClass; }" , aClass )); } String equalsMethodBody = "public boolean equals(Object theObj) {\n" + " if (theObj == this) return true;\n" + " if (!(theObj instanceof com.clarkparsia.empire.SupportsRdfId)) return false;\n" + " if (!(mInterfaceClass.isAssignableFrom(theObj.getClass()))) return false;\n" + " return getRdfId().equals( ((com.clarkparsia.empire.SupportsRdfId) theObj).getRdfId()) && super.equals(theObj);\n" + "}\n"; aClass.addMethod(CtNewMethod.make(equalsMethodBody, aClass)); if (theInterface.isInterface()) { aClass.addMethod(CtNewMethod.make("public String toString() { return getRdfId() != null ? getRdfId().toString() : super.toString(); } ", aClass)); aClass.addMethod(CtNewMethod.make("public int hashCode() { return getRdfId() != null ? getRdfId().hashCode() : 0; } ", aClass)); } aClass.freeze(); Class<T> aResult = null; try { aResult = (Class<T>) aClass.toClass(); } catch (CannotCompileException e) { throw e; } try { // make sure this is a valid class, that is, we can create instances of it! aResult.newInstance(); } catch (Exception ex) { // TODO: log this? throw ex; } return aResult; } /** * For all the parent interfaces of a class, generate implementations of all their methods. And for their parents, do the same, and the same for their parents, and so on... * @param theInterface the interface * @param thePool the class pool to use * @param theCtClass the concrete implementation of the interface(s) * @param <T> the type of the interface * @throws NotFoundException thrown if there is an error generating the methods * @throws CannotCompileException thrown if there is an error generating the methods */ private static <T> void generateMethodsForSuperInterfaces(final Class<T> theInterface, ClassPool thePool, CtClass theCtClass) throws NotFoundException, CannotCompileException { if (theInterface.getSuperclass() != null) { generateMethods(theInterface.getSuperclass(), thePool, theCtClass); generateMethodsForSuperInterfaces(theInterface.getSuperclass(), thePool, theCtClass); } for (Class<?> aSuperInterface : theInterface.getInterfaces()) { generateMethods(aSuperInterface, thePool, theCtClass); generateMethodsForSuperInterfaces(aSuperInterface, thePool, theCtClass); } } /** * For a given interface, generate basic getter and setter methods for all the properties on the interface. * @param theInterface the interface * @param thePool the class pool * @param theClass the concrete implementation of the interface * @param <T> the type of the interface * @throws CannotCompileException thrown if there is an error generating the methods * @throws NotFoundException thrown if there is an error generating the methods */ private static <T> void generateMethods(final Class<T> theInterface, final ClassPool thePool, final CtClass theClass) throws CannotCompileException, NotFoundException { Map<String, CtField> aProps = properties(thePool, theClass, theInterface); for (String aProp : aProps.keySet()) { //CtField aNewField = new CtField(thePool.get(aProps.get(aProp).getName()), aProp, theClass); // CtField aNewField = field(theInterface, thePool, theClass, aProp); CtField aNewField = aProps.get(aProp); if (!hasField(theClass, aNewField.getName())) { theClass.addField(aNewField); } if (!hasMethod(theClass, getterName(aProp)) && !hasMethod(theClass, booleanGetterName(aProp))) { CtMethod aMethod = CtNewMethod.getter(getterName(aProp), aNewField); if (aNewField.getType() == CtPrimitiveType.booleanType && !hasMethod(theClass, aMethod)) { aMethod = CtNewMethod.getter(booleanGetterName(aProp), aNewField); } inheritAnnotations(theClass, aMethod); SignatureAttribute attr = (SignatureAttribute) aNewField.getFieldInfo().getAttribute(SignatureAttribute.tag); if (attr != null) { aMethod.getMethodInfo().addAttribute(new SignatureAttribute(aMethod.getMethodInfo().getConstPool(), "()"+attr.getSignature())); } theClass.addMethod(aMethod); } if (!hasMethod(theClass, setterName(aProp))) { CtMethod aMethod = CtNewMethod.setter(setterName(aProp), aNewField); inheritAnnotations(theClass, aMethod); SignatureAttribute attr = (SignatureAttribute) aNewField.getFieldInfo().getAttribute(SignatureAttribute.tag); if (attr != null) { aMethod.getMethodInfo().addAttribute(new SignatureAttribute(aMethod.getMethodInfo().getConstPool(), "("+attr.getSignature()+")V")); } theClass.addMethod(aMethod); } } } private static boolean hasMethod(final CtClass theClass, final CtMethod theMethod) { try { return theClass.getMethod(theMethod.getName(), theMethod.getSignature()) != null; } catch (NotFoundException e) { return false; } } private static void inheritAnnotations(final CtClass theClass, final CtMethod theMethod) throws NotFoundException { if (hasMethod(theClass, theMethod)) { CtMethod aOtherMethod = theClass.getMethod(theMethod.getName(), theMethod.getSignature()); // method we're probably overriding or implementing in the case of an abstract method. AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) aOtherMethod.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag); if (annotationsAttribute != null) { ConstPool cp = theClass.getClassFile().getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); for (Object obj : annotationsAttribute.getAnnotations()) { Annotation a = (Annotation) obj; Annotation theAnnotation = new Annotation(a.getTypeName(), cp); if (a.getMemberNames() != null) { for (Object aName : a.getMemberNames()) { theAnnotation.addMemberValue(aName.toString(), a.getMemberValue(aName.toString())); } } attr.addAnnotation(theAnnotation); } theMethod.getMethodInfo().addAttribute(attr); } } } private static String classToStringRepresentation(Class c) { if (byte.class.equals(c)) { return "B"; } else if (char.class.equals(c)) { return "C"; } else if (double.class.equals(c)) { return "D"; } else if (float.class.equals(c)) { return "F"; } else if (int.class.equals(c)) { return "I"; } else if (long.class.equals(c)) { return "J"; } else if (short.class.equals(c)) { return "S"; } else if (boolean.class.equals(c)) { return "Z"; } else if (c.isArray()) { return c.getName().replace(".", "/"); } else if (c.equals(Void.class)) { return "V"; } else { return "L" + c.getName().replace('.', '/') + ";"; } } // private static <T> CtField field(Class<T> theInterface, ClassPool thePool, CtClass theClass, String theProp) throws NotFoundException, CannotCompileException { // for (Method aMethod : theInterface.getDeclaredMethods()) { // if (!aMethod.getName().startsWith("get") // && !aMethod.getName().startsWith("is") // && !aMethod.getName().startsWith("has") // && !aMethod.getName().startsWith("set")) { // // if (EmpireOptions.STRICT_MODE) { // throw new IllegalArgumentException("Non-bean style methods found, implementations for them cannot not be generated"); // } // else { // LOGGER.warn("Non-bean style methods found, implementations for them cannot not be generated : " + aMethod.getName() ); // } // } // // String aProp = aMethod.getName().substring(aMethod.getName().startsWith("is") ? 2 : 3); // // aProp = String.valueOf(aProp.charAt(0)).toLowerCase() + aProp.substring(1); // // if (aProp.equals(theProp)) { // Class aType = null; // Type generics = null; // // if (aMethod.getName().startsWith("get") || aMethod.getName().startsWith("is") || aMethod.getName().startsWith("has")) { // aType = aMethod.getReturnType(); // generics = aMethod.getGenericReturnType(); // } // else if (aMethod.getName().startsWith("set") && aMethod.getParameterTypes().length > 0) { // aType = aMethod.getParameterTypes()[0]; // // if (aMethod.getGenericParameterTypes() != null && aMethod.getGenericParameterTypes().length > 0) // generics = aMethod.getGenericParameterTypes()[0]; // } // // CtField aNewField = new CtField(thePool.get(aType.getName()), aProp, theClass); // // if (generics != null && generics instanceof ParameterizedTypeImpl) { // for (Type t : ((ParameterizedTypeImpl)generics).getActualTypeArguments()) { // // aNewField.getFieldInfo().addAttribute(new SignatureAttribute(aNewField.getFieldInfo().getConstPool(), "L"+aType.getName().replace('.','/')+ "<L" + ((Class)t).getName().replace('.','/') + ";>;")) ; // } // } // // return aNewField; // } // } // // return null; // } /** * Return whether or not the class has a field with the given name * @param theClass the class to inspect * @param theField the name of the field to look for * @return true if the class contains the field, false otherwise */ private static boolean hasField(CtClass theClass, String theField) { try { return theClass.getDeclaredField(theField) != null; } catch (NotFoundException e) { return false; } } /** * Return whether or not the class has a method with the given name * @param theClass the class to inspect * @param theName the name of the method to look for * @return true if the class contains the method, false otherwise */ private static boolean hasMethod(CtClass theClass, String theName) { try { return theClass.getDeclaredMethod(theName) != null && !Modifier.isAbstract(theClass.getDeclaredMethod(theName).getModifiers()); } catch (NotFoundException e) { try { if (theClass.getSuperclass() != null) { return hasMethod(theClass.getSuperclass(), theName); } else { return false; } } catch (NotFoundException e1) { return false; } } } /** * Reurn the name of the getter method given the bean property name. For example, if there is a property "name" * this will return "getName" * @param theProperyName the bean property name * @return the name of the getter for the property */ private static String getterName(String theProperyName) { return "get" + String.valueOf(theProperyName.charAt(0)).toUpperCase() + theProperyName.substring(1); } /** * Reurn the name of the getter method given the bean property name. For example, if there is a property "connected" * this will return "isConnected" * @param theProperyName the bean property name * @return the name of the getter for the property */ private static String booleanGetterName(String theProperyName) { return "is" + String.valueOf(theProperyName.charAt(0)).toUpperCase() + theProperyName.substring(1); } /** * Return the name of the setter method given the bean property name. For example, if there is a property "name" * this will return "setName" * @param theProperyName the bean property name * @return the setter name for the bean property */ private static String setterName(String theProperyName) { return "set" + String.valueOf(theProperyName.charAt(0)).toUpperCase() + theProperyName.substring(1); } /** * Get the bean properties from the given class * @param thePool the class pool to use when creating new fields * @param theClass the class the new fields will belong to * @param theInterface the original bean class * @return a Map of the bean property names with the new field for the property as the value * @throws javassist.CannotCompileException thrown if there is an error generating the methods * @throws javassist.NotFoundException thrown if there is an error generating the methods */ private static <T> Map<String, CtField> properties(final ClassPool thePool, final CtClass theClass, final Class<T> theInterface) throws NotFoundException, CannotCompileException { Map<String, CtField> aMap = new HashMap<String, CtField>(); for (Method aMethod : theInterface.getDeclaredMethods()) { // see if we've already processed this method. Normal .equals for a method will not work because the classes have to be the same, // what we want is the semantics of isAssignableFrom, not .equals between the classes declaring the methods. Thus, the FINDER // predicate implementation does exactly that. It's a copy of the Method.equals function, but with the .equals for the declaring // class changed to isAssignableFrom so we get the expected behavior. FINDER.method = aMethod; if (Iterables2.find(processedMethods, FINDER)) { continue; } // we want to ignore methods with implementations, we should not override them. if (!Modifier.isAbstract(aMethod.getModifiers())) { // mark the method as one we've already handled in case we get this method again on a superclass/interface processedMethods.add(aMethod); continue; } if (!aMethod.getName().startsWith("get") && !aMethod.getName().startsWith("is") && !aMethod.getName().startsWith("has") && !aMethod.getName().startsWith("set")) { if (EmpireOptions.STRICT_MODE) { throw new IllegalArgumentException("Non-bean style methods found '" + aMethod + "', implementations for them cannot not be generated"); } else { LOGGER.warn("Non-bean style methods found, implementations for them cannot not be generated : {}", aMethod.getName()); } } Class aType = null; Type generics = null; if (aMethod.getName().startsWith("get") || aMethod.getName().startsWith("is") || aMethod.getName().startsWith("has")) { aType = aMethod.getReturnType(); generics = aMethod.getGenericReturnType(); } else if (aMethod.getName().startsWith("set") && aMethod.getParameterTypes().length > 0) { aType = aMethod.getParameterTypes()[0]; if (aMethod.getGenericParameterTypes() != null && aMethod.getGenericParameterTypes().length > 0) { generics = aMethod.getGenericParameterTypes()[0]; } } if (aType != null) { String aProp = aMethod.getName().substring(aMethod.getName().startsWith("is") ? 2 : 3); aProp = String.valueOf(aProp.charAt(0)).toLowerCase() + aProp.substring(1); CtField aNewField = new CtField(thePool.get(aType.getName()), aProp, theClass); if (generics != null && generics instanceof ParameterizedTypeImpl) { for (Type t : ((ParameterizedTypeImpl)generics).getActualTypeArguments()) { String aFlag = ""; String aName; if (t instanceof WildcardTypeImpl) { WildcardTypeImpl aWildcard = (WildcardTypeImpl) t; // trying to suss out super v extends w/o resorting to string munging. if (aWildcard.getLowerBounds().length == 0 && aWildcard.getUpperBounds().length > 0) { // no lower bounds afaik indicates ? extends Foo aFlag = "+"; aName = ((Class)aWildcard.getUpperBounds()[0]).getName(); } else if (aWildcard.getLowerBounds().length > 0) { // lower & upper bounds I believe indicates something of the form Foo super Bar aFlag = "-"; aName = ((Class)aWildcard.getLowerBounds()[0]).getName(); } else { throw new CannotCompileException("Unknown or unsupported type signature found: " + t); } } else { aName = ((Class)t).getName(); } aNewField.getFieldInfo().addAttribute(new SignatureAttribute(aNewField.getFieldInfo().getConstPool(), "L"+aType.getName().replace('.','/')+ "<"+aFlag+"L" + aName.replace('.','/') + ";>;")) ; } } aMap.put(aProp, aNewField); } // mark the method as one we've already handled in case we get this method again on a superclass/interface processedMethods.add(aMethod); } return aMap; } private static final FinderPredicate FINDER = new FinderPredicate(); private static class FinderPredicate implements Predicate<Method> { Method method; public boolean apply(final Method theValue) { return overrideEquals(theValue, method); } } /** * Basically a copy of Method.equals, but rather than enforcing a strict equals, it tests for "overridable" equals. So this will * return true if the methods are .equals or if the second method can override the first. * @param obj the first object * @param other the other object * @return true if the method is equal to or overrides the other, false otherwise */ private static boolean overrideEquals(Method obj, Method other) { if ((other.getDeclaringClass().isAssignableFrom(obj.getDeclaringClass())) && (obj.getName().equals(other.getName()))) { if (!obj.getReturnType().equals(other.getReturnType())) { return false; } Class[] params1 = obj.getParameterTypes(); Class[] params2 = other.getParameterTypes(); if (params1.length == params2.length) { for (int i = 0; i < params1.length; i++) { if (params1[i] != params2[i]) { return false; } } } return true; } else { return false; } } }