/*
* 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;
}
}
}