/* * 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.transformation.tree.context.impl; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import eu.esdihumboldt.hale.common.align.model.AlignmentUtil; import eu.esdihumboldt.hale.common.align.model.Cell; import eu.esdihumboldt.hale.common.align.model.EntityDefinition; import eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode; import eu.esdihumboldt.hale.common.align.model.transformation.tree.GroupNode; import eu.esdihumboldt.hale.common.align.model.transformation.tree.SourceNode; import eu.esdihumboldt.hale.common.align.model.transformation.tree.TargetNode; import eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationTree; import eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationTreeUtil; import eu.esdihumboldt.hale.common.align.model.transformation.tree.context.TransformationContext; import eu.esdihumboldt.hale.common.align.model.transformation.tree.impl.CellNodeImpl; import eu.esdihumboldt.hale.common.align.model.transformation.tree.impl.SourceNodeImpl; import eu.esdihumboldt.hale.common.align.model.transformation.tree.impl.TargetNodeImpl; import eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractSourceToTargetVisitor; import eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTargetToSourceVisitor; import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog; import eu.esdihumboldt.hale.common.core.service.ServiceProvider; import eu.esdihumboldt.util.IdentityWrapper; import eu.esdihumboldt.util.Pair; /** * Transformation context that duplicates subgraphs leading to certain target * nodes. * * @author Simon Templer */ public class TargetContext implements TransformationContext { /** * Context of a duplication process. It serves to ensure that each cell or * target node is only created once per context duplication. */ public static class DuplicationContext { private final Map<Cell, CellNodeImpl> cellNodes = new HashMap<Cell, CellNodeImpl>(); private final List<Pair<CellNodeImpl, CellNode>> cellNodePairs = new ArrayList<Pair<CellNodeImpl, CellNode>>(); private final Map<EntityDefinition, TargetNodeImpl> targetNodes = new HashMap<EntityDefinition, TargetNodeImpl>(); private final List<Pair<TargetNodeImpl, TargetNode>> targetNodePairs = new ArrayList<Pair<TargetNodeImpl, TargetNode>>(); private final Set<Cell> ignoreCells; /** * Create a duplication context * * @param ignoreCells the cells to be ignored */ public DuplicationContext(Set<Cell> ignoreCells) { super(); if (ignoreCells != null) { this.ignoreCells = Collections.unmodifiableSet(ignoreCells); } else { this.ignoreCells = Collections.emptySet(); } } /** * Get the cell node associated to the given cell. * * @param cell the cell * @return the cell node or <code>null</code> if none has yet been * associated to the cell */ public CellNodeImpl getNode(Cell cell) { return cellNodes.get(cell); } /** * Add a node to the duplication context. * * @param cellNode the cell node to add to the duplication context * @param originalCell the original cell node where the cell node was * duplicated from */ public void addNode(CellNodeImpl cellNode, CellNode originalCell) { cellNodes.put(cellNode.getCell(), cellNode); cellNodePairs.add(new Pair<CellNodeImpl, CellNode>(cellNode, originalCell)); } /** * Get the target node associated to the given entity. * * @param entity the entity * @return the target node or <code>null</code> if none has yet been * associated to the entity */ public TargetNodeImpl getNode(EntityDefinition entity) { return targetNodes.get(entity); } /** * Add a node to the duplication context. * * @param targetNode the target node to add to the duplication context * @param originalTarget the original target node where the target node * was duplicated from */ public void addNode(TargetNodeImpl targetNode, TargetNode originalTarget) { targetNodes.put(targetNode.getEntityDefinition(), targetNode); targetNodePairs.add(new Pair<TargetNodeImpl, TargetNode>(targetNode, originalTarget)); } /** * Get the cells to be ignored during duplication. * * @return the cells to be ignored */ public Set<Cell> getIgnoreCells() { return ignoreCells; } /** * Get cell nodes that have incomplete sources compared to the original. * * @return the incomplete cell node paired with the original cell node * it was duplicated from */ public Collection<Pair<CellNodeImpl, CellNode>> getIncompleteCellNodes() { return Collections2.filter(cellNodePairs, new Predicate<Pair<CellNodeImpl, CellNode>>() { @Override public boolean apply(Pair<CellNodeImpl, CellNode> input) { CellNodeImpl duplicate = input.getFirst(); CellNode original = input.getSecond(); return original.getSources().size() > duplicate.getSources().size(); } }); } /** * Get target nodes that have incomplete children or assignments * compared to the original. * * @return the incomplete target node paired with the original target * node it was duplicated from */ public Collection<Pair<TargetNodeImpl, TargetNode>> getIncompleteTargetNodes() { return new ArrayList<Pair<TargetNodeImpl, TargetNode>>(Collections2 .filter(targetNodePairs, new Predicate<Pair<TargetNodeImpl, TargetNode>>() { @Override public boolean apply(Pair<TargetNodeImpl, TargetNode> input) { TargetNodeImpl duplicate = input.getFirst(); TargetNode original = input.getSecond(); return original.getChildren(true).size() > duplicate.getChildren(true) .size() || original.getAssignments().size() > duplicate.getAssignments() .size(); } })); } } private static class DuplicationInformation { private final SourceNode duplicatedNode; private final Set<Cell> ignoreCells; private final Set<TargetNode> contextTargets; // Existing cell nodes for this duplication. private final Multimap<Cell, IdentityWrapper<CellNode>> oldCellNodes; // Existing target nodes for this duplication. // All nodes needed! // For example "T(a*, b*) -> T(x(a, b)*)": duplication of "a" leads to // multiple "x"s, which should be used one after another for the "b"s. private final Multimap<EntityDefinition, IdentityWrapper<TargetNode>> oldTargetNodes; // Existing source nodes for this duplication. private final Multimap<EntityDefinition, SourceNode> oldSourceNodes; // Created cell nodes in this duplication. A cell node is only created // once in a duplication! private final Map<Cell, CellNodeImpl> newCellNodes; // Created target nodes in this duplication. XXX Are multiple created // target nodes of the same definition important? private final Multimap<EntityDefinition, TargetNode> newTargetNodes; /** * Create a duplication information. * * @param duplicatedNode the duplicated node this is all about * @param ignoreCells the cells to be ignored * @param contextTargets the target nodes that can be used as subgraph * end-points */ DuplicationInformation(SourceNode duplicatedNode, Set<Cell> ignoreCells, Set<TargetNode> contextTargets) { this.duplicatedNode = duplicatedNode; if (ignoreCells != null) this.ignoreCells = Collections.unmodifiableSet(ignoreCells); else this.ignoreCells = Collections.emptySet(); this.contextTargets = contextTargets; oldCellNodes = LinkedHashMultimap.create(); oldTargetNodes = LinkedHashMultimap.create(); oldSourceNodes = ArrayListMultimap.create(); newCellNodes = new HashMap<Cell, CellNodeImpl>(); newTargetNodes = ArrayListMultimap.create(); } /** * Returns the node that this duplication is all about. * * @return the duplicated node */ public SourceNode getDuplicatedNode() { return duplicatedNode; } /** * Checks whether the given cell is in the ignored cells. * * @param cell the cell to check * @return true, if the given cell is in the ignored cells, false * otherwise */ boolean isIgnoreCell(Cell cell) { return ignoreCells.contains(cell); } /** * Adds the given target node to the map of existing nodes. * * @param entityDef the entity definition * @param target the target node */ void addOldTargetNode(EntityDefinition entityDef, TargetNode target) { oldTargetNodes.put(entityDef, new IdentityWrapper<>(target)); } /** * Adds the given source node to the map of existing nodes. * * @param entityDef the entity definition * @param source the source node */ void addOldSourceNode(EntityDefinition entityDef, SourceNode source) { oldSourceNodes.put(entityDef, source); } /** * Adds the given cell node to the map of existing nodes. * * @param cell the cell * @param cellNode the cell node */ void addOldCellNode(Cell cell, CellNode cellNode) { oldCellNodes.put(cell, new IdentityWrapper<>(cellNode)); } /** * Adds the given target node to the map of newly created nodes. * * @param entityDef the entity definition * @param target the target node */ void addNewTargetNode(EntityDefinition entityDef, TargetNode target) { newTargetNodes.put(entityDef, target); } /** * Adds the given cell node to the map of newly created nodes. * * @param cell the cell * @param cellNode the cell node */ void addNewCellNode(Cell cell, CellNodeImpl cellNode) { newCellNodes.put(cell, cellNode); } /** * Returns a collection of existing cell nodes with the given cell. * * @param cell the cell * @return a collection of existing cell nodes, the collection uses the * objects identity instead of equals */ Collection<IdentityWrapper<CellNode>> getOldCellNodes(Cell cell) { return oldCellNodes.get(cell); } /** * Returns a newly created cell node with the given cell. * * @param cell the cell * @return a newly created cell node or null */ CellNodeImpl getNewCellNode(Cell cell) { return newCellNodes.get(cell); } /** * Returns a collection of existing source nodes with the given entity * definition. * * @param entityDef the entity definition * @return a collection of existing source nodes, the collection uses * the objects identity instead of equals */ Collection<SourceNode> getOldSourceNodes(EntityDefinition entityDef) { return oldSourceNodes.get(entityDef); } /** * Returns all target nodes of the given definition. First newly * created, then existing ones. * * @param entityDef the entity definition * @return existing target nodes */ Iterable<TargetNode> getAllTargetNodes(EntityDefinition entityDef) { Collection<TargetNode> newTargets = newTargetNodes.get(entityDef); Collection<TargetNode> oldTargets = Collections2.transform( oldTargetNodes.get(entityDef), new Function<IdentityWrapper<TargetNode>, TargetNode>() { @Override public TargetNode apply(IdentityWrapper<TargetNode> input) { return input.getValue(); } }); if (newTargets.isEmpty()) return oldTargets; if (oldTargets.isEmpty()) return newTargets; return Iterables.concat(newTargets, oldTargets); } /** * Returns existing target nodes of the given definition. * * @param entityDef the entity definition * @return existing target nodes, the collection uses the objects * identity instead of equals */ Collection<IdentityWrapper<TargetNode>> getOldTargetNodes(EntityDefinition entityDef) { return oldTargetNodes.get(entityDef); } /** * Returns the target nodes that can be used as subgraph end-points. * * @return the target nodes that can be used as subgraph end-points */ public Set<TargetNode> getContextTargets() { return contextTargets; } } private final Set<TargetNode> contextTargets; private final ServiceProvider serviceProvider; /** * Create a transformation context that duplicates subgraphs leading to * given target nodes. * * @param serviceProvider the service provider */ public TargetContext(ServiceProvider serviceProvider) { super(); this.contextTargets = new HashSet<TargetNode>(); this.serviceProvider = serviceProvider; } /** * Adds the given target nodes as duplication targets. * * @param targets the target nodes to use as subgraph end-points */ public void addContextTargets(Collection<TargetNode> targets) { contextTargets.addAll(targets); } // /** // * @see TransformationContext#duplicateContext(SourceNode, Object) // */ // @Override // public void duplicateContext(SourceNode contextSource, Object value) { // // create a new duplication context // DuplicationContext duplicationContext = new DuplicationContext(); // // // duplicate context source // SourceNode source = duplicateSource(contextSource, // contextSource.getParent(), false, duplicationContext); // // if (source != null) { // // add duplicated source as annotation to context source parent // contextSource.getParent().addAnnotatedChild(source); // // // apply value to source // if (value instanceof Group) { // // value is a group, duplication may again be necessary for its properties // InstanceVisitor visitor = new InstanceVisitor((Group) value); // source.accept(visitor); // } // else { // // value is a simple value // source.setValue(value); // } // } // } /** * @see TransformationContext#duplicateContext(SourceNode, SourceNode, Set, * TransformationLog) */ @Override public void duplicateContext(SourceNode originalSource, final SourceNode duplicate, Set<Cell> ignoreCells, TransformationLog log) { DuplicationInformation info = new DuplicationInformation(duplicate, ignoreCells, contextTargets); SourceNode parent = duplicate.getParent(); if (parent == null) parent = duplicate.getAnnotatedParent(); if (parent != null) { // Find existing cell/target nodes over children of the parent node. Map<EntityDefinition, SourceNode> contextPath = new HashMap<EntityDefinition, SourceNode>(); SourceNode pathNode = duplicate; SourceNode root; do { contextPath.put(pathNode.getEntityDefinition(), pathNode); root = pathNode; if (pathNode.getParent() != null) pathNode = pathNode.getParent(); else pathNode = pathNode.getAnnotatedParent(); } while (pathNode != null); collectExistingNodes(root, contextPath, info, false); // add target nodes reachable through original source node // XXX not all target nodes are needed, collection of some of them // could even lead to wrong results collectExistingNodes(originalSource, Collections.<EntityDefinition, SourceNode> emptyMap(), info, true); duplicateTree(originalSource, duplicate, info, log, serviceProvider); } else throw new IllegalStateException( "Duplicate node neither got a parent, nor an annotated parent."); // Code matching the old stuff at bottom! // // create a new duplication context // DuplicationContext duplicationContext = new DuplicationContext(ignoreCells); // // // configure duplicate, but don't add to parent (as it is already added as annotated child) // // at this point duplicate may have a parent, even if the original source hasn't // configureSourceDuplicate(originalSource, duplicate, // duplicate.getParent(), duplicationContext, false); // // // track back to sources from cells where sources are missing // for (Pair<CellNodeImpl, CellNode> cellPair : duplicationContext.getIncompleteCellNodes()) { // CellNodeImpl cellNode = cellPair.getFirst(); // CellNode originalCell = cellPair.getSecond(); // // cellTrackback(cellNode, originalCell); // } // // // track back from targets where augmentations are missing // for (Pair<TargetNodeImpl, TargetNode> targetPair : duplicationContext.getIncompleteTargetNodes()) { //// TargetNodeImpl targetNode = targetPair.getFirst(); // TargetNode originalTarget = targetPair.getSecond(); // // augmentationTrackback(originalTarget, duplicationContext); // } } /** * Duplicates the transformation tree for the given source node to the given * duplicate source node. * * @param source the original source node * @param duplicate the duplication target * @param info the duplication info object * @param log the transformation log * @param serviceProvider service provider for resolving functions */ private static void duplicateTree(SourceNode source, SourceNode duplicate, DuplicationInformation info, TransformationLog log, ServiceProvider serviceProvider) { // Duplicate relations. for (CellNode cell : source.getRelations(false)) { // check whether the cell is eager for the source node if (TransformationTreeUtil.isEager(cell, source, log, serviceProvider)) continue; // Check whether the cell is ignored. if (info.isIgnoreCell(cell.getCell())) continue; // XXX prioritize already created cell nodes (in this run) over old // nodes? // First check whether an old cell node with a missing source // exists. // If so, add this source to the first so found cell. boolean usedOld = false; for (IdentityWrapper<CellNode> wrapper : info.getOldCellNodes(cell.getCell())) { CellNode oldCellNode = wrapper.getValue(); // Skip the cell if it's full. if (oldCellNode.getSources().size() == cell.getSources().size()) continue; // XXX Other logic to decide which cell to use? // Check whether out duplicate source node is missing... if (!oldCellNode.getSources().contains(duplicate)) { usedOld = true; // Has to be a cell created with the cell constructor. ((CellNodeImpl) oldCellNode).addSource(cell.getSourceNames(source), duplicate); duplicate.addRelation(oldCellNode); break; } } // If no old cell was used, use a newly created one / create one. if (!usedOld) { CellNodeImpl duplicatedCell = info.getNewCellNode(cell.getCell()); if (duplicatedCell == null) { // Create a new cell, augment it with existing sources, add // it to info and duplicate targets. duplicatedCell = new CellNodeImpl(cell.getCell()); augmentCell(cell, duplicatedCell, info); info.addNewCellNode(cell.getCell(), duplicatedCell); duplicateTree(cell, duplicatedCell, info, log); } // Add as relation/source. duplicate.addRelation(duplicatedCell); duplicatedCell.addSource(cell.getSourceNames(source), duplicate); } } // Duplicate children. for (SourceNode child : source.getChildren(false)) { SourceNode duplicatedChild = new SourceNodeImpl(child.getEntityDefinition(), duplicate, true); duplicatedChild.setContext(child.getContext()); duplicateTree(child, duplicatedChild, info, log, serviceProvider); } } /** * Adds existing sources to the duplicated cell. * * @param cell the original cell * @param duplicatedCell the duplicated cell * @param info the duplication info object */ private static void augmentCell(CellNode cell, CellNodeImpl duplicatedCell, DuplicationInformation info) { // This handles the case "a,b* -(a,b)> c": If there is exactly one a and // multiple "b"s and there is a // cell using an "a" and a "b" the a is used multiple times. for (SourceNode source : cell.getSources()) { if (!AlignmentUtil.isParent(info.getDuplicatedNode().getEntityDefinition(), source.getEntityDefinition())) { // This source node does not belong to the duplicated tree, add // an existing fitting source node! Collection<SourceNode> possibleSources = info .getOldSourceNodes(source.getEntityDefinition()); // Only add the node here if there is exactly one // possible // source. // For zero there is nothing to add, for more they are // duplicates // which should add themselves to this cell when they // are // handled. // False? // In case of zero there may be something to add, not duplicated // yet. // -> no problem? it adds itself later. // In case of one it is possible, that some parent of the one // has unhandled leftovers // which would result in more than one! Should the parents be // checked for leftovers? if (possibleSources.size() == 1) duplicatedCell.addSource(cell.getSourceNames(source), possibleSources.iterator().next()); } } } /** * Duplicates the transformation tree for the given cell node to the given * duplicate cell node. * * @param cell the original cell node * @param duplicateCell the duplication target * @param info the duplication info object * @param log the transformation log */ private static void duplicateTree(CellNode cell, CellNode duplicateCell, DuplicationInformation info, TransformationLog log) { // Duplicate targets. for (TargetNode target : cell.getTargets()) { TargetNodeImpl duplicatedTarget = duplicateTree(target, info, log); if (duplicatedTarget != null) { duplicateCell.addTarget(duplicatedTarget); duplicatedTarget.addAssignment(target.getAssignmentNames(cell), duplicateCell); } } } /** * Duplicates the transformation tree for the given target node to the given * duplicate target node. * * @param target the original target node * @param info the duplication info object * @param log the transformation log * @return a collection of newly created target nodes */ private static TargetNodeImpl duplicateTree(TargetNode target, DuplicationInformation info, TransformationLog log) { GroupNode parent = null; // Check if the parent node exists in the given context already. if (target.getParent() instanceof TargetNode) { Iterator<TargetNode> possibleParents = info .getAllTargetNodes(((TargetNode) target.getParent()).getEntityDefinition()) .iterator(); while (possibleParents.hasNext()) { TargetNode possibleParent = possibleParents.next(); if (info.getContextTargets().contains(target) || !possibleParent.getChildren(false).contains(target)) { parent = possibleParent; break; } } } else if (target.getParent() instanceof TransformationTree && info.getContextTargets().contains(target)) { // Reached root, but this is a possible target. parent = target.getParent(); } else { // Reached root, but this is no possible target or the parent is // null! // XXX instead log and return null or not connected TargetNode? See // T O D O below log.warn(log.createMessage("DuplicationContext present, but no matching target found.", null)); return null; // throw new IllegalStateException( // "DuplicationContext present, but no matching target found."); } // TODO What about cases where contextTargets parent doesn't exist yet, // and there is no // place to build (no direct free place, and no other contextTarget) it // on? // If yes, what do? Right now it would end at the exception in the // beginning of this method. // Basically the duplication should fail, right? Completely, or only of // this target? // Construct an example where that happens. if (parent == null) { // Does not exist: recursion. TargetNodeImpl duplicatedTarget = duplicateTree((TargetNode) target.getParent(), info, log); if (duplicatedTarget == null) { return null; } TargetNodeImpl newTarget = new TargetNodeImpl(target.getEntityDefinition(), duplicatedTarget); info.addNewTargetNode(newTarget.getEntityDefinition(), newTarget); duplicatedTarget.addChild(newTarget); return newTarget; } else { // Exists: add as child. TargetNodeImpl newTarget = new TargetNodeImpl(target.getEntityDefinition(), parent); info.addNewTargetNode(newTarget.getEntityDefinition(), newTarget); // If the child is not already present, add it directly, otherwise // as annotated child. if (parent instanceof TargetNodeImpl && !parent.getChildren(false).contains(newTarget)) ((TargetNodeImpl) parent).addChild(newTarget); else parent.addAnnotatedChild(newTarget); return newTarget; } } /** * Track back target nodes and duplicate any augmentation cells. * * @param tree the tree to work on */ public static void augmentationTrackback(TransformationTree tree) { final Map<EntityDefinition, TargetNode> targetNodesWithAugmentations = new HashMap<EntityDefinition, TargetNode>(); // Search for original target nodes tree.accept(new AbstractTargetToSourceVisitor() { Deque<Boolean> hasAugmentation = new ArrayDeque<Boolean>(); /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode) */ @Override public boolean visit(CellNode cell) { // If the cell is an augmentation cell, set the last entry in // hasAugmentation to true. if (cell.getSources().isEmpty()) { if (!hasAugmentation.getLast()) { hasAugmentation.pop(); hasAugmentation.push(true); } } return false; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.TargetNode) */ @Override public boolean visit(TargetNode target) { // Simply add a new level to hasAugmentation starting with // false. hasAugmentation.push(false); return true; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#leave(eu.esdihumboldt.hale.common.align.model.transformation.tree.TargetNode) */ @Override public void leave(TargetNode target) { // If this nodes level in hasAugmentation is true... if (hasAugmentation.pop()) { // ... add it to targetNodesWithAugmentations and set // parents level to true, too. targetNodesWithAugmentations.put(target.getEntityDefinition(), target); if (!hasAugmentation.isEmpty() && !hasAugmentation.getLast()) { hasAugmentation.pop(); hasAugmentation.push(true); } } } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationNodeVisitor#includeAnnotatedNodes() */ @Override public boolean includeAnnotatedNodes() { // Only look for original target nodes. return false; } }); // Add augmentations to all target nodes (no copied target node got them // yet) tree.accept(new AbstractTargetToSourceVisitor() { /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode) */ @Override public boolean visit(CellNode cell) { return false; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.TargetNode) */ @Override public boolean visit(TargetNode target) { // TODO more intelligent behavior of when NOT to create the // augmentation. // For example if the augmentation belongs to a complex // structure that can // occur 0..n times, it should not be created for the first time // due to an // augmentation. TargetNode originalTarget = targetNodesWithAugmentations .get(target.getEntityDefinition()); // Only have to do something if the node is present in the map. if (originalTarget != null && originalTarget != target) { // Check for missing relations (all relations without // sources are missing). for (CellNode originalAssignment : originalTarget.getAssignments()) { if (originalAssignment.getSources().isEmpty()) { CellNodeImpl duplicatedAssignment = new CellNodeImpl( originalAssignment.getCell()); duplicatedAssignment.addTarget(target); ((TargetNodeImpl) target).addAssignment( originalTarget.getAssignmentNames(originalAssignment), duplicatedAssignment); } } // Check for missing children. for (TargetNode child : originalTarget.getChildren(false)) { // Only add missing children that need an augmentation. if (targetNodesWithAugmentations.containsKey(child.getEntityDefinition()) && !target.getChildren(false).contains(child)) { TargetNodeImpl duplicatedChild = new TargetNodeImpl( child.getEntityDefinition(), target); ((TargetNodeImpl) target).addChild(duplicatedChild); // The child will be handled by this visior later. } } return true; } else return false; } @Override public boolean includeAnnotatedNodes() { return true; } }); } /** * Collects all TargetNodes associated with the given SourceNode excluding * SourceNodes with the given EntityDefinition. * * @param source the source to start from * @param contextPath source nodes with a definition in this map are only * followed if they are exactly the node in this map * @param info the duplication info object * @param targetsOnly whether to only collect target nodes, or all nodes */ private void collectExistingNodes(SourceNode source, final Map<EntityDefinition, SourceNode> contextPath, final DuplicationInformation info, final boolean targetsOnly) { source.accept(new AbstractSourceToTargetVisitor() { /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationNodeVisitor#includeAnnotatedNodes() */ @Override public boolean includeAnnotatedNodes() { return true; // Really include annotated nodes? Maybe only // special handling for types in // visit(SourceNode)? } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode) */ @Override public boolean visit(TargetNode target) { // TargetNodes can be found more than once... Collection<IdentityWrapper<TargetNode>> targetNodes = info .getOldTargetNodes(target.getEntityDefinition()); if (targetNodes.contains(new IdentityWrapper<>(target))) { return false; // already found & followed... } info.addOldTargetNode(target.getEntityDefinition(), target); return true; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode) */ @Override public boolean visit(CellNode cell) { // CellNodes can be found more than once... Collection<IdentityWrapper<CellNode>> cellNodes = info .getOldCellNodes(cell.getCell()); if (cellNodes.contains(new IdentityWrapper<>(cell))) { return false; // already found & followed... } if (!targetsOnly) info.addOldCellNode(cell.getCell(), cell); return true; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationTree) */ @Override public boolean visit(TransformationTree root) { return false; } /** * @see eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.AbstractTransformationNodeVisitor#visit(eu.esdihumboldt.hale.common.align.model.transformation.tree.SourceNode) */ @Override public boolean visit(SourceNode source) { SourceNode contextNode = contextPath.get(source.getEntityDefinition()); if (contextNode == null || source == contextNode) { if (!targetsOnly) info.addOldSourceNode(source.getEntityDefinition(), source); return true; } else return false; } }); } // /** // * Checks whether the given source node is part of the context for the duplication. // * // * @param source the source node to check // * @param info the context information // * @return true, if the given node is in the context for the duplication, false otherwise // */ // private boolean isInContext(SourceNode source, DuplicationInformation info) { // Collection<SourceNode> contextNodes = info.getOldSourceNodes(source.getEntityDefinition()); // for (SourceNode node : contextNodes) // if (node == source) // return true; // return false; // } // OLD STUFF! // /** // * Track back target nodes and duplicate any augmentation cells. // * @param originalTarget the original target node // * @param duplicationContext the duplication context // */ // private void augmentationTrackback(TargetNode originalTarget, // DuplicationContext duplicationContext) { // // track back child augmentations // for (TargetNode child : originalTarget.getChildren(false)) { //XXX should annotated children be included? // augmentationTrackback(child, duplicationContext); // } // // // track back augmentations // for (CellNode originalAssignment : originalTarget.getAssignments()) { // /* // * Duplicated target does not contain an assignment representing // * the same cell as originalAssignment. // */ // if (originalAssignment.getSources().isEmpty()) { // // the cell is an augmentation, thus we duplicate it // duplicateCell(originalAssignment, null, duplicationContext); // /* // * it is automatically added to the target nodes (which are // * retrieved from the duplication context or created as // * necessary) // */ // } // } // } // // /** // * Track the graph back to sources that are missing in a cell node compared // * to the original cell node. // * @param cellNode the cell node // * @param originalCell the original cell node the node was duplicated from // */ // private void cellTrackback(CellNodeImpl cellNode, CellNode originalCell) { // for (SourceNode originalSource : originalCell.getSources()) { // if (!cellNode.getSources().contains(originalSource)) { // /* // * Duplicated cell does not contain a source representing the // * same entity as originalSource. // */ // SourceNode newSource = null; // // // now there are several possible cases // // a) the original source has leftovers and we grab one // Leftovers leftovers = originalSource.getLeftovers(); // if (leftovers != null) { // newSource = leftovers.consumeValue(originalCell.getCell()); // // if (newSource != null) { // // interconnect both // newSource.addRelation(cellNode); // cellNode.addSource(originalCell.getSourceNames(originalSource), // newSource); // //XXX hard connections are OK here, as a leftover source is a duplicate // } // else { // //TODO add an undefined source node in this case? // } // } // // // b) the original source has a parent (ot it has a parent etc.) // // that has leftovers // if (newSource == null) { // //TODO // } // // // c) we use the original source node // if (newSource == null) { // newSource = originalSource; // // // interconnect both // newSource.addAnnotatedRelation(cellNode); //FIXME should be an augmentated relation!!!! // cellNode.addSource(originalCell.getSourceNames(originalSource), // newSource); // } // } // } // } /** * Duplicate a source node. * * @param source the source node to duplicate * @param parent the parent of the new source node * @param addToParent if the new source node should be added as child to the * parent * @param duplicationContext the duplication context * @return the new duplicated source node or <code>null</code> if * duplication was prohibited */ private SourceNode duplicateSource(SourceNode source, SourceNode parent, boolean addToParent, DuplicationContext duplicationContext) { // create duplicate SourceNode duplicate = new SourceNodeImpl(source.getEntityDefinition(), parent, addToParent); duplicate.setContext(source.getContext()); return configureSourceDuplicate(source, duplicate, parent, duplicationContext, addToParent); } /** * Configure a duplicated source node. * * @param originalSource the original source node * @param duplicate the duplicated source node * @param parent the parent for the duplicated source node * @param duplicationContext the duplication context * @param addToParent if the duplicated source node should be added as child * to its parent * @return the duplicated source node or <code>null</code> if it has no * further connections */ private SourceNode configureSourceDuplicate(SourceNode originalSource, SourceNode duplicate, SourceNode parent, DuplicationContext duplicationContext, boolean addToParent) { // duplicate relations List<CellNode> relations = new ArrayList<CellNode>( originalSource.getRelations(false).size()); // though each cell node is only duplicated once per duplication context for (CellNode relation : originalSource.getRelations(false)) { // XXX // should // the // annotated // relations // be // included? CellNode duplicatedRelation = duplicateCell(relation, duplicate, duplicationContext); if (duplicatedRelation != null) { relations.add(duplicatedRelation); } } // duplicate children List<SourceNode> children = new ArrayList<SourceNode>( originalSource.getChildren(false).size()); for (SourceNode child : originalSource.getChildren(false)) { // XXX // should // the // annotated // children // be // included? SourceNode duplicatedChild = duplicateSource(child, duplicate, true, duplicationContext); if (duplicatedChild != null) { children.add(duplicatedChild); } } if (children.isEmpty() && relations.isEmpty()) { // abort return null; } // add duplicated relations for (CellNode relation : relations) { duplicate.addRelation(relation); } // add duplicated children for (SourceNode child : children) { duplicate.addChild(child); } if (addToParent) { parent.addChild(duplicate); } return duplicate; } /** * Get the duplicated cell node. * * @param relation the original cell node * @param duplicateSource the duplicated source node to be associated with * the duplicated cell node, may be <code>null</code> if the cell * is an augmentation * @param duplicationContext the context of the current duplication process * @return the duplicated cell node or <code>null</code> if duplication was * prohibited */ private CellNode duplicateCell(CellNode relation, SourceNode duplicateSource, DuplicationContext duplicationContext) { if (duplicationContext.getIgnoreCells().contains(relation.getCell())) { // cancel if the cell has to be ignored (which usually means it was // already handled) return null; } // try to retrieve cell node from context CellNodeImpl duplicate = duplicationContext.getNode(relation.getCell()); if (duplicate == null) { // if not already done, create a duplicate of the cell duplicate = new CellNodeImpl(relation.getCell()); // duplicate the target nodes as necessary List<TargetNode> targets = new ArrayList<TargetNode>(relation.getTargets().size()); for (TargetNode target : relation.getTargets()) { TargetNode duplicatedTarget = duplicateTarget(target, duplicate, duplicationContext); if (duplicatedTarget != null) { targets.add(duplicatedTarget); } } if (targets.isEmpty()) { // exit if there are no targets nodes return null; // FIXME if a cell has different associated sources the cell // node will be repeatedly created and discarded } // add duplicated targets to duplicated cell for (TargetNode target : targets) { duplicate.addTarget(target); } // store duplicate in duplication context duplicationContext.addNode(duplicate, relation); } // add the duplicated source if (duplicateSource != null) { duplicate.addSource(relation.getSourceNames(duplicateSource), duplicateSource); } return duplicate; } /** * Duplicate a target node. * * @param target the original target node * @param relation the relation to associated to the target node * @param duplicationContext the duplication context * @return the duplicated target node or <code>null</code> if duplication * was prohibited */ private TargetNode duplicateTarget(TargetNode target, CellNode relation, DuplicationContext duplicationContext) { TargetNodeImpl duplicatedTarget = duplicationContext.getNode(target.getEntityDefinition()); if (duplicatedTarget == null) { // target node not created yet boolean duplicateParent = true; if (contextTargets.contains(target)) { // this is an endpoint, as such this is the last node to be // duplicated duplicateParent = false; } GroupNode duplicatedParent; if (duplicateParent) { GroupNode parent = target.getParent(); if (parent instanceof TargetNode) { // create a duplicated parent duplicatedParent = duplicateTarget((TargetNode) parent, null, duplicationContext); if (duplicatedParent == null) { return null; } } else { // parent is either null or the root // this means there was no match for a context endpoint // along the way // thus this is not a valid path for duplication return null; } } else { duplicatedParent = target.getParent(); } // create duplicate duplicatedTarget = new TargetNodeImpl(target.getEntityDefinition(), duplicatedParent); // add as annotated child to parent duplicatedParent.addAnnotatedChild(duplicatedTarget); // add to duplication context duplicationContext.addNode(duplicatedTarget, target); } if (relation != null) { // assign relation duplicatedTarget.addAssignment(target.getAssignmentNames(relation), relation); } return duplicatedTarget; } }