/** * */ package org.openflexo.model.factory; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.lang.reflect.Array; 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.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyObject; import javax.annotation.Nonnull; import org.openflexo.model.ModelContext; import org.openflexo.model.ModelEntity; import org.openflexo.model.ModelProperty; import org.openflexo.model.annotations.Adder; import org.openflexo.model.annotations.Finder; import org.openflexo.model.annotations.Getter; import org.openflexo.model.annotations.Initializer; import org.openflexo.model.annotations.PastingPoint; import org.openflexo.model.annotations.Remover; import org.openflexo.model.annotations.Setter; import org.openflexo.model.exceptions.InvalidDataException; import org.openflexo.model.exceptions.ModelDefinitionException; import org.openflexo.model.exceptions.ModelExecutionException; import org.openflexo.model.exceptions.NoSuchEntityException; import org.openflexo.model.exceptions.UnitializedEntityException; import org.openflexo.model.factory.ModelFactory.PAMELAProxyFactory; import org.openflexo.toolbox.HasPropertyChangeSupport; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; public class ProxyMethodHandler<I> implements MethodHandler, PropertyChangeListener { private static final String DELETED = "deleted"; private static final String MODIFIED = "modified"; private static final String DESERIALIZING = "deserializing"; private static final String SERIALIZING = "serializing"; private I object; private Map<String, Object> values; private Map<String, Object> oldValues; private boolean deleting = false; private boolean deleted = false; private boolean initialized = false; private boolean serializing = false; private boolean deserializing = false; private boolean createdByCloning = false; private boolean beingCloned = false; private boolean modified = false; private PropertyChangeSupport propertyChangeSupport; private boolean initializing; private static Method PERFORM_SUPER_GETTER; private static Method PERFORM_SUPER_SETTER; private static Method PERFORM_SUPER_ADDER; private static Method PERFORM_SUPER_REMOVER; private static Method PERFORM_SUPER_DELETER; private static Method PERFORM_SUPER_FINDER; private static Method PERFORM_SUPER_GETTER_ENTITY; private static Method PERFORM_SUPER_SETTER_ENTITY; private static Method PERFORM_SUPER_ADDER_ENTITY; private static Method PERFORM_SUPER_REMOVER_ENTITY; private static Method PERFORM_SUPER_DELETER_ENTITY; private static Method PERFORM_SUPER_FINDER_ENTITY; private static Method PERFORM_SUPER_SET_MODIFIED; private static Method IS_MODIFIED; private static Method SET_MODIFIED; private static Method IS_SERIALIZING; private static Method IS_DESERIALIZING; private static Method TO_STRING; private static Method GET_PROPERTY_CHANGE_SUPPORT; private static Method GET_DELETED_PROPERTY; private static Method CLONE_OBJECT; private static Method CLONE_OBJECT_WITH_CONTEXT; private static Method IS_CREATED_BY_CLONING; private static Method IS_BEING_CLONED; private static Method DELETE_OBJECT; private static Method DELETE_OBJECT_WITH_CONTEXT; private static Method IS_DELETED; private final PAMELAProxyFactory<I> pamelaProxyFactory; static { try { PERFORM_SUPER_GETTER = AccessibleProxyObject.class.getMethod("performSuperGetter", String.class); PERFORM_SUPER_SETTER = AccessibleProxyObject.class.getMethod("performSuperSetter", String.class, Object.class); PERFORM_SUPER_ADDER = AccessibleProxyObject.class.getMethod("performSuperAdder", String.class, Object.class); PERFORM_SUPER_REMOVER = AccessibleProxyObject.class.getMethod("performSuperRemover", String.class, Object.class); PERFORM_SUPER_DELETER = AccessibleProxyObject.class.getMethod("performSuperDelete"); PERFORM_SUPER_FINDER = AccessibleProxyObject.class.getMethod("performSuperFinder", String.class, Object.class); PERFORM_SUPER_GETTER_ENTITY = AccessibleProxyObject.class.getMethod("performSuperGetter", String.class, Class.class); PERFORM_SUPER_SETTER_ENTITY = AccessibleProxyObject.class.getMethod("performSuperSetter", String.class, Object.class, Class.class); PERFORM_SUPER_ADDER_ENTITY = AccessibleProxyObject.class .getMethod("performSuperAdder", String.class, Object.class, Class.class); PERFORM_SUPER_REMOVER_ENTITY = AccessibleProxyObject.class.getMethod("performSuperRemover", String.class, Object.class, Class.class); PERFORM_SUPER_DELETER_ENTITY = AccessibleProxyObject.class.getMethod("performSuperDelete", Class.class); PERFORM_SUPER_FINDER_ENTITY = AccessibleProxyObject.class.getMethod("performSuperFinder", String.class, Object.class, Class.class); IS_SERIALIZING = AccessibleProxyObject.class.getMethod("isSerializing"); IS_DESERIALIZING = AccessibleProxyObject.class.getMethod("isDeserializing"); IS_MODIFIED = AccessibleProxyObject.class.getMethod("isModified"); IS_DELETED = AccessibleProxyObject.class.getMethod("isDeleted"); SET_MODIFIED = AccessibleProxyObject.class.getMethod("setModified", boolean.class); PERFORM_SUPER_SET_MODIFIED = AccessibleProxyObject.class.getMethod("performSuperSetModified", boolean.class); DELETE_OBJECT = AccessibleProxyObject.class.getMethod("delete"); DELETE_OBJECT_WITH_CONTEXT = AccessibleProxyObject.class.getMethod("delete", Array.newInstance(Object.class, 0).getClass()); GET_PROPERTY_CHANGE_SUPPORT = HasPropertyChangeSupport.class.getMethod("getPropertyChangeSupport"); GET_DELETED_PROPERTY = HasPropertyChangeSupport.class.getMethod("getDeletedProperty"); TO_STRING = Object.class.getMethod("toString"); CLONE_OBJECT = CloneableProxyObject.class.getMethod("cloneObject"); CLONE_OBJECT_WITH_CONTEXT = CloneableProxyObject.class.getMethod("cloneObject", Array.newInstance(Object.class, 0).getClass()); IS_CREATED_BY_CLONING = CloneableProxyObject.class.getMethod("isCreatedByCloning"); IS_BEING_CLONED = CloneableProxyObject.class.getMethod("isBeingCloned"); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } public ProxyMethodHandler(PAMELAProxyFactory<I> pamelaProxyFactory) throws ModelDefinitionException { this.pamelaProxyFactory = pamelaProxyFactory; values = new HashMap<String, Object>(getModelEntity().getPropertiesSize(), 1.0f); initialized = !getModelEntity().hasInitializers(); } public I getObject() { return object; } public void setObject(I object) { this.object = object; } public ModelFactory getModelFactory() { return pamelaProxyFactory.getModelFactory(); } public ModelEntity<I> getModelEntity() { return pamelaProxyFactory.getModelEntity(); } public Class getSuperClass() { return pamelaProxyFactory.getSuperclass(); } public Class getOverridingSuperClass() { return pamelaProxyFactory.getOverridingSuperClass(); } private ModelContext getModelContext() { return getModelFactory().getModelContext(); } @Override public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable { Initializer initializer = method.getAnnotation(Initializer.class); if (initializer != null) { internallyInvokeInitializer(getModelEntity().getInitializers(method), args); return self; } if (!initialized && !initializing) { throw new UnitializedEntityException(getModelEntity()); } Getter getter = method.getAnnotation(Getter.class); if (getter != null) { String id = getter.value(); return internallyInvokeGetter(id); } Setter setter = method.getAnnotation(Setter.class); if (setter != null) { String id = setter.value(); internallyInvokeSetter(id, args); return null; } Adder adder = method.getAnnotation(Adder.class); if (adder != null) { String id = adder.value(); internallyInvokeAdder(id, args); return null; } Remover remover = method.getAnnotation(Remover.class); if (remover != null) { String id = remover.value(); internallyInvokerRemover(id, args); return null; } Finder finder = method.getAnnotation(Finder.class); if (finder != null) { return internallyInvokeFinder(finder, args); } if (methodIsEquivalentTo(method, GET_PROPERTY_CHANGE_SUPPORT)) { return getPropertyChangeSuppport(); } else if (methodIsEquivalentTo(method, PERFORM_SUPER_GETTER)) { return internallyInvokeGetter(getModelEntity().getModelProperty((String) args[0])); } else if (methodIsEquivalentTo(method, PERFORM_SUPER_SETTER)) { internallyInvokeSetter(getModelEntity().getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_ADDER)) { internallyInvokeAdder(getModelEntity().getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_REMOVER)) { internallyInvokeRemover(getModelEntity().getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_FINDER)) { internallyInvokeFinder(finder, args); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_GETTER_ENTITY)) { ModelEntity<? super I> e = getModelEntityFromArg((Class<?>) args[1]); return internallyInvokeGetter(e.getModelProperty((String) args[0])); } else if (methodIsEquivalentTo(method, PERFORM_SUPER_SETTER_ENTITY)) { ModelEntity<? super I> e = getModelEntityFromArg((Class<?>) args[2]); internallyInvokeSetter(e.getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_ADDER_ENTITY)) { ModelEntity<? super I> e = getModelEntityFromArg((Class<?>) args[2]); internallyInvokeAdder(e.getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_REMOVER_ENTITY)) { ModelEntity<? super I> e = getModelEntityFromArg((Class<?>) args[2]); internallyInvokeRemover(e.getModelProperty((String) args[0]), args[1]); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_DELETER_ENTITY)) { internallyInvokeDeleter(); return null; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_FINDER_ENTITY)) { Class<?> class1 = (Class<?>) args[2]; ModelEntity<? super I> e = getModelEntityFromArg(class1); finder = e.getFinder((String) args[0]); if (finder != null) { return internallyInvokeFinder(finder, args); } else { throw new ModelExecutionException("No such finder defined. Finder '" + args[0] + "' could not be found on entity " + class1.getName()); } } else if (methodIsEquivalentTo(method, PERFORM_SUPER_FINDER)) { finder = getModelEntity().getFinder((String) args[0]); if (finder != null) { return internallyInvokeFinder(finder, args); } else { throw new ModelExecutionException("No such finder defined. Finder '" + args[0] + "' could not be found on entity " + getModelEntity().getImplementedInterface().getName()); } } else if (methodIsEquivalentTo(method, IS_SERIALIZING)) { return isSerializing(); } else if (methodIsEquivalentTo(method, IS_DESERIALIZING)) { return isDeserializing(); } else if (methodIsEquivalentTo(method, IS_MODIFIED)) { return isModified(); } else if (methodIsEquivalentTo(method, SET_MODIFIED) || methodIsEquivalentTo(method, PERFORM_SUPER_SET_MODIFIED)) { internallyInvokeSetModified((Boolean) args[0]); return null; } else if (methodIsEquivalentTo(method, TO_STRING)) { return internallyInvokeToString(); } else if (methodIsEquivalentTo(method, CLONE_OBJECT)) { return cloneObject(); } else if (methodIsEquivalentTo(method, IS_DELETED)) { return deleted; } else if (methodIsEquivalentTo(method, IS_BEING_CLONED)) { return beingCloned; } else if (methodIsEquivalentTo(method, IS_CREATED_BY_CLONING)) { return createdByCloning; } else if (methodIsEquivalentTo(method, GET_DELETED_PROPERTY)) { return DELETED; } else if (methodIsEquivalentTo(method, PERFORM_SUPER_DELETER)) { internallyInvokeDeleter(); return null; } else if (methodIsEquivalentTo(method, DELETE_OBJECT)) { internallyInvokeDeleter(); return null; } else if (methodIsEquivalentTo(method, DELETE_OBJECT_WITH_CONTEXT)) { internallyInvokeDeleter(args); return null; } else if (methodIsEquivalentTo(method, CLONE_OBJECT_WITH_CONTEXT)) { return cloneObject(args); } System.err.println("Cannot handle method " + method); return null; } private boolean methodIsEquivalentTo(Method method, Method to) { return method.getName().equals(to.getName()) && method.getReturnType().equals(to.getReturnType()) && Arrays.deepEquals(method.getParameterTypes(), to.getParameterTypes()); } private PropertyChangeSupport getPropertyChangeSuppport() { if (propertyChangeSupport == null) { propertyChangeSupport = new PropertyChangeSupport(getObject()); } return propertyChangeSupport; } private void internallyInvokeInitializer(org.openflexo.model.ModelInitializer in, Object[] args) throws ModelDefinitionException { initializing = true; try { List<String> parameters = in.getParameters(); for (int i = 0; i < parameters.size(); i++) { String parameter = parameters.get(i); if (parameter != null) { internallyInvokeSetter(getModelEntity().getModelProperty(parameter), args[i]); } } } finally { initialized = true; initializing = false; } } private @Nonnull ModelEntity<? super I> getModelEntityFromArg(Class<?> class1) throws ModelDefinitionException { ModelEntity<?> e = getModelContext().getModelEntity(class1); if (e == null) { throw new NoSuchEntityException(class1); } if (!e.isAncestorOf(getModelEntity())) { throw new ModelExecutionException(((Class<?>) class1).getName() + " is not a super interface of " + getModelEntity().getImplementedInterface().getName()); } // Is e is an ancestor of modelEntity, this means that e is a super interface of the implementedInterface of modelEntity and we can // therefore cast e to ModelEntity<? super I> return (ModelEntity<? super I>) e; } private Object internallyInvokeGetter(String propertyIdentifier) throws ModelDefinitionException { ModelProperty<? super I> property = getModelEntity().getModelProperty(propertyIdentifier); return internallyInvokeGetter(property); } private void internallyInvokeSetter(String propertyIdentifier, Object[] args) throws ModelDefinitionException { ModelProperty<? super I> property = getModelEntity().getModelProperty(propertyIdentifier); internallyInvokeSetter(property, args[0]); } private void internallyInvokeAdder(String propertyIdentifier, Object[] args) throws ModelDefinitionException { ModelProperty<? super I> property = getModelEntity().getModelProperty(propertyIdentifier); internallyInvokeAdder(property, args[0]); } private void internallyInvokerRemover(String id, Object[] args) throws ModelDefinitionException { ModelProperty<? super I> property = getModelEntity().getModelProperty(id); internallyInvokeRemover(property, args[0]); } private void internallyInvokeDeleter(Object... context) throws ModelDefinitionException { if (deleted || deleting) { return; } deleting = true; if (context == null) { context = new Object[] { getObject() }; } else { context = Arrays.copyOf(context, context.length + 1); context[context.length - 1] = getObject(); } oldValues = new HashMap<String, Object>(values); ModelEntity<I> modelEntity = getModelEntity(); Set<Object> objects = new HashSet<Object>(); for (Object o : context) { objects.add(o); } List<Object> embeddedObjects = getModelFactory().getEmbeddedObjects(getObject(), EmbeddingType.DELETION, objects.toArray(new Object[objects.size()])); objects.addAll(embeddedObjects); context = objects.toArray(new Object[objects.size()]); for (Object object : embeddedObjects) { if (object instanceof AccessibleProxyObject) { ((AccessibleProxyObject) object).delete(context); } } Iterator<ModelProperty<? super I>> i = modelEntity.getProperties(); while (i.hasNext()) { ModelProperty<? super I> property = i.next(); if (property.getSetterMethod() != null) { invokeSetter(property, null); } else { internallyInvokeSetter(property, null); } } deleted = true; deleting = false; getPropertyChangeSuppport().firePropertyChange(DELETED, false, true); propertyChangeSupport = null; } public Object invokeGetter(ModelProperty<? super I> property) { try { return property.getGetterMethod().invoke(getObject(), (Object[]) null); } catch (IllegalArgumentException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } catch (InvocationTargetException e) { throw new ModelExecutionException(e); } } public void invokeSetter(ModelProperty<? super I> property, Object value) { try { property.getSetterMethod().invoke(getObject(), value); } catch (IllegalArgumentException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } catch (InvocationTargetException e) { throw new ModelExecutionException(e); } } public void invokeAdder(ModelProperty<? super I> property, Object value) { try { property.getAdderMethod().invoke(getObject(), value); } catch (IllegalArgumentException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } catch (InvocationTargetException e) { throw new ModelExecutionException(e); } } public void invokeRemover(ModelProperty<? super I> property, Object value) { try { property.getRemoverMethod().invoke(getObject(), value); } catch (IllegalArgumentException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } catch (InvocationTargetException e) { throw new ModelExecutionException(e); } } public Object invokeGetter(String propertyIdentifier) throws ModelDefinitionException { return invokeGetter(getModelEntity().getModelProperty(propertyIdentifier)); } public void invokeSetter(String propertyIdentifier, Object value) throws ModelDefinitionException { invokeSetter(getModelEntity().getModelProperty(propertyIdentifier), value); } public void invokeAdder(String propertyIdentifier, Object value) throws ModelDefinitionException { invokeAdder(getModelEntity().getModelProperty(propertyIdentifier), value); } public void invokeRemover(String propertyIdentifier, Object value) throws ModelDefinitionException { invokeRemover(getModelEntity().getModelProperty(propertyIdentifier), value); } private Object internallyInvokeGetter(ModelProperty<? super I> property) throws ModelDefinitionException { switch (property.getCardinality()) { case SINGLE: return invokeGetterForSingleCardinality(property); case LIST: return invokeGetterForListCardinality(property); case MAP: return invokeGetterForMapCardinality(property); default: throw new ModelExecutionException("Invalid cardinality: " + property.getCardinality()); } } private Object invokeGetterForSingleCardinality(ModelProperty<? super I> property) throws ModelDefinitionException { if (property.getGetter() == null) { throw new ModelExecutionException("Getter is not defined for property " + property); } if (property.getReturnedValue() != null) { // Simple implementation of ReturnedValue. This can be drastically improved String returnedValue = property.getReturnedValue().value(); StringTokenizer st = new StringTokenizer(returnedValue, "."); Object value = this; ProxyMethodHandler<?> handler = this; while (st.hasMoreTokens()) { String token = st.nextToken(); value = handler.invokeGetter(token); if (value != null) { if (st.hasMoreTokens()) { if (!(value instanceof ProxyObject)) { throw new ModelExecutionException("Cannot invoke " + st.nextToken() + " on object of type " + value.getClass().getName() + " (caused by returned value: " + returnedValue + ")"); } handler = (ProxyMethodHandler<?>) ((ProxyObject) value).getHandler(); } } else { return null; } } return value; } Object returned = values.get(property.getPropertyIdentifier()); if (returned != null) { return returned; } else { Object defaultValue; try { defaultValue = property.getDefaultValue(getModelFactory()); } catch (InvalidDataException e) { throw new ModelExecutionException("Invalid default value '" + property.getGetter().defaultValue() + "' for property " + property + " with type " + property.getType(), e); } if (defaultValue != null) { values.put(property.getPropertyIdentifier(), defaultValue); return defaultValue; } if (property.getType().isPrimitive()) { throw new ModelExecutionException("No default value defined for primitive property " + property); } return null; } } private List<?> invokeGetterForListCardinality(ModelProperty<? super I> property) { if (property.getGetter() == null) { throw new ModelExecutionException("Getter is not defined for property " + property); } List<?> returned = (List<?>) values.get(property.getPropertyIdentifier()); if (returned != null) { return returned; } else { Class<? extends List> listClass = getModelFactory().getListImplementationClass(); try { returned = listClass.newInstance(); } catch (InstantiationException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } if (returned != null) { values.put(property.getPropertyIdentifier(), returned); return returned; } return null; } } private Map<?, ?> invokeGetterForMapCardinality(ModelProperty<? super I> property) { if (property.getGetter() == null) { throw new ModelExecutionException("Getter is not defined for property " + property); } Map<?, ?> returned = (Map<?, ?>) values.get(property.getPropertyIdentifier()); if (returned != null) { return returned; } else { Class<? extends Map> mapClass = getModelFactory().getMapImplementationClass(); try { returned = mapClass.newInstance(); } catch (InstantiationException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } if (returned != null) { values.put(property.getPropertyIdentifier(), returned); return returned; } return null; } } void invokeSetterForDeserialization(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { if (property.getSetterMethod() != null) { invokeSetter(property, value); } else { internallyInvokeSetter(property, value); } } private void internallyInvokeSetter(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { switch (property.getCardinality()) { case SINGLE: invokeSetterForSingleCardinality(property, value); break; case LIST: invokeSetterForListCardinality(property, value); break; case MAP: invokeSetterForMapCardinality(property, value); break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getCardinality()); } } private void invokeSetterForSingleCardinality(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { if (property.getSetter() == null && !isDeserializing() && !initializing && !createdByCloning && !deleting) { throw new ModelExecutionException("Setter is not defined for property " + property); } Object oldValue = invokeGetter(property); // Is it a real change ? if (!isEqual(oldValue, value)) { // First handle inverse property for oldValue if (property.getInverseProperty() != null) { switch (property.getInverseProperty().getCardinality()) { case SINGLE: if (oldValue != null) { getModelFactory().getHandler(oldValue).invokeSetter(property.getInverseProperty(), null); } break; case LIST: if (oldValue != null) { getModelFactory().getHandler(oldValue).invokeRemover(property.getInverseProperty(), getObject()); } break; case MAP: break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getInverseProperty().getCardinality()); } } // Now do the job, internally if (value == null) { values.remove(property.getPropertyIdentifier()); } else { values.put(property.getPropertyIdentifier(), value); } firePropertyChange(property.getPropertyIdentifier(), oldValue, value); if (getModelEntity().getModify() != null && getModelEntity().getModify().synchWithForward() && property.getPropertyIdentifier().equals(getModelEntity().getModify().forward())) { if (oldValue instanceof HasPropertyChangeSupport) { ((HasPropertyChangeSupport) oldValue).getPropertyChangeSupport().removePropertyChangeListener(MODIFIED, this); } if (value instanceof HasPropertyChangeSupport) { ((HasPropertyChangeSupport) value).getPropertyChangeSupport().addPropertyChangeListener(MODIFIED, this); } } // First handle inverse property for newValue if (property.getInverseProperty() != null) { ProxyMethodHandler<Object> handler = getModelFactory().getHandler(value); switch (property.getInverseProperty().getCardinality()) { case SINGLE: if (value != null) { handler.invokeSetter(property.getInverseProperty(), getObject()); } break; case LIST: // System.out.println("Je viens de faire le setter pour "+property+" value="+value); if (value != null) { handler.invokeAdder(property.getInverseProperty(), getObject()); // System.out.println("J'ai execute: "+property.getInverseProperty().getAdderMethod()+" avec "+getObject()); } break; case MAP: break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getInverseProperty().getCardinality()); } } if (property.isSerializable()) { invokeSetModified(true); } } } private void firePropertyChange(String propertyIdentifier, Object oldValue, Object value) { if (getObject() instanceof HasPropertyChangeSupport) { ((HasPropertyChangeSupport) getObject()).getPropertyChangeSupport().firePropertyChange(propertyIdentifier, oldValue, value); } } @Override public void propertyChange(PropertyChangeEvent evt) { try { if (getModelEntity().getModify() != null && getModelEntity().getModify().synchWithForward()) { Object forwarded = internallyInvokeGetter(getModelEntity().getModify().forward()); if (evt.getSource() == forwarded) { if (MODIFIED.equals(evt.getPropertyName())) { invokeSetModified((Boolean) evt.getNewValue()); } } } } catch (ModelDefinitionException e) { e.printStackTrace(); } } private void invokeSetModified(boolean modified) throws ModelDefinitionException { if (getObject() instanceof AccessibleProxyObject) { ((AccessibleProxyObject) getObject()).setModified(modified); } else { internallyInvokeSetModified(modified); } } private void invokeSetterForListCardinality(ModelProperty<? super I> property, Object value) { if (property.getSetter() == null && !isDeserializing() && !initializing && !createdByCloning && !deleting) { throw new ModelExecutionException("Setter is not defined for property " + property); } if (value != null && !(value instanceof List)) { throw new ModelExecutionException("Trying to set a " + value.getClass().getName() + " on property " + property + " but only " + List.class.getName() + " instances or null is allowed"); } List<?> oldValue = (List<?>) invokeGetter(property); for (Object o : new ArrayList<Object>(oldValue)) { invokeRemover(property, o); } if (value != null) { for (Object o : (List<?>) value) { invokeAdder(property, o); } } } private void invokeSetterForMapCardinality(ModelProperty<? super I> property, Object value) { if (property.getSetter() == null && !isDeserializing() && !initializing && !createdByCloning && !deleting) { throw new ModelExecutionException("Setter is not defined for property " + property); } // TODO implement this throw new UnsupportedOperationException("Setter for MAP: not implemented yet"); } void invokeAdderForDeserialization(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { if (property.getAdderMethod() != null) { invokeAdder(property, value); } else { internallyInvokeAdder(property, value); } } private void internallyInvokeAdder(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { // System.out.println("Invoke ADDER "+property.getPropertyIdentifier()); switch (property.getCardinality()) { case SINGLE: throw new ModelExecutionException("Cannot invoke ADDER on " + property.getPropertyIdentifier() + ": Invalid cardinality SINGLE"); case LIST: invokeAdderForListCardinality(property, value); break; case MAP: invokeAdderForMapCardinality(property, value); break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getCardinality()); } } private void invokeAdderForListCardinality(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { if (property.getAdder() == null && !isDeserializing() && !initializing && !createdByCloning && !deleting) { throw new ModelExecutionException("Adder is not defined for property " + property); } List list = (List) invokeGetter(property); if (!list.contains(value)) { list.add(value); firePropertyChange(property.getPropertyIdentifier(), null, value); // Handle inverse property for new value if (property.getInverseProperty() != null) { switch (property.getInverseProperty().getCardinality()) { case SINGLE: if (value != null) { getModelFactory().getHandler(value).invokeSetter(property.getInverseProperty(), getObject()); } break; case LIST: if (value != null) { getModelFactory().getHandler(value).invokeAdder(property.getInverseProperty(), getObject()); } break; case MAP: break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getInverseProperty().getCardinality()); } } if (property.isSerializable()) { invokeSetModified(true); } } } private void invokeAdderForMapCardinality(ModelProperty<? super I> property, Object value) { if (property.getAdder() == null && !isDeserializing() && !initializing && !createdByCloning) { throw new ModelExecutionException("Adder is not defined for property " + property); } // TODO implement this throw new UnsupportedOperationException("Adder for MAP: not implemented yet"); } private void internallyInvokeRemover(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { // System.out.println("Invoke REMOVER "+property.getPropertyIdentifier()); switch (property.getCardinality()) { case SINGLE: throw new ModelExecutionException("Cannot invoke REMOVER on " + property.getPropertyIdentifier() + ": Invalid cardinality SINGLE"); case LIST: invokeRemoverForListCardinality(property, value); break; case MAP: invokeRemoverForMapCardinality(property, value); break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getCardinality()); } } private void invokeRemoverForListCardinality(ModelProperty<? super I> property, Object value) throws ModelDefinitionException { if (property.getRemover() == null) { throw new ModelExecutionException("Remover is not defined for property " + property); } List<?> list = (List<?>) invokeGetter(property); if (list.contains(value)) { list.remove(value); // Handle inverse property for new value if (property.getInverseProperty() != null) { switch (property.getInverseProperty().getCardinality()) { case SINGLE: if (value != null) { getModelFactory().getHandler(value).invokeSetter(property.getInverseProperty(), null); } break; case LIST: if (value != null) { getModelFactory().getHandler(value).invokeAdder(property.getInverseProperty(), null); } break; case MAP: break; default: throw new ModelExecutionException("Invalid cardinality: " + property.getInverseProperty().getCardinality()); } } if (property.isSerializable()) { invokeSetModified(true); } } } private void invokeRemoverForMapCardinality(ModelProperty<? super I> property, Object value) { if (property.getRemover() == null) { throw new ModelExecutionException("Remover is not defined for property " + property); } // TODO implement this throw new UnsupportedOperationException("Remover for MAP: not implemented yet"); } private Object internallyInvokeFinder(@Nonnull Finder finder, Object[] args) throws ModelDefinitionException { if (args.length == 0) { throw new ModelDefinitionException("Finder " + finder.collection() + " by attribute " + finder.attribute() + " does not declare enough argument!"); } String collectionID = finder.collection(); ModelProperty<? super I> property = getModelEntity().getModelProperty(collectionID); Object collection = internallyInvokeGetter(property); if (collection == null) { return null; } Object value = args[0]; String attribute = finder.attribute(); if (collection instanceof Map<?, ?>) { collection = ((Map<?, ?>) collection).values(); } if (collection instanceof Iterable) { if (finder.isMultiValued()) { List<Object> objects = new ArrayList<Object>(); for (Object o : (Iterable<?>) collection) { if (isObjectAttributeEquals(o, attribute, value)) { objects.add(o); } } return objects; } else { for (Object o : (Iterable<?>) collection) { if (isObjectAttributeEquals(o, attribute, value)) { return o; } } return null; } } throw new ModelDefinitionException("finder works only on maps and iterable"); } private boolean isObjectAttributeEquals(Object o, String attribute, Object value) throws ModelDefinitionException { ProxyMethodHandler<?> handler = getModelFactory().getHandler(o); if (handler != null) { Object attributeValue = handler.invokeGetter(attribute); return isEqual(attributeValue, value); } else { throw new ModelDefinitionException("Found object of type " + o.getClass().getName() + " but is not an instanceof ProxyObject:\n" + o); } } private static boolean isEqual(Object oldValue, Object newValue) { if (oldValue == null) { return newValue == null; } if (oldValue == newValue) { return true; } return oldValue.equals(newValue); } /*private Object cloneObject() throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { System.out.println("Tiens je clone "+getObject()); if (!(getObject() instanceof CloneableProxyObject)) throw new CloneNotSupportedException(); Hashtable<CloneableProxyObject,Object> clonedObjects = new Hashtable<CloneableProxyObject, Object>(); Object returned = performClone(clonedObjects); for (CloneableProxyObject o : clonedObjects.keySet()) { ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(o); clonedObjectHandler.finalizeClone(clonedObjects); } return returned; } private Object appendToClonedObjects(Hashtable<CloneableProxyObject,Object> clonedObjects, CloneableProxyObject objectToCloneOrReference) throws ModelExecutionException, ModelDefinitionException { Object returned = clonedObjects.get(objectToCloneOrReference); if (returned != null) return returned; ProxyMethodHandler<?> clonedValueHandler = getModelFactory().getHandler(objectToCloneOrReference); returned = clonedValueHandler.performClone(clonedObjects); System.out.println("for "+objectToCloneOrReference+" clone is "+returned); return returned; } private Object performClone(Hashtable<CloneableProxyObject,Object> clonedObjects) throws ModelExecutionException, ModelDefinitionException { System.out.println("******* performClone "+getObject()); Object returned = null; try { returned = getModelEntity().newInstance(); } catch (IllegalArgumentException e) { throw new ModelExecutionException(e); } catch (NoSuchMethodException e) { throw new ModelExecutionException(e); } catch (InstantiationException e) { throw new ModelExecutionException(e); } catch (IllegalAccessException e) { throw new ModelExecutionException(e); } catch (InvocationTargetException e) { throw new ModelExecutionException(e); } clonedObjects.put((CloneableProxyObject)getObject(),returned); ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(returned); Enumeration<ModelProperty<? super I>> properties = getModelEntity().getProperties(); while(properties.hasMoreElements()) { ModelProperty p = properties.nextElement(); switch (p.getCardinality()) { case SINGLE: Object singleValue = invokeGetter(p); switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().isModelEntity(p.getType()) && singleValue instanceof CloneableProxyObject) { appendToClonedObjects(clonedObjects, (CloneableProxyObject)singleValue); } break; case REFERENCE: break; case FACTORY: break; case IGNORE: break; } break; case LIST: List values = (List)invokeGetter(p); for (Object value : values) { switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().isModelEntity(p.getType()) && value instanceof CloneableProxyObject) { appendToClonedObjects(clonedObjects, (CloneableProxyObject)value); } break; case REFERENCE: break; case FACTORY: break; case IGNORE: break; } } break; default: break; } } return returned; } private Object finalizeClone(Hashtable<CloneableProxyObject,Object> clonedObjects) throws ModelExecutionException, ModelDefinitionException { Object clonedObject = clonedObjects.get(getObject()); System.out.println("Tiens je finalise le clone pour "+getObject()+" le clone c'est "+clonedObject); ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(clonedObject); Enumeration<ModelProperty<? super I>> properties = getModelEntity().getProperties(); while(properties.hasMoreElements()) { ModelProperty p = properties.nextElement(); switch (p.getCardinality()) { case SINGLE: Object singleValue = invokeGetter(p); switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().getStringEncoder().isConvertable(p.getType())) { Object clonedValue = null; try { String clonedValueAsString = getModelFactory().getStringEncoder().toString(singleValue); clonedValue = getModelFactory().getStringEncoder().fromString(p.getType(),clonedValueAsString); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } clonedObjectHandler.invokeSetter(p,clonedValue); } else if (getModelFactory().isModelEntity(p.getType()) && singleValue instanceof CloneableProxyObject) { Object clonedValue = clonedObjects.get(singleValue); clonedObjectHandler.invokeSetter(p,clonedValue); } break; case REFERENCE: Object referenceValue = (singleValue != null ? clonedObjects.get(singleValue) : null); if (referenceValue == null) referenceValue = singleValue; clonedObjectHandler.invokeSetter(p,referenceValue); break; case FACTORY: // TODO Not implemented break; case IGNORE: break; } break; case LIST: List values = (List)invokeGetter(p); System.out.println("values:"+values.hashCode()+" "+values); List valuesToClone = new ArrayList<Object>(values); for (Object value : valuesToClone) { switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().getStringEncoder().isConvertable(p.getType())) { Object clonedValue = null; try { String clonedValueAsString = getModelFactory().getStringEncoder().toString(value); clonedValue = getModelFactory().getStringEncoder().fromString(p.getType(),clonedValueAsString); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } List l = (List)clonedObjectHandler.invokeGetter(p); System.out.println("l:"+l.hashCode()+" "+l); clonedObjectHandler.invokeAdder(p,clonedValue); } else if (getModelFactory().isModelEntity(p.getType()) && value instanceof CloneableProxyObject) { Object clonedValue = clonedObjects.get(value); clonedObjectHandler.invokeAdder(p,clonedValue); } break; case REFERENCE: Object referenceValue = (value != null ? clonedObjects.get(value) : null); if (referenceValue == null) referenceValue = value; clonedObjectHandler.invokeAdder(p,referenceValue); break; case FACTORY: // TODO Not implemented break; case IGNORE: break; } } break; default: break; } } return clonedObject; }*/ /** * Clone current object, using meta informations provided by related class All property should be annoted with a @CloningStrategy * 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. If a property is marked as @CloningStrategy.CLONE but lead to an object outside scope of cloning * (the closure being computed), then resulting value is nullified. When context is not set, don't compute any closure, and clone all * required objects * * @param context * @return * @throws ModelExecutionException * @throws ModelDefinitionException * @throws CloneNotSupportedException * when supplied object is not implementing CloneableProxyObject interface */ protected Object cloneObject(Object... context) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { // System.out.println("Cloning "+getObject()); if (context != null && context.length == 1 && context[0].getClass().isArray()) { context = (Object[]) context[0]; } // Append this object to supplied context if (context != null && context.length > 0) { Object[] newContext = new Object[context.length + 1]; for (int i = 0; i < context.length; i++) { newContext[i] = context[i]; } newContext[context.length] = getObject(); context = newContext; } if (!(getObject() instanceof CloneableProxyObject)) { throw new CloneNotSupportedException(); } Hashtable<CloneableProxyObject, Object> clonedObjects = new Hashtable<CloneableProxyObject, Object>(); Object returned = performClone(clonedObjects, context); for (CloneableProxyObject o : clonedObjects.keySet()) { ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(o); clonedObjectHandler.finalizeClone(clonedObjects, context); } return returned; } /** * Internally used for cloning computation */ private Object appendToClonedObjects(Hashtable<CloneableProxyObject, Object> clonedObjects, CloneableProxyObject objectToCloneOrReference) throws ModelExecutionException, ModelDefinitionException { Object returned = clonedObjects.get(objectToCloneOrReference); if (returned != null) { return returned; } ProxyMethodHandler<?> clonedValueHandler = getModelFactory().getHandler(objectToCloneOrReference); returned = clonedValueHandler.performClone(clonedObjects); // System.out.println("for "+objectToCloneOrReference+" clone is "+returned); return returned; } /** * Internally used for cloning computation */ private Object performClone(Hashtable<CloneableProxyObject, Object> clonedObjects, Object... context) throws ModelExecutionException, ModelDefinitionException { // System.out.println("******* performClone "+getObject()); boolean setIsBeingCloned = !beingCloned; beingCloned = true; Object returned = null; try { returned = getModelFactory().newInstance(getModelEntity()); ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(returned); clonedObjectHandler.createdByCloning = true; clonedObjectHandler.initialized = true; try { clonedObjects.put((CloneableProxyObject) getObject(), returned); Iterator<ModelProperty<? super I>> properties = getModelEntity().getProperties(); while (properties.hasNext()) { ModelProperty p = properties.next(); switch (p.getCardinality()) { case SINGLE: Object singleValue = invokeGetter(p); switch (p.getCloningStrategy()) { case CLONE: if (ModelEntity.isModelEntity(p.getType()) && singleValue instanceof CloneableProxyObject) { if (!isPartOfContext(singleValue, EmbeddingType.CLOSURE, context)) { // Don't do it, outside of context } else { appendToClonedObjects(clonedObjects, (CloneableProxyObject) singleValue); } } break; case REFERENCE: break; case FACTORY: break; case IGNORE: break; } break; case LIST: List values = (List) invokeGetter(p); for (Object value : values) { switch (p.getCloningStrategy()) { case CLONE: if (ModelEntity.isModelEntity(p.getType()) && value instanceof CloneableProxyObject) { if (!isPartOfContext(value, EmbeddingType.CLOSURE, context)) { // Don't do it, outside of context } else { appendToClonedObjects(clonedObjects, (CloneableProxyObject) value); } } break; case REFERENCE: break; case FACTORY: break; case IGNORE: break; } } break; default: break; } } } finally { clonedObjectHandler.createdByCloning = false; } } finally { if (setIsBeingCloned) { beingCloned = false; } } return returned; } /** * Internally used for cloning computation */ private Object finalizeClone(Hashtable<CloneableProxyObject, Object> clonedObjects, Object... context) throws ModelExecutionException, ModelDefinitionException { Object clonedObject = clonedObjects.get(getObject()); // System.out.println("Finalizing clone for "+getObject()+" clone is "+clonedObject); ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(clonedObject); clonedObjectHandler.createdByCloning = true; try { Iterator<ModelProperty<? super I>> properties = getModelEntity().getProperties(); while (properties.hasNext()) { ModelProperty p = properties.next(); // TODO: cross-check that we should invoke continue // In the case of the deletedProperty, it is only normal that there are no setters. // We should either prevent this by validating that all properties (that are not deleted properties) // have a setter or allow properties to live without a setter. switch (p.getCardinality()) { case SINGLE: Object singleValue = invokeGetter(p); switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().getStringEncoder().isConvertable(p.getType())) { Object clonedValue = null; try { String clonedValueAsString = getModelFactory().getStringEncoder().toString(singleValue); clonedValue = getModelFactory().getStringEncoder().fromString(p.getType(), clonedValueAsString); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } clonedObjectHandler.internallyInvokeSetter(p, clonedValue); } else if (ModelEntity.isModelEntity(p.getType()) && singleValue instanceof CloneableProxyObject) { Object clonedValue = clonedObjects.get(singleValue); if (!isPartOfContext(singleValue, EmbeddingType.CLOSURE, context)) { clonedValue = null; } clonedObjectHandler.internallyInvokeSetter(p, clonedValue); } break; case REFERENCE: Object referenceValue = singleValue != null ? clonedObjects.get(singleValue) : null; if (referenceValue == null) { referenceValue = singleValue; } clonedObjectHandler.internallyInvokeSetter(p, referenceValue); break; case FACTORY: if (p.getStrategyTypeFactory().equals("deriveName()")) { // TODO: just to test // TODO: implement this properly! // System.out.println("TODO: implement this (FACTORY whine cloning)"); // Object factoredValue = ((FlexoModelObject)getObject()).deriveName(); // clonedObjectHandler.internallyInvokeSetter(p,factoredValue); } break; case IGNORE: break; } break; case LIST: List<?> values = (List<?>) invokeGetter(p); List<?> valuesToClone = new ArrayList<Object>(values); for (Object value : valuesToClone) { switch (p.getCloningStrategy()) { case CLONE: if (getModelFactory().getStringEncoder().isConvertable(p.getType())) { Object clonedValue = null; try { String clonedValueAsString = getModelFactory().getStringEncoder().toString(value); clonedValue = getModelFactory().getStringEncoder().fromString(p.getType(), clonedValueAsString); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } List<?> l = (List<?>) clonedObjectHandler.invokeGetter(p); clonedObjectHandler.invokeAdder(p, clonedValue); } else if (ModelEntity.isModelEntity(p.getType()) && value instanceof CloneableProxyObject) { Object clonedValue = clonedObjects.get(value); if (!isPartOfContext(value, EmbeddingType.CLOSURE, context)) { clonedValue = null; } if (clonedValue != null) { clonedObjectHandler.internallyInvokeAdder(p, clonedValue); } } break; case REFERENCE: Object referenceValue = value != null ? clonedObjects.get(value) : null; if (referenceValue == null) { referenceValue = value; } clonedObjectHandler.internallyInvokeAdder(p, referenceValue); break; case FACTORY: // TODO Not implemented break; case IGNORE: break; } } break; default: break; } } } finally { clonedObjectHandler.createdByCloning = false; } return clonedObject; } /** * Internally used for cloning computation This is the method which determine if a value belongs to derived object graph closure */ private boolean isPartOfContext(Object aValue, EmbeddingType embeddingType, Object... context) { if (context == null || context.length == 0) { return true; } for (Object o : context) { if (getModelFactory().isEmbedddedIn(o, aValue, embeddingType, context)) { return true; } } // System.out.println("Sorry "+aValue+" is not part of context "+context); return false; } /** * Clone several object, using meta informations provided by related class All property should be annoted with a @CloningStrategy * annotation which determine the way of handling this property * * The list of objects is used as the context considered to determine the closure of objects graph being constructed during this * operation. If a property is marked as @CloningStrategy.CLONE but lead to an object outside scope of cloning (the closure being * computed), then resulting value is nullified. * * @param someObjects * @return * @throws ModelExecutionException * @throws ModelDefinitionException * @throws CloneNotSupportedException */ protected List<Object> cloneObjects(Object... someObjects) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { if (someObjects != null && someObjects.length == 1 && someObjects[0].getClass().isArray()) { someObjects = (Object[]) someObjects[0]; } for (Object o : someObjects) { if (!(o instanceof CloneableProxyObject)) { throw new CloneNotSupportedException(); } } Hashtable<CloneableProxyObject, Object> clonedObjects = new Hashtable<CloneableProxyObject, Object>(); for (Object o : someObjects) { ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(o); clonedObjectHandler.performClone(clonedObjects, someObjects); } for (CloneableProxyObject o : clonedObjects.keySet()) { ProxyMethodHandler<?> clonedObjectHandler = getModelFactory().getHandler(o); clonedObjectHandler.finalizeClone(clonedObjects, someObjects); } List<Object> returned = new ArrayList<Object>(); for (int i = 0; i < someObjects.length; i++) { Object o = someObjects[i]; returned.add(clonedObjects.get(o)); } return returned; } protected Object paste(Clipboard clipboard) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { Collection<ModelProperty<? super I>> propertiesAssignableFrom = getModelEntity().getPropertiesAssignableFrom(clipboard.getType()); Collection<ModelProperty<? super I>> pastingPointProperties = Collections2.filter(propertiesAssignableFrom, new Predicate<ModelProperty<?>>() { @Override public boolean apply(ModelProperty<?> arg0) { return arg0.getAddPastingPoint() != null || arg0.getSetPastingPoint() != null; } }); if (pastingPointProperties.size() == 0) { throw new ClipboardOperationException("Cannot paste here: no pasting point found"); } else if (pastingPointProperties.size() > 1) { throw new ClipboardOperationException("Ambiguous pasting operations: several properties are compatible for pasting type " + clipboard.getType()); } return paste(clipboard, pastingPointProperties.iterator().next()); } protected Object paste(Clipboard clipboard, ModelProperty<? super I> modelProperty) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { if (modelProperty.getSetPastingPoint() == null && modelProperty.getAddPastingPoint() == null) { throw new ClipboardOperationException("Cannot paste here: no pasting point found"); } if (modelProperty.getSetPastingPoint() != null && modelProperty.getAddPastingPoint() != null) { throw new ClipboardOperationException("Ambiguous pasting operations: both add and set operations are available for property " + modelProperty); } if (modelProperty.getSetPastingPoint() != null) { return paste(clipboard, modelProperty, modelProperty.getSetPastingPoint()); } else { return paste(clipboard, modelProperty, modelProperty.getAddPastingPoint()); } } protected Object paste(Clipboard clipboard, ModelProperty<? super I> modelProperty, PastingPoint pp) throws ModelExecutionException, ModelDefinitionException, CloneNotSupportedException { ModelEntity<?> entity = getModelEntity(); if (pp == null) { throw new ClipboardOperationException("Cannot paste here: no pasting point found"); } // System.out.println("Found pasting point: "+pp); if (modelProperty == null) { throw new ClipboardOperationException("Cannot paste here: no suitable property found for type " + clipboard.getType()); // System.out.println("Found property: "+ppProperty); } Object clipboardContent = clipboard.getContents(); if (getModelEntity().hasProperty(modelProperty)) { if (modelProperty.getSetPastingPoint() == pp) { if (!clipboard.isSingleObject()) { throw new ClipboardOperationException("Cannot paste here: multiple cardinality clipboard for a SINGLE property"); } invokeSetter(modelProperty, clipboardContent); } else if (modelProperty.getAddPastingPoint() == pp) { if (clipboard.isSingleObject()) { invokeAdder(modelProperty, clipboardContent); } else { for (Object o : (List) clipboardContent) { invokeAdder(modelProperty, o); } } } } clipboard.consume(); return clipboardContent; } @Override public String toString() { return internallyInvokeToString(); } private String internallyInvokeToString() { StringBuilder sb = new StringBuilder(); List<String> variables = new ArrayList<String>(values.keySet()); Collections.sort(variables); for (String var : variables) { Object obj = values.get(var); String s = null; if (obj != null) { if (!(obj instanceof ProxyObject)) { s = indent(obj.toString(), var.length() + 1); } else { s = ((ProxyMethodHandler) ((ProxyObject) obj).getHandler()).getModelEntity().getImplementedInterface().getSimpleName(); } } sb.append(var).append("=").append(s).append('\n'); } return sb.toString(); } private static String indent(String s, int indent) { if (indent > 0 && s != null) { String[] split = s.split("\n\r"); if (split.length > 1) { StringBuilder sb = new StringBuilder(); for (String string : split) { // TODO: optimize this for (int i = 0; i < indent; i++) { sb.append(' '); } sb.append(string).append('\n'); } return sb.toString(); } } return s; } public boolean isSerializing() { return serializing; } public void setSerializing(boolean serializing) throws ModelDefinitionException { if (this.serializing != serializing) { this.serializing = serializing; firePropertyChange(SERIALIZING, !serializing, serializing); if (!serializing) { internallyInvokeSetModified(false); } } } public boolean isDeserializing() { return deserializing; } public void setDeserializing(boolean deserializing) { if (this.deserializing != deserializing) { this.deserializing = deserializing; if (deserializing) { // At the begining of the deserialization process, we also need to mark the object as initialized initialized = true; } else { modified = false; } firePropertyChange(DESERIALIZING, !deserializing, deserializing); } } public boolean isModified() { return modified; } private void internallyInvokeSetModified(boolean modified) throws ModelDefinitionException { if (modified) { if (!isDeserializing() && !isSerializing()) { boolean old = this.modified; this.modified = modified; if (!old) { firePropertyChange(MODIFIED, old, modified); if (getModelEntity().getModify() != null && getModelEntity().getModify().forward() != null) { ModelProperty<? super I> modelProperty = getModelEntity().getModelProperty(getModelEntity().getModify().forward()); if (modelProperty != null) { Object forward = invokeGetter(modelProperty); if (forward instanceof ProxyObject) { ((ProxyMethodHandler<?>) ((ProxyObject) forward).getHandler()).invokeSetModified(modified); } } } } } } else if (this.modified != modified) { this.modified = modified; firePropertyChange(MODIFIED, !modified, modified); } } }