/* * Copyright (c) 2009, 2010, James Leigh All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package net.enilink.composition.helpers; import static java.lang.reflect.Modifier.isAbstract; import static java.lang.reflect.Modifier.isTransient; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.aopalliance.intercept.MethodInvocation; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import net.enilink.composition.ClassDefiner; import net.enilink.composition.annotations.ParameterTypes; import net.enilink.composition.annotations.Precedes; import net.enilink.composition.annotations.Iri; import net.enilink.composition.asm.AsmUtils; import net.enilink.composition.asm.CompositeClassNode; import net.enilink.composition.asm.ExtendedMethod; import net.enilink.composition.asm.Types; import net.enilink.composition.asm.processors.CompositeConstructorGenerator; import net.enilink.composition.asm.util.ExtendedMethodGenerator; import net.enilink.composition.asm.util.MethodNodeGenerator; import net.enilink.composition.exceptions.CompositionException; import net.enilink.composition.traits.Behaviour; import com.google.inject.Inject; /** * This class takes a collection of roles (interfaces or classes) and uses * composition to combine this into a single class. * */ public class ClassComposer<T> implements Types, Opcodes { private String RDFS_SUBCLASSOF = "http://www.w3.org/2000/01/rdf-schema#subClassOf"; private ClassDefiner definer; private String className; private Class<?> baseClass = Object.class; private Set<Class<?>> interfaces; private Set<Class<?>> javaClasses; private Collection<Method> methods; private Map<String, Method> namedMethods; private List<Class<?>> behaviours; private Map<Method, String> superMethods = new HashMap<Method, String>(); private CompositeClassNode compositeClass; public ClassComposer(String className, int size) { this.className = className; interfaces = new LinkedHashSet<Class<?>>(size); javaClasses = new LinkedHashSet<Class<?>>(size); } @Inject public void setClassDefiner(ClassDefiner definer) { this.definer = definer; } public void setBaseClass(Class<?> baseClass) { this.baseClass = baseClass; } public Set<Class<?>> getInterfaces() { return interfaces; } public void addInterface(Class<?> iface) { this.interfaces.add(iface); } public void addAllBehaviours(Collection<Class<?>> javaClasses) { this.javaClasses.addAll(javaClasses); } public Class<?> compose() throws Exception { compositeClass = new CompositeClassNode(Type.getObjectType(className .replace('.', '/')), baseClass); for (Class<?> clazz : javaClasses) { addInterfaces(clazz); } for (Class<?> face : interfaces) { compositeClass.addInterfaceClass(face); } new CompositeConstructorGenerator().process(compositeClass); compositeClass.addInjectorField(); behaviours = new ArrayList<Class<?>>(); for (Class<?> clazz : javaClasses) { if (addBehaviour(clazz)) { behaviours.add(clazz); } } if (baseClass != null && !Object.class.equals(baseClass)) { javaClasses.add(baseClass); } methods = getMethods(); namedMethods = new HashMap<String, Method>(methods.size()); for (Method method : methods) { if (method.isAnnotationPresent(Iri.class)) { String uri = method.getAnnotation(Iri.class).value(); if (!namedMethods.containsKey(uri) || !isBridge(method, methods)) { namedMethods.put(uri, method); } } } for (Method method : methods) { if (!method.getName().startsWith("_$")) { boolean bridge = isBridge(method, methods); implementMethod(method, method.getName(), bridge); } } return AsmUtils.defineExtendedClass(definer, compositeClass); } private void addInterfaces(Class<?> clazz) { if (interfaces.contains(clazz)) { return; } if (clazz.isInterface() && !isSpecial(clazz)) { interfaces.add(clazz); } Class<?> superclass = clazz.getSuperclass(); if (superclass != null) { addInterfaces(superclass); } for (Class<?> face : clazz.getInterfaces()) { addInterfaces(face); } } private Collection<Method> getMethods() { Map<List<?>, Method> map = new HashMap<List<?>, Method>(); for (Class<?> face : interfaces) { for (Method m : face.getMethods()) { if (isSpecial(m)) { continue; } Class<?>[] ptypes = getParameterTypes(m); List<Object> list = new ArrayList<Object>(ptypes.length + 2); list.add(m.getName()); list.add(m.getReturnType()); list.addAll(Arrays.asList(ptypes)); if (map.containsKey(list)) { if (getRank(m) > getRank(map.get(list))) { map.put(list, m); } } else { map.put(list, m); } } } for (Class<?> face : javaClasses) { for (Method m : face.getMethods()) { if (isSpecial(m)) { continue; } Class<?>[] ptypes = getParameterTypes(m); List<Object> list = new ArrayList<Object>(ptypes.length + 2); list.add(m.getName()); list.add(m.getReturnType()); list.addAll(Arrays.asList(ptypes)); if (map.containsKey(list)) { if (getRank(m) > getRank(map.get(list))) { map.put(list, m); } } else { map.put(list, m); } } } return map.values(); } private int getRank(Method m) { int rank = m.getAnnotations().length; if (m.isAnnotationPresent(ParameterTypes.class)) return rank - 1; return rank; } private boolean isSpecial(Class<?> iface) { return Behaviour.class.isAssignableFrom(iface); } private boolean isSpecial(Method m) { if (Modifier.isStatic(m.getModifiers()) || Modifier.isTransient(m.getModifiers())) { return true; } return Object.class.equals(m.getDeclaringClass()); } private Class<?>[] getParameterTypes(Method m) { if (m.isAnnotationPresent(ParameterTypes.class)) return m.getAnnotation(ParameterTypes.class).value(); return m.getParameterTypes(); } private boolean isBridge(Method method, Collection<Method> methods) { for (Method m : methods) { if (!m.getName().equals(method.getName())) continue; if (!Arrays.equals(getParameterTypes(m), getParameterTypes(method))) continue; if (m.getReturnType().equals(method.getReturnType())) continue; if (m.getReturnType().isAssignableFrom(method.getReturnType())) return true; } return false; } private Type[] toTypes(Class<?>[] classes) { Type[] types = new Type[classes.length]; for (int i = 0; i < classes.length; i++) { types[i] = Type.getType(classes[i]); } return types; } private boolean implementMethod(Method method, String name, boolean bridge) throws Exception { List<Class<?>> chain = chain(method); List<Object[]> implementations = getImplementations(chain, method); if (implementations.isEmpty()) { return false; } Class<?> returnType = method.getReturnType(); boolean returnsVoid = returnType.equals(Void.TYPE); boolean dynamicChained = false; for (Object[] ar : implementations) { Method m = (Method) ar[1]; Class<?>[] parameterTypes = m.getParameterTypes(); if (parameterTypes.length == 1 && MethodInvocation.class.equals(parameterTypes[0])) { dynamicChained = true; break; } } Method face = AsmUtils.findInterfaceOrSuperMethod(method, method.getDeclaringClass(), compositeClass.getInterfacesClasses()); ExtendedMethod newMethod = compositeClass.addExtendedMethod(face, definer); // clear instruction in the case when faceNode is not an abstract method newMethod.instructions.clear(); if (bridge) { newMethod.access |= ACC_BRIDGE; } MethodNodeGenerator gen = new MethodNodeGenerator(newMethod); Label endLabel = gen.newLabel(); boolean chainStarted = false; for (Iterator<Object[]> it = implementations.iterator(); it.hasNext();) { Object[] ar = it.next(); Object target = ar[0]; Method m = (Method) ar[1]; if (dynamicChained) { if (!chainStarted) { chainStarted = true; gen.newInstance(METHODINVOCATIONCHAIN_TYPE); gen.dup(); gen.loadThis(); // push outer method loadMethodObject(Type.getType(method.getDeclaringClass()), method.getName(), Type.getType(method.getReturnType()), toTypes(method.getParameterTypes()), gen); gen.loadArgArray(); gen.invokeConstructor( METHODINVOCATIONCHAIN_TYPE, new org.objectweb.asm.commons.Method("<init>", Type.VOID_TYPE, new Type[] { OBJECT_TYPE, Type.getType(Method.class), Type.getType(Object[].class) })); } if ("super".equals(target)) { String dname = createSuperCall(m); appendInvocation("this", compositeClass.getType(), dname, Type.getType(m.getReturnType()), toTypes(m.getParameterTypes()), gen); } else { appendInvocation(target, Type.getType(m.getDeclaringClass()), m.getName(), Type.getType(m.getReturnType()), toTypes(m.getParameterTypes()), gen); } } else { // call behaviour method without reflection callMethod(target, m, gen); if (!m.getReturnType().equals(Void.TYPE)) { if (returnsVoid) { // remove the behaviour method's return value from stack gen.pop(); } else { // test if the behaviour method's return value is nil gen.box(Type.getType(m.getReturnType())); gen.dup(); gen.push(Type.getType(m.getReturnType())); gen.invoke(Methods.METHODINVOCATIONCHAIN_ISNIL); Label isNilLabel = gen.newLabel(); gen.ifZCmp(IFNE, isNilLabel); // convert value to correct type if it is not nil gen.push(Type.getType(m.getReturnType())); gen.push(Type.getType(returnType)); gen.invoke(Methods.METHODINVOCATIONCHAIN_CAST); gen.unbox(Type.getType(returnType)); gen.goTo(endLabel); gen.mark(isNilLabel); gen.pop(); } } } } if (!(dynamicChained || returnsVoid)) { // create a return type specific nil value if not chained by // reflection gen.push(Type.getType(returnType)); gen.invoke(Methods.METHODINVOCATIONCHAIN_NIL); gen.unbox(Type.getType(returnType)); } if (chainStarted) { gen.invokeVirtual(METHODINVOCATIONCHAIN_TYPE, org.objectweb.asm.commons.Method .getMethod("Object proceed()")); if (returnsVoid) { gen.pop(); } else { gen.unbox(Type.getType(returnType)); } } gen.mark(endLabel); gen.returnValue(); gen.endMethod(); return true; } @SuppressWarnings("unchecked") private String createSuperCall(Method m) { if (superMethods.containsKey(m)) { return superMethods.get(m); } String name = "_$super" + superMethods.size() + "_" + m.getName(); MethodNode mn = new MethodNode(ACC_PRIVATE, name, Type.getMethodDescriptor(m), null, null); compositeClass.methods.add(mn); MethodNodeGenerator gen = new MethodNodeGenerator(mn); // call super method gen.loadThis(); gen.loadArgs(); gen.invokeSpecial(compositeClass.getParentType(), org.objectweb.asm.commons.Method.getMethod(m)); gen.returnValue(); gen.endMethod(); superMethods.put(m, name); return name; } private List<Class<?>> chain(Method method) throws Exception { if (behaviours == null) { return null; } int size = behaviours.size(); List<Class<?>> all = new ArrayList<Class<?>>(size); for (Class<?> behaviour : behaviours) { if (isMethodPresent(behaviour, method)) { all.add(behaviour); } } Iterator<Class<?>> iter; List<Class<?>> rest = new ArrayList<Class<?>>(all.size()); // sort plain methods before @precedes methods iter = all.iterator(); while (iter.hasNext()) { Class<?> behaviour = iter.next(); if (!isOverridesPresent(behaviour)) { rest.add(behaviour); iter.remove(); } } rest.addAll(all); all = rest; rest = new ArrayList<Class<?>>(all.size()); // sort intercepting methods before plain methods iter = all.iterator(); while (iter.hasNext()) { Class<?> behaviour = iter.next(); if (getMethod(behaviour, method).isAnnotationPresent( ParameterTypes.class)) { rest.add(behaviour); iter.remove(); } } rest.addAll(all); // sort by @precedes annotations List<Class<?>> list = new ArrayList<Class<?>>(rest.size()); while (!rest.isEmpty()) { int before = rest.size(); iter = rest.iterator(); loop: while (iter.hasNext()) { Class<?> b1 = iter.next(); for (Class<?> b2 : rest) { if (b2 != b1 && overrides(b2, b1)) { continue loop; } } list.add(b1); iter.remove(); } if (before <= rest.size()) throw new CompositionException("Invalid method chain: " + rest.toString()); } return list; } /** * @return list of <String, Method> */ private List<Object[]> getImplementations(List<Class<?>> behaviours, Method method) throws Exception { List<Object[]> list = new ArrayList<Object[]>(); Class<?> type = method.getReturnType(); Class<?> superclass = compositeClass.getParentClass(); Class<?>[] types = getParameterTypes(method); if (behaviours != null) { for (Class<?> behaviour : behaviours) { list.add(new Object[] { behaviour, getMethod(behaviour, method) }); } } if (!superclass.equals(Object.class)) { try { Method m = superclass.getMethod(method.getName(), types); Class<?> returnType = m.getReturnType(); if (!isAbstract(m.getModifiers()) && returnType.equals(type)) { list.add(new Object[] { "super", m }); } } catch (NoSuchMethodException e) { // no super method } } for (Method m : getSuperMethods(method)) { if (m.equals(method)) { continue; } list.addAll(getImplementations(chain(m), m)); } return list; } private List<Method> getSuperMethods(Method method) { List<Method> list = new ArrayList<Method>(); for (String uri : getAnnotationValueByIri(method, RDFS_SUBCLASSOF)) { Method m = namedMethods.get(uri); if (m != null && !isSpecial(m)) { list.add(m); } } return list; } private String[] getAnnotationValueByIri(Method method, String annotationID) { for (Annotation ann : method.getAnnotations()) { for (Method am : ann.annotationType().getDeclaredMethods()) { if (am.getParameterTypes().length > 0) continue; Iri Iri = am.getAnnotation(Iri.class); if (Iri != null && annotationID.equals(Iri.value())) { Object value = invoke(am, ann); if (value instanceof String[]) { return (String[]) value; } } } } return new String[0]; } private Object invoke(Method method, Annotation ann) { try { return method.invoke(ann); } catch (IllegalAccessException e) { IllegalAccessError error = new IllegalAccessError(e.getMessage()); error.initCause(e); throw error; } catch (InvocationTargetException e) { throw new CompositionException(e.getCause()); } } private void appendInvocation(Object target, Type declaringClass, String name, Type returnType, Type[] paramTypes, MethodNodeGenerator gen) { gen.dup(); gen.loadThis(); if (!target.equals("this")) { loadBehaviour((Class<?>) target, gen); } loadMethodObject(declaringClass, name, returnType, paramTypes, gen); gen.invokeVirtual(METHODINVOCATIONCHAIN_TYPE, new org.objectweb.asm.commons.Method("appendInvocation", METHODINVOCATIONCHAIN_TYPE, new Type[] { OBJECT_TYPE, Type.getType(Method.class) })); } private void loadBehaviour(Class<?> behaviourClass, MethodNodeGenerator gen) { gen.invokeVirtual( compositeClass.getType(), new org.objectweb.asm.commons.Method( getGetterName(behaviourClass), Type .getType(behaviourClass), new Type[0])); } private void loadMethodObject(Type declaringClass, String name, Type returnType, Type[] paramTypes, MethodNodeGenerator gen) { FieldNode methodField = compositeClass.addStaticMethodField( declaringClass, name, returnType, paramTypes); gen.getStatic(compositeClass.getType(), methodField.name, Type.getType(methodField.desc)); } private void callMethod(Object target, Method method, MethodNodeGenerator gen) { gen.loadThis(); if ("super".equals(target)) { gen.loadArgs(); gen.invokeSpecial(Type.getType(baseClass), org.objectweb.asm.commons.Method.getMethod(method)); } else { if (!"this".equals(target)) { loadBehaviour((Class<?>) target, gen); } gen.loadArgs(); gen.invokeVirtual(Type.getType(method.getDeclaringClass()), org.objectweb.asm.commons.Method.getMethod(method)); } } private boolean isMethodPresent(Class<?> javaClass, Method method) throws Exception { return getMethod(javaClass, method) != null; } private Method getMethod(Class<?> javaClass, Method method) throws Exception { Class<?>[] types = method.getParameterTypes(); try { Method m = javaClass.getMethod(method.getName(), types); if (!isAbstract(m.getModifiers()) && !isTransient(m.getModifiers()) && !isObjectMethod(m)) { return m; } } catch (NoSuchMethodException e) { // look at @parameterTypes } for (Method m : javaClass.getMethods()) { if (m.getName().equals(method.getName())) { ParameterTypes ann = m.getAnnotation(ParameterTypes.class); if (ann != null && Arrays.equals(ann.value(), types)) { return m; } } } return null; } private boolean isOverridesPresent(Class<?> javaClass) { return javaClass.getAnnotation(Precedes.class) != null; } private boolean overrides(Class<?> javaClass, Class<?> b1) throws Exception { Precedes precedes = javaClass.getAnnotation(Precedes.class); if (precedes != null) { for (Class<?> c : precedes.value()) { if (c.isAssignableFrom(b1)) { return true; } } } return false; } private String getGetterName(Class<?> javaClass) { return "_$get" + javaClass.getSimpleName() + Integer.toHexString(javaClass.getName().hashCode()); } @SuppressWarnings("unchecked") private boolean addBehaviour(Class<?> javaClass) throws Exception { Type behaviourType = Type.getType(javaClass); try { String getterName = getGetterName(javaClass); String fieldName = "_$" + getterName.substring(5); ExtendedMethod mn = new ExtendedMethod(compositeClass, ACC_PRIVATE, getterName, Type.getMethodDescriptor(behaviourType, new Type[0]), null, null); ExtendedMethodGenerator gen = new ExtendedMethodGenerator(mn); Label exists = gen.newLabel(); gen.loadThis(); gen.getField(compositeClass.getType(), fieldName, behaviourType); gen.dup(); gen.ifNonNull(exists); gen.pop(); gen.newInstance(behaviourType); gen.dup(); Constructor<?> constructor; try { constructor = javaClass.getConstructor(Object.class); gen.loadThis(); } catch (NoSuchMethodException e) { constructor = javaClass.getConstructor(); } gen.invokeConstructor(behaviourType, org.objectweb.asm.commons.Method.getMethod(constructor)); gen.injectMembers(); gen.dup(); gen.loadThis(); gen.swap(); gen.putField(compositeClass.getType(), fieldName, behaviourType); gen.mark(exists); gen.returnValue(); gen.endMethod(); compositeClass.methods.add(mn); compositeClass.fields.add(new FieldNode(ACC_PRIVATE, fieldName, behaviourType.getDescriptor(), null, null)); return true; } catch (NoSuchMethodException e) { // no default constructor return false; } } private boolean isObjectMethod(Method m) { return m.getDeclaringClass().getName().equals(Object.class.getName()); } }