/* * Copyright (c) 2009-2010, James Leigh and Zepheira LLC Some rights reserved. * Copyright (c) 2011 Talis Inc., Some 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 org.openrdf.repository.object.composition.helpers; import static java.lang.reflect.Modifier.isAbstract; import static java.lang.reflect.Modifier.isPublic; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.openrdf.annotations.InstancePrivate; import org.openrdf.annotations.Iri; import org.openrdf.annotations.ParameterTypes; import org.openrdf.model.vocabulary.OWL; import org.openrdf.model.vocabulary.RDFS; import org.openrdf.repository.object.composition.BehaviourFactory; import org.openrdf.repository.object.composition.ClassFactory; import org.openrdf.repository.object.composition.ClassTemplate; import org.openrdf.repository.object.composition.CodeBuilder; import org.openrdf.repository.object.composition.MethodBuilder; import org.openrdf.repository.object.traits.RDFObjectBehaviour; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class takes a collection of roles (interfaces or classes) and uses * composition to combine this into a single class. * * @author James Leigh * */ public class ClassComposer { public static void calling(Object target, String method, Object[] args) { if (++count % 512 == 0){ Throwable stack = new Throwable(); StackTraceElement[] trace = stack.getStackTrace(); if(trace.length > 512) { String string = Arrays.asList(args).toString(); String substring = string.substring(1, string.length() - 1); logger.warn(target + "<-" + method + "(" + substring + ")", stack); } } } private static int count; private static Set<String> special = new HashSet<String>(Arrays.asList( "groovy.lang.GroovyObject", RDFObjectBehaviour.class.getName())); private static Logger logger = LoggerFactory.getLogger(ClassComposer.class); private ClassFactory cp; private final String className; private Class<?> baseClass = Object.class; private final Set<Class<?>> interfaces; private final Set<BehaviourFactory> allBehaviours; private Collection<Method> methods; private Map<String, Method> namedMethods; private Map<Method, String> superMethods = new HashMap<Method, String>(); private Map<String, Set<BehaviourFactory>> behaviours; private ClassTemplate cc; public ClassComposer(String className, int size) { this.className = className; interfaces = new LinkedHashSet<Class<?>>(size); allBehaviours = new LinkedHashSet<BehaviourFactory>(size); } public void setClassFactory(ClassFactory cp) { this.cp = cp; } 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<? extends BehaviourFactory> factories) { this.allBehaviours.addAll(factories); } public Class<?> compose() throws Exception { logger.trace("public class {} extends {}", className, baseClass); cc = cp.createClassTemplate(className, baseClass); for (BehaviourFactory behaviours : allBehaviours) { for (Class<?> clazz : behaviours.getInterfaces()) { addInterfaces(clazz); } } for (Class<?> face : interfaces) { cc.addInterface(face); } behaviours = new HashMap<String, Set<BehaviourFactory>>(); for (BehaviourFactory clazz : allBehaviours) { addBehaviour(clazz); for (Method m : clazz.getMethods()) { if (!isSpecial(m)) { Set<BehaviourFactory> s = behaviours.get(m.getName()); if (s == null) { s = new HashSet<BehaviourFactory>(); behaviours.put(m.getName(), s); } s.add(clazz); } } } Collection<Method> all = getAllMethods(); methods = getUniqueMethods(all); namedMethods = getNamedMethods(all); for (Method method : methods) { if (!method.getName().startsWith("_$")) { boolean bridge = isBridge(method, methods); implementMethod(method, method.getName(), bridge); } } try { Class<?> createdClass = cp.createClass(cc); for (BehaviourFactory clazz : allBehaviours) { populateBehaviourField(clazz, createdClass); } return createdClass; } catch (LinkageError e) { String msg = e.getMessage() + " while composing " + baseClass.getSimpleName() + " with " + interfaces; LinkageError error = new LinkageError(msg); error.initCause(e); throw error; } } private void addInterfaces(Class<?> clazz) { if (interfaces.contains(clazz)) return; if (clazz.isInterface()) { interfaces.add(clazz); } Class<?> superclass = clazz.getSuperclass(); if (superclass != null) { addInterfaces(superclass); } for (Class<?> face : clazz.getInterfaces()) { if (isPublic(face.getModifiers()) && !isSpecial(face)) { addInterfaces(face); } } } private boolean isSpecial(Class<?> face) { return special.contains(face.getName()); } private Collection<Method> getAllMethods() { List<Method> methods = new LinkedList<Method>(); for (Class<?> jc : interfaces) { for (Method m : jc.getMethods()) { if (isSpecial(m)) continue; methods.add(m); } } for (BehaviourFactory factory : allBehaviours) { for (Method m : factory.getMethods()) { if (isSpecial(m)) continue; methods.add(m); } } return methods; } private Collection<Method> getUniqueMethods(Collection<Method> methods) { Map<List<?>, Method> map = new HashMap<List<?>, Method>(methods.size()); for (Method m : methods) { Class<?>[] ptypes = getParameterTypes(m); List list = new ArrayList(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 Map<String, Method> getNamedMethods(Collection<Method> all) { Map<String, Method> namedMethods = new HashMap<String, Method>(all.size()); for (Method method : all) { if (method.isAnnotationPresent(Iri.class)) { String uri = method.getAnnotation(Iri.class).value(); if (!namedMethods.containsKey(uri) || !isBridge(method, methods)) { namedMethods.put(uri, method); } } } return namedMethods; } private int getRank(Method m) { int rank = m.getAnnotations().length; if (m.isAnnotationPresent(ParameterTypes.class)) return rank - 2; return rank; } private boolean isSpecial(Method m) { if (isObjectMethod(m)) return true; if ("methodMissing".equals(m.getName())) return true; if ("propertyMissing".equals(m.getName())) return true; return m.isAnnotationPresent(InstancePrivate.class); } 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().isAssignableFrom(method.getReturnType())) return true; } return false; } private boolean implementMethod(Method method, String name, boolean bridge) throws Exception { List<BehaviourMethod> chain = chain(method); List<Object[]> implementations = getImpls(chain, method); if (implementations.isEmpty()) return false; Class<?> type = method.getReturnType(); boolean voidReturnType = type.equals(Void.TYPE); boolean chained = implementations.size() > 1 && !voidReturnType || isChainRequired(chain, method); Method face = findInterfaceMethod(method); CodeBuilder body = cc.copyMethod(face, name, bridge); boolean primitiveReturnType = type.isPrimitive(); boolean setReturnType = type.equals(Set.class); if (logger.isTraceEnabled()) { body.code(ClassComposer.class.getName() + ".calling(this, \"" + method.getName() + "\", $args);"); } if (chained) { if (!voidReturnType && primitiveReturnType) { body.code(type.getName()).code(" result;\n"); } else if (setReturnType) { body.code(Set.class.getName() + " result;\n"); } else if (!voidReturnType) { body.code(Object.class.getName() + " result;\n"); } } else if (!voidReturnType) { body.code("return ($r) "); } boolean chainStarted = false; for (Object[] ar : implementations) { assert ar.length == 2; String target = (String) ar[0]; Method m = (Method) ar[1]; if (chained) { if (!chainStarted) { chainStarted = true; if (!type.equals(Void.TYPE)) { body.code("result = ($r) "); } body.code("new "); body.code(InvocationMessageContext.class.getName()); body.code("($0, "); body.insert(face); body.code(", $args)\n"); } if ("super".equals(target)) { String dname = createSuperCall(m); appendInvocation("this", dname, m.getParameterTypes(), body); } else { appendInvocation(target, m, body); } } else { body.code(getMethodCall(target, m)); } } if (chainStarted) { body.code(".proceed();\n"); chainStarted = false; } if (chained && !voidReturnType) { body.code("return ($r) result;\n"); } body.end(); return true; } private String createSuperCall(Method m) { if (superMethods.containsKey(m)) return superMethods.get(m); Class<?> rtype = m.getReturnType(); Class<?>[] ptypes = m.getParameterTypes(); String name = "_$super" + superMethods.size() + "_" + m.getName(); MethodBuilder delegating = cc.createMethod(rtype, name, ptypes); if (!Void.TYPE.equals(rtype)) delegating.code("return "); delegating.code(getMethodCall("super", m)).end(); superMethods.put(m, name); return name; } private List<BehaviourMethod> chain(Method method) throws Exception { if (behaviours.isEmpty()) return null; List<BehaviourMethod> list = new ArrayList<BehaviourMethod>(); addAllBehaviourMethods(method, list); for (Method m : getEquivalentMethods(method)) { if (m.equals(method)) continue; addAllBehaviourMethods(m, list); } for (Method m : getSuperMethods(method)) { if (m.equals(method)) continue; list.addAll(chain(m)); } return list; } private void addAllBehaviourMethods(Method method, List<BehaviourMethod> list) { Set<BehaviourFactory> set = behaviours.get(method.getName()); if (set != null) { for (BehaviourFactory behaviour : set) { Method m = behaviour.getInvocation(method); if (m != null) { list.add(new BehaviourMethod(behaviour, m)); } } } } private List<BehaviourMethod> sort(List<BehaviourMethod> list, int size) { if (size < 2) return list; List<BehaviourMethod> sorted = new ArrayList<BehaviourMethod>(size); loop: for (BehaviourMethod bm : list) { for (int i = 0, n = sorted.size(); i < n; i++) { if (bm.precedes(sorted.get(i))) { sorted.add(i, bm); sorted = sort(sorted, size); continue loop; } } sorted.add(bm); } return sorted; } private boolean isChainRequired(List<BehaviourMethod> behaviours, Method method) throws Exception { if (behaviours != null) { for (BehaviourMethod behaviour : behaviours) { if (behaviour.isMessage()) return true; Class<?> rt = behaviour.getMethod().getReturnType(); if (!method.getReturnType().equals(rt)) return true; } } return false; } /** * @return list of <String, Method> */ private List<Object[]> getImpls(List<BehaviourMethod> behaviours, Method method) throws Exception { List<Object[]> list = new ArrayList<Object[]>(); Class<?> type = method.getReturnType(); Class<?> superclass = cc.getSuperclass(); Class<?>[] types = getParameterTypes(method); if (behaviours != null) { for (BehaviourMethod behaviour : sort(behaviours, behaviours.size())) { if (behaviour.getFactory().isSingleton()) { String target = getBehaviourFieldName(behaviour.getFactory()); list.add(new Object[] { target, behaviour.getMethod() }); } else { String target = getPrivateBehaviourMethod(behaviour.getFactory()) + "()"; list.add(new Object[] { target, behaviour.getMethod() }); } } } if (!superclass.equals(Object.class)) { try { Method m = superclass.getMethod(method.getName(), types); Class<?> returnType = m.getReturnType(); if (!isSpecial(m) && !isAbstract(m.getModifiers()) && returnType.equals(type)) { list.add(new Object[] { "super", m }); } } catch (NoSuchMethodException e) { // no super method } } return list; } private List<Method> getSuperMethods(Method method) { List<Method> list = new ArrayList<Method>(); String subClassOf = RDFS.SUBCLASSOF.stringValue(); for (String uri : getAnnotationValueByIri(method, subClassOf)) { Method m = namedMethods.get(uri); if (m != null && !isSpecial(m)) { list.add(m); } } return list; } private List<Method> getEquivalentMethods(Method method) { List<Method> list = new ArrayList<Method>(); String equivalentClass = OWL.EQUIVALENTCLASS.stringValue(); for (String uri : getAnnotationValueByIri(method, equivalentClass)) { 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 ExceptionInInitializerError(e.getCause()); } } private void appendInvocation(String target, Method method, CodeBuilder body) { body.code(".appendInvocation("); body.code(target).code(", "); body.insert(method); body.code(")\n"); } private void appendInvocation(String target, String name, Class<?>[] params, CodeBuilder body) { body.code(".appendInvocation("); body.code(target).code(", "); body.insertMethod(name, params); body.code(")\n"); } private String getMethodCall(String target, Method method) { StringBuilder eval = new StringBuilder(); eval.append(target); eval.append(".").append(method.getName()).append("($$);\n"); return eval.toString(); } private Method findInterfaceMethod(Method method) { String name = method.getName(); Class<?> type = method.getReturnType(); Class<?>[] types = getParameterTypes(method); Class<?>[] faces = cc.getInterfaces(); Method m = findInterfaceMethod(faces, name, type, types); if (m != null) return m; m = findSuperMethod(cc.getSuperclass(), name, type, types); if (m != null) return m; return method; } private Method findInterfaceMethod(Class<?>[] interfaces, String name, Class<?> type, Class<?>[] types) { Collection<Method> methods = findInterfaceMethods(interfaces, name, type, types); Collection<Method> withIdentifiers = new LinkedHashSet<Method>(); for (Method m : methods) { Annotation[][] anns = m.getParameterAnnotations(); for (int i = 0; i < anns.length; i++) { for (int j = 0; j < anns[i].length; j++) { if (anns[i][j].annotationType().equals(Iri.class)) { withIdentifiers.add(m); // parameter IRI present } } } } if (withIdentifiers.isEmpty()) return findSubMethod(methods); return findSubMethod(withIdentifiers); } private Method findSubMethod(Collection<Method> methods) { if (methods.isEmpty()) return null; Iterator<Method> iter = methods.iterator(); if (methods.size() == 1) return iter.next(); while (iter.hasNext()) { Method m1 = iter.next(); Class<?> c1 = m1.getDeclaringClass(); for (Method m2 : methods) { Class<?> c2 = m2.getDeclaringClass(); if (!c1.equals(c2) && c1.isAssignableFrom(c2)) { iter.remove(); break; } } } return methods.iterator().next(); } private Collection<Method> findInterfaceMethods(Class<?>[] interfaces, String name, Class<?> type, Class<?>[] types) { Collection<Method> methods = new LinkedHashSet<Method>(); for (Class face : interfaces) { try { Method m = face.getDeclaredMethod(name, types); if (m.getReturnType().equals(type)) { methods.add(m); continue; } } catch (NoSuchMethodException e) { // continue } Class[] faces = face.getInterfaces(); Method m = findInterfaceMethod(faces, name, type, types); if (m != null) { methods.add(m); } } return methods; } private Method findSuperMethod(Class<?> base, String name, Class<?> type, Class<?>[] types) { if (base == null) return null; try { Method m = base.getDeclaredMethod(name, types); if (m.getReturnType().equals(type)) return m; } catch (NoSuchMethodException e) { // continue } Method m = findSuperMethod(base.getSuperclass(), name, type, types); if (m == null) return null; return m; } private void addBehaviour(BehaviourFactory factory) throws Exception { if (factory.isSingleton()) { String fieldName = getBehaviourFieldName(factory); Class<?> clazz = factory.getBehaviourType(); cc.assignStaticField(clazz, fieldName).code("null").end(); } else { String getterName = getPrivateBehaviourMethod(factory); String fieldFactoryName = getBehaviourFactoryFieldName(factory); cc.assignStaticField(BehaviourFactory.class, fieldFactoryName).code("null").end(); String fieldName = getBehaviourFieldName(factory); Class<?> clazz = factory.getBehaviourType(); cc.createField(clazz, fieldName); CodeBuilder code = cc.createPrivateMethod(clazz, getterName); code.code("if (").code(fieldName).code(" != null){\n"); code.code("return ").code(fieldName).code(";\n} else {\n"); code.code("return ").code(fieldName).code(" = ($r) "); code.code(fieldFactoryName).code(".newInstance($0)"); code.code(";\n}").end(); } } private void populateBehaviourField(BehaviourFactory factory, Class<?> createdClass) throws NoSuchFieldException, IllegalAccessException { if (factory.isSingleton()) { String fieldName = getBehaviourFieldName(factory); createdClass.getField(fieldName).set(null, factory.getSingleton()); } else { String fieldName = getBehaviourFactoryFieldName(factory); createdClass.getField(fieldName).set(null, factory); } } private String getPrivateBehaviourMethod(BehaviourFactory factory) { String simpleName = factory.getName().replaceAll("\\W", "_"); return "_$get" + simpleName + "Behaviour" + Integer.toHexString(System.identityHashCode(factory)); } private String getBehaviourFieldName(BehaviourFactory factory) { String getterName = getPrivateBehaviourMethod(factory); return "_$" + getterName.substring(5); } private String getBehaviourFactoryFieldName(BehaviourFactory factory) { String getterName = getPrivateBehaviourMethod(factory); return "_$" + getterName.substring(5) + "Factory"; } private boolean isObjectMethod(Method m) { return m.getDeclaringClass().getName().equals(Object.class.getName()); } }