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.Collections; 013import java.util.HashMap; 014import java.util.Iterator; 015import java.util.List; 016import java.util.Map; 017import java.util.Set; 018import java.util.logging.Level; 019import java.util.logging.Logger; 020 021import javax.jmi.reflect.RefBaseObject; 022import javax.jmi.reflect.RefObject; 023import javax.jmi.reflect.RefStruct; 024 025import net.mdatools.modelant.core.api.match.MatchingCriteria; 026import net.mdatools.modelant.core.operation.element.PrintElementRestricted; 027import net.mdatools.modelant.core.operation.element.PrintModelElement; 028import net.mdatools.modelant.core.util.Navigator; 029import net.mdatools.modelant.core.util.key.Hash; 030 031/** 032 * This class represents a single model element with its associated other elements and attributes 033 * among the attributes and associations defined in the owner Topology. 034 * <b> 035 * In a separate step each node should be assigned the nodes that represent the associated model 036 * elements among those that are contained in the topology. 037 * </b> 038 * @param <V> 039 * @see Node#assignAssociatedNodes(Map) 040 */ 041public class Node<V extends RefObject> { 042 043 /** 044 * This is a common logger 045 */ 046 private static Logger LOGGER = Logger.getLogger( Node.class.getPackage().getName() ); 047 048 049 private static final PrintModelElement PRINT_MODEL_ELEMENT = new PrintModelElement(); 050 051 /** 052 * The wrapped model element this node is for 053 */ 054 private final V wrapped; 055 056 /** 057 * The attribute values to compare, 058 * Contains the names of all attributes of the model element, as defined in the mdetamodel, 059 * mapped to their actual values. null values are allowed. 060 * Used for nodes matching 061 */ 062 private final Map<String, List<String>> attributeValues = new HashMap<>(3); 063 private final int attributesHash; 064 065 /** 066 * The model elements that are associated to the wrapped model element. 067 * referredModelElements.get(key) == wrapped.refGet(key) (as a list) 068 * Used for nodes matching 069 */ 070 private final Map<String, Collection<V>> referredModelElements = new HashMap<>(3); 071 072 /** 073 * The effective list of Nodes that reference this 074 */ 075 private final List<Node<V>> referers = new ArrayList<>(); 076 077 /** 078 * The common criteria to compare model elements (and print the wrapped model element) 079 */ 080 private final MatchingCriteria criteria; 081 082 /** 083 * The number of model elements still not matched to correspondents in the other model. 084 * Invariant: 085 * non-negative 086 * level == count(referredModelElements.values().values()\{processed nodes}) 087 */ 088 private int level; 089 090 /** 091 * Stores the current key when calculating it is not possible 092 */ 093 private Key key; 094 095 /** 096 * Represents the wrapped model element as a Node with its associations and attributes 097 * @param wrapped is not-null 098 * @param criteria 099 */ 100 public Node(V wrapped, MatchingCriteria criteria) { 101 Iterator<String> attributesIterator; 102 String attribute; 103 List<?> values; 104 List<String> printableValues; 105 String stringValue; 106 int valuesHash; 107 108 this.wrapped = wrapped; 109 this.criteria = criteria; 110 this.level = 0; 111 112 valuesHash = 0; 113 114 attributesIterator = criteria.getAttributes(wrapped).iterator(); 115 while ( attributesIterator.hasNext() ) { 116 attribute = attributesIterator.next(); 117 118 valuesHash <<= 1; 119 try { 120 values = Navigator.collectValues( wrapped, 121 attribute, 122 LOGGER.isLoggable( Level.FINE )); 123 124 // convert all structures, enum, etc. values into printable & comparable ones, independent of their the order 125 printableValues = new ArrayList<>(); 126 for ( Object value: values ) { 127 if ( value instanceof RefBaseObject || value instanceof RefStruct ) { 128 stringValue = PRINT_MODEL_ELEMENT.execute( value ); 129 130 valuesHash += Hash.hash( stringValue ); 131 printableValues.add(stringValue); 132 133 } else if ( value != null ) { 134 stringValue = value.toString(); 135 136 valuesHash += Hash.hash( stringValue ); 137 printableValues.add(stringValue); 138 } 139 } 140 attributeValues.put(attribute, printableValues ); 141 } catch (Exception ex) { 142 LOGGER.log( Level.FINE, 143 "Model element: {0} does not support attribute: {1}", 144 new Object[]{PRINT_MODEL_ELEMENT.execute(getWrapped()), attribute}); 145 } 146 } 147 this.attributesHash = valuesHash; 148 149 assignKey(); 150 } 151 152 private void assignKey() { 153 key = new Key(getWrapped(), getLevel(), attributesHash); 154 } 155 156 /** 157 * This method assigns the Nodes that represent the model elements associated to the wrapped one 158 * and sets in level the number of referenced objects. 159 * @param modelToNodeMap non-null 160 */ 161 public void assignAssociatedNodes(Map<V, Node<V>> modelToNodeMap) { 162 Iterator<String> associationsIterator; 163 String association; 164 Collection<V> associated; 165 Node<V> associatedNode; 166 167 associationsIterator = criteria.getAssociations(getWrapped()).iterator(); 168 while ( associationsIterator.hasNext() ) { 169 association = associationsIterator.next(); 170 171 try { 172 associated = (Collection<V>) Navigator.collectValues( wrapped, 173 association, 174 LOGGER.isLoggable( Level.FINE )); 175 176 for (V associatedModelElement: associated ) { 177 associatedNode = modelToNodeMap.get( associatedModelElement ); 178 179 assert associatedNode != null 180 : "Expected a node bound for object "+PRINT_MODEL_ELEMENT.execute(associatedModelElement); 181 182 level++; 183 184 associatedNode.referers.add(this); 185 } 186 referredModelElements.put( association, associated ); 187 } catch (Exception ex) { 188 LOGGER.log( Level.FINE, 189 "Model element: {0} does not support association: {1}", 190 new Object[]{PRINT_MODEL_ELEMENT.execute(getWrapped()), association}); 191 } 192 } 193 } 194 195 /** 196 * Find all ready nodes in this topology, that are equal to the provided node (from the other topology) 197 * @param equivalenceClasses not null collected so far matches/classes of equivalence 198 * @param nodeToMatch not null node to match 199 * @param generationReady TODO 200 * @return non-null list of all ready nodes in this topology, that match the provided node. The provided 201 * nodes are not mapped in the equivalence classes map, as they are found among the ready nodes, 202 * mapped in equivalence classes map only when they are reported in this result. 203 */ 204 public static <V extends RefObject> List<Node<V>> findReadyMatches(EquivalenceClassesMap<V> equivalenceClasses, 205 Node<V> nodeToMatch, 206 List<Node<V>> generationReady) { 207 List<Node<V>> result; 208 209 result = new ArrayList<Node<V>>(); 210 211 assert nodeToMatch.isReady() : "Expected a ready node " + nodeToMatch; 212 213 for (Node<V> target : generationReady) { 214 if ( nodeToMatch.getKey().equals(target.getKey()) 215 && nodeToMatch.matches( target, equivalenceClasses ) ) { 216 result.add( target ); 217 } 218 } 219 return result; 220 } 221 222 /** 223 * Two nodes match if and only if their attribute values are equal and they are associated to 224 * already matched nodes. 225 * Treat the nodes with no attributes and associations as not comparable (different). 226 * PRE-CONDITION: 227 * getLevel() == 0 228 * otherNode.getLeve() == 0 229 * 230 * @param otherNode is a non-null node whose wrapped element is of the same metaclass as of this one 231 * @param equivalenceClasses holds the already matched pairs, which classes never are replaced or deleted (i.e. only new classes may be added) 232 * @return true if this matches to the other node 233 */ 234 private boolean matches(Node otherNode, EquivalenceClassesMap<V> equivalenceClasses) { 235 boolean result; 236 Map.Entry<String, List<String>> entry; 237 Iterator<Map.Entry<String, List<String>>> attributeEntriesIterator; 238 Iterator<String> associationNamesIterator; 239 String thisAssociationName; 240 Object thisAttributeValue; 241 Object otherAttributeValue; 242 Set<V> theseMappedRepersentatives; 243 Set<V> otherRepersentatives; 244 245 assert getLevel() == 0 : "Expeceted only level 0 objects are matched from, instead of "+getLevel(); 246 assert otherNode.getLevel() == 0 : "Expeceted only level 0 objects are matched to, instead of "+otherNode.getLevel(); 247 248 result = this.attributesHash == otherNode.attributesHash; 249 250 // compare attributes 251 attributeEntriesIterator = attributeValues.entrySet().iterator(); 252 while ( result && attributeEntriesIterator.hasNext() ) { 253 entry = attributeEntriesIterator.next(); 254 255 thisAttributeValue = entry.getValue(); // not null 256 otherAttributeValue= otherNode.attributeValues.get( entry.getKey() ); 257 258 result = thisAttributeValue.equals( otherAttributeValue ); 259 } 260 261 // compare associations - make sure that the nodes refer the same matched objects 262 associationNamesIterator = referredModelElements.keySet().iterator(); 263 while ( result && associationNamesIterator.hasNext() ) { 264 thisAssociationName = associationNamesIterator.next(); 265 266 theseMappedRepersentatives= equivalenceClasses.getMappedRepresentatives( this.referredModelElements( thisAssociationName )); 267 otherRepersentatives = equivalenceClasses.getRepresentatives( otherNode.referredModelElements( thisAssociationName )); 268 269 result = theseMappedRepersentatives.equals( otherRepersentatives ); 270 } 271 return result; 272 } 273 274 /** 275 * @param associationName non-null association name 276 * @return non-null set of model elements, this wrapped element refers in the association with the provided name 277 */ 278 private Collection<V> referredModelElements(String associationName) { 279 Collection<V> result; 280 281 result = referredModelElements.get( associationName ); 282 if (result == null) { 283 result = Collections.EMPTY_SET; 284 } 285 return result; 286 } 287 288 /** 289 * Decreases the number of references this 290 */ 291 public void decreaseLevel() { 292 level--; 293 assert level >= 0 : "Expected a non-negative number of objects this refers to"; 294 295 assignKey(); 296 } 297 298 /** 299 * @return the list of all nodes 300 */ 301 public final List<Node<V>> getReferers() { 302 return referers; 303 } 304 305 306 private int getLevel() { 307 return level; 308 } 309 310 public final boolean isReady() { 311 return level == 0; 312 } 313 314 public final V getWrapped() { 315 return wrapped; 316 } 317 318 final Key getKey() { 319 return key; 320 } 321 322 public String toString() { 323 StringBuilder result = new StringBuilder(); 324 325 result.append("Node ") 326 .append(level) 327 .append(" ") 328 .append(new PrintElementRestricted(" ",criteria).execute(wrapped)); 329 return result.toString(); 330 } 331 332 /** 333 * Convert the list of nodes to a list of their wrapped objects 334 * @param nodes not null 335 * @return not null list of the wrapped objects in the elements of the nodes 336 */ 337 public static <V extends RefObject> Collection<V> unwrap(List<Node<V>> nodes) { 338 Collection<V> result; 339 340 result = new ArrayList<>(); 341 for (Node<V> node : nodes) { 342 result.add( node.getWrapped() ); 343 } 344 return result; 345 } 346}