001/*
002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved.
003 *
004 * This program and the accompanying materials are made available under the terms of the
005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v20.html
007 */
008package net.mdatools.modelant.core.operation.model.topology;
009
010import java.util.ArrayList;
011import java.util.Collection;
012import java.util.IdentityHashMap;
013import java.util.Iterator;
014import java.util.List;
015import java.util.Map;
016
017import javax.jmi.reflect.RefObject;
018
019import net.mdatools.modelant.core.api.match.MatchingCriteria;
020import net.mdatools.modelant.core.util.map.MapToCollection;
021import net.mdatools.modelant.core.util.map.MapToSet;
022
023/**
024 * This class allows structurally comparing models, independently of their actual meta-models. It is
025 * intended to serve the model change tracking.
026 *
027 * <pre>
028 * Usage:
029 *   new ModelTopology()
030 *   load()
031 *   findEquals() / findEquals(EquivalenceMap) returns the element matches
032 *   getNotProcessed() returns not matched elements
033 * </pre>
034 *
035 * NOTE: The instances should not be reused
036 *
037 * @author Rusi Popov (popovr@mdatools.net)
038 */
039public class ModelTopology {
040
041  /**
042   * Maps node.level to set of nodes with that value
043   *
044   * @see #add(Node)
045   * @see #remove(Node)
046   */
047  private final MapToCollection<Key, Node<RefObject>> nodes = new MapToSet<>();
048
049  /**
050   * Contains only nodes pertaining to <b>nodes</b> and having level()==0 == isReady()
051   * Used for controlled iteration in topological order over the nodes.
052   */
053  private final List<Node<RefObject>> readyNodes = new ArrayList<>();
054
055  /**
056   * The correspondence from model elements to their internal representation as nodes
057   */
058  private final Map<RefObject, Node<RefObject>> elementToNodeMap = new IdentityHashMap<>( 101 );
059
060  /**
061   * @return true iff getNodes() is empty
062   */
063  public boolean isEmpty() {
064    return nodes.isEmpty();
065  }
066
067  /**
068   * Load into this model topology the listed associations and attributes, so that model processing
069   * would treat as equal any model elements with same values of the listed attributes and equal
070   * objects bound in the listed associations.
071   *
072   * @param criteria is a not null criteria defining the attribute names to evaluate for all model
073   *          elements
074   * @param elements is a non-null list of model elements to order
075   */
076  public void load(MatchingCriteria criteria, Collection<RefObject> elements) {
077    Iterator<RefObject> elementsIterator;
078    Iterator<Node<RefObject>> nodesIterator;
079    RefObject element;
080    Node<RefObject> node;
081
082    assert criteria != null : "Expected non-null criteria";
083
084    // enlist the model elements to order
085    elementsIterator = elements.iterator();
086    while ( elementsIterator.hasNext() ) {
087      element = elementsIterator.next();
088
089      node = new Node<>( element, criteria );
090      elementToNodeMap.put( element, node );
091    }
092
093    // arrange the nodes in this topology according to its level (relations to other nodes it refers)
094    nodesIterator = elementToNodeMap.values().iterator();
095    while ( nodesIterator.hasNext() ) {
096      node = nodesIterator.next();
097
098      node.assignAssociatedNodes( elementToNodeMap );
099      add( node );
100    }
101  }
102
103
104  /**
105   * (Re)calculate the key and bind the node and identify the ready nodes
106   * @param node
107   */
108  private void add(Node<RefObject> node) {
109    nodes.put( node.getKey(), node );
110
111    if ( node.isReady() ) {
112      readyNodes.add( node );
113    }
114  }
115
116  /**
117   * @return a copy of the current ready nodes, this way forming a "generation of ready nodes",
118   *         that could be manipulated independently of the current set of ready nodes
119   */
120  public final ArrayList<Node<RefObject>> getGenerationOfReady() {
121    return new ArrayList<>(readyNodes);
122  }
123
124
125  /**
126   * Removes from this topology all nodes from the equivalence class the representative is of
127   * @param equivalents not null
128   */
129  public final void remove(Collection<RefObject> equivalents) {
130    for (RefObject element : equivalents) {
131      removeFromTopology( elementToNodeMap.get( element ) );
132    }
133  }
134
135
136  /**
137   * This method excludes the nodes provided from the owner topology, decreases the level of all
138   * nodes that refer this and rearranges the topology to reflect level of the changed nodes.
139   *
140   * @param nodes is a collection of ready nodes
141   */
142  public void removeFromTopology(Collection<Node<RefObject>> nodes) {
143    for (Node<RefObject> node : new ArrayList<>( nodes )) {
144      removeFromTopology( node );
145    }
146  }
147
148  /**
149   * Exclude the node from the owner topology, decreasing the level of all nodes that refer this and
150   * rearranges the topology to reflect level of the changed nodes. Because of the explicitly
151   * provided mappings of nodes that to be treated as equal, but they are not, we cannot expect
152   * anymore that
153   * <ul>
154   * <li>the nodes to remove are in ready state and
155   * <li>when removing an object its referers are in the topology (because they might have been
156   * removed)
157   * </ul>
158   *
159   * @param node is a ready node
160   */
161  private void removeFromTopology(Node<RefObject> node) {
162    Iterator<Node<RefObject>> referencingIterator;
163    Node<RefObject> referer;
164    boolean actuallyRemoved;
165
166    actuallyRemoved = remove( node );
167
168    assert actuallyRemoved : "Expected a node that existed in this topology " + node;
169
170    // for each of the referencing node nodes - decrease number of referenced and rearrange
171    referencingIterator = node.getReferers().iterator();
172    while ( referencingIterator.hasNext() ) {
173      referer = referencingIterator.next();
174
175      actuallyRemoved = remove( referer );
176
177      // assert actuallyRemoved : "Expected the referrer node exists in this topology "+referer;
178      if ( actuallyRemoved ) {
179        referer.decreaseLevel();
180        add( referer );
181      }
182    }
183  }
184
185
186  /**
187   * @return true if actually removed the node
188   */
189  private boolean remove(Node<RefObject> node) {
190    if ( node.isReady() ) {
191      readyNodes.remove( node );
192    }
193    return nodes.remove( node.getKey(), node );
194  }
195
196  /**
197   * Exclude the listed nodes from the READY list, but keep them in the topology itself
198   * @param targetGenerationReady not null
199   */
200  public void removeFromReadyNodes(List<Node<RefObject>> targetGenerationReady) {
201    readyNodes.removeAll( targetGenerationReady );
202  }
203
204  /**
205   * @return a non-null list of model elements that where left in the topology.
206   */
207  public List<RefObject> getContents() {
208    List<RefObject> result;
209
210    result = new ArrayList<RefObject>();
211
212    // left are circular dependencies only (if any)
213    for (Node<RefObject> node : nodes.values()) {
214      result.add( node.getWrapped() );
215    }
216    return result;
217  }
218
219
220  /**
221   * Leave this topology empy and ready to load another model
222   */
223  public void clear() {
224    readyNodes.clear();
225    nodes.clear();
226    elementToNodeMap.clear();
227  }
228
229
230  public String toString() {
231    return getClass().getSimpleName() + "{" + nodes + "}";
232  }
233}