package org.openflexo.model.factory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import org.jdom2.JDOMException;
import org.openflexo.model.ModelContext;
import org.openflexo.model.ModelContextLibrary;
import org.openflexo.model.ModelEntity;
import org.openflexo.model.ModelInitializer;
import org.openflexo.model.ModelProperty;
import org.openflexo.model.StringConverterLibrary.Converter;
import org.openflexo.model.StringEncoder;
import org.openflexo.model.annotations.PastingPoint;
import org.openflexo.model.exceptions.InvalidDataException;
import org.openflexo.model.exceptions.ModelDefinitionException;
import org.openflexo.model.exceptions.ModelExecutionException;
public class ModelFactory {
private Class<?> defaultModelClass = Object.class;
private Class<? extends List> listImplementationClass = Vector.class;
private Class<? extends Map> mapImplementationClass = Hashtable.class;
private Map<Class, PAMELAProxyFactory> proxyFactories;
private StringEncoder stringEncoder;
private ModelContext modelContext;
private ModelContext extendedContext;
public class PAMELAProxyFactory<I> extends ProxyFactory {
private final ModelEntity<I> modelEntity;
private boolean locked = false;
private boolean overridingSuperClass = false;
public PAMELAProxyFactory(ModelEntity<I> modelEntity) throws ModelDefinitionException {
super();
this.modelEntity = modelEntity;
setFilter(new MethodFilter() {
@Override
public boolean isHandled(Method method) {
return Modifier.isAbstract(method.getModifiers()) || method.getName().equals("toString")
&& method.getParameterTypes().length == 0 && method.getDeclaringClass() == Object.class;
}
});
Class<?> implementingClass = modelEntity.getImplementingClass();
if (implementingClass == null) {
implementingClass = defaultModelClass;
}
super.setSuperclass(implementingClass);
Class<?>[] interfaces = { modelEntity.getImplementedInterface() };
setInterfaces(interfaces);
}
public Class<?> getOverridingSuperClass() {
if (overridingSuperClass) {
return getSuperclass();
} else {
return null;
}
}
@Override
public void setSuperclass(Class clazz) {
if (getSuperclass() != clazz) {
if (locked) {
throw new IllegalStateException("ProxyFactory for " + modelEntity
+ " is locked. Super-class can no longer be modified.");
}
}
overridingSuperClass = true;
super.setSuperclass(clazz);
locked = true;
}
public ModelFactory getModelFactory() {
return ModelFactory.this;
}
public ModelEntity<I> getModelEntity() {
return modelEntity;
}
public I newInstance(Object... args) throws IllegalArgumentException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException, ModelDefinitionException {
if (modelEntity.isAbstract()) {
throw new InstantiationException(modelEntity + " is declared as an abstract entity, cannot instantiate it");
}
locked = true;
ProxyMethodHandler<I> handler = new ProxyMethodHandler<I>(this);
I returned = (I) create(new Class<?>[0], new Object[0], handler);
handler.setObject(returned);
if (args == null) {
args = new Object[0];
}
if (args.length > 0 || modelEntity.hasInitializers()) {
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
Object o = args[i];
if (isProxyObject(o)) {
ModelEntity<?> modelEntity = getModelEntityForInstance(o);
types[i] = modelEntity.getImplementedInterface();
} else {
types[i] = o != null ? o.getClass() : null;
}
}
ModelInitializer initializerForArgs = modelEntity.getInitializerForArgs(types);
if (initializerForArgs != null) {
initializerForArgs.getInitializingMethod().invoke(returned, args);
} else {
if (args.length > 0) {
StringBuilder sb = new StringBuilder();
for (Class<?> c : types) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(c.getName());
}
throw new NoSuchMethodException("Could not find any initializer with args " + sb.toString());
}
}
}
return returned;
}
}
public ModelFactory(Class<?> baseClass) throws ModelDefinitionException {
this(ModelContextLibrary.getModelContext(baseClass));
}
public ModelFactory(ModelContext modelContext) {
this.modelContext = modelContext;
proxyFactories = new HashMap<Class, PAMELAProxyFactory>();
stringEncoder = new StringEncoder(this);
}
public ModelContext getModelContext() {
return modelContext;
}
ModelContext getExtendedContext() {
return extendedContext != null ? extendedContext : modelContext;
}
public <I> I newInstance(ModelEntity<I> modelEntity) {
return newInstance(modelEntity, (Object[]) null);
}
public <I> I newInstance(ModelEntity<I> modelEntity, Object... args) {
return newInstance(modelEntity.getImplementedInterface(), args);
}
public <I> I newInstance(Class<I> implementedInterface) {
return newInstance(implementedInterface, (Object[]) null);
}
public <I> I newInstance(Class<I> implementedInterface, Object... args) {
try {
PAMELAProxyFactory<I> proxyFactory = getProxyFactory(implementedInterface, true);
return proxyFactory.newInstance(args);
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (InstantiationException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (ModelDefinitionException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
}
}
<I> I _newInstance(Class<I> implementedInterface, boolean useExtended) {
return _newInstance(implementedInterface, useExtended, (Object[]) null);
}
<I> I _newInstance(Class<I> implementedInterface, boolean useExtended, Object... args) {
try {
PAMELAProxyFactory<I> proxyFactory = getProxyFactory(implementedInterface, true, useExtended);
return proxyFactory.newInstance(args);
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (InstantiationException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
} catch (ModelDefinitionException e) {
e.printStackTrace();
throw new ModelExecutionException(e);
}
}
private <I> PAMELAProxyFactory<I> getProxyFactory(Class<I> implementedInterface) throws ModelDefinitionException {
return getProxyFactory(implementedInterface, true);
}
private <I> PAMELAProxyFactory<I> getProxyFactory(Class<I> implementedInterface, boolean create) throws ModelDefinitionException {
return getProxyFactory(implementedInterface, create, false);
}
private <I> PAMELAProxyFactory<I> getProxyFactory(Class<I> implementedInterface, boolean create, boolean useExtended)
throws ModelDefinitionException {
PAMELAProxyFactory<I> proxyFactory = proxyFactories.get(implementedInterface);
if (proxyFactory == null) {
ModelEntity<I> entity;
if (useExtended) {
entity = getExtendedContext().getModelEntity(implementedInterface);
} else {
entity = getModelContext().getModelEntity(implementedInterface);
}
if (entity == null) {
throw new ModelExecutionException("Unknown entity '" + implementedInterface.getName()
+ "'! Did you forget to import it or to annotated it with @ModelEntity?");
} else {
if (create) {
proxyFactories.put(implementedInterface, proxyFactory = new PAMELAProxyFactory<I>(entity));
}
}
}
return proxyFactory;
}
public Class<?> getDefaultModelClass() {
return defaultModelClass;
}
public void setDefaultModelClass(Class<?> defaultModelClass) {
Class<?> old = defaultModelClass;
this.defaultModelClass = defaultModelClass;
for (PAMELAProxyFactory<?> factory : proxyFactories.values()) {
if (factory.getSuperclass() == old) {
factory.setSuperclass(defaultModelClass);
}
}
}
public <I> void setImplementingClassForInterface(Class<? extends I> implementingClass, Class<I> implementedInterface)
throws ModelDefinitionException {
PAMELAProxyFactory<I> proxyFactory = getProxyFactory(implementedInterface, true);
proxyFactory.setSuperclass(implementingClass);
}
<I> void setImplementingClassForInterface(Class<? extends I> implementingClass, Class<I> implementedInterface, boolean useExtended)
throws ModelDefinitionException {
PAMELAProxyFactory<I> proxyFactory = getProxyFactory(implementedInterface, true, useExtended);
if (proxyFactory != null) {
proxyFactory.setSuperclass(implementingClass);
}
}
public <I> void setImplementingClassForEntity(Class<? extends I> implementingClass, ModelEntity<I> entity)
throws ModelDefinitionException {
setImplementingClassForInterface(implementingClass, entity.getImplementedInterface());
}
<I> void setImplementingClassForEntity(Class<? extends I> implementingClass, ModelEntity<I> entity, boolean useExtended)
throws ModelDefinitionException {
setImplementingClassForInterface(implementingClass, entity.getImplementedInterface(), useExtended);
}
public Class<? extends List> getListImplementationClass() {
return listImplementationClass;
}
public void setListImplementationClass(Class<? extends List> listImplementationClass) {
this.listImplementationClass = listImplementationClass;
}
public Class<? extends Map> getMapImplementationClass() {
return mapImplementationClass;
}
public void setMapImplementationClass(Class<? extends Map> mapImplementationClass) {
this.mapImplementationClass = mapImplementationClass;
}
public boolean isProxyObject(Object object) {
return object instanceof ProxyObject;
}
public <I> ModelEntity<I> getModelEntityForInstance(I object) {
ProxyMethodHandler<I> handler = getHandler(object);
if (handler != null) {
return handler.getModelEntity();
}
return null;
}
public <I> ProxyMethodHandler<I> getHandler(I object) {
if (object instanceof ProxyObject) {
return (ProxyMethodHandler<I>) ((ProxyObject) object).getHandler();
}
return null;
}
<I> ModelEntity<I> importClass(Class<I> klass) throws ModelDefinitionException {
ModelEntity<I> modelEntity = modelContext.getModelEntity(klass);
if (modelEntity == null) {
extendedContext = new ModelContext(klass, getExtendedContext());
modelEntity = extendedContext.getModelEntity(klass);
}
return modelEntity;
}
public StringEncoder getStringEncoder() {
return stringEncoder;
}
public void addConverter(Converter<?> converter) {
stringEncoder.addConverter(converter);
}
public boolean isEmbedddedIn(Object parentObject, Object childObject, EmbeddingType embeddingType) {
return getEmbeddedObjects(parentObject, embeddingType).contains(childObject);
}
public boolean isEmbedddedIn(Object parentObject, Object childObject, EmbeddingType embeddingType, Object... context) {
return getEmbeddedObjects(parentObject, embeddingType, context).contains(childObject);
}
/**
* Build and return a List of embedded objects, using meta informations contained in related class All property should be annotated with
* a @Embedded annotation which determine the way of handling this property
*
* Supplied context is used to determine the closure of objects graph being constructed during this operation.
*
* @param root
* @param context
* @return
*/
public List<Object> getEmbeddedObjects(Object root, EmbeddingType embeddingType, Object... context) {
if (!isProxyObject(root)) {
return Collections.emptyList();
}
List<Object> derivedObjectsFromContext = new ArrayList<Object>();
if (context != null && context.length > 0) {
for (Object o : context) {
derivedObjectsFromContext.add(o);
derivedObjectsFromContext.addAll(getEmbeddedObjects(o, embeddingType));
}
}
List<Object> returned = new ArrayList<Object>();
try {
appendEmbeddedObjects(root, returned, embeddingType);
} catch (ModelDefinitionException e) {
throw new ModelExecutionException(e);
}
List<Object> discardedObjects = new ArrayList<Object>();
for (int i = 0; i < returned.size(); i++) {
Object o = returned.get(i);
if (o instanceof ConditionalPresence) {
boolean allOthersArePresent = true;
for (Object other : ((ConditionalPresence) o).requiredPresence) {
if (!returned.contains(other) && !derivedObjectsFromContext.contains(other)) {
allOthersArePresent = false;
break;
}
}
if (allOthersArePresent && !returned.contains(((ConditionalPresence) o).object)) {
// Closure is fine and object is not already present, add object
returned.set(i, ((ConditionalPresence) o).object);
} else {
// Discard object
discardedObjects.add(o);
}
}
}
for (Object o : discardedObjects) {
returned.remove(o);
}
return returned;
}
private class ConditionalPresence {
private Object object;
private List<Object> requiredPresence;
public ConditionalPresence(Object object, List<Object> requiredPresence) {
super();
this.object = object;
this.requiredPresence = requiredPresence;
}
}
private void appendEmbedded(ModelProperty p, Object father, List<Object> list, Object child, EmbeddingType embeddingType)
throws ModelDefinitionException {
if (!isProxyObject(child)) {
return;
}
if (p.getEmbedded() == null && p.getComplexEmbedded() == null) {
// this property is not embedded
return;
}
boolean append = false;
switch (embeddingType) {
case CLOSURE:
append = p.getEmbedded() != null && p.getEmbedded().closureConditions().length == 0 || p.getComplexEmbedded() != null
&& p.getComplexEmbedded().closureConditions().length == 0;
break;
case DELETION:
append = p.getEmbedded() != null && p.getEmbedded().deletionConditions().length == 0 || p.getComplexEmbedded() != null
&& p.getComplexEmbedded().deletionConditions().length == 0;
break;
}
if (append) {
// There is no condition, just append it
if (!list.contains(child)) {
// System.out.println("Embedded in "+father+" because of "+p+" : "+child);
list.add(child);
appendEmbeddedObjects(child, list, embeddingType);
}
} else {
List<Object> requiredPresence = new ArrayList<Object>();
if (p.getEmbedded() != null) {
switch (embeddingType) {
case CLOSURE:
for (String c : p.getEmbedded().closureConditions()) {
ModelEntity closureConditionEntity = getModelEntityForInstance(child);
ModelProperty closureConditionProperty = closureConditionEntity.getModelProperty(c);
Object closureConditionRequiredObject = getHandler(child).invokeGetter(closureConditionProperty);
if (closureConditionRequiredObject != null) {
requiredPresence.add(closureConditionRequiredObject);
}
}
break;
case DELETION:
for (String c : p.getEmbedded().deletionConditions()) {
ModelEntity deletionConditionEntity = getModelEntityForInstance(child);
ModelProperty deletionConditionProperty = deletionConditionEntity.getModelProperty(c);
Object deletionConditionRequiredObject = getHandler(child).invokeGetter(deletionConditionProperty);
if (deletionConditionRequiredObject != null) {
requiredPresence.add(deletionConditionRequiredObject);
}
}
break;
}
if (requiredPresence.size() > 0) {
ConditionalPresence conditionalPresence = new ConditionalPresence(child, requiredPresence);
list.add(conditionalPresence);
} else {
if (!list.contains(child)) {
// System.out.println("Embedded in "+father+" because of "+p+" : "+child);
list.add(child);
appendEmbeddedObjects(child, list, embeddingType);
}
}
}
// System.out.println("Embedded in "+father+" : "+child+" conditioned to required presence of "+requiredPresence);
}
}
private void appendEmbeddedObjects(Object father, List<Object> list, EmbeddingType embeddingType) throws ModelDefinitionException {
ProxyMethodHandler handler = getHandler(father);
ModelEntity modelEntity = handler.getModelEntity();
Iterator<ModelProperty<?>> properties = modelEntity.getProperties();
while (properties.hasNext()) {
ModelProperty<?> p = properties.next();
switch (p.getCardinality()) {
case SINGLE:
Object oValue = handler.invokeGetter(p);
appendEmbedded(p, father, list, oValue, embeddingType);
break;
case LIST:
List<?> values = (List<?>) handler.invokeGetter(p);
for (Object o : values) {
appendEmbedded(p, father, list, o, embeddingType);
}
break;
default:
break;
}
}
}
public Clipboard copy(Object... objects) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException {
return new Clipboard(this, objects);
}
public Clipboard cut(Object... objects) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException {
return null;
}
public Object paste(Clipboard clipboard, Object context) throws ModelExecutionException, ModelDefinitionException,
CloneNotSupportedException {
if (!isProxyObject(context)) {
throw new ClipboardOperationException("Cannot paste here: context is not valid");
}
return getHandler(context).paste(clipboard);
}
public Object paste(Clipboard clipboard, ModelProperty<?> modelProperty, Object context) throws ModelExecutionException,
ModelDefinitionException, CloneNotSupportedException {
if (!isProxyObject(context)) {
throw new ClipboardOperationException("Cannot paste here: context is not valid");
}
return getHandler(context).paste(clipboard, (ModelProperty) modelProperty);
}
public Object paste(Clipboard clipboard, ModelProperty<?> modelProperty, PastingPoint pp, Object context)
throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException {
if (!isProxyObject(context)) {
throw new ClipboardOperationException("Cannot paste here: context is not valid");
}
return getHandler(context).paste(clipboard, (ModelProperty) modelProperty, pp);
}
public void serialize(Object object, OutputStream os) throws IOException {
serialize(object, os, SerializationPolicy.PERMISSIVE);
}
public void serialize(Object object, OutputStream os, SerializationPolicy policy) throws IOException {
XMLSerializer serializer = new XMLSerializer(this, policy);
serializer.serializeDocument(object, os);
}
public Object deserialize(InputStream is) throws IOException, JDOMException, InvalidDataException, ModelDefinitionException {
return deserialize(is, DeserializationPolicy.PERMISSIVE);
}
public Object deserialize(InputStream is, DeserializationPolicy policy) throws IOException, JDOMException, InvalidDataException,
ModelDefinitionException {
XMLDeserializer deserializer = new XMLDeserializer(this, policy);
return deserializer.deserializeDocument(is);
}
public Object deserialize(String input) throws IOException, JDOMException, InvalidDataException, ModelDefinitionException {
return deserialize(input, DeserializationPolicy.PERMISSIVE);
}
public Object deserialize(String input, DeserializationPolicy policy) throws IOException, JDOMException, InvalidDataException,
ModelDefinitionException {
XMLDeserializer deserializer = new XMLDeserializer(this, policy);
return deserializer.deserializeDocument(input);
}
}