package org.openflexo.model; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import org.openflexo.antar.binding.TypeUtils; import org.openflexo.model.StringConverterLibrary.Converter; import org.openflexo.model.annotations.Adder; import org.openflexo.model.annotations.Finder; import org.openflexo.model.annotations.Getter; import org.openflexo.model.annotations.ImplementationClass; import org.openflexo.model.annotations.Import; import org.openflexo.model.annotations.Imports; import org.openflexo.model.annotations.Modify; import org.openflexo.model.annotations.Remover; import org.openflexo.model.annotations.Setter; import org.openflexo.model.annotations.StringConverter; import org.openflexo.model.annotations.XMLElement; import org.openflexo.model.exceptions.ModelDefinitionException; import org.openflexo.model.exceptions.ModelExecutionException; import org.openflexo.model.exceptions.PropertyClashException; /** * This class represents an instance of the {@link org.openflexo.model.annotations.ModelEntity} annotation declared on an interface. * * @author Guillaume * * @param <I> */ public class ModelEntity<I> { /** * The implemented interface corresponding to this model entity */ private final Class<I> implementedInterface; /** * The model entity annotation describing this entity */ private org.openflexo.model.annotations.ModelEntity entityAnnotation; /** * The implementationClass associated with this model entity */ private ImplementationClass implementationClass; /** * The {@link XMLElement} annotation, if any */ private XMLElement xmlElement; /** * The properties of this entity. The key is the identifier of the property */ private Map<String, ModelProperty<? super I>> properties; /** * The properties of this entity. The key is the xml attribute name of the property */ private Map<String, ModelProperty<? super I>> modelPropertiesByXMLAttributeName; /** * The xmlTag of this entity, if any */ private String xmlTag; /** * The modify annotation of this entity, if any */ private Modify modify; /** * Whether this entity is an abstract entity. Abstract entities cannot be instantiated. */ private boolean isAbstract; /** * The default implementing class of this entity. The class can be abstract. This value may be null. */ private Class<?> implementingClass; /** * The list of super interfaces of this entity. This may be null. */ private List<Class<? super I>> superImplementedInterfaces; /** * The complete list of all the super entities of this entity. */ private List<ModelEntity<? super I>> allSuperEntities; /** * The list of super entities (matching the list of super interfaces). This may be null */ private List<ModelEntity<? super I>> directSuperEntities; /** * The initializers of this entity. */ private Map<Method, ModelInitializer> initializers; private boolean initialized; private HashMap<String, ModelProperty<I>> declaredModelProperties; private Set<ModelEntity<?>> embeddedEntities; ModelEntity(@Nonnull Class<I> implementedInterface) throws ModelDefinitionException { this.implementedInterface = implementedInterface; declaredModelProperties = new HashMap<String, ModelProperty<I>>(); properties = new HashMap<String, ModelProperty<? super I>>(); initializers = new HashMap<Method, ModelInitializer>(); embeddedEntities = new HashSet<ModelEntity<?>>(); entityAnnotation = implementedInterface.getAnnotation(org.openflexo.model.annotations.ModelEntity.class); implementationClass = implementedInterface.getAnnotation(ImplementationClass.class); xmlElement = implementedInterface.getAnnotation(XMLElement.class); modify = implementedInterface.getAnnotation(Modify.class); isAbstract = entityAnnotation.isAbstract(); // We resolve here the model super interface // The corresponding model entity MUST be resolved later for (Class<?> i : implementedInterface.getInterfaces()) { if (i.isAnnotationPresent(org.openflexo.model.annotations.ModelEntity.class)) { if (superImplementedInterfaces == null) { superImplementedInterfaces = new ArrayList<Class<? super I>>(); } superImplementedInterfaces.add((Class<? super I>) i); } } for (Field field : getImplementedInterface().getDeclaredFields()) { StringConverter converter = field.getAnnotation(StringConverter.class); if (converter != null) { try { StringConverterLibrary.getInstance().addConverter((Converter<?>) field.get(null)); } catch (IllegalArgumentException e) { // This should not happen since interfaces can only have static fields // and we pass 'null' throw new ModelDefinitionException("Field " + field + " is not static! Cannot use it as string converter."); } catch (IllegalAccessException e) { throw new ModelDefinitionException("Illegal access to field " + field); } catch (ClassCastException e) { throw new ModelDefinitionException("Field " + field.getName() + " is annotated with " + StringConverter.class.getName() + " but the value of the field is not an instance of " + Converter.class.getName()); } } } // We scan already all the declared properties but we do not resolve their type. We do not resolve inherited properties either. for (Method m : getImplementedInterface().getDeclaredMethods()) { String propertyIdentifier = null; Getter aGetter = m.getAnnotation(Getter.class); if (aGetter != null) { propertyIdentifier = aGetter.value(); } else { Setter aSetter = m.getAnnotation(Setter.class); if (aSetter != null) { propertyIdentifier = aSetter.value(); } else { Adder anAdder = m.getAnnotation(Adder.class); if (anAdder != null) { propertyIdentifier = anAdder.value(); } else { Remover aRemover = m.getAnnotation(Remover.class); if (aRemover != null) { propertyIdentifier = aRemover.value(); } } } } if (propertyIdentifier != null) { // The next line creates the property ModelProperty<I> property = ModelProperty.getModelProperty(propertyIdentifier, this); declaredModelProperties.put(propertyIdentifier, property); } org.openflexo.model.annotations.Initializer initializer = m.getAnnotation(org.openflexo.model.annotations.Initializer.class); if (initializer != null) { initializers.put(m, new ModelInitializer(initializer, m)); } } } void init() throws ModelDefinitionException { // We now resolve our inherited entities and properties if (getDirectSuperEntities() != null) { embeddedEntities.addAll(getDirectSuperEntities()); } for (ModelProperty<? super I> property : declaredModelProperties.values()) { if (property.getType() != null && !StringConverterLibrary.getInstance().hasConverter(property.getType()) && !property.getType().isEnum() && !property.isStringConvertable() && !property.ignoreType()) { embeddedEntities.add(ModelEntityLibrary.get(property.getType(), true)); } } // We also resolve our imports Imports imports = implementedInterface.getAnnotation(Imports.class); if (imports != null) { for (Import imp : imports.value()) { embeddedEntities.add(ModelEntityLibrary.get(imp.value(), true)); } } embeddedEntities = Collections.unmodifiableSet(embeddedEntities); } void mergeProperties() throws ModelDefinitionException { if (initialized) { return; } properties.putAll(declaredModelProperties); // Resolve inherited properties (we only scan direct parent properties, since themselves will scan for their inherited parents) if (getDirectSuperEntities() != null) { for (ModelEntity<? super I> parentEntity : getDirectSuperEntities()) { parentEntity.mergeProperties(); for (ModelProperty<? super I> property : parentEntity.properties.values()) { createMergedProperty(property.getPropertyIdentifier(), true); } } } // Validate properties now (they should all have a getter and a return type, etc... for (ModelProperty<? super I> p : properties.values()) { p.validate(); } initialized = true; // TODO: maybe it would be better to be closer to what constructors do, ie, if there are super-initializer, // And none of them are without arguments, then this entity should define an initializer with the same // method signature (this is to enforce the developer to be aware of what the parameters do): // FlexoModelObject.init(String flexoID) vs AbstractNode.init(String nodeName)-->same signature but the semantics of the parameter // is different // Validate initializers for (ModelInitializer i : initializers.values()) { for (String s : i.getParameters()) { if (s == null) { continue; } ModelProperty<? super I> modelProperty = getModelProperty(s); if (modelProperty == null) { throw new ModelDefinitionException("Initializer " + i.getInitializingMethod().toGenericString() + " declares a parameter " + s + " but this entity has no such declared property"); } } } } public Set<ModelEntity<?>> getEmbeddedEntities() { return embeddedEntities; } public Class<?> getImplementingClass() throws ModelDefinitionException { if (implementingClass != null) { return implementingClass; } if (implementationClass != null) { if (implementedInterface.isAssignableFrom(implementationClass.value())) { return implementingClass = implementationClass.value(); } else { throw new ModelDefinitionException("Class " + implementationClass.value().getName() + " is declared as an implementation class of " + this + " but does not extend " + implementedInterface.getName()); } } else { if (getDirectSuperEntities() != null) { for (ModelEntity<? super I> e : getDirectSuperEntities()) { Class<?> klass = e.getImplementingClass(); if (klass != null) { if (implementingClass == null) { implementingClass = klass; } else { throw new ModelDefinitionException("Ambiguous implementing klass for entity '" + this + "'. Found more than one valid super klass: " + implementingClass.getName() + " and " + klass.getName()); } } } } } return implementingClass; } public boolean singleInheritance() { return superImplementedInterfaces != null && superImplementedInterfaces.size() == 1; } public boolean multipleInheritance() { return superImplementedInterfaces != null && superImplementedInterfaces.size() > 1; } public List<ModelEntity<? super I>> getDirectSuperEntities() throws ModelDefinitionException { if (directSuperEntities == null && superImplementedInterfaces != null) { directSuperEntities = new ArrayList<ModelEntity<? super I>>(superImplementedInterfaces.size()); for (Class<? super I> superInterface : superImplementedInterfaces) { ModelEntity<? super I> superEntity = ModelEntityLibrary.get(superInterface, true); directSuperEntities.add(superEntity); } } return directSuperEntities; } /** * Returns a list of all the (direct & indirect) super entities of this entity. * * @return all the (direct & indirect) super entities of this entity. * @throws ModelDefinitionException */ public List<ModelEntity<? super I>> getAllSuperEntities() throws ModelDefinitionException { if (allSuperEntities == null && superImplementedInterfaces != null) { allSuperEntities = new ArrayList<ModelEntity<? super I>>(); // 1. We add the direct ancestors of this entity allSuperEntities.addAll(getDirectSuperEntities()); // 2. We add the indirect ancestors of this entity to have a topologically sorted array. for (ModelEntity<? super I> superEntity : new ArrayList<ModelEntity<? super I>>(allSuperEntities)) { allSuperEntities.addAll(superEntity.getAllSuperEntities()); } } return allSuperEntities; } public boolean hasProperty(String propertyIdentifier) { return properties.containsKey(propertyIdentifier); } public boolean hasProperty(ModelProperty<?> modelProperty) { return properties.containsValue(modelProperty); } public ModelProperty<? super I> getPropertyForXMLAttributeName(String name) throws ModelDefinitionException { if (modelPropertiesByXMLAttributeName == null) { synchronized (this) { if (modelPropertiesByXMLAttributeName == null) { modelPropertiesByXMLAttributeName = new HashMap<String, ModelProperty<? super I>>(); for (ModelProperty<? super I> property : properties.values()) { if (property.getXMLTag() != null) { modelPropertiesByXMLAttributeName.put(property.getXMLTag(), property); } } modelPropertiesByXMLAttributeName = Collections.unmodifiableMap(modelPropertiesByXMLAttributeName); } } } return modelPropertiesByXMLAttributeName.get(name); } /** * Returns whether the implemented interface associated with <code>this</code> model entity has a method annotated with a {@link Getter} * with its value set to the provided <code>propertyIdentifier</code>. * * @param propertyIdentifier * @return */ private boolean declaresProperty(String propertyIdentifier) { return declaredModelProperties.containsKey(propertyIdentifier); } /** * Returns the {@link ModelProperty} with the identifier <code>propertyIdentifier</code>. * * @param propertyIdentifier * the identifier of the property * @return the property with the identifier <code>propertyIdentifier</code>. * @throws ModelDefinitionException */ public ModelProperty<? super I> getModelProperty(String propertyIdentifier) throws ModelDefinitionException { return properties.get(propertyIdentifier); } /** * Creates the {@link ModelProperty} with the identifier <code>propertyIdentifier</code>. * * @param propertyIdentifier * the identifier of the property * @param create * whether the property should be create or not, if not found * @return the property with the identifier <code>propertyIdentifier</code>. * @throws ModelDefinitionException */ void createMergedProperty(String propertyIdentifier, boolean create) throws ModelDefinitionException { ModelProperty<? super I> returned = buildModelProperty(propertyIdentifier); properties.put(propertyIdentifier, returned); } /** * Builds the {@link ModelProperty} with identifier <code>propertyIdentifier</code>, if it is declared at least once in the hierarchy * (i.e., at least one method is annotated with the {@link Getter} annotation and the given identifier, <code>propertyIdentifier</code> * ). In case of inheritance, the property is combined with all its ancestors. In case of multiple inheritance of the same property, * conflicts are resolved to the possible extent. In case of contradiction, a {@link PropertyClashException} is thrown. * * @param propertyIdentifier * the identifier of the property * @return the new, possibly combined, property. * @throws ModelDefinitionException * in case of an inconsistency in the model of a clash of property inheritance. */ private ModelProperty<? super I> buildModelProperty(String propertyIdentifier) throws ModelDefinitionException { ModelProperty<I> property = ModelProperty.getModelProperty(propertyIdentifier, this); if (singleInheritance() || multipleInheritance()) { ModelProperty<? super I> parentProperty = buildModelPropertyUsingParentProperties(propertyIdentifier, property); return combine(property, parentProperty); } return property; } /** * Returns a model property with the identifier <code>propertyIdentifier</code> which is a combination of all the model properties with * the identifier <code>propertyIdentifier</code> of the parent entities. This method may return <code>null</code> in case amongst all * parents, non of them declare a property with identifier <code>propertyIdentifier</code>. * * @param propertyIdentifier * the identifier of the property * @param property * the model property with the identifier defined for <code>this</code> {@link ModelEntity}. * @return * @throws ModelDefinitionException */ private ModelProperty<? super I> buildModelPropertyUsingParentProperties(String propertyIdentifier, ModelProperty<I> property) throws ModelDefinitionException { ModelProperty<? super I> returned = null; for (ModelEntity<? super I> parent : getDirectSuperEntities()) { if (!parent.hasProperty(propertyIdentifier)) { continue; } if (returned == null) { returned = parent.getModelProperty(propertyIdentifier); } else { returned = combineAsAncestors(parent.getModelProperty(propertyIdentifier), returned, property); } } return returned; } /** * Returns a combined property which is the merge of the property <code>property</code> and its parent property * <code>parentProperty</code>. In case of conflicts, the behaviour defined by <code>property</code> superseeds the one defined by * <code>parentProperty</code> * * @param property * the property to merge * @param parentProperty * the parent property to merge * @return a combined/merged property * @throws ModelDefinitionException */ private ModelProperty<? super I> combine(ModelProperty<I> property, ModelProperty<? super I> parentProperty) throws ModelDefinitionException { return property.combineWith(parentProperty, property); } private ModelProperty<? super I> combineAsAncestors(ModelProperty<? super I> property1, ModelProperty<? super I> property2, ModelProperty<I> declaredProperty) throws ModelDefinitionException { if (property1 == null) { return property2; } if (property2 == null) { return property1; } checkForContradictions(property1, property2, declaredProperty); return property1.combineWith(property2, declaredProperty); } private void checkForContradictions(ModelProperty<? super I> property1, ModelProperty<? super I> property2, ModelProperty<I> declaredProperty) throws PropertyClashException { String contradiction = property1.contradicts(property2, declaredProperty); if (contradiction != null) { throw new PropertyClashException("Property '" + property1.getPropertyIdentifier() + "' contradiction between entity '" + property1.getModelEntity() + "' and entity '" + property2.getModelEntity() + "'.\nReason:" + contradiction); } } protected boolean declaresModelProperty(String propertyIdentifier) { for (Method m : getImplementedInterface().getDeclaredMethods()) { Getter aGetter = m.getAnnotation(Getter.class); if (aGetter != null && aGetter.value().equals(propertyIdentifier)) { return true; } Setter aSetter = m.getAnnotation(Setter.class); if (aSetter != null && aSetter.value().equals(propertyIdentifier)) { return true; } Adder anAdder = m.getAnnotation(Adder.class); if (anAdder != null && anAdder.value().equals(propertyIdentifier)) { return true; } Remover aRemover = m.getAnnotation(Remover.class); if (aRemover != null && aRemover.value().equals(propertyIdentifier)) { return true; } } return false; } public Class<I> getImplementedInterface() { return implementedInterface; } public boolean isAbstract() { return isAbstract; } public ImplementationClass getImplementationClass() { return implementationClass; } public XMLElement getXMLElement() { return xmlElement; } public String getXMLTag() { if (xmlTag == null) { if (xmlElement != null) { xmlTag = xmlElement.xmlTag(); } if (xmlTag == null || xmlTag.equals(XMLElement.DEFAULT_XML_TAG)) { xmlTag = getImplementedInterface().getSimpleName(); } } return xmlTag; } public Iterator<ModelProperty<? super I>> getProperties() throws ModelDefinitionException { return properties.values().iterator(); } public int getPropertiesSize() { return properties.size(); } @Override public String toString() { return "ModelEntity[" + getImplementedInterface().getSimpleName() + "]"; } public List<ModelEntity> getAllDescendantsAndMe(ModelContext modelContext) throws ModelDefinitionException { List<ModelEntity> returned = getAllDescendants(modelContext); returned.add(this); return returned; } public List<ModelEntity> getAllDescendants(ModelContext modelContext) throws ModelDefinitionException { List<ModelEntity> returned = new ArrayList<ModelEntity>(); Iterator<ModelEntity> i = modelContext.getEntities(); while (i.hasNext()) { ModelEntity entity = i.next(); if (isAncestorOf(entity)) { returned.add(entity); } } return returned; } public boolean isAncestorOf(ModelEntity<?> entity) throws ModelDefinitionException { if (entity == null) { return false; } if (entity.getDirectSuperEntities() != null) { for (ModelEntity<?> e : entity.getDirectSuperEntities()) { if (e == this) { return true; } else if (isAncestorOf(e)) { return true; } } } return false; } private Boolean hasInitializers; public boolean hasInitializers() throws ModelDefinitionException { if (hasInitializers == null) { if (initializers.size() > 0) { return hasInitializers = true; } else if (getDirectSuperEntities() != null) { for (ModelEntity<?> e : getDirectSuperEntities()) { if (e.hasInitializers()) { return hasInitializers = true; } } } return hasInitializers = false; } return hasInitializers; } public ModelInitializer getInitializers(Method m) throws ModelDefinitionException { if (m.getDeclaringClass() != implementedInterface) { ModelEntity<?> e = ModelEntityLibrary.get(m.getDeclaringClass()); if (e == null) { throw new ModelExecutionException("Could not find initializer for method " + m.toGenericString() + ". Make sure that " + m.getDeclaringClass().getName() + " is annotated with ModelEntity and has been imported."); } return e.getInitializers(m); } return initializers.get(m); } public ModelInitializer getInitializerForArgs(Class<?>[] types) throws ModelDefinitionException { List<ModelInitializer> list = getPossibleInitializers(types); if (list.size() == 0) { if (initializers.size() > 0) { return null; } ModelInitializer found = null; if (getDirectSuperEntities() != null) { for (ModelEntity<? super I> e : getDirectSuperEntities()) { ModelInitializer initializer = e.getInitializerForArgs(types); if (found == null) { found = initializer; } else { throw new ModelDefinitionException("Initializer clash: " + found.getInitializingMethod().toGenericString() + " cannot be distinguished with " + initializer.getInitializingMethod().toGenericString() + ". Please override initializer in " + getImplementedInterface()); } } } return found; } return list.get(0); } public List<ModelInitializer> getPossibleInitializers(Class<?>[] types) { List<ModelInitializer> list = new ArrayList<ModelInitializer>(); for (ModelInitializer init : initializers.values()) { int i = 0; Class<?>[] parameterTypes = init.getInitializingMethod().getParameterTypes(); boolean ok = parameterTypes.length == types.length; if (ok) { for (Class<?> c : parameterTypes) { if (types[i] != null && !c.isAssignableFrom(types[i])) { ok = false; break; } i++; } if (ok) { list.add(init); } } } return list; } /** * Returns the list of model properties of this model entity which are of the type provided by <code>type</code> or any of its * compatible type (ie, a super-type of <code>type</code>). * * @param type * the type used for model properties lookup * @return a list of model properties to which an instance of <code>type</code> can be assigned or added */ public Collection<ModelProperty<? super I>> getPropertiesAssignableFrom(Class<?> type) { Collection<ModelProperty<? super I>> ppProperties = new ArrayList<ModelProperty<? super I>>(); for (ModelProperty<? super I> p : properties.values()) { if (TypeUtils.isTypeAssignableFrom(p.getType(), type)) { ppProperties.add(p); } } return ppProperties; } public Modify getModify() throws ModelDefinitionException { if (modify != null) { return modify; } else { if (getDirectSuperEntities() != null) { for (ModelEntity<? super I> e : getDirectSuperEntities()) { if (e.getModify() != null) { if (modify == null) { modify = e.getModify(); } else { throw new ModelDefinitionException("Duplicated modify annotation on " + this + ". Please add modify annotation on " + implementedInterface.getName()); } } } } } return null; } public Finder getFinder(String string) { // TODO Auto-generated method stub return null; } public static boolean isModelEntity(Class<?> type) { return type.isAnnotationPresent(org.openflexo.model.annotations.ModelEntity.class); } }