
org.leialearns.bridge.BridgeFactory Maven / Gradle / Ivy
package org.leialearns.bridge;
import org.leialearns.utilities.ExceptionWrapper;
import org.leialearns.utilities.Expression;
import org.leialearns.utilities.Function;
import org.leialearns.utilities.HasWrappedIterable;
import org.leialearns.utilities.Setting;
import org.leialearns.utilities.TransformingIterable;
import org.leialearns.utilities.TypedIterable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.leialearns.bridge.Static.getFarObject;
import static org.leialearns.utilities.Display.asDisplayWithTypes;
import static org.leialearns.utilities.Display.display;
import static org.leialearns.utilities.Display.displayWithTypes;
import static org.leialearns.utilities.Display.displayParts;
import static org.leialearns.utilities.Static.getLoggingClass;
import static org.leialearns.utilities.Static.offer;
/**
* Provides a factory with supporting classes that generates implementations of a logical interface whose methods are
* delegated to various helper objects on both sides of an abstraction separation. See {@link org.leialearns.bridge}.
*
* This class is designed for use in Spring to define several beans, one for each logical interface
* (near type). A bridge factory bean is defined by its far type, i.e., the type of
* its concrete delegation index. The logical interface is determined by reflection (by looking up the return
* type of the {@link DeclaresNearType#declareNearType() declareNearType} method.
*
* Additional helpers can be specified as desired. Bindings for the methods specified in the logical interface
* are looked up when the factory is instantiated in the order in which the helpers are given. If a method is not found
* in one of the helpers it is looked up in the far type. If it is desired to search the far type before some of the
* helpers, include a null pointer in the array of helpers.
*
* There are five types of helpers:
*
* - Helper object that uses near types exclusively
* - Class object
* - Helper object that uses far types exclusively
* - Helper object that mixes near and far types
* null
*
* For helpers of type 2 a new instance is created using the default no-arg constructor for each bridge object
* that is created by the factory. These instances are not configured by the spring bean factory. The helper
* null
represents the far object. For the other three types of helpers, either the near object or the
* far object is prepended to the parameter list, as appropriate. The use of helpers of type 4 is strongly
* discouraged.
*
* In the context of an ORM framework it is common to use not only the DAO of that corresponds the far type as a
* helper, but also DAOs of other entities. For example, the SymbolDAO can provide a method that finds all
* symbols that belong to a particular alphabet. This method can be exposed very naturally on the Alphabet bridged
* object.
*
* Spring configuration example
*
* <bean id="alphabetFactory" class="org.leialearns.bridge.BridgeFactory">
* <constructor-arg value="org.leialearns.jpa.interaction.AlphabetDTO"/>
* <constructor-arg>
* <array>
* <ref bean="alphabetDAO"/>
* <ref bean="symbolDAO"/>
* <null/> <!-- AlphabetDTO -->
* <value type="java.lang.Class">org.leialearns.logic.interaction.AlphabetAugmenter</value>
* </array>
* </constructor-arg>
* </bean>
*
*
* @see org.leialearns.bridge
*/
public class BridgeFactory {
private final Logger logger = LoggerFactory.getLogger(getLoggingClass(this));
private final Class> nearType;
private final Class extends FarObject>> farType;
private final Object[] helpers;
private final Map methodMap = new HashMap<>();
private final Method facetsGetter;
private final Method facetsChecker;
private BridgeHeadTypeRegistry registry;
/**
* Creates a new BridgeFactory instance.
*
* @param farTypeName The name of the far type class
* @param helpers The helpers that implement additional methods on the near type
*/
public BridgeFactory(String farTypeName, Object... helpers) {
this(helpers, getFarType(farTypeName));
}
/**
* Creates a new BridgeFactory instance.
*
* @param farType The far type class
* @param helpers The helpers that implement additional methods on the near type
*/
public BridgeFactory(Class extends FarObject>> farType, Object... helpers) {
this(helpers, farType);
}
protected BridgeFactory(Object[] helpers, Class extends FarObject>> farType) {
this.helpers = (helpers == null ? new Object[0] : helpers);
this.farType = farType;
nearType = getNearType(farType);
if (logger.isTraceEnabled()) {
logger.trace(display(nearType) + ": " + display(farType));
}
try {
facetsGetter = BridgeFacet.class.getMethod("getBridgeFacets");
facetsChecker = BridgeFacet.class.getMethod("hasBridgeFacets");
} catch (Throwable throwable) {
throw ExceptionWrapper.wrap(throwable);
}
}
/**
* Returns the near type of the bridge.
* @return The near type
*/
public Class> getNearType() {
return nearType;
}
/**
* Returns the far type of the bridge.
* @return The far type
*/
public Class> getFarType() {
return farType;
}
protected static Class> getNearType(Class> farType) {
Class> result;
try {
Method typeGetter = farType.getMethod("declareNearType");
result = typeGetter.getReturnType();
} catch (Throwable throwable) {
throw ExceptionWrapper.wrap(throwable);
}
return result;
}
/**
* Sets the {@link org.leialearns.bridge.BridgeHeadTypeRegistry BridgeHeadTypeRegistry}, registers this factory
* instance with it, and looks up bindings for the methods in the near type.
* @param registry The BridgeHeadTypeRegistry instance to use
*/
@Autowired
public void setRegistry(BridgeHeadTypeRegistry registry) {
this.registry = registry;
registry.register(this);
Method[] methods = offer(Object.class.getMethods(), nearType.getMethods());
for (Method method : methods) {
if (logger.isTraceEnabled()) {
logger.trace("Method: " + display(method) + ": in: " + display(nearType));
}
Binding binding = getBinding(method.getReturnType(), method.getName(), method.getParameterTypes());
if (binding == null) {
String message = "No binding found: " + display(nearType) + ": " + display(method);
logger.warn(message);
throw new UnsupportedOperationException(message);
}
if (logger.isTraceEnabled()) {
logger.trace("Binding: " + display(binding));
}
methodMap.put(method, binding);
}
}
/**
* Finds or creates a near object for the given far object.
* @param farObject The far object that is to be wrapped by the near object
* @return The near object that corresponds to the given far object
*/
public Object getNearObject(Object farObject) {
Object result = null;
if (farObject instanceof BridgeFacet) {
BridgeFacet facet = (BridgeFacet) farObject;
if (facet.hasBridgeFacets()) {
BridgeFacets facets = facet.getBridgeFacets();
if (facets.hasNearObject()) {
result = nearType.cast(facets.getNearObject());
}
}
}
if (result == null) {
if (!farType.isInstance(farObject)) {
RuntimeException exception = new ClassCastException("Class: " + display(farType) + ": object: " + display(farObject));
if (logger.isTraceEnabled()) {
logger.trace("Far type: " + display(farType), exception);
}
throw exception;
}
Class>[] interfaces = new Class[] { BridgeFacet.class };
final BridgeFacets facets;
if (farObject instanceof BaseBridgeFacet && ((BaseBridgeFacet) farObject).hasBridgeFacets()) {
facets = ((BaseBridgeFacet) farObject).getBridgeFacets();
} else {
Map helperInstances = new HashMap<>();
for (int i = 0; i < helpers.length; i++) {
Object helper = helpers[i];
if (helper instanceof Class) {
Class> helperType = (Class>) helper;
try {
Object helperInstance = helperType.newInstance();
helperInstances.put(i, helperInstance);
} catch (Throwable throwable) {
throw ExceptionWrapper.wrap(throwable);
}
}
}
facets = new BridgeFacets(farType.cast(farObject), this, helperInstances);
for (Object helperInstance : helperInstances.values()) {
if (helperInstance instanceof BaseBridgeFacet) {
((BaseBridgeFacet) helperInstance).setBridgeFacets(facets);
}
}
}
if (farObject instanceof BaseBridgeFacet && !((BaseBridgeFacet) farObject).hasBridgeFacets()) {
((BaseBridgeFacet) farObject).setBridgeFacets(facets);
}
result = newProxyInstance(getClass().getClassLoader(), nearType, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] parameters) throws Throwable {
Object result;
if (logger.isTraceEnabled()) {
logger.trace("Facets getter: " + display(facetsGetter));
}
if (method.equals(facetsGetter)) {
result = facets;
} else if (method.equals(facetsChecker)) {
result = Boolean.TRUE;
} else {
Binding binding = methodMap.get(method);
if (binding == null) {
String message = "No binding found: " + display(nearType) + ": " + display(method);
logger.warn(message);
throw new UnsupportedOperationException(message);
}
result = binding.invoke(facets, parameters);
Class> returnType = method.getReturnType();
if (logger.isTraceEnabled()) {
logger.trace("Return type: " + displayWithTypes(returnType));
}
if (result instanceof TypedIterable) {
TypedIterable> typedIterable = (TypedIterable>) result;
Class> baseType = typedIterable.getType();
if (logger.isTraceEnabled()) {
logger.trace("Iterable base type: " + display(baseType));
}
if (registry.hasFarType(baseType)) {
Class> type = getNearType(baseType);
result = getAdaptedIterable(returnType, typedIterable, type);
} else if (registry.hasNearType(baseType)) {
result = getAdaptedIterable(returnType, typedIterable, baseType);
} else if (logger.isTraceEnabled()) {
logger.trace("Iterable base type not found in registry");
}
} else if (registry.hasNearType(returnType)) {
Class> farType = registry.getFarType(method.getReturnType());
if (logger.isTraceEnabled()) {
logger.trace("Far type of return type: " + display(farType));
}
if (farType.isInstance(result)) {
BridgeFactory factory = registry.getBridgeFactory(farType);
if (logger.isTraceEnabled()) {
logger.trace("Far type of return type factory: " + display(factory.getFarType()));
logger.trace("Result: " + display(result));
logger.trace("Return type: " + display(returnType) + ": factory PO type: " + display(factory.getNearType()));
}
try {
result = factory.getNearObject(result);
} catch (ClassCastException exception) {
if (logger.isTraceEnabled()) {
logger.trace("Stack trace", exception);
}
throw exception;
}
}
} else if (logger.isTraceEnabled()) {
logger.trace("Not adapted");
}
}
return result;
}
});
facets.setNearObject(result);
}
return result;
}
protected Object getAdaptedIterable(Class> returnType, TypedIterable> typedIterable, Class> type) {
Class> baseType = typedIterable.getType();
if (DeclaresNearType.class.isAssignableFrom(returnType)) {
Method declaration;
try {
declaration = returnType.getDeclaredMethod("declareNearType");
} catch (NoSuchMethodException exception) {
throw ExceptionWrapper.wrap(exception);
}
Class> declaredType = declaration.getReturnType();
if (!declaredType.isAssignableFrom(type)) {
throw new ClassCastException("Near object list: " + display(baseType) + " # " + display(declaredType));
} else if (logger.isTraceEnabled()) {
logger.trace("Mapped list: " + display(baseType) + " -> " + display(declaredType));
}
} else if (logger.isTraceEnabled()) {
logger.trace("Return type does not declare near type: " + display(returnType));
}
Object result = null;
if (returnType.isInterface() && NearIterable.class.isAssignableFrom(returnType)) {
if (logger.isTraceEnabled()) {
logger.trace("Return near object iterable");
}
result = getNearObjectList(typedIterable, returnType, type, baseType);
} else if (TypedIterable.class.isAssignableFrom(returnType)) {
if (logger.isTraceEnabled()) {
logger.trace("Return typed iterable that casts items on-the-fly");
}
result = getTypedIterable(typedIterable, type, baseType);
}
if (result == null) {
result = typedIterable;
}
return result;
}
protected Object getNearObjectList(final TypedIterable> typedIterable, Class> returnType, final Class type, Class> baseType) {
Class> interfaces[] = new Class>[] { returnType, HasWrappedIterable.class };
final TypedIterable delegate = getTypedIterable(typedIterable, type, baseType);
Method method;
try {
method = HasWrappedIterable.class.getDeclaredMethod("getWrappedIterable");
} catch (NoSuchMethodException exception) {
logger.warn("Method 'getWrappedIterable' not found", exception);
method = null;
}
final Method wrappedGetter = method;
return Proxy.newProxyInstance(getClass().getClassLoader(), interfaces, new InvocationHandler() {
@Override
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
Method delegateMethod = null;
if (objects == null || objects.length < 1) {
try {
delegateMethod = delegate.getClass().getMethod(method.getName());
} catch (NoSuchMethodException exception) {
// Ignore
}
}
if (delegateMethod == wrappedGetter) {
return typedIterable;
}
if (delegateMethod == null) {
throw new UnsupportedOperationException(display(method));
}
return delegateMethod.invoke(delegate);
}
});
}
protected TypedIterable> getTypedIterable(TypedIterable> typedIterable, final Class> type, Class> baseType) {
TypedIterable> result;
if (registry.hasNearType(baseType)) {
result = typedIterable;
} else {
result = getBridgedTypedIterable(typedIterable, type, baseType);
}
return result;
}
protected TypedIterable getBridgedTypedIterable(TypedIterable> typedIterable, final Class type, Class> baseType) {
final BridgeFactory factory = registry.getBridgeFactory(baseType);
return new BaseNearIterable<>(typedIterable, type, new Function