/*
* Copyright (c) 2009-2010, James Leigh and Zepheira LLC 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;
import static java.lang.reflect.Modifier.isAbstract;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isProtected;
import static org.openrdf.repository.object.traits.RDFObjectBehaviour.GET_ENTITY_METHOD;
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openrdf.repository.object.composition.helpers.BehaviourConstructor;
import org.openrdf.repository.object.exceptions.ObjectCompositionException;
import org.openrdf.repository.object.managers.PropertyMapper;
import org.openrdf.repository.object.traits.RDFObjectBehaviour;
/**
* Creates subclasses of abstract behaviours that can be instaniated.
*
* @author James Leigh
*
*/
public class AbstractClassFactory implements BehaviourProvider {
private static final String BEAN_FIELD_NAME = "_$bean";
private static final String CLASS_PREFIX = "object.behaviours.";
private ClassFactory cp;
private Set<Class<?>> bases;
public void setClassDefiner(ClassFactory definer) {
this.cp = definer;
}
public void setBaseClasses(Set<Class<?>> bases) {
this.bases = bases;
}
public void setPropertyMapper(PropertyMapper mapper) {
// don't need it
}
public Collection<? extends BehaviourFactory> getBehaviourFactories(
Collection<Class<?>> classes) throws ObjectCompositionException {
Set<Class<?>> faces = new HashSet<Class<?>>();
for (Class<?> i : classes) {
faces.add(i);
faces = getImplementingClasses(i, faces);
}
List<BehaviourFactory> result = new ArrayList<BehaviourFactory>(faces.size());
for (Class<?> concept : faces) {
result.addAll(getBehaviourFactories(concept));
}
return result;
}
private Collection<BehaviourFactory> getBehaviourFactories(Class<?> concept) throws ObjectCompositionException {
try {
List<BehaviourFactory> result = new ArrayList<BehaviourFactory>();
if (isEnhanceable(concept)) {
for (Class<?> mapper : findImplementations(concept)) {
result.add(new BehaviourConstructor(mapper));
}
}
return result;
} catch (ObjectCompositionException e) {
throw e;
} catch (Exception e) {
throw new ObjectCompositionException(e);
}
}
private Collection<? extends Class<?>> findImplementations(
Class<?> concept) throws Exception {
return Collections.singleton(findBehaviour(concept));
}
private boolean isBaseClass(Class<?> role) {
return bases != null && bases.contains(role);
}
private final Class<?> findBehaviour(Class<?> concept) throws Exception {
String className = getJavaClassName(concept);
synchronized (cp) {
try {
return cp.classForName(className);
} catch (ClassNotFoundException e2) {
return implement(className, concept);
}
}
}
private ClassTemplate createBehaviourTemplate(String className,
Class<?> concept) {
ClassTemplate cc = createClassTemplate(className, concept);
cc.addInterface(RDFObjectBehaviour.class);
addNewConstructor(cc, concept);
addRDFObjectBehaviourMethod(cc);
return cc;
}
private boolean isOverridden(Method m) {
if (m.getParameterTypes().length > 0)
return false;
if (RDFObjectBehaviour.GET_ENTITY_METHOD.equals(m.getName()))
return true;
return false;
}
private String getJavaClassName(Class<?> concept) {
String suffix = getClass().getSimpleName().replaceAll("Factory$", "");
return CLASS_PREFIX + concept.getName() + suffix;
}
private Class<?> implement(String className, Class<?> concept)
throws Exception {
ClassTemplate cc = createBehaviourTemplate(className, concept);
enhance(cc, concept);
return cp.createClass(cc);
}
private void addNewConstructor(ClassTemplate cc, Class<?> concept) {
if (!concept.isInterface()) {
try {
concept.getConstructor(); // must have a default constructor
} catch (NoSuchMethodException e) {
throw new ObjectCompositionException(concept.getSimpleName()
+ " must have a default constructor");
}
}
cc.createField(Object.class, BEAN_FIELD_NAME);
cc.addConstructor(new Class<?>[] { Object.class },
BEAN_FIELD_NAME + " = $1;");
}
private void addRDFObjectBehaviourMethod(ClassTemplate cc) {
cc.createMethod(Object.class,
RDFObjectBehaviour.GET_ENTITY_METHOD).code("return ").code(
BEAN_FIELD_NAME).code(";").end();
}
private Set<Class<?>> getImplementingClasses(Class<?> role,
Set<Class<?>> implementations) {
// don't consider interfaces or super classes
return implementations;
}
private ClassTemplate createClassTemplate(String className, Class<?> role) {
ClassTemplate cc = cp.createClassTemplate(className, role);
cc.copyAnnotationsFrom(role);
return cc;
}
private boolean isEnhanceable(Class<?> role) {
return !role.isInterface() && isAbstract(role.getModifiers())
&& !isBaseClass(role);
}
private void enhance(ClassTemplate cc, Class<?> c) throws Exception {
if (Object.class.equals(c.getMethod("toString").getDeclaringClass())) {
overrideToStringMethod(cc);
}
if (Object.class.equals(c.getMethod("equals", Object.class)
.getDeclaringClass())
&& Object.class.equals(c.getMethod("hashCode")
.getDeclaringClass())) {
overrideEqualsMethod(cc);
}
for (Method m : getMethods(c)) {
if (isFinal(m.getModifiers()))
continue;
if (!isAbstract(m.getModifiers()))
continue;
if (isOverridden(m))
continue;
Class<?> r = m.getReturnType();
Class<?>[] types = m.getParameterTypes();
CodeBuilder code = cc.createInstancePrivateMethod(m);
boolean isInterface = m.getDeclaringClass().isInterface();
if (!isInterface) {
code.code("try {");
}
if (!Void.TYPE.equals(r)) {
code.code("return ($r) ");
}
if (isInterface) {
code.code("(").castObject(m.getDeclaringClass()).code(BEAN_FIELD_NAME);
code.code(").").code(m.getName()).code("($$);");
} else {
code.code(BEAN_FIELD_NAME).code(".getClass().getMethod(");
code.insert(m.getName()).code(", ").insert(types).code(")")
.code(".invoke(");
code.code(BEAN_FIELD_NAME).code(", $args);");
}
if (!isInterface) {
code.code("} catch (").code(
InvocationTargetException.class.getName());
code.code(" e) {throw e.getCause();}");
}
code.end();
}
}
private Collection<Method> getMethods(Class<?> c) {
List<Method> methods = new ArrayList<Method>();
methods.addAll(Arrays.asList(c.getMethods()));
HashMap<Object, Method> map = new HashMap<Object, Method>();
Map<Object, Method> pms = getProtectedMethods(c, map);
methods.addAll(pms.values());
return methods;
}
private Map<Object, Method> getProtectedMethods(Class<?> c,
Map<Object, Method> methods) {
if (c == null)
return methods;
for (Method m : c.getDeclaredMethods()) {
if (isProtected(m.getModifiers())) {
Object types = Arrays.asList(m.getParameterTypes());
Object key = Arrays.asList(m.getName(), types);
if (!methods.containsKey(key)) {
methods.put(key, m);
}
}
}
return getProtectedMethods(c.getSuperclass(), methods);
}
private void overrideToStringMethod(ClassTemplate cc) {
try {
Method toString = Object.class.getMethod("toString");
MethodBuilder m = cc.createInstancePrivateMethod(toString);
m.code("return ").code(GET_ENTITY_METHOD);
m.code("().toString()").semi().end();
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
private void overrideEqualsMethod(ClassTemplate cc) {
try {
Method hashCode = Object.class.getMethod("hashCode");
MethodBuilder m = cc.createInstancePrivateMethod(hashCode);
m.code("return ").code(GET_ENTITY_METHOD);
m.code("().hashCode()").semi().end();
Method equals = Object.class.getMethod("equals", Object.class);
m = cc.createInstancePrivateMethod(equals);
m.code("return ").code(GET_ENTITY_METHOD);
m.code("().equals($1)").semi().end();
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
}