Java tutorial
/* * Modelibra * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.modelibra; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Observable; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.modelibra.action.Action; import org.modelibra.action.AddAction; import org.modelibra.action.EntitiesAction; import org.modelibra.action.RemoveAction; import org.modelibra.action.UpdateAction; import org.modelibra.config.CombinationConfig; import org.modelibra.config.ConceptConfig; import org.modelibra.config.ModelConfig; import org.modelibra.config.NeighborConfig; import org.modelibra.config.NeighborsConfig; import org.modelibra.config.PropertiesConfig; import org.modelibra.config.PropertyConfig; import org.modelibra.exception.ActionRuntimeException; import org.modelibra.exception.ConfigRuntimeException; import org.modelibra.exception.Errors; import org.modelibra.exception.ModelibraRuntimeException; import org.modelibra.exception.OrderRuntimeException; import org.modelibra.exception.SelectionRuntimeException; import org.modelibra.exception.TypeRuntimeException; import org.modelibra.type.PropertyClass; import org.modelibra.util.CaseInsensitiveStringComparator; import org.modelibra.util.OutTester; import org.modelibra.util.Reflector; import org.modelibra.util.Sorter; import org.modelibra.util.Transformer; /** * <p> * Abstract class to implement common things about all groups of entities. * </p> * * <p> * Entities can be added, removed, updated, retrieved, selected and ordered. * </p> * * @author Dzenan Ridjanovic * @author Vedad Kirlic * @version 2009-02-22 */ @SuppressWarnings("serial") public abstract class Entities<T extends IEntity<T>> extends Observable implements IEntities<T> { private static Log log = LogFactory.getLog(Entities.class); private IDomainModel model; private ConceptConfig conceptConfig; private IEntities<T> sourceEntities = null; private boolean propagateToSource = true; private boolean persistent; private boolean pre = true; private boolean post = true; private int lastAutoIncrement = 0; private OidIndex<T> oidIndex; private UniqueIndex<T> uniqueIndex; private Index<T> index; private Errors errors = new Errors(); private List<T> entityList = new ArrayList<T>(); private Random randomGenerator = new Random(); /** * Constructs entities without the domain model and concept configuration. * Used only in the domain configuration classes. */ public Entities() { setPropagateToSource(false); setPersistent(false); } /** * Constructs entities within the given domain model. * * @param model * domain model */ public Entities(IDomainModel model) { this.model = model; if (model == null) { String msg = "Entities.constructor -- model is null."; throw new ModelibraRuntimeException(msg); } ModelConfig modelConfig = model.getModelConfig(); if (modelConfig.isAbstraction()) { String msg = "Entities.constructor -- model is configured as an abstraction: " + modelConfig.getDomainConfig().getCode() + "." + modelConfig.getCode(); throw new ConfigRuntimeException(msg); } String conceptCode = getClass().getSimpleName(); conceptConfig = getModel().getModelConfig().getConceptConfig(conceptCode); if (conceptConfig == null) { String msg = "Entities.constructor -- concept code is not valid: " + modelConfig.getDomainConfig().getCode() + "." + modelConfig.getCode() + "." + conceptCode; throw new ConfigRuntimeException(msg); } if (conceptConfig.isAbstraction()) { String msg = "Entities.constructor -- concept is configured as an abstraction: " + modelConfig.getDomainConfig().getCode() + "." + modelConfig.getCode() + "." + conceptConfig.getCode(); throw new ConfigRuntimeException(msg); } if (conceptConfig.isEntry()) { ((DomainModel) model).addEntry(this); } if (conceptConfig.isIndex()) { oidIndex = new OidIndex<T>(this); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex = new UniqueIndex<T>(this); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index = new Index<T>(this); } } setPersistent(model.getModelConfig().isPersistent()); } /** * Sets the entities domain model. * * @param model * domain model */ public void setModel(IDomainModel model) { if (model == null) { String msg = "Entities.setModel -- model is null."; throw new ModelibraRuntimeException(msg); } IDomainModel currentModel = this.model; ConceptConfig currentConceptConfig = this.conceptConfig; String entitiesCode = getClass().getSimpleName(); ConceptConfig conceptConfig = model.getModelConfig().getConceptConfig(entitiesCode); if (conceptConfig != null) { this.model = model; this.conceptConfig = conceptConfig; try { for (T entity : this) { ((Entity<T>) entity).setModel(model); } } catch (ConfigRuntimeException e) { this.model = currentModel; this.conceptConfig = currentConceptConfig; throw e; } } else { String msg = "Entities.setModel -- entities code is not valid: " + model.getModelConfig().getDomainConfig().getCode() + "." + model.getModelConfig().getCode() + "." + entitiesCode; throw new ConfigRuntimeException(msg); } // this code is executed only if model is successfully set if (conceptConfig.isEntry() && !conceptConfig.isAbstraction()) { ((DomainModel) model).replaceEntry(this); } if (conceptConfig.isIndex()) { oidIndex = new OidIndex<T>(this); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex = new UniqueIndex<T>(this); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index = new Index<T>(this); } } setPersistent(model.getModelConfig().isPersistent()); for (T entity : this) { updateIndexes(entity); } errors = new Errors(); } /** * Gets the entities domain model. * * @return entities domain model */ public IDomainModel getModel() { return model; } /** * Gets the entities concept configuration. * * @return entities concept configuration */ public ConceptConfig getConceptConfig() { return conceptConfig; } /** * Sets the source entities. If the entities are a model view (destination * entities), the source entities are used to obtain the destination * entities. * * @param sourceEntities * source entities */ protected void setSourceEntities(IEntities<T> sourceEntities) { this.sourceEntities = sourceEntities; } /** * Gets the source entities. If the entities are a model view (destination * entities), the source entities are used to obtain the destination * entities. If the entities are not a model view, null is returned. * * @return source entities */ public IEntities<T> getSourceEntities() { return sourceEntities; } /** * Sets the propagate to source switch, which is true by default. If false, * there will be no update (add or remove) of source entities. * * @param propagateToSource * <code>true</code> if to propagate */ public void setPropagateToSource(boolean propagateToSource) { this.propagateToSource = propagateToSource; } /** * Checks the propagate to source switch, which is true by default. If * false, there will be no update (add or remove) of source entities. * * @return <code>true</code> if to propagate */ public boolean isPropagateToSource() { return propagateToSource; } /** * Sets to <code>true</code> if the entities are persistent. By default, the * entities are persistent. This method should be used only in some special * cases but only on a new Entities object such as in the copy method or in * methods that derive an entry point. * * @param persistent * <code>true</code> if the entities are persistent */ public void setPersistent(boolean persistent) { this.persistent = persistent; } /** * Checks if the entities are persistent. * * @return <code>true</code> if the entities are persistent */ public boolean isPersistent() { return persistent; } /** * Sets to <code>true</code> if the pre action will be done. By default, the * pre action will be done. This method should be used only in some special * cases but only on a new Entities object such as in the copy method or in * methods that derive an entry point. * * @param pre * <code>true</code> if the pre action will be done */ public void setPre(boolean pre) { this.pre = pre; } /** * Checks if the pre action will be done. * * @return <code>true</code> if the pre action will be done */ public boolean isPre() { return pre; } /** * Sets to <code>true</code> if the post action will be done. By default, * the post action will be done. This method should be used only in some * special cases but only on a new Entities object such as in the copy * method or in methods that derive an entry point. * * @param post * <code>true</code> if the post action will be done */ public void setPost(boolean post) { this.post = post; } /** * Checks if the post action will be done. * * @return <code>true</code> if the post action will be done */ public boolean isPost() { return post; } /** * Creates an iterator over the entities. * * @return iterator */ public Iterator<T> iterator() { return new EntitiesIterator(); } /** * Returns the number of entities. * * @return number of entities */ public synchronized int size() { return entityList.size(); } /** * Checks if the entities are empty. * * @return <code>true</code> if the entities are empty */ public synchronized boolean isEmpty() { return entityList.isEmpty(); } /** * Orders entities by the property. Returns null if there is no domain * model. Produces new destination entities (a model view) -- it is not in * place order -- from the (this) source entities. The destination entities * have the same context (the same domain mode, the same concept * configuration) as the source entities. The source entities are indicated * in the destination entities. Adds and removals are propagated by default * from the destination entities to the source entities. Meta handling is * used. * * @param propertyCode * property code * @param ascending * <code>true</code> if the order is ascending * @return destination entities ordered by the property */ public IEntities<T> orderByProperty(String propertyCode, boolean ascending) { Entities<T> destinationEntities = null; DomainModel model = (DomainModel) getModel(); if (model != null) { List<T> list = getList(propertyCode, ascending); ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); destinationEntities.setEntitiesList(list); modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.orderByProperty -- there is no domain model."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Orders entities by the comparator. Returns null if there is no domain * model. Produces new destination entities (a model view) -- it is not in * place order -- from the (this) source entities. The destination entities * have the same context (the same domain mode, the same concept * configuration) as the source entities. The source entities are indicated * in the destination entities. Adds and removals are propagated by default * from the destination entities to the source entities. * * Meta handling is used. * * @param comparator * comparator * @param ascending * <code>true</code> if the order is ascending * @return destination entities ordered by the comparator */ public IEntities<T> orderByComparator(Comparator<T> comparator, boolean ascending) { Entities<T> destinationEntities = null; DomainModel model = (DomainModel) getModel(); if (model != null) { List<T> list = getList(comparator, ascending); ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); destinationEntities.setEntitiesList(list); modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.orderByComparator -- there is no domain model."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Gets a list of entities based on the property order. * * @param propertyCode * property code * @param ascending * <code>true</code> if the order is ascending * @return list of entities based on the property */ protected List<T> getList(String propertyCode, boolean ascending) { if (propertyCode == null) { String msg = "Entities.getList -- property code is null."; throw new OrderRuntimeException(msg); } List<T> result = new ArrayList<T>(); Sorter<T> sorter = new Sorter<T>(); if (ascending) { result = sorter.sort(getList(), propertyCode, true); } else { result = sorter.sort(getList(), propertyCode, false); } return result; } /** * Gets a list of entities based on the comparator. * * @param comparator * comparator * @return list of entities based on the comparator * @param ascending * <code>true</code> if the order is ascending */ protected List<T> getList(Comparator<T> comparator, boolean ascending) { if (comparator == null) { String msg = "Entities.getList -- comparator is null."; throw new OrderRuntimeException(msg); } List<T> result = new ArrayList<T>(); Sorter<T> sorter = new Sorter<T>(); if (ascending) { result = sorter.sort(getList(), comparator, true); } else { result = sorter.sort(getList(), comparator, false); } return result; } /** * Orders entities by the code property. Returns null if there is no code * property configuration. * * @return destination entities ordered by the code property */ public Entities<T> orderByCode() { ConceptConfig conceptConfig = getConceptConfig(); if (conceptConfig.getPropertyConfig("code") != null) { Entities<T> orderByCode = null; CaseInsensitiveStringComparator cisc = new CaseInsensitiveStringComparator(); PropertyComparator<T> propertyComparator = new PropertyComparator<T>("code", cisc); orderByCode = (Entities<T>) orderByComparator(propertyComparator, true); return orderByCode; } else { String msg = "Entities.orderByCode -- concept does not have the code property configuration: " + conceptConfig.getModelConfig().getDomainConfig().getCode() + "." + conceptConfig.getModelConfig().getCode() + "." + conceptConfig.getCode(); throw new ConfigRuntimeException(msg); } } /** * Gets a list of entities based on a selector. Returns an empty list if * there are no entities. * * @param selector * selector * @return list of entities based on the selector */ protected List<T> getList(ISelector selector) { List<T> result = new ArrayList<T>(); if (selector == null) { String msg = "Entities.getList -- selector is null."; throw new SelectionRuntimeException(msg); } if (selector instanceof PropertySelector) { PropertySelector propertySelector = (PropertySelector) selector; result = getList(propertySelector); } else if (selector instanceof CompositeSelector) { CompositeSelector compositeSelector = (CompositeSelector) selector; result = getList(compositeSelector); } return result; } /** * Gets a list of entities based on a property selector. Returns an empty * list if there are no entities. * * The iteration is synchronized. * * @param propertySelector * property selector * @return list of entities based on the property selector * @throws selection * exception if there is a problem */ private List<T> getList(PropertySelector propertySelector) throws SelectionRuntimeException { String relationalOperator = propertySelector.getRelationalOperator(); if (relationalOperator == null) { String msg = "Entities.getList -- relational operator is null."; throw new SelectionRuntimeException(msg); } String propertyCode = propertySelector.getPropertyCode(); if (propertyCode == null) { if (!relationalOperator.equals(ISelector.ALL) && !relationalOperator.equals(ISelector.NONE) ) { String msg = "Entities.getList -- for a relational operator different from NONE and ALL, the property code cannot be null."; throw new SelectionRuntimeException(msg); } } List<T> result = new ArrayList<T>(); if (relationalOperator.equals(ISelector.NONE)) { return result; } synchronized (this) { for (T entity : entityList) { if (entity.isSelected(propertySelector)) { result.add(entity); } } } return result; } /** * Gets a list of entities based on a composite selector. Returns an empty * list if there are no entities. * * The iteration is synchronized. * * @param compositeSelector * composite selector * @return list of entities based on the composite selector */ private List<T> getList(CompositeSelector compositeSelector) { List<T> result = new ArrayList<T>(); synchronized (this) { for (T entity : entityList) { if (entity.isSelected(compositeSelector)) { result.add(entity); } } } return result; } /** * Selects entities by the selector. Returns empty entities if there are no * entities that satisfy the selector. Returns null if there is no domain * model. Produces new destination entities (a model view) -- it is not in * place selection -- from the (this) source entities. The destination * entities have the same context (the same domain model, the same concept * configuration) as the source entities. The add and remove actions are * propagated from the destination entities to the source entities. * * @param selector * selector * @return destination entities selected by the selector */ public IEntities<T> selectBySelector(ISelector selector) { Entities<T> destinationEntities = null; DomainModel model = (DomainModel) getModel(); if (model != null) { List<T> list = getList(selector); ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); destinationEntities.setEntitiesList(list); modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.selectBySelector -- domain model is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Selects entities by the entity select method that returns * <code>true</code> or <code>false</code>. Returns empty entities if there * are no entities that satisfy the select method. Returns null if there is * no domain model. Produces new destination entities (a model view) -- it * is not in place selection -- from the (this) source entities. The * destination entities have the same context (the same domain model, the * same concept configuration) as the source entities. The source entities * are indicated in the destination entities. Adds and removals are * propagated by default from the destination entities to the source * entities. * * <p> * If the entity select method has parameters use <b>selectByMethod(String * entitySelectMethodName, List<?> parameterList)</b>. * </p> * * @param entitySelectMethodName * entity select method name * * @return destination entities selected by the entity select method */ public IEntities<T> selectByMethod(String entitySelectMethodName) { return selectByMethod(entitySelectMethodName, null); } /** * Selects entities by the entity select method that returns * <code>true</code> or <code>false</code>. If the entity select method does * not have parameters use <b>selectByMethod(String * entitySelectMethodName)</b>, otherwise the entity select method * parameters cannot be primitives. Returns empty entities if there are no * entities that satisfy the select method. Returns null if there is no * domain model. Produces new destination entities (a model view) -- it is * not in place selection -- from the (this) source entities. The * destination entities have the same context (the same domain model, the * same concept configuration) as the source entities. The source entities * are indicated in the destination entities. Adds and removals are * propagated by default from the destination entities to the source * entities. Meta handling is used. * * @param entitySelectMethodName * entity select method name * @param parameterList * method parameters * @return destination entities selected by the entity select method */ public IEntities<T> selectByMethod(String entitySelectMethodName, List<?> parameterList) { Entities<T> destinationEntities = null; DomainModel model = (DomainModel) getModel(); if (model != null) { ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); if (parameterList != null) { Object[] parametersArray = parameterList.toArray(); synchronized (this) { for (T entity : this) { Boolean selection = (Boolean) Reflector.executeMethod(entity, entitySelectMethodName, parametersArray); if (selection.booleanValue()) { destinationEntities.add(entity); } } } } else { synchronized (this) { for (T entity : this) { Boolean selection = (Boolean) Reflector.executeMethod(entity, entitySelectMethodName); if (selection.booleanValue()) { destinationEntities.add(entity); } } } } modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.selectByMethod -- domain model is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Selects entities by the concept index based on the equality. Returns * empty entities if no selection. Returns null if there is no domain model. * There may be at most one index for the concept. The index is used if the * concept is configured to use the index. * * @param indexCombination * entity index combination * @return destination entities selected by the concept index */ public IEntities<T> selectByIndex(IndexCombination indexCombination) { Entities<T> destinationEntities = null; if (indexCombination != null) { DomainModel model = (DomainModel) getModel(); if (model != null) { ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); ConceptConfig conceptConfig = getConceptConfig(); if (conceptConfig.getIndexConfig().isNotEmpty()) { if (getConceptConfig().isIndex()) { List<T> ixList = index.getList(indexCombination); if (ixList == null) { String msg = "Entities.selectByIndex -- there is no index for the index combination: " + indexCombination.toString(); throw new SelectionRuntimeException(msg); } else { destinationEntities.setEntitiesList(ixList); } } else { synchronized (this) { for (T entity : entityList) { if (entity.getIndexCombination().equals(indexCombination)) { destinationEntities.add(entity); } } } } modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.selectByIndex -- concept does not have the index configuration: " + conceptConfig.getCode(); throw new ConfigRuntimeException(msg); } } else { String msg = "Entities.selectByIndex -- there is no domain model."; throw new ModelibraRuntimeException(msg); } } else { String msg = "Entities.selectByIndex -- the ix argument is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Selects entities whose property with a property code is equal to a * property object. Returns empty entities if no selection. Returns null if * there is no domain model. Produces new destination entities (a model * view) -- it is not in place selection -- from the (this) source entities. * The destination entities have the same context (the same domain model, * the same concept configuration) as the source entities. The add and * remove actions are propagated from the destination entities to the source * entities. The index is used if it is defined only on this property code, * and if the concept is configured to use the index. The iteration is * synchronized. * * @param propertyCode * property code * @param property * property * @return destination entities selected by the property */ public IEntities<T> selectByProperty(String propertyCode, Object property) { Entities<T> destinationEntities = null; if (propertyCode != null) { DomainModel model = (DomainModel) getModel(); if (model != null) { ModelMeta modelMeta = model.getModelMeta(); destinationEntities = modelMeta.createEntities(this); ConceptConfig conceptConfig = getConceptConfig(); if (conceptConfig.getPropertyConfig(propertyCode) != null) { if (conceptConfig.isIndex()) { CombinationConfig ixConfig = conceptConfig.getIndexConfig(); if (ixConfig.size() == 1 && ixConfig.propertySize() == 1) { if (ixConfig.getPropertyConfig(propertyCode) != null) { IndexCombination ix = new IndexCombination(); ix.addProperty(propertyCode, property); List<T> ixList = index.getList(ix); destinationEntities.setEntitiesList(ixList); } } } else { synchronized (this) { for (T entity : entityList) { Object propertyObject = entity.getProperty(propertyCode); if (propertyObject != null) { if (propertyObject.equals(property)) { destinationEntities.add(entity); } } } } } modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.selectByProperty -- concept does not have the property configuration: " + conceptConfig.getCode() + "." + propertyCode; throw new ConfigRuntimeException(msg); } } else { String msg = "Entities.selectByProperty -- domain model is null."; throw new ModelibraRuntimeException(msg); } } else { String msg = "Entities.selectByProperty -- the property code argument is null."; throw new ModelibraRuntimeException(msg); } return destinationEntities; } /** * Selects entities whose parent neighbor entity with a given neighbor code * is equal to a neighbor object. Returns empty entities if no selection. * Returns null if there is no domain model. The index is used if it is * defined only on this neighbor code, and if the concept is configured to * use the index. * * @param neighborCode * neighbor code * @param neighbor * neighbor * @return destination entities selected by the parent neighbor */ public IEntities<T> selectByParentNeighbor(String neighborCode, IEntity<?> neighbor) { Entities<T> destinationEntities = null; if (neighborCode != null) { DomainModel model = (DomainModel) getModel(); if (model != null) { ModelMeta modelMeta = model.getModelMeta(); destinationEntities = (Entities<T>) modelMeta.createEntities(this); ConceptConfig conceptConfig = getConceptConfig(); NeighborConfig neighborConfig = conceptConfig.getNeighborConfig(neighborCode); if (neighborConfig != null && neighborConfig.isParent()) { if (conceptConfig.isIndex()) { CombinationConfig ixConfig = conceptConfig.getIndexConfig(); if (ixConfig.size() == 1 && ixConfig.neighborSize() == 1) { if (ixConfig.getNeighborConfig(neighborCode) != null) { IndexCombination ix = new IndexCombination(); ix.addNeighbor(neighborCode, neighbor); List<T> ixList = index.getList(ix); destinationEntities.setEntitiesList(ixList); } } } else { synchronized (this) { for (T entity : entityList) { IEntity<?> neighorEntity = entity.getParentNeighbor(neighborCode); if (neighorEntity != null) { if (neighorEntity.equals(neighbor)) { destinationEntities.add(entity); } } } // for } // synchronized } modelMeta.setParents(destinationEntities, this); destinationEntities.setSourceEntities(this); } else { String msg = "Entities.selectByParentNeighbor -- concept does not have the neighbor configuration: " + conceptConfig.getCode() + "." + neighborCode; throw new ConfigRuntimeException(msg); } } else { String msg = "Entities.selectByParentNeighbor --domain model is null."; throw new ModelibraRuntimeException(msg); } } else { String msg = "Entities.selectByParentNeighbor -- the neighbor code argument is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) destinationEntities; } /** * Union of two entities objects of the same type, where every entity in the * union is a member of one or another of the union input objects (OR * operator). Propagations are done if both union input entities have the * same source entities. * * @param entities * the union other input entities * @return union of two entities objects of the same type */ public IEntities<T> union(IEntities<T> entities) { Entities<T> union = null; DomainModel model = (DomainModel) getModel(); if (model != null) { String entitiesArgumentClass = entities.getConceptConfig().getEntitiesClass(); String thisEntitiesClass = getConceptConfig().getEntitiesClass(); if (!entitiesArgumentClass.equals(thisEntitiesClass)) { String error = "Union input entities must be of the same type."; throw new SelectionRuntimeException(error); } List<T> list = getList(); ModelMeta modelMeta = model.getModelMeta(); union = (Entities<T>) modelMeta.createEntities(this); union.setEntitiesList(list); union.setPropagateToSource(false); synchronized (entities) { for (T entity : entities) { if (!union.contain(entity)) { union.add(entity); } } } modelMeta.setParents(union, this); if (getSourceEntities() != null && entities.getSourceEntities() != null && getSourceEntities() == entities.getSourceEntities()) { union.setSourceEntities(getSourceEntities()); union.setPropagateToSource(true); } } else { String msg = "Entities.union -- domain model is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) union; } /** * Intersection of two entities objects of the same type, where every entity * in the intersection is a member of both intersection input objects (AND * operator). Propagations are done if both intersection input entities have * the same source entities. * * @param entities * the intersection other input entities * @return intersection of two objects of the same type */ public IEntities<T> intersection(IEntities<T> entities) { Entities<T> intersection = null; DomainModel model = (DomainModel) getModel(); if (model != null) { String entitiesArgumentClass = entities.getConceptConfig().getEntitiesClass(); String thisEntitiesClass = getConceptConfig().getEntitiesClass(); if (!entitiesArgumentClass.equals(thisEntitiesClass)) { String msg = "Entities.intersection -- intersection input entities must be of the same type."; throw new SelectionRuntimeException(msg); } ModelMeta modelMeta = model.getModelMeta(); intersection = (Entities<T>) modelMeta.createEntities(this); intersection.setPropagateToSource(false); synchronized (this) { for (T entity : this) { if (entities.contain(entity)) { intersection.add(entity); } } } synchronized (entities) { for (T entity : entities) { if (!intersection.contain(entity) && this.contain(entity)) { intersection.add(entity); } } } modelMeta.setParents(intersection, this); if (getSourceEntities() != null && entities.getSourceEntities() != null && getSourceEntities() == entities.getSourceEntities()) { intersection.setSourceEntities(getSourceEntities()); intersection.setPropagateToSource(true); } } else { String msg = "Entities.intersection -- domain model is null."; throw new ModelibraRuntimeException(msg); } return (IEntities<T>) intersection; } /** * Checks if this entities object is a subset of the superset entities. Both * subset and superset entities must be of the same type. * * @param entities * superset entities * @return <code>true</code> if this entities object is a subset of the * superset entities */ public boolean isSubsetOf(IEntities<T> entities) { boolean subsetOf = true; DomainModel model = (DomainModel) getModel(); if (model != null) { String entitiesArgumentClass = entities.getConceptConfig().getEntitiesClass(); String thisEntitiesClass = getConceptConfig().getEntitiesClass(); if (!entitiesArgumentClass.equals(thisEntitiesClass)) { String msg = "Entities.isSubsetOf -- subset and superset entities must be of the same type."; throw new SelectionRuntimeException(msg); } synchronized (this) { for (T entity : this) { if (!entities.contain(entity)) { subsetOf = false; break; } } } } else { String msg = "Entities.intersection -- domain model is null."; throw new ModelibraRuntimeException(msg); } return subsetOf; } /** * If an entity exists based on its oid. * * @return <code>true</code> if an entity exists based on its oid */ private boolean oidExists(T entity) { boolean oidExists = false; Oid oid = entity.getOid(); if (oid != null) { T entityExists = retrieveByOid(entity.getOid()); if (entityExists != null) { oidExists = true; } } return oidExists; } /** * Validates the entities max. cardinality with the add count. * * @return <code>true</code> if the max. cardinality is satisfied */ private boolean validMaxCardinality(int addEntitiesCount) { boolean validation = true; if (getModel() != null) { String entitiesMaxSizeString = getConceptConfig().getMax(); if (!entitiesMaxSizeString.equals("N")) { try { Integer entitiesMaxSizeInteger = Transformer.integer(entitiesMaxSizeString); int entitiesMaxSize = entitiesMaxSizeInteger.intValue(); if (size() + addEntitiesCount > entitiesMaxSize) { validation = false; String errorKey = getConceptConfig().getEntitiesCode() + "." + "max" + "." + "cardinality"; String error = getConceptConfig().getEntitiesCode() + " cannot have more than " + entitiesMaxSizeString + " entities."; getErrors().add(errorKey, error); } } catch (TypeRuntimeException e) { String msg = "Entities.validMaxCardinality -- max. cardinality is neither N nor an integer."; throw new ConfigRuntimeException(msg); } } } return validation; } /** * Validates the entities min. cardinality with the remove count. * * @return <code>true</code> if the min. cardinality is satisfied */ private boolean validMinCardinality(int removeEntitiesCount) { boolean validation = true; if (getModel() != null) { int entitiesMinSize = getConceptConfig().getMinInt(); if (size() - removeEntitiesCount < entitiesMinSize) { validation = false; String errorKey = getConceptConfig().getEntitiesCode() + "." + "min" + "." + "cardinality"; String error = getConceptConfig().getEntitiesCode() + " cannot have less than " + entitiesMinSize + " entities."; getErrors().add(errorKey, error); } } return validation; } /** * Checks if the add precondition is <code>true</code>. Called by the add * method. The precondition checks if a required property has a value. It * also validates the property type and the property length if they are * present in the property configuration. Default value and auto incerement * are also handled. If the precondition is not true, the add is not done. * Meta handling is used. * * @param entity * entity * @return <code>true</code> if the add precondition is satisfied */ protected boolean preAdd(T entity) { if (!isPre()) { return true; } boolean validation = true; DomainModel model = (DomainModel) getModel(); if (model != null && model.isInitialized()) { validation = !oidExists(entity); if (!validation) { String errorKey = entity.getConceptConfig().getCode() + "." + "oid" + ".new"; String error = entity.getConceptConfig().getCode() + "." + "oid" + " is not unique."; getErrors().add(errorKey, error); return false; } } if (model != null && entity != null) { ConceptConfig conceptConfig = entity.getConceptConfig(); if (conceptConfig != null) { PropertiesConfig propertiesConfig = conceptConfig.getPropertiesConfig(); for (PropertyConfig propertyConfig : propertiesConfig) { if (propertyConfig.isAutoIncrement() && propertyConfig.getPropertyClass().equals(PropertyClass.getInteger())) { String propertyCode = propertyConfig.getCode(); Object property = entity.getProperty(propertyCode); if (property == null) { Integer autoIncrementInteger = new Integer(++lastAutoIncrement); entity.setProperty(propertyCode, autoIncrementInteger); } else { Integer autoIncrement = (Integer) property; int auto = autoIncrement.intValue(); if (auto > lastAutoIncrement) { lastAutoIncrement = auto; } else { ModelMeta modelMeta = model.getModelMeta(); if (!modelMeta.uniqueProperty(this, entity, propertyCode)) { Integer autoIncrementInteger = new Integer(++lastAutoIncrement); entity.setProperty(propertyCode, autoIncrementInteger); } } } } } } if (model.isInitialized()) { if (conceptConfig != null) { validation = validMaxCardinality(1); if (validation) { ModelMeta modelMeta = model.getModelMeta(); PropertiesConfig propertiesConfig = conceptConfig.getPropertiesConfig(); for (PropertyConfig propertyConfig : propertiesConfig) { String propertyCode = propertyConfig.getCode(); Object property = entity.getProperty(propertyCode); if (propertyConfig.isRequired()) { if (property != null) { if (propertyConfig.getPropertyClass().equals(PropertyClass.getString())) { if (propertyConfig.isValidateType()) { validation = modelMeta.validPropertyType(this, entity, propertyConfig, property) && validation; } validation = modelMeta.validMaxLength(this, entity, propertyConfig, property) && validation; } } else { // required && null if (propertyConfig.getDefaultValue() != null) { boolean defaultValue = modelMeta.setPropertyDefaultValue(entity, propertyConfig, property); if (!defaultValue) { modelMeta.addRequiredPropertyError(this, entity, propertyConfig); validation = false; } } else { modelMeta.addRequiredPropertyError(this, entity, propertyConfig); validation = false; } } } else { // not required if (property != null) { if (propertyConfig.getPropertyClass().equals(PropertyClass.getString())) { if (propertyConfig.isValidateType()) { validation = modelMeta.validPropertyType(this, entity, propertyConfig, property) && validation; } validation = modelMeta.validMaxLength(this, entity, propertyConfig, property) && validation; } } else { // not required && null if (propertyConfig.getDefaultValue() != null) { modelMeta.setPropertyDefaultValue(entity, propertyConfig, property); } } // if (property != null) { } // if (propertyConfig.isRequired()) { } // for } // if } // if } // if } // if return validation; } /** * Checks if the add postcondition is <code>true</code>. Called by the add * method. If the concept has the id, the postcondition checks if the id is * unique. If the postcondition is not true, the add is undone. Meta * handling is used. * * @param entity * entity * @return <code>true</code> if the add postcondition is satisfied */ protected boolean postAdd(T entity) { if (!isPost()) { return true; } boolean validation = true; ConceptConfig conceptConfig = entity.getConceptConfig(); if (conceptConfig != null) { DomainModel model = (DomainModel) getModel(); if (model.isInitialized()) { ModelMeta modelMeta = model.getModelMeta(); if (!conceptConfig.getUniqueConfig().isEmpty()) { validation = modelMeta.uniqueId(this, entity) && validation; } } if (validation && conceptConfig.isIndex()) { oidIndex.add(entity); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex.add(entity); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index.add(entity); } } } return validation; } /** * Adds a new entity to the entities. A new oid is created if the entity * does not have it. The parent neighbors are set. If the precondition is * not true, the add is not done. If the postcondition is not true, the add * is undone. Meta handling is used. * * @param entity * entity * @return <code>true</code> if the entity is added */ public boolean add(T entity) { EntitiesAction entitiesAddAction = new AddAction(this, entity); synchronized (this) { if (!preAdd(entity)) { return false; } if (entity.getOid() == null) { Oid oid = new Oid(); entity.setOid(oid); } boolean added = false; added = entityList.add(entity); if (added) { if (postAdd(entity)) { DomainModel model = (DomainModel) getModel(); if (model != null) { if (getSourceEntities() == null) { ModelMeta modelMeta = model.getModelMeta(); // modelMeta.setParents(entity, this); updateIndexes(entitiesAddAction); if (model.isInitialized() && !model.isSession()) { model.notifyObservers(entitiesAddAction); } } else { if (isPropagateToSource()) { getSourceEntities().add(entity); } } return true; } else { // The following code cannot be used since a config // does // not have a model! // Only the add action is used to load a config (not // the // update and remove actions). // String error = "The model is null"; // throw new ActionException(error, // entitiesAddAction); } } else { if (!remove(entity)) { String msg = "Entities.add -- cannot undo an add action that has a not valid postcondition."; log.info(msg); ((Entity<?>) entity).output("Add"); throw new ActionRuntimeException(msg); } } } else { // cannot happen? String msg = "Entities.add -- the entity was not added to the internal list.."; log.info(msg); ((Entity<?>) entity).output("Add"); throw new ActionRuntimeException(msg); } } return false; } /** * Adds new list of entities to the entitiesList. * * @param list * list of entities * @return <code>true</code> if add is successful */ private boolean add(List<T> list) { return entityList.addAll(list); } /** * Sets the entityList. Used in order and selection methods to set list * obtained by some selector as entitiesList for this entities. * * @param list * list of entities */ private void setEntitiesList(List<T> list) { entityList = list; } /** * Checks if the remove precondition is <code>true</code>. Called by the * remove method. The precondition checks if the min. cardinality will be * satisfied for the remove count of 1. The entity to be removed must also * exist. If the precondition is not true, the remove is not done. * * @param entity * entity * @return <code>true</code> if the remove precondition is satisfied */ protected boolean preRemove(T entity) { if (!isPre()) { return true; } boolean validation = validMinCardinality(1); if (validation) { validation = oidExists(entity); if (!validation) { String errorKey = entity.getConceptConfig().getCode() + "." + "oid" + ".old"; String error = entity.getOid() + " " + entity.getConceptConfig().getCode() + "." + "oid" + " does not exist."; getErrors().add(errorKey, error); } } return validation; } /** * Checks if the remove postcondition is <code>true</code>. Called by the * remove method. There is no generic postcondition. * * @param entity * entity * @return <code>true</code> if the remove postcondition is satisfied */ protected boolean postRemove(T entity) { if (!isPost()) { return true; } if (conceptConfig.isIndex()) { oidIndex.remove(entity); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex.remove(entity); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index.remove(entity); } } return true; } /** * Removes the given entity from the entities. If the precondition is not * true, the remove is not done. If the postcondition is not true, the * remove is undone. * * @param entity * entity * @return <code>true</code> if the entity is removed */ public boolean remove(T entity) { EntitiesAction entitiesRemoveAction = new RemoveAction(this, entity); synchronized (this) { if (!preRemove(entity)) { return false; } boolean removed = false; removed = entityList.remove(entity); if (removed) { if (postRemove(entity)) { DomainModel model = (DomainModel) getModel(); if (model != null) { if (getSourceEntities() == null) { updateIndexes(entitiesRemoveAction); if (model.isInitialized() && !model.isSession()) { model.notifyObservers(entitiesRemoveAction); } } else { if (isPropagateToSource()) { getSourceEntities().remove(entity); } } return true; } else { // cannot happen? String msg = "Entities.remove -- model is null."; throw new ActionRuntimeException(msg); } } else { if (!add(entity)) { String msg = "Entities.remove -- cannot undo a remove action that has a not valid postcondition."; log.info(msg); ((Entity<?>) entity).output("Remove"); throw new ActionRuntimeException(msg); } } } else { // cannot happen? String msg = "Entities.remove -- the entity was not removed from the internal list."; log.info(msg); ((Entity<?>) entity).output("Remove"); throw new ActionRuntimeException(msg); } } return false; } /** * Checks if the update precondition is <code>true</code>. Called by the * corresponding update method. The precondition checks if a required * property has a value. It also validates the property type and the * property length if they are present in the property configuration. If the * precondition is not true, the update is not done. Meta handling is used. * * @param entity * entity * @param updateEntity * update entity * @return <code>true</code> if the update precondition is satisfied */ protected boolean preUpdate(T entity, T updateEntity) { if (!isPre()) { return true; } boolean validation = true; ConceptConfig conceptConfig = updateEntity.getConceptConfig(); if (conceptConfig != null) { PropertiesConfig propertiesConfig = conceptConfig.getPropertiesConfig(); DomainModel model = (DomainModel) getModel(); if (model != null) { validation = oidExists(entity); if (!validation) { String errorKey = entity.getConceptConfig().getCode() + "." + "oid" + ".old"; String error = entity.getOid() + " " + entity.getConceptConfig().getCode() + "." + "oid" + " does not exist."; getErrors().add(errorKey, error); } else { ModelMeta modelMeta = model.getModelMeta(); for (PropertyConfig propertyConfig : propertiesConfig) { String propertyCode = propertyConfig.getCode(); Object afterProperty = updateEntity.getProperty(propertyCode); if (propertyConfig.isRequired()) { if (afterProperty != null) { if (propertyConfig.isValidateType()) { validation = modelMeta.validPropertyType(this, updateEntity, propertyConfig, afterProperty) && validation; } if (propertyConfig.getPropertyClass().equals("java.lang.String")) { validation = modelMeta.validMaxLength(this, updateEntity, propertyConfig, afterProperty) && validation; } } else { modelMeta.addRequiredPropertyError(this, entity, propertyConfig); validation = false; } } else if (afterProperty != null) { if (propertyConfig.isValidateType()) { validation = modelMeta.validPropertyType(this, updateEntity, propertyConfig, afterProperty) && validation; } if (propertyConfig.getPropertyClass().equals("java.lang.String")) { validation = modelMeta.validMaxLength(this, updateEntity, propertyConfig, afterProperty) && validation; } } // if } // for } // } // if } // if return validation; } /** * Checks if the update postcondition is <code>true</code>. Called by the * corresponding update method. If the concept has the id, the postondition * checks if the id is unique. If the postcondition is not true, the update * is undone. Meta handling is used. * * @param entity * entity * @param updateEntity * update entity * @return <code>true</code> if the update postcondition is satisfied */ protected boolean postUpdate(T entity, T updateEntity) { if (!isPost()) { return true; } boolean validation = true; ConceptConfig conceptConfig = entity.getConceptConfig(); if (conceptConfig != null) { if (entity.getOid().equals(updateEntity.getOid())) { DomainModel model = (DomainModel) getModel(); if (model.isInitialized()) { ModelMeta modelMeta = model.getModelMeta(); if (!conceptConfig.getUniqueConfig().isEmpty()) { validation = modelMeta.uniqueId(this, entity, updateEntity) && validation; } } if (validation && conceptConfig.isIndex()) { if (conceptConfig.getUniqueConfig().isNotEmpty()) { if (!entity.getUniqueCombination().equals(updateEntity.getUniqueCombination())) { uniqueIndex.remove(entity); uniqueIndex.add(updateEntity); } } if (conceptConfig.getIndexConfig().isNotEmpty()) { if (!entity.getIndexCombination().equals(updateEntity.getIndexCombination())) { index.remove(entity); index.add(updateEntity); } } } } else { validation = false; log.info("Cannot update the oid: " + entity.getOid().toString()); } } return validation; } /** * Updates the entity with the update entity. If the precondition is not * true, the update is not done. If the postcondition is not true, the * update is undone. * * @param entity * entity * @param updateEntity * update entity * @return <code>true</code> if the entity is updated */ public boolean update(T entity, T updateEntity) { return update(entity, updateEntity, true); } /** * Updates the entity with the update entity. The before update entity * (entity) must be a member of the entities. The before update entity and * the after update entity cannot be the same object. Use IEntity.copy to * prepare the update. If the precondition is not true, the update is not * done. If the postcondition is not true, the update is undone. Meta * handling is used. * * @param entity * entity * @param updateEntity * update entity * @param updateSensitive * <code>true</code> if the sensitive information will be updated * @return <code>true</code> if the entity is updated */ public boolean update(T entity, T updateEntity, boolean updateSensitive) { EntitiesAction entitiesUpdateAction = new UpdateAction(this, entity, updateEntity); synchronized (this) { if (entity == updateEntity) { String msg = "Entities.update -- before update entity and after update entity cannot be the same object."; log.info(msg); ((Entity<?>) entity).output("Update"); throw new ActionRuntimeException(msg); } T retrievedEntity = retrieveByOid(entity.getOid()); if (retrievedEntity != entity) { String msg = "Entities.update -- wrong before update entity."; log.info(msg); ((Entity<?>) entity).output("Entity"); if (retrievedEntity == null) { log.info( "Retrieved entity is null -- trying to update an entity that has not been added before the update."); } else { ((Entity<?>) retrievedEntity).output("Retrieved Entity"); } throw new ActionRuntimeException(msg); } if (!preUpdate(entity, updateEntity)) { return false; } boolean updated = false; T backupBeforeEntity = (T) entity.copy(); updated = ((Entity<T>) entity).update(updateEntity, updateSensitive); if (updated) { if (postUpdate(backupBeforeEntity, entity)) { DomainModel model = (DomainModel) getModel(); if (model != null) { // The following code in comment cannot be done // since // updating an entity in a // selection or an order would not notify the model. // if (getSourceEntities() == null) { if (model.isInitialized() && !model.isSession()) { ModelMeta modelMeta = model.getModelMeta(); modelMeta.setParents(updateEntity, this); model.notifyObservers(entitiesUpdateAction); } // } else { // The following code in comment cannot be done // since it // is the same entity to be // updated and it has already been updated. // if (isPropagateToSource()) { // getSourceEntities().update(entity, updateEntity); // } // } return true; } else { // cannot happen? String msg = "Entities.update -- model is null."; throw new ActionRuntimeException(msg); } } else { if (!entity.update(backupBeforeEntity)) { String msg = "Entities.update -- cannot undo an update action that has a not valid postcondition."; log.info(msg); ((Entity<?>) entity).output("Update"); throw new ActionRuntimeException(msg); } } } else { // cannot happen? String msg = "Entities.update -- entity was not updated."; log.info(msg); ((Entity<?>) entity).output("Update"); throw new ActionRuntimeException(msg); } } return false; } /** * Updates only the properties of the entity with the update entity. If the * precondition is not true, the update is not done. If the postcondition is * not true, the update is undone. * * @param entity * entity * @param updateEntity * update entity * @return <code>true</code> if the properties of the entity were updated */ public boolean updateProperties(T entity, T updateEntity) { return update(entity, updateEntity, true); } /** * Updates only the properties of the entity with the update entity. The * before update entity (entity) must be a member of the entities. The * before update entity and the after update entity cannot be the same * object. Use IEntity.copyProperties to prepare the update. If the * precondition is not true, the update is not done. If the postcondition is * not true, the update is undone. Meta handling is used. * * @param entity * entity * @param updateEntity * update entity * @param updateSensitive * <code>true</code> if the sensitive information will be updated * @return <code>true</code> if the properties of the entity were updated */ public boolean updateProperties(T entity, T updateEntity, boolean updateSensitive) { EntitiesAction entitiesUpdateAction = new UpdateAction(this, entity, updateEntity); if (entity == updateEntity) { String msg = "Entities.updateProperties -- before update entity and after update entity cannot be the same object."; log.info(msg); ((Entity<?>) entity).output("Update properties"); throw new ActionRuntimeException(msg); } synchronized (this) { T retrievedEntity = retrieveByOid(entity.getOid()); if (retrievedEntity != entity) { String msg = "Entities.updateProperties -- wrong before update entity."; log.info(msg); ((Entity<?>) entity).output("Update properties"); throw new ActionRuntimeException(msg); } if (!preUpdate(entity, updateEntity)) { return false; } boolean updated = false; T backupBeforeEntity = (T) entity.copyProperties(); updated = ((Entity<T>) entity).updateProperties(updateEntity, updateSensitive); if (updated) { if (postUpdate(backupBeforeEntity, entity)) { DomainModel model = (DomainModel) getModel(); if (model != null) { if (model.isInitialized() && !model.isSession()) { ModelMeta modelMeta = model.getModelMeta(); model.notifyObservers(entitiesUpdateAction); } return true; } else { // cannot happen? String msg = "Entities.updateProperties -- model is null."; throw new ActionRuntimeException(msg); } } else { if (!entity.update(backupBeforeEntity)) { String msg = "Entities.updateProperties -- cannot undo an update action that has a not valid postcondition."; log.info(msg); ((Entity<?>) entity).output("Update properties"); throw new ActionRuntimeException(msg); } } } else { // cannot happen? String msg = "Entities.updateProperties -- properties were not updated."; log.info(msg); ((Entity<?>) entity).output("Update properties"); throw new ActionRuntimeException(msg); } } return false; } /** * Checks if the entities contain a given entity. The oid index is used if * the concept is configured to use the index. * * @param entity * entity * * @return <code> true </code> if the entities contain a given entity */ public boolean contain(T entity) { if (getConceptConfig().isIndex()) { if (oidIndex.getEntity(entity.getOid()) != null) { return true; } else { return false; } } synchronized (this) { return entityList.contains(entity); } } /** * Checks if the entities contain an entity with the given code. The ix * index is used if it is defined only on the code property, and if the * concept is configured to use the index. * * @param code * code * @return <code>true</code> if the entities contain an entity with the * given code */ public boolean containCode(String code) { boolean containCode = false; T codeEntity = retrieveByCode(code); if (codeEntity != null) { containCode = true; } return containCode; } /** * Retrieves the entity with a given oid from the entities. Null if not * found. The oid index is used if the concept is configured to use the * index. The iteration is synchronized. * * @param oid * entity oid * @return entity */ public T retrieveByOid(Oid oid) { if (oid != null) { if (getConceptConfig().isIndex()) { return oidIndex.getEntity(oid); } else { synchronized (this) { for (T entity : entityList) { if (entity.getOid().equals(oid)) { return entity; } } } } } else { String msg = "Entities.retrieveByOid -- oid argument is null."; throw new SelectionRuntimeException(msg); } return null; } /** * Retrieves the entity with a given unique combination (id) from the * entities. Null if not found or id not configured. There may be at most * one id for the concept. The id index is used if the concept is configured * to use the index. * * @param uniqueCombination * entity unique combination * @return entity */ public T retrieveByUnique(UniqueCombination uniqueCombination) { if (uniqueCombination != null) { ConceptConfig conceptConfig = getConceptConfig(); if (conceptConfig.getUniqueConfig().isNotEmpty()) { if (conceptConfig.isIndex()) { return uniqueIndex.getEntity(uniqueCombination); } else { synchronized (this) { for (T entity : entityList) { if (entity.getUniqueCombination().equals(uniqueCombination)) { return entity; } } } } } else { String msg = "Entities.retrieveByUnique -- concept does not have the unique configuration: " + conceptConfig.getCode(); throw new ConfigRuntimeException(msg); } } else { String msg = "Entities.retrieveByUnique -- unique combination argument is null."; throw new SelectionRuntimeException(msg); } return null; } /** * Retrieves the entity with a given index combination from the entities. * Null if not found or if index not configured. The index is used if the * concept is configured to use the index and if it is not configured as * unique. * * @param indexCombination * entity index combination * @return entity */ public T retrieveByIndex(IndexCombination indexCombination) { if (indexCombination != null) { ConceptConfig conceptConfig = getConceptConfig(); if (conceptConfig.getIndexConfig().isNotEmpty()) { if (conceptConfig.isIndex()) { List<T> indexList = index.getList(indexCombination); if (indexList.size() > 0) { return indexList.get(0); } else { return null; } } } else { String msg = "Entities.retrieveByIndex -- concept does not have the index configuration: " + conceptConfig.getCode(); throw new ConfigRuntimeException(msg); } } else { String msg = "Entities.retrieveByIndex -- index combination argument is null."; throw new SelectionRuntimeException(msg); } return null; } /** * Retrieves the first entity with a given code from the entities. Null if * not found. The iteration is synchronized. Used in config classes, thus no * indexes are used. * * @param code * entity code * @return entity */ public T retrieveByCode(String code) { String entityCode; synchronized (this) { for (T entity : entityList) { entityCode = ((Entity<T>) entity).getCode(); if (entityCode != null && entityCode.equals(code)) { return entity; } } } return null; } /** * Retrieves the first entity whose property code is equal to a property * object. Null if not found. If the concept is configured to use the index, * the index is used if it is defined only on the property. If not found, * returns null. * * @param propertyCode * property code * @param property * property * @return entity */ public T retrieveByProperty(String propertyCode, Object property) { ConceptConfig conceptConfig = getConceptConfig(); // used in config classes that do not have the concept config if (conceptConfig != null) { if (conceptConfig.getPropertyConfig(propertyCode) != null) { if (conceptConfig.isIndex()) { CombinationConfig idConfig = conceptConfig.getUniqueConfig(); if (idConfig.size() == 1 && idConfig.propertySize() == 1) { UniqueCombination id = new UniqueCombination(); id.addProperty(propertyCode, property); T entity = uniqueIndex.getEntity(id); if (entity != null) { return entity; } } CombinationConfig ixConfig = conceptConfig.getIndexConfig(); if (ixConfig.size() == 1 && ixConfig.propertySize() == 1) { if (ixConfig.getPropertyConfig(propertyCode) != null) { IndexCombination ix = new IndexCombination(); ix.addProperty(propertyCode, property); List<T> ixList = index.getList(ix); if (ixList.size() > 0) { return ixList.get(0); } else { return null; } } } } } else { String msg = "Entities.retrieveByProperty -- concept does not have the property configuration: " + conceptConfig.getCode() + "." + propertyCode; throw new ConfigRuntimeException(msg); } } synchronized (this) { for (T entity : entityList) { Object propertyObject = entity.getProperty(propertyCode); if (propertyObject != null && propertyObject.equals(property)) { return entity; } } } return null; } /** * Gets a list of entities. Returns an empty list if there are no entities. * * @return list of entities */ public synchronized List<T> getList() { return new ArrayList<T>(entityList); } /** * Returns the first entity based on the order. Null if not found. * * @return first entity */ public synchronized T first() { T first = null; if (entityList.size() > 0) { first = entityList.get(0); } return first; } /** * Returns the last entity based on the order. Null if not found. * * @return last entity */ public synchronized T last() { T last = null; int size = entityList.size(); if (size > 0) { last = entityList.get(size - 1); } return last; } /** * Returns the next entity, with respect to a given entity, based on the * order. Null if not found. * * @return next entity */ public synchronized T next(T entity) { T next = null; int size = entityList.size(); if (size > 0) { int currentIndex = entityList.indexOf(entity); int nextIndex = ++currentIndex; if (nextIndex < size) { next = entityList.get(nextIndex); } } return next; } /** * Returns the prior entity, with respect to a given entity, based on the * order. Null if not found. * * @return prior entity */ public synchronized T prior(T entity) { T prior = null; if (entityList.size() > 0) { int currentIndex = entityList.indexOf(entity); int priorIndex = --currentIndex; if (priorIndex >= 0) { prior = entityList.get(priorIndex); } } return prior; } /** * Returns the random entity based on the size of entities and its order. * Null if the entities are empty. * * @return random entity */ public T random() { int randomIndex = randomGenerator.nextInt(entityList.size()); return locate(randomIndex); } /** * Locates the entity positioned based on the order of entities. Null if the * entities are empty or the position argument is out of bounds. The * position argument must be between 0 and size - 1. * * @return positioned entity */ public synchronized T locate(int position) { T entity = null; int upperBound = entityList.size() - 1; if (position >= 0 && position <= upperBound) { entity = entityList.get(position); } return entity; } /** * Gets a list of not null codes. * * @return list of not null codes */ public List<String> getCodeList() { List<String> codeList = new ArrayList<String>(); synchronized (this) { for (T t : this) { Entity<T> entity = (Entity<T>) t; String code = entity.getCode(); if (code != null) { codeList.add(entity.getCode()); } } } return codeList; } /** * Gets a list of property not null values. * * @param propertyCode * property code * @return list of property not null values */ public List<Object> getPropertyList(String propertyCode) { List<Object> propertyList = new ArrayList<Object>(); DomainModel model = (DomainModel) getModel(); ModelMeta modelMeta = model.getModelMeta(); if (modelMeta != null) { propertyList = modelMeta.getPropertyList(this, propertyCode); } return propertyList; } /** * Gets errors. * * @return errors */ public Errors getErrors() { return errors; } /** * Copies the entities. * * @return deep copied entities */ public IEntities<T> copy() { return copy(true); } /** * Copies the entities. Meta handling is used. * * @param copySensitive * <code>true</code> if the sensitive information will be copied * @return deep copied entities */ public IEntities<T> copy(boolean copySensitive) { IEntities<T> copiedEntities = null; DomainModel baseModel = (DomainModel) model; if (baseModel != null) { ModelMeta modelMeta = baseModel.getModelMeta(); copiedEntities = (IEntities<T>) modelMeta.createEntities(this); synchronized (this) { for (T entity : this) { Entity<T> baseEntity = (Entity<T>) entity; T copiedEntity = (T) baseEntity.copy(copySensitive); copiedEntities.add(copiedEntity); } } } return copiedEntities; } /** * Copies the entities by copying the whole internal tree for each entity. * The copied entities may be a part of another model. * * @param model * another model * @return deep copied entities */ public IEntities<T> deepCopy(IDomainModel model) { return deepCopy(model, true); } /** * Copies the entities by copying the whole internal tree for each entity. * The copied entities may be a part of another model. Meta handling is * used. * * @param model * another model * @param copySensitive * <code>true</code> if the sensitive information will be copied * @return deep copied entities */ public IEntities<T> deepCopy(IDomainModel model, boolean copySensitive) { IEntities<T> copiedEntities = null; DomainModel anotherModel = (DomainModel) model; if (anotherModel != null) { ModelMeta modelMeta = anotherModel.getModelMeta(); copiedEntities = (IEntities<T>) modelMeta.createEntities(this); synchronized (this) { for (T entity : this) { Entity<T> baseEntity = (Entity<T>) entity; T copiedEntity = (T) baseEntity.deepCopy(anotherModel, copySensitive); copiedEntities.add(copiedEntity); } } } return copiedEntities; } /** * Exports the base entities to the taken entities. * * @param takenEntities * taken entities * @param exportSensitive * <code>true</code> if the sensitive information will be * exported */ public void export(IEntities<T> takenEntities, boolean exportSensitive) { IEntities<T> copiedEntities = deepCopy(takenEntities.getModel(), exportSensitive); for (T entityCopy : copiedEntities) { if (!takenEntities.add(entityCopy)) { log.info("Export error: " + entityCopy.getOid().getUniqueNumber()); } } } /** * Synchronizes the returned entity with the base entity. The returned * entity is a new version of the taken entity. * * @param takenEntity * taken entity * @param returnedEntity * returned entity * @param synchronizeSensitive * <code>true</code> if the sensitive information will be * synchronized */ public void synchronize(T baseEntity, T takenEntity, T returnedEntity, boolean synchronizeSensitive) { if (baseEntity.equalProperties(takenEntity)) { updateProperties(baseEntity, returnedEntity, synchronizeSensitive); } NeighborsConfig neighborsConfig = getConceptConfig().getNeighborsConfig(); for (NeighborConfig neighborConfig : neighborsConfig) { if (neighborConfig.isChild() && neighborConfig.isInternal()) { String neighborCode = neighborConfig.getCode(); IEntities baseEntityNeighborEntities = baseEntity.getChildNeighbor(neighborCode); IEntities takenEntityNeighborEntities = takenEntity.getChildNeighbor(neighborCode); IEntities returnedEntityNeighborEntities = returnedEntity.getChildNeighbor(neighborCode); baseEntityNeighborEntities.getErrors().empty(); baseEntityNeighborEntities.synchronize(takenEntityNeighborEntities, returnedEntityNeighborEntities, synchronizeSensitive); List<String> errors = baseEntityNeighborEntities.getErrors().getErrorList(); if (errors.size() > 0) { OutTester.outputCollection(errors, baseEntityNeighborEntities.getConceptConfig().getCode() + " Synchronize Errors"); } } } // for } /** * Synchronizes the returned entities with the base entities. The returned * entities are a new version of the taken entities. * * @param takenEntities * taken entities * @param returnedEntities * returned entities * @param synchronizeSensitive * <code>true</code> if the sensitive information will be * synchronized */ public void synchronize(IEntities<T> takenEntities, IEntities<T> returnedEntities, boolean synchronizeSensitive) { for (T entity : returnedEntities) { Entity<T> returnedEntity = (Entity<T>) entity; Oid oid = returnedEntity.getOid(); T baseEntity = retrieveByOid(oid); T takenEntity = takenEntities.retrieveByOid(oid); if (baseEntity == null) { if (takenEntity == null) { T returnedEntityCopy = (T) returnedEntity.deepCopy(model, synchronizeSensitive); if (!add(returnedEntityCopy)) { log.info("Synhronize add error: " + returnedEntity.getOid().getUniqueNumber()); } } else { log.info("Base entity with the following oid has been removed from the base model: " + oid.getUniqueNumber()); } } else { if (takenEntity == null) { log.error("Base entity has the same oid as the returned entity but the taken entity is null: " + oid.getUniqueNumber()); } else { synchronize(baseEntity, takenEntity, entity, synchronizeSensitive); } } } // for } /** * Entities removed from the returned model (in comparison with the taken * model) are removed from the base model. Entities must share the same * configuration. This is not yet verified. * * @param takenEntities * taken entities * @param returnedEntities * returned entities */ public void clean(IEntities<T> takenEntities, IEntities<T> returnedEntities) { for (T takenEntity : takenEntities) { Oid oid = takenEntity.getOid(); T baseEntity = retrieveByOid(oid); T returnedEntity = returnedEntities.retrieveByOid(oid); if (returnedEntity == null) { if (baseEntity == null) { log.info("Base entity with the following oid has been removed from the base model: " + oid.getUniqueNumber()); } else { if (!remove(takenEntity)) { log.info("Clean remove error: " + takenEntity.getOid().getUniqueNumber()); } } } else { NeighborsConfig neighborsConfig = getConceptConfig().getNeighborsConfig(); for (NeighborConfig neighborConfig : neighborsConfig) { if (neighborConfig.isChild() && neighborConfig.isInternal()) { String neighborCode = neighborConfig.getCode(); IEntities baseEntityNeighborEntities = baseEntity.getChildNeighbor(neighborCode); IEntities takenEntityNeighborEntities = takenEntity.getChildNeighbor(neighborCode); IEntities returnedEntityNeighborEntities = returnedEntity.getChildNeighbor(neighborCode); baseEntityNeighborEntities.getErrors().empty(); baseEntityNeighborEntities.clean(takenEntityNeighborEntities, returnedEntityNeighborEntities); List<String> errors = baseEntityNeighborEntities.getErrors().getErrorList(); if (errors.size() > 0) { OutTester.outputCollection(errors, baseEntityNeighborEntities.getConceptConfig().getCode() + " Clean Errors"); } } } // for } } } /** * Outputs entities. Used in testing. Meta handling is used. * * @param title * title */ public void output(String title) { DomainModel model = (DomainModel) getModel(); if (model != null) { ModelMeta modelMeta = model.getModelMeta(); modelMeta.output(this, title); } } /** * Gets a list of oids of entities. * * @return list of oids of entities */ public List<Oid> getOidList() { List<Oid> oidList = new ArrayList<Oid>(); for (IEntity<?> entity : this) { oidList.add(entity.getOid()); } return oidList; } /** * Gets a list of strings where each string represents toString() of an * entity. * * @return list of strings where each string represents toString() of an * entity */ public List<String> getEntityToStringList() { List<String> entityToStringList = new ArrayList<String>(); for (IEntity<?> entity : this) { entityToStringList.add(((Entity<?>) entity).toString()); } return entityToStringList; } /** * Gets a list of strings where each string represents essential properties * of an entity. * * @return list of strings where each string represents essential properties * of an entity */ public List<String> getEssentialPropertiesStringList() { List<String> essentialPropertiesStringList = new ArrayList<String>(); for (IEntity<?> entity : this) { essentialPropertiesStringList.add(((Entity<?>) entity).toEssentialPropertiesString()); } return essentialPropertiesStringList; } /** * Updates the entities's indexes with the update entity. Called by the add * method when the parent neighbors have been set. * * @param entity */ private void updateIndexes(T entity) { if (conceptConfig.isIndex()) { oidIndex.add(entity); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex.add(entity); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index.add(entity); } } } /** * Updates the entities's indexes using entities action. * * @param action */ private void updateIndexes(Action action) { if (action instanceof EntitiesAction) { EntitiesAction entitiesAction = (EntitiesAction) action; T entity; String actionName = entitiesAction.getName(); if (actionName.equals("add")) { entity = (T) entitiesAction.getEntity(); if (conceptConfig.isIndex()) { oidIndex.add(entity); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex.add(entity); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index.add(entity); } } } else if (actionName.equals("remove")) { entity = (T) entitiesAction.getEntity(); if (conceptConfig.isIndex()) { oidIndex.remove(entity); if (conceptConfig.getUniqueConfig().isNotEmpty()) { uniqueIndex.remove(entity); } if (conceptConfig.getIndexConfig().isNotEmpty()) { index.remove(entity); } } } else if (actionName.equals("update")) { // TODO: implement indexes update } else if (actionName.equals("attach")) { // TODO: implement indexes update } else if (actionName.equals("detach")) { // TODO: implement indexes update } } } /** * Notifies observes. * * @param arg * arg */ @Override public void notifyObservers(Object arg) { this.setChanged(); super.notifyObservers(arg); } /** * Entities iterator. Encapsulates entityList iterator to prevent remove * action directly on the entityList, thus bypassing modelibra. * */ private class EntitiesIterator implements Iterator<T> { private Iterator<T> iterator = entityList.iterator(); @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return iterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } }