/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.common.align.model; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import javax.xml.namespace.QName; import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultiset; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multiset; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.align.model.impl.ChildEntityDefinition; import eu.esdihumboldt.hale.common.align.model.impl.DefaultCell; import eu.esdihumboldt.hale.common.align.model.impl.DefaultProperty; import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition; import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition; import eu.esdihumboldt.hale.common.instance.extension.filter.FilterDefinitionManager; import eu.esdihumboldt.hale.common.instance.model.Filter; import eu.esdihumboldt.hale.common.instance.model.Group; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.instance.model.MutableInstance; import eu.esdihumboldt.hale.common.instance.model.impl.DefaultInstance; import eu.esdihumboldt.hale.common.schema.SchemaSpaceID; import eu.esdihumboldt.hale.common.schema.model.ChildDefinition; import eu.esdihumboldt.hale.common.schema.model.Definition; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.util.groovy.paths.Path; /** * Alignment model utility methods. * * @author Simon Templer */ public abstract class AlignmentUtil { private static final ALogger log = ALoggerFactory.getLogger(AlignmentUtil.class); /** * Determines if the given cell is a type cell. * * @param cell the cell * @return if the cell is a type cell */ public static boolean isTypeCell(Cell cell) { // check if cell is a type cell return cell.getTarget().values().iterator().next() instanceof Type; } /** * Determines if the given alignment has any type relations. * * @param alignment the alignment * @return if any type cells are present in the alignment */ public static boolean hasTypeRelation(Alignment alignment) { for (Cell cell : alignment.getCells()) { if (isTypeCell(cell)) { return true; } } return false; } /** * Determines if the given alignment contains a relation between the given * types. * * @param alignment the alignment * @param sourceType the source type, may be <code>null</code> for any * source type * @param targetType the target type, may be <code>null</code> for any * target type * @return if a relation between the given types exists in the alignment */ public static boolean hasTypeRelation(Alignment alignment, TypeEntityDefinition sourceType, TypeEntityDefinition targetType) { if (sourceType == null && targetType == null) { // accept any type relation return hasTypeRelation(alignment); } else if (sourceType == null) { // accept any relation to the given target type Collection<? extends Cell> cells = alignment.getCells(targetType); return !cells.isEmpty(); } else if (targetType == null) { // accept any relation to the given source type Collection<? extends Cell> cells = alignment.getCells(sourceType); return !cells.isEmpty(); } else { // accept relations only if they combine both types Collection<? extends Cell> targetCells = alignment.getCells(targetType); Collection<? extends Cell> sourceCells = alignment.getCells(sourceType); targetCells.retainAll(sourceCells); return !targetCells.isEmpty(); } } /** * Determines if the given cell is an augmentation. * * @param cell the cell * @return if the cell is an augmentation */ public static boolean isAugmentation(Cell cell) { // check if cell is an augmentation cell return cell.getSource() == null || cell.getSource().isEmpty(); } /** * Get the parent entity definition for the given entity definition. * * @param entity the entity definition * @return the parent entity definition or <code>null</code> if it has no * parent */ public static EntityDefinition getParent(EntityDefinition entity) { List<ChildContext> path = entity.getPropertyPath(); if (path == null || path.isEmpty()) { // entity is a type and has no parent return null; } else { List<ChildContext> newPath = new ArrayList<ChildContext>(path); newPath.remove(newPath.size() - 1); return createEntity(entity.getType(), newPath, entity.getSchemaSpace(), entity.getFilter()); } } /** * Get the default child of the given entity. * * @param entity the parent entity * @param childName the child name * @return the child entity or <code>null</code> if no child with the given * name exists */ public static EntityDefinition getChild(EntityDefinition entity, QName childName) { ChildDefinition<?> child = DefinitionUtil.getChild(entity.getDefinition(), childName); if (child == null) { return null; } List<ChildContext> path = new ArrayList<ChildContext>(entity.getPropertyPath()); path.add(new ChildContext(child)); return createEntity(entity.getType(), path, entity.getSchemaSpace(), entity.getFilter()); } /** * Create an entity definition from a type and a child path. * * @param type the path parent * @param path the child path * @param schemaSpace the associated schema space * @param filter the entity filter on the type, may be <code>null</code> * @return the created entity definition */ public static EntityDefinition createEntity(TypeDefinition type, List<ChildContext> path, SchemaSpaceID schemaSpace, Filter filter) { if (path == null || path.isEmpty()) { // entity is a type return new TypeEntityDefinition(type, schemaSpace, filter); } else if (path.get(path.size() - 1).getChild() instanceof PropertyDefinition) { // last element in path is a property return new PropertyEntityDefinition(type, path, schemaSpace, filter); } else { // last element is a child but no property return new ChildEntityDefinition(type, path, schemaSpace, filter); } } /** * Create an entity definition from a definition path. Child contexts will * all be defaults contexts. * * @param path the definition path, the topmost element has to represent a * {@link TypeDefinition}, all other elements must be * {@link ChildDefinition}s * @param schemaSpace the associated schema space * @param filter the entity filter on the type, may be <code>null</code> * @return the created entity definition */ public static EntityDefinition createEntity(Path<Definition<?>> path, SchemaSpaceID schemaSpace, Filter filter) { List<Definition<?>> defs = path.getElements(); // create entity definition Definition<?> top = defs.get(0); if (!(top instanceof TypeDefinition)) throw new IllegalArgumentException("Topmost accessor must represent a type definition"); List<ChildDefinition<?>> childPath = Lists.transform(defs.subList(1, defs.size()), new Function<Definition<?>, ChildDefinition<?>>() { @Override public ChildDefinition<?> apply(Definition<?> input) { if (input instanceof ChildDefinition<?>) return (ChildDefinition<?>) input; throw new IllegalArgumentException( "All definitions in child accessors must be ChildDefinitions"); } }); // childPath will be copied in there, no need to do it here return createEntityFromDefinitions((TypeDefinition) top, childPath, schemaSpace, filter); } /** * Create an entity definition from a definition path. Child contexts will * all be defaults contexts. * * @param type the path parent * @param path the child path * @param schemaSpace the associated schema space * @param filter the entity filter on the type, may be <code>null</code> * @return the created entity definition */ public static EntityDefinition createEntityFromDefinitions(TypeDefinition type, List<? extends ChildDefinition<?>> path, SchemaSpaceID schemaSpace, Filter filter) { ArrayList<ChildContext> contextPath = new ArrayList<>(path.size()); // use a copy for efficiency later contextPath.addAll(Lists.transform(path, new Function<ChildDefinition<?>, ChildContext>() { @Override public ChildContext apply(ChildDefinition<?> input) { return new ChildContext(input); } })); return createEntity(type, contextPath, schemaSpace, filter); } /** * Get the entity definition with the default instance context which is a * sibling to (or the same as) the given entity definition. * * @param entity the entity definition * @return the entity definition with the default context in the last path * element */ public static EntityDefinition getDefaultEntity(EntityDefinition entity) { List<ChildContext> path = entity.getPropertyPath(); if (path == null || path.isEmpty()) { if (entity.getFilter() == null) { return entity; } // sibling of type w/o filter return createEntity(entity.getType(), path, entity.getSchemaSpace(), null); } else { ChildContext lastConext = path.get(path.size() - 1); if (lastConext.getContextName() == null && lastConext.getIndex() == null && lastConext.getCondition() == null) { return entity; } } List<ChildContext> newPath = new ArrayList<ChildContext>(path); ChildDefinition<?> lastChild = newPath.get(newPath.size() - 1).getChild(); newPath.remove(newPath.size() - 1); newPath.add(new ChildContext(lastChild)); return createEntity(entity.getType(), newPath, entity.getSchemaSpace(), entity.getFilter()); } /** * Get the entity definition based on the given entity definition with the * default instance context for each path entry and w/o filter. * * @param entity the entity definition * @return the entity definition with the default context in all path * elements and w/o type filter, if no contexts and type filter are * present the supplied entity is returned directly */ public static EntityDefinition getAllDefaultEntity(EntityDefinition entity) { return getAllDefaultEntity(entity, false); } /** * Get the entity definition based on the given entity definition with the * default instance context for each path entry and w/o filter. * * @param entity the entity definition * @param stripSchemaSpace if the schema space information should be * stripped from the entity * @return the entity definition with the default context in all path * elements and w/o type filter, if no contexts and type filter are * present the supplied entity is returned directly */ public static EntityDefinition getAllDefaultEntity(EntityDefinition entity, final boolean stripSchemaSpace) { final boolean keepSchemaSpace = !stripSchemaSpace; List<ChildContext> path = entity.getPropertyPath(); if (path == null || path.isEmpty()) { // no path if (entity.getFilter() == null && (keepSchemaSpace || entity.getSchemaSpace() == null)) { return entity; } else { return new TypeEntityDefinition(entity.getType(), keepSchemaSpace ? entity.getSchemaSpace() : null, null); } } boolean contextInPath = false; List<ChildContext> newPath = new ArrayList<ChildContext>(); for (ChildContext context : path) { if (context.getCondition() != null || context.getIndex() != null || context.getContextName() != null) { // context found in path contextInPath = true; } ChildContext newcontext = new ChildContext(context.getChild()); newPath.add(newcontext); } if (contextInPath || entity.getFilter() != null || (stripSchemaSpace && entity.getSchemaSpace() != null)) { // contexts or filter found, return default entity return createEntity(entity.getType(), newPath, keepSchemaSpace ? entity.getSchemaSpace() : null, null); } else { // no contexts or filter found, yield unchanged return entity; } } /** * Apply the given schema space to the entity definition. Creates a new * entity definition if necessary. * * @param entity the entity definition * @param schemaSpace the schema space to apply * @return the entity definition with the schema space applied */ public static EntityDefinition applySchemaSpace(EntityDefinition entity, final SchemaSpaceID schemaSpace) { if (entity == null || java.util.Objects.equals(schemaSpace, entity.getSchemaSpace())) { return entity; } List<ChildContext> path = entity.getPropertyPath(); if (path == null || path.isEmpty()) { return new TypeEntityDefinition(entity.getType(), schemaSpace, entity.getFilter()); } return createEntity(entity.getType(), path, schemaSpace, entity.getFilter()); } /** * Derive an entity definition from the given one but with a maximum path * length. * * @param entity the entity definition * @param pathLength the maximum path length * @return the entity definition derived from the given entity definition * but with the property path shortened if needed, otherwise the * given definition will be returned */ public static EntityDefinition deriveEntity(EntityDefinition entity, int pathLength) { if (pathLength < 0) { pathLength = 0; } List<ChildContext> path = entity.getPropertyPath(); if (path == null || path.size() <= pathLength) { return entity; } List<ChildContext> newPath = new ArrayList<ChildContext>(pathLength); for (int i = 0; i < pathLength; i++) { newPath.add(path.get(i)); } return createEntity(entity.getType(), newPath, entity.getSchemaSpace(), entity.getFilter()); } /** * Determines if a given entity definition is a parent of another entity * definition or if both are equal. * * @param parent the parent * @param child the potential child * @return if the first entity definition is a parent of the second or if * both are equal */ public static boolean isParent(EntityDefinition parent, EntityDefinition child) { if (!parent.getType().equals(child.getType())) { // if the types do not match, there can't be a relation return false; } // check type context if (!Objects.equals(parent.getFilter(), child.getFilter())) { // if the filters do not match, there can't be a relation return false; } // check the property paths List<ChildContext> parentPath = parent.getPropertyPath(); List<ChildContext> childPath = child.getPropertyPath(); if (parentPath.size() > childPath.size()) { // property path for parent is longer, can't be parent of child return false; } // check parent path elements for equality with child path for (int i = 0; i < parentPath.size(); i++) { ChildContext parentContext = parentPath.get(i); ChildContext childContext = childPath.get(i); if (!parentContext.equals(childContext)) { return false; } } return true; } /** * States if the given entity definition or one of its children is mapped in * the given alignment. * * @param entity the entity definition * @param alignment the alignment * @return if the alignment contains a relation where the given entity or * one of its children (including grand-children etc.) is involved */ public static boolean entityOrChildMapped(EntityDefinition entity, Alignment alignment) { // check for a direct mapping if (!alignment.getCells(entity).isEmpty()) { return true; } // check for child mappings Collection<? extends Cell> typeCells = alignment.getCells(entity.getType(), entity.getSchemaSpace()); for (Cell cell : typeCells) { if (entity.getSchemaSpace() == SchemaSpaceID.SOURCE && cell.getSource() != null && entityOrChildContained(entity, cell.getSource().values())) { return true; } if (entity.getSchemaSpace() == SchemaSpaceID.TARGET && cell.getTarget() != null && entityOrChildContained(entity, cell.getTarget().values())) { return true; } } return false; } /** * Determines if the given entity definition or one of its children is * contained in the given entity candidates. * * @param entity the entity definition * @param candidates the entity candidates to test * @return if at least one of the entity candidates is the given entity or a * child (or grand-child etc.) */ public static boolean entityOrChildContained(EntityDefinition entity, Iterable<? extends Entity> candidates) { for (Entity candidate : candidates) { EntityDefinition def = candidate.getDefinition(); if (isParent(entity, def)) { return true; } } return false; } /** * Get the type entity definition of the given entity definition. * * @param entityDef the entity definition * @return the entity definition if it is a {@link TypeEntityDefinition}, * otherwise a new type entity definition is created */ public static TypeEntityDefinition getTypeEntity(EntityDefinition entityDef) { if (entityDef instanceof TypeEntityDefinition) { return (TypeEntityDefinition) entityDef; } else { return new TypeEntityDefinition(entityDef.getType(), entityDef.getSchemaSpace(), entityDef.getFilter()); } } /** * Get the context name of the given entity definition. * * @param candidate the entity definition * @return the context name, <code>null</code> for the default context */ public static Integer getContextName(EntityDefinition candidate) { List<ChildContext> path = candidate.getPropertyPath(); if (path == null || path.isEmpty()) { // type currently always a default context return null; } else { return path.get(path.size() - 1).getContextName(); } } /** * Get the context index of the given entity definition. * * @param candidate the entity definition * @return the context name, <code>null</code> for the default context */ public static Integer getContextIndex(EntityDefinition candidate) { List<ChildContext> path = candidate.getPropertyPath(); if (path == null || path.isEmpty()) { // type currently always a default context return null; } else { return path.get(path.size() - 1).getIndex(); } } /** * Get the context condition of the given entity definition. * * @param candidate the entity definition * @return the context name, <code>null</code> for the default context */ public static Condition getContextCondition(EntityDefinition candidate) { List<ChildContext> path = candidate.getPropertyPath(); if (path == null || path.isEmpty()) { // wrap the type filter in a condition Filter filter = candidate.getFilter(); if (filter == null) { return null; } else { return new Condition(filter); } } else { return path.get(path.size() - 1).getCondition(); } } /** * Get a text representation for the entity definition context. * * @param entityDef the entity definition * @return the context text or <code>null</code> if it is the default * context or the context has no text representation */ public static String getContextText(EntityDefinition entityDef) { List<ChildContext> path = entityDef.getPropertyPath(); if (path != null && !path.isEmpty()) { ChildContext lastContext = path.get(path.size() - 1); if (lastContext.getIndex() != null) { return "[" + lastContext.getIndex() + "]"; } else if (lastContext.getCondition() != null) { return getFilterText(lastContext.getCondition().getFilter()); } } else { // type filter return getFilterText(entityDef.getFilter()); } return null; } /** * Get the text to display for a filter. * * @param filter the filter, may be <code>null</code> * @return the filter text or <code>null</code> */ public static String getFilterText(Filter filter) { String filterString = FilterDefinitionManager.getInstance().asString(filter); if (filterString != null) { int pos = filterString.indexOf(':'); if (pos >= 0 && pos + 1 < filterString.length()) { filterString = filterString.substring(pos + 1); } } return filterString; } /** * Determines if the given entity is a default entity. * * @param entity the entity to check * @return if the entity is a default entity */ public static boolean isDefaultEntity(EntityDefinition entity) { if (entity.getFilter() != null) { return false; } for (ChildContext context : entity.getPropertyPath()) { if (context.getCondition() != null || context.getContextName() != null || context.getIndex() != null) { return false; } } return true; } /** * Assures that an entity is a {@link TypeEntityDefinition}, * {@link PropertyEntityDefinition} or {@link ChildEntityDefinition} and * that the inherent classification is correct. * * @param entity the entity definition * @return the entity that is guaranteed to be a * {@link TypeEntityDefinition}, {@link PropertyEntityDefinition} or * {@link ChildEntityDefinition} */ public static EntityDefinition normalizeEntity(EntityDefinition entity) { if (entity instanceof TypeEntityDefinition) { return entity; } else if (entity instanceof PropertyEntityDefinition) { // check if use of PED is correct if (entity.getDefinition() instanceof ChildDefinition<?> && ((ChildDefinition<?>) entity.getDefinition()).asGroup() != null) { // should be a CED return new ChildEntityDefinition(entity.getType(), entity.getPropertyPath(), entity.getSchemaSpace(), entity.getFilter()); } else if (entity.getPropertyPath().isEmpty()) { return new TypeEntityDefinition(entity.getType(), entity.getSchemaSpace(), entity.getFilter()); } else { return entity; } } else if (entity instanceof ChildEntityDefinition) { // check if use of CED is correct if (entity.getDefinition() instanceof ChildDefinition<?> && ((ChildDefinition<?>) entity.getDefinition()).asProperty() != null) { // should be a PED return new PropertyEntityDefinition(entity.getType(), entity.getPropertyPath(), entity.getSchemaSpace(), entity.getFilter()); } else if (entity.getPropertyPath().isEmpty()) { return new TypeEntityDefinition(entity.getType(), entity.getSchemaSpace(), entity.getFilter()); } else { return entity; } } else { if (entity.getPropertyPath().isEmpty()) { return new TypeEntityDefinition(entity.getType(), entity.getSchemaSpace(), entity.getFilter()); } else { if (entity.getDefinition() instanceof ChildDefinition<?>) { ChildDefinition<?> child = (ChildDefinition<?>) entity.getDefinition(); if (child.asProperty() != null) { // should be a PED return new PropertyEntityDefinition(entity.getType(), entity.getPropertyPath(), entity.getSchemaSpace(), entity.getFilter()); } else if (child.asGroup() != null) { // should be a CED return new ChildEntityDefinition(entity.getType(), entity.getPropertyPath(), entity.getSchemaSpace(), entity.getFilter()); } else { throw new IllegalArgumentException("Illegal entity definition"); } } else { throw new IllegalArgumentException("Illegal entity definition"); } } } } /** * Match a property condition against a property value. * * @param condition the property condition * @param value the property value * @param parent the parent of the property value, may be <code>null</code> * if there is none * @return if the value matched the property condition */ public static boolean matchCondition(Condition condition, Object value, Object parent) { // create dummy instance MutableInstance dummy = new DefaultInstance(null, null); // add value as property dummy.addProperty(new QName("value"), value); // add parent value as property if (parent != null) { dummy.addProperty(new QName("parent"), parent); } return condition.getFilter().match(dummy); } /** * Returns a cell like the given property cell with all source and target * types matching those off the given type cell.<br> * If the types already match they are unchanged. If the types are sub types * of the types of the type cell they are changed. If no change is necessary * the cell itself is returned. * * @param propertyCell the property cell to update * @param typeCell the type cell with the target types * @param strict If false and the target type cell has no sources or target, * the property cell is updated to have the sources/target in * their declaring type. If true, said properties are left * unchanged. Does not matter for complete type cells, since they * have sources and a target. * @return the updated cell or <code>null</code> if an update isn't possible */ public static Cell reparentCell(Cell propertyCell, Cell typeCell, boolean strict) { ListMultimap<String, Entity> sources = ArrayListMultimap.create(); ListMultimap<String, Entity> targets = ArrayListMultimap.create(); boolean updateNecessary = false; // XXX are updates to the property path needed? // Currently not, since ChildDefinitions are compared by their names // only. // TARGETS Entity targetEntity = CellUtil.getFirstEntity(typeCell.getTarget()); if (targetEntity != null) { TypeDefinition typeCellTargetType = ((Type) targetEntity).getDefinition() .getDefinition(); for (Entry<String, ? extends Entity> target : propertyCell.getTarget().entries()) { TypeDefinition propertyCellTargetType = target.getValue().getDefinition().getType(); if (propertyCellTargetType.equals(typeCellTargetType)) targets.put(target.getKey(), target.getValue()); else if (DefinitionUtil.isSuperType(typeCellTargetType, propertyCellTargetType)) { PropertyEntityDefinition oldDef = (PropertyEntityDefinition) target.getValue() .getDefinition(); targets.put(target.getKey(), new DefaultProperty(new PropertyEntityDefinition(typeCellTargetType, oldDef.getPropertyPath(), SchemaSpaceID.TARGET, null))); updateNecessary = true; } else { // a cell with targets in more than one type return null; } } } else if (!strict) updateNecessary |= reparentToDeclaring(propertyCell.getTarget(), targets); else targets.putAll(propertyCell.getTarget()); // SOURCES if (propertyCell.getSource() != null && !propertyCell.getSource().isEmpty()) { if (typeCell.getSource() != null && !typeCell.getSource().isEmpty()) { // collect source entity definitions Collection<TypeEntityDefinition> typeCellSourceTypes = new ArrayList<TypeEntityDefinition>(); for (Entity entity : typeCell.getSource().values()) typeCellSourceTypes.add((TypeEntityDefinition) entity.getDefinition()); for (Entry<String, ? extends Entity> source : propertyCell.getSource().entries()) { TypeEntityDefinition propertyCellSourceType = getTypeEntity( source.getValue().getDefinition()); if (typeCellSourceTypes.contains(propertyCellSourceType)) sources.put(source.getKey(), source.getValue()); else { boolean matchFound = false; // try to find a matching source // XXX what if multiple sources match? // currently all are added // maybe the whole cell should be duplicated? for (TypeEntityDefinition typeCellSourceType : typeCellSourceTypes) { if (DefinitionUtil.isSuperType(typeCellSourceType.getDefinition(), propertyCellSourceType.getDefinition()) && (propertyCellSourceType.getFilter() == null || propertyCellSourceType.getFilter() .equals(typeCellSourceType.getFilter()))) { if (matchFound) log.warn( "Inherited property cell source matches multiple sources of type cell."); matchFound = true; PropertyEntityDefinition oldDef = (PropertyEntityDefinition) source .getValue().getDefinition(); sources.put(source.getKey(), new DefaultProperty(new PropertyEntityDefinition( typeCellSourceType.getDefinition(), oldDef.getPropertyPath(), SchemaSpaceID.SOURCE, typeCellSourceType.getFilter()))); updateNecessary = true; // XXX break; if only one match should be added } } if (!matchFound) { // a cell with a source that does not match the type // cell return null; } } } } else if (!strict) updateNecessary |= reparentToDeclaring(propertyCell.getSource(), sources); else targets.putAll(propertyCell.getTarget()); } if (updateNecessary) { MutableCell copy = new DefaultCell(propertyCell); copy.setSource(sources); copy.setTarget(targets); propertyCell = copy; } return propertyCell; } /** * Copies the properties from original to modified, modifying properties to * their declaring type, if the type has no filter set. * * @param original the original Multimap of properties * @param modified the target Multimap * @return true, if a property was changed, false otherwise */ private static boolean reparentToDeclaring(ListMultimap<String, ? extends Entity> original, ListMultimap<String, Entity> modified) { boolean changed = false; for (Entry<String, ? extends Entity> oEntity : original.entries()) { PropertyEntityDefinition property = (PropertyEntityDefinition) oEntity.getValue() .getDefinition(); ChildDefinition<?> childDef = property.getPropertyPath().get(0).getChild(); if (Objects.equals(childDef.getDeclaringGroup(), childDef.getParentType()) || property.getFilter() != null) modified.put(oEntity.getKey(), oEntity.getValue()); else if (childDef.getDeclaringGroup() instanceof TypeDefinition) { modified.put(oEntity.getKey(), new DefaultProperty(new PropertyEntityDefinition( (TypeDefinition) childDef.getDeclaringGroup(), property.getPropertyPath(), property.getSchemaSpace(), null))); changed = true; } else { // declaring group of first level property no type // definition? // simply add it without change, shouldn't happen modified.put(oEntity.getKey(), oEntity.getValue()); } } return changed; } /** * Checks whether the given entity (or one of its children) is associated * with the given cell (considering inheritance). * * @param entity the entity to check * @param cell the cell to check the entity against * @param allowInheritance whether inheritance is allowed * @param orChildren will also check against the entities children * @return whether the entity is associated with the cell */ public static boolean associatedWith(EntityDefinition entity, Cell cell, boolean allowInheritance, boolean orChildren) { ListMultimap<String, ? extends Entity> entities; switch (entity.getSchemaSpace()) { case SOURCE: entities = cell.getSource(); break; case TARGET: entities = cell.getTarget(); break; default: throw new IllegalStateException( "Entity definition with illegal schema space encountered"); } if (entities == null) return false; for (Entity e : entities.values()) { EntityDefinition def = e.getDefinition(); if (allowInheritance) { if (DefinitionUtil.isSuperType(entity.getType(), def.getType()) && (def.getFilter() == null || def.getFilter().equals(entity.getFilter()))) { // type is a match according to inheritance, make sure to // have common type def = createEntity(entity.getType(), def.getPropertyPath(), def.getSchemaSpace(), entity.getFilter()); } } if ((orChildren && isParent(entity, def)) || entity.equals(def)) return true; } return false; } /** * Returns the values of the given instance which match the given property * entity definition. * * @param instance the instance to collect values from * @param definition the property * @param onlyValues whether to only return values, or to return whatever * can be found (including groups/instances) * @return all values of the given property */ public static Multiset<Object> getValues(Instance instance, PropertyEntityDefinition definition, boolean onlyValues) { Multiset<Object> result = HashMultiset.create(); addValues(instance, definition.getPropertyPath(), result, onlyValues); return result; } /** * Add the values found on the given path to the given set. * * @param group the parent group * @param path the path on the group * @param collectedValues the set to add the values to * @param onlyValues whether to only return values, or to return whatever * can be found (including groups/instances) */ public static void addValues(Group group, List<ChildContext> path, Multiset<Object> collectedValues, boolean onlyValues) { if (path == null || path.isEmpty()) { // group/instance at end of path if (onlyValues) { // only include instance values if (group instanceof Instance) { Object value = ((Instance) group).getValue(); if (value != null) { collectedValues.add(value); } } } else { // include the group/instance as is collectedValues.add(group); } // empty path - retrieve value from instance } else { // go down the path ChildContext context = path.get(0); List<ChildContext> subPath = path.subList(1, path.size()); Object[] values = group.getProperty(context.getChild().getName()); if (values != null) { // apply the possible source contexts if (context.getIndex() != null) { // select only the item at the index int index = context.getIndex(); if (index < values.length) { values = new Object[] { values[index] }; } else { values = new Object[] {}; } } if (context.getCondition() != null) { // select only values that match the condition List<Object> matchedValues = new ArrayList<Object>(); for (Object value : values) { if (AlignmentUtil.matchCondition(context.getCondition(), value, group)) { matchedValues.add(value); } } values = matchedValues.toArray(); } // check all values for (Object value : values) { if (value instanceof Group) { addValues((Group) value, subPath, collectedValues, onlyValues); } else if (subPath.isEmpty()) { // normal value and at the end of the path if (value != null) { collectedValues.add(value); } } } } } } /** * Get children of an {@link EntityDefinition} without context conditions * * @param entityDef the entity definition * @return Collection of entity definitions */ public static Collection<? extends EntityDefinition> getChildrenWithoutContexts( EntityDefinition entityDef) { List<ChildContext> path = entityDef.getPropertyPath(); Collection<? extends ChildDefinition<?>> children; if (path == null || path.isEmpty()) { // entity is a type, children are the type children children = entityDef.getType().getChildren(); } else { // get parent context ChildContext parentContext = path.get(path.size() - 1); if (parentContext.getChild().asGroup() != null) { children = parentContext.getChild().asGroup().getDeclaredChildren(); } else if (parentContext.getChild().asProperty() != null) { children = parentContext.getChild().asProperty().getPropertyType().getChildren(); } else { throw new IllegalStateException("Illegal child definition type encountered"); } } if (children == null || children.isEmpty()) { return Collections.emptyList(); } Collection<EntityDefinition> result = new ArrayList<EntityDefinition>(children.size()); for (ChildDefinition<?> child : children) { // add default child entity definition to result ChildContext context = new ChildContext(child); EntityDefinition defaultEntity = AlignmentUtil.createEntity(entityDef.getType(), createPath(entityDef.getPropertyPath(), context), entityDef.getSchemaSpace(), entityDef.getFilter()); result.add(defaultEntity); } return result; } private static List<ChildContext> createPath(List<ChildContext> parentPath, ChildContext context) { if (parentPath == null || parentPath.isEmpty()) { return Collections.singletonList(context); } else { List<ChildContext> result = new ArrayList<ChildContext>(parentPath); result.add(context); return result; } } }