Coverage Report - com.sdmetrics.model.XMIReader - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
XMIReader
99%
259/260
95%
167/174
4,917
XMIReader$DeferredRelation
100%
1/1
N/A
4,917
XMIReader$ProgressMessageHandler
N/A
N/A
4,917
 
 1  
 /*
 2  
  * SDMetrics Open Core for UML design measurement
 3  
  * Copyright (c) Juergen Wuest
 4  
  * To contact the author, see <http://www.sdmetrics.com/Contact.html>.
 5  
  * 
 6  
  * This file is part of the SDMetrics Open Core.
 7  
  * 
 8  
  * SDMetrics Open Core is free software: you can redistribute it and/or modify
 9  
  * it under the terms of the GNU Affero General Public License as
 10  
  * published by the Free Software Foundation, either version 3 of the
 11  
  * License, or (at your option) any later version.
 12  
 
 13  
  * SDMetrics Open Core is distributed in the hope that it will be useful,
 14  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16  
  * GNU Affero General Public License for more details.
 17  
  *
 18  
  * You should have received a copy of the GNU Affero General Public License
 19  
  * along with SDMetrics Open Core.  If not, see <http://www.gnu.org/licenses/>.
 20  
  *
 21  
  */
 22  
 package com.sdmetrics.model;
 23  
 
 24  
 import static com.sdmetrics.model.XMITrigger.TriggerType.ATTRVAL;
 25  
 import static com.sdmetrics.model.XMITrigger.TriggerType.CATTRVAL;
 26  
 import static com.sdmetrics.model.XMITrigger.TriggerType.CONSTANT;
 27  
 import static com.sdmetrics.model.XMITrigger.TriggerType.CTEXT;
 28  
 import static com.sdmetrics.model.XMITrigger.TriggerType.GCATTRVAL;
 29  
 import static com.sdmetrics.model.XMITrigger.TriggerType.REFLIST;
 30  
 import static com.sdmetrics.model.XMITrigger.TriggerType.XMI2ASSOC;
 31  
 
 32  
 import java.util.Collection;
 33  
 import java.util.HashMap;
 34  
 import java.util.HashSet;
 35  
 import java.util.LinkedList;
 36  
 import java.util.List;
 37  
 import java.util.StringTokenizer;
 38  
 
 39  
 import org.xml.sax.Attributes;
 40  
 import org.xml.sax.SAXException;
 41  
 
 42  
 import com.sdmetrics.util.SAXHandler;
 43  
 
 44  
 /**
 45  
  * Reads an XMI source file, processing it as specified by an XMI transformation
 46  
  * file. The UML model elements retrieved from the file are stored in a
 47  
  * {@link Model}.
 48  
  * <p>
 49  
  * 
 50  
  * In the XMI, design information for one model element is spread across a
 51  
  * number of XML elements in the XML tree. Therefore, a DOM XML parser would be
 52  
  * a reasonable choice to use for processing an XMI file. DOM parsers read the
 53  
  * entire tree and store it in memory. However, XMI files can become large, and
 54  
  * we usually only need a fraction of the information contained in an XMI file,
 55  
  * so this is not economical.
 56  
  * <p>
 57  
  * Therefore, a deliberate choice was made to use a SAX XML parser. The SAX
 58  
  * parser reads one XML element at the time, and we need to keep track of the
 59  
  * context in which an XML element is embedded. This is done using a stack
 60  
  * holding the model elements to process, and the state at which processing is
 61  
  * for each model element on the stack.
 62  
  */
 63  
 
 64  
 public class XMIReader extends SAXHandler {
 65  
 
 66  
         /**
 67  
          * Message handler for progress messages of the XMI parser. Reading a large
 68  
          * XMI file can take some time (several tens of seconds). Therefore, the XMI
 69  
          * reader continuously emits progress messages. You can use these messages
 70  
          * to convey to the user that the application is still alive.
 71  
          */
 72  
         public interface ProgressMessageHandler {
 73  
                 /**
 74  
                  * Report a progress message.
 75  
                  * 
 76  
                  * @param msg Message to display.
 77  
                  */
 78  
                 void reportXMIProgress(String msg);
 79  
         }
 80  
 
 81  
         /** The model to store the extracted model elements. */
 82  
         private final Model model;
 83  
         /** Object that knows the available XMI transformations. */
 84  
         private final XMITransformations transformations;
 85  
 
 86  
         /** Current nesting level in the XML tree. */
 87  
         private int nestingLevel;
 88  
         /** Stack of model element being processed. */
 89  
         private XMIContextStack stack;
 90  
         /** Running id for artificial XMI IDs that the reader creates. */
 91  
         private int artificialIDCounter;
 92  
         /**
 93  
          * Temporarily stores links via the "linkbackattr" attribute. These links
 94  
          * can be registered with the model elements only after the entire XMI file
 95  
          * has been read and all model elements are known.
 96  
          */
 97  
         private List<DeferredRelation> deferredRelationsList;
 98  
 
 99  
         /** An entry on the deferred relations list. */
 100  1882
         private static class DeferredRelation {
 101  
                 ModelElement sourceElement;
 102  
                 String targetXMIID;
 103  
                 String linkBackAttr;
 104  
         }
 105  
 
 106  
         /** Collects text between XML element tags for "ctext" trigger. */
 107  142
         private StringBuilder ctextBuffer = new StringBuilder();
 108  
 
 109  
         /**
 110  
          * Hash map holding all model elements. Key is the XMI ID, value the model
 111  
          * element. Used to collect the model elements while parsing the XMI file,
 112  
          * and cross-referencing them by their XMI ID.
 113  
          */
 114  
         private HashMap<String, ModelElement> xmiIDMap;
 115  
 
 116  
         /**
 117  
          * Cache of string values used for attributes values of model elements. The
 118  
          * attribute value strings in a UML model contain a fair amount of
 119  
          * redundancy, XML parsers however will return distinct string objects for
 120  
          * equal string. Therefore, we cache those string values during parsing and
 121  
          * replace duplicates with the cached version.
 122  
          */
 123  
         private HashMap<String, String> stringCache;
 124  
 
 125  
         /** Count of the number of model elements extracted from the XMI file. */
 126  
         private int elementsExtracted;
 127  
 
 128  
         /** Object to report progress messages to during parsing. */
 129  
         private ProgressMessageHandler messageHandler;
 130  
 
 131  
         /**
 132  
          * Constructor.
 133  
          * @param trans The XMI transformations to use.
 134  
          * @param model The model that will contain the extracted model elements.
 135  
          */
 136  142
         public XMIReader(XMITransformations trans, Model model) {
 137  142
                 this.model = model;
 138  142
                 this.transformations = trans;
 139  142
         }
 140  
 
 141  
         /**
 142  
          * Registers a message handler for the XMI reader to report progress to.
 143  
          * 
 144  
          * @param handler The message handler.
 145  
          */
 146  
         public void setProgressMessageHandler(ProgressMessageHandler handler) {
 147  1
                 messageHandler = handler;
 148  1
         }
 149  
 
 150  
         /**
 151  
          * Gets the number of elements that have been extracted.
 152  
          * 
 153  
          * @return number of elements extracted from the XMI file
 154  
          */
 155  
         public int getNumberOfElements() {
 156  1
                 return elementsExtracted;
 157  
         }
 158  
 
 159  
         // SAX Parser callbacks
 160  
 
 161  
         /** Prepare parser for new XMI file. */
 162  
         @Override
 163  
         public void startDocument() {
 164  142
                 stack = new XMIContextStack();
 165  142
                 deferredRelationsList = new LinkedList<DeferredRelation>();
 166  142
                 xmiIDMap = new HashMap<String, ModelElement>(2048);
 167  142
                 stringCache = new HashMap<String, String>(2048);
 168  142
                 elementsExtracted = 0;
 169  142
                 nestingLevel = 0;
 170  142
                 artificialIDCounter = 0;
 171  142
         }
 172  
 
 173  
         /**
 174  
          * Processes the opening tag of an XML element in the XMI source file.
 175  
          * Depending on the parser state, this either creates a new model element,
 176  
          * or adds information from a child or grandchild node of the XMI tree to
 177  
          * the current model element.
 178  
          * 
 179  
          * @throws SAXException An error occurred during the evaluation of a
 180  
          *         conditional XMI transformation.
 181  
          */
 182  
         @Override
 183  
         public void startElement(String uri, String local, String raw,
 184  
                         Attributes attrs) throws SAXException {
 185  38150
                 nestingLevel++;
 186  
 
 187  38150
                 if (stack.isAcceptingNewElements()) {
 188  
                         // Check if this is the beginning of a new model element
 189  28889
                         if (checkAndProcessNewElement(raw, attrs)) {
 190  13607
                                 return;
 191  
                         }
 192  
                 }
 193  
 
 194  24543
                 if (stack.isEmpty()) {
 195  1116
                         return;
 196  23427
                 } else if (nestingLevel == stack.getNestingLevel() + 1) {
 197  10844
                         processXMLFirstLevelChild(raw, attrs);
 198  10844
                 } else if (nestingLevel == stack.getNestingLevel() + 2) {
 199  8018
                         processXMLSecondLevelChild(attrs);
 200  
                 }
 201  23427
         }
 202  
 
 203  
         /**
 204  
          * Processes text between XML element tags.
 205  
          */
 206  
         @Override
 207  
         public void characters(char[] ch, int start, int length) {
 208  60727
                 if (stack.activeTriggerTypeEquals(CTEXT)) {
 209  487
                         ctextBuffer.append(ch, start, length);
 210  
                 }
 211  60727
         }
 212  
 
 213  
         /**
 214  
          * Processes the closing tag of an XML element. If the end of the definition
 215  
          * of the model element on the top of the stack has been reached, add this
 216  
          * element to the {@link Model}.
 217  
          */
 218  
         @Override
 219  
         public void endElement(String uri, String local, String raw) {
 220  
                 // Check for end of XML tag relevant to "ctext" triggers
 221  38150
                 if (nestingLevel == stack.getNestingLevel() + 1) {
 222  10984
                         if (stack.activeTriggerTypeEquals(CTEXT)) {
 223  
                                 // add the character data to the current model element
 224  369
                                 ModelElement element = stack.getModelElement();
 225  738
                                 setModelAttributeValue(element, stack.getActiveTrigger().name,
 226  369
                                                 ctextBuffer.toString());
 227  369
                                 ctextBuffer.setLength(0);
 228  
                         }
 229  
                         // any currently active trigger is expired by now.
 230  10984
                         stack.setActiveTrigger(null);
 231  
                 }
 232  
 
 233  
                 // Check if a model element has completed and add that
 234  
                 // element to the model
 235  38150
                 if (nestingLevel == stack.getNestingLevel()) {
 236  13607
                         ModelElement element = stack.pop();
 237  
 
 238  
                         // if element is its own "owner" (Visio XMI exporter), delete its
 239  
                         // context info
 240  27214
                         String contextID = element
 241  13607
                                         .getPlainAttribute(MetaModelElement.CONTEXT);
 242  13607
                         if (contextID.equals(element.getXMIID())) {
 243  10
                                 contextID = "";
 244  
                         }
 245  
 
 246  
                         // if element has no context info, insert id of owner (element below
 247  
                         // on the stack)
 248  13607
                         if (contextID.length() == 0) {
 249  13602
                                 ModelElement parent = stack.getModelElement();
 250  13602
                                 if (parent != null) {
 251  13438
                                         contextID = parent.getXMIID();
 252  
                                 }
 253  27204
                                 setModelAttributeValue(element, MetaModelElement.CONTEXT,
 254  13602
                                                 contextID);
 255  
                         }
 256  
 
 257  
                         // now we can add the element to the model
 258  13607
                         addModelElement(element.getXMIID(), element);
 259  
                 }
 260  
 
 261  38150
                 nestingLevel--;
 262  38150
         }
 263  
 
 264  
         /**
 265  
          * Performs the post-processing after the entire XMI file has been read.
 266  
          * Process the elements on the deferredRelations list, and cross-references
 267  
          * all model element links.
 268  
          */
 269  
         @Override
 270  
         public void endDocument() {
 271  
                 // Now that all model elements are known, we can process deferred
 272  
                 // relations
 273  1225
                 for (DeferredRelation defRel : deferredRelationsList) {
 274  941
                         ModelElement target = xmiIDMap.get(defRel.targetXMIID);
 275  941
                         if (target == null) {
 276  5
                                 continue; // referenced element does not exist
 277  
                         }
 278  
 
 279  
                         // set attribute of target element to point back to the source
 280  936
                         if (target.getType().hasAttribute(defRel.linkBackAttr)) {
 281  366
                                 setModelAttributeValue(target, defRel.linkBackAttr,
 282  183
                                                 defRel.sourceElement.getXMIID());
 283  
                         }
 284  
                 }
 285  
 
 286  
                 // The string cache and deferred relations list have served their
 287  
                 // purpose and can be gc'ed
 288  142
                 stringCache = null;
 289  142
                 deferredRelationsList = null;
 290  
 
 291  
                 // Process extension attributes
 292  142
                 processProfileExtensions();
 293  
 
 294  
                 // Now we can do the cross-referencing of model elements
 295  142
                 doCrossreferencing();
 296  142
         }
 297  
 
 298  
         /**
 299  
          * Checks if an XML element in the XMI file defines a new model element, and
 300  
          * process it accordingly.
 301  
          * 
 302  
          * @param xmlElement Name of the XML element.
 303  
          * @param attrs The attributes of the XML element.
 304  
          * @return <code>true</code> if a new model element was created, else
 305  
          *         <code>false</code>.
 306  
          * @throws SAXException An error occurred evaluating the condition
 307  
          *         expression of a conditional XMI transformation.
 308  
          */
 309  
         private boolean checkAndProcessNewElement(String xmlElement,
 310  
                         Attributes attrs) throws SAXException {
 311  57778
                 XMITransformation trans = transformations.getTransformation(xmlElement,
 312  28889
                                 attrs);
 313  
 
 314  28889
                 if (trans == null) {
 315  
                         // XML element defines xmi:type or xsi:type attribute, e.g.:
 316  
                         // <someRelation xmi:id='12' xmi:type="uml:Property" ... />
 317  
                         // Use its value to find an XMI transformation for the element
 318  12561
                         int xmiTypeIndex = findAttributeIndex(attrs, "xmi:type", "xsi:type");
 319  12561
                         if (xmiTypeIndex >= 0) {
 320  20
                                 String xmiPattern = attrs.getValue(xmiTypeIndex);
 321  20
                                 trans = transformations.getTransformation(xmiPattern, attrs);
 322  
                         }
 323  
                 }
 324  
 
 325  28889
                 if (trans == null) {
 326  
                         // Check if current element has a "xmi2assoc" trigger where
 327  
                         // xmlElement==trigger.attr, for example:
 328  
                         // XMI Source: <ownedAttribute xmi:id='xmi.43' name='attr1'
 329  
                         // type='xmi.2001'/>
 330  
                         // Candidate Trigger: <trigger name="properties" type="xmi2assoc"
 331  
                         // attr="ownedAttribute" src="uml:Property"/>
 332  12551
                         XMITransformation currentTrans = stack.getXMITransformation();
 333  12551
                         if (currentTrans != null) {
 334  105978
                                 for (XMITrigger trigger : currentTrans.getTriggerList()) {
 335  83097
                                         if (trigger.type == XMI2ASSOC && trigger.src != null
 336  20
                                                         && trigger.attr.equals(xmlElement)) {
 337  10
                                                 trans = transformations.getTransformation(trigger.src,
 338  5
                                                                 attrs);
 339  5
                                                 break;
 340  
                                         }
 341  
                                 }
 342  
                         }
 343  
                 }
 344  
 
 345  28889
                 if (trans == null) {
 346  12549
                         return false;
 347  
                 }
 348  
 
 349  16340
                 if (trans.requiresXMIID()) {
 350  
                         // Test if XML element has an XMI id specified.
 351  
                         // If not, return false to ignore the element.
 352  15850
                         if (findAttributeIndex(attrs, "xmi.id", "xmi:id") < 0) {
 353  2668
                                 return false;
 354  
                         }
 355  
                 } else {
 356  
                         // Ignore the XML element if it has an XMI idref specified and is not
 357  
                         // explicitly allowed to.
 358  490
                         if (!trans.allowsXMIIDRef() 
 359  489
                                         && findAttributeIndex(attrs, "xmi.idref", "xmi:idref") >= 0) {
 360  65
                                 return false;
 361  
                         }
 362  
                 }
 363  
 
 364  13607
                 processNewElement(trans, xmlElement, attrs);
 365  13607
                 return true;
 366  
         }
 367  
 
 368  
         /**
 369  
          * Finds the index of an XML attribute that can be specified using one of
 370  
          * two alternative names.
 371  
          * 
 372  
          * @param attrs SAX attributes of an XML element
 373  
          * @param attr1 First name used for the attribute.
 374  
          * @param attr2 Alternative name used for the attribute.
 375  
          * @return Index of the attribute, or -1 if the attribute is not contained
 376  
          *         under either name
 377  
          */
 378  
         private int findAttributeIndex(Attributes attrs, String attr1, String attr2) {
 379  28915
                 int idindex = attrs.getIndex(attr1);
 380  28915
                 return (idindex < 0) ? attrs.getIndex(attr2) : idindex;
 381  
         }
 382  
 
 383  
         /**
 384  
          * Processes a new model element in the XMI file. Creates a new
 385  
          * {@link ModelElement}, processes the XML elements, and pushes the new
 386  
          * model element on the context stack.
 387  
          * 
 388  
          * @param trans XMI transformations to apply for the model element.
 389  
          * @param xmlElement The name of the XML element in the XMI file.
 390  
          * @param attrs The XML attributes of the XML element.
 391  
          */
 392  
         private void processNewElement(XMITransformation trans, String xmlElement,
 393  
                         Attributes attrs) {
 394  13607
                 ModelElement element = new ModelElement(trans.getType());
 395  13607
                 processAttrTriggers(element, trans, attrs);
 396  
 
 397  
                 // if element has no XMI ID yet, add one
 398  13607
                 String xmiID = element.getPlainAttribute(MetaModelElement.ID);
 399  13607
                 if (xmiID.length() == 0) {
 400  5548
                         xmiID = "SDMetricsID." + (artificialIDCounter++);
 401  5548
                         setModelAttributeValue(element, MetaModelElement.ID, xmiID);
 402  
                 }
 403  
 
 404  13607
                 processRelationTriggers(xmlElement, xmiID);
 405  
 
 406  13607
                 stack.push(element, trans, nestingLevel);
 407  13607
         }
 408  
 
 409  
         /**
 410  
          * Processes triggers "attrval", "xmi2assoc" and "constant" for a new model
 411  
          * element. These triggers can be determined from the attributes of the XML
 412  
          * element that defines the model element.
 413  
          * 
 414  
          * @param element The new model element.
 415  
          * @param trans XMI transformation to use for the new model element.
 416  
          * @param attrs XML attributes defining the model element.
 417  
          */
 418  
         private void processAttrTriggers(ModelElement element,
 419  
                         XMITransformation trans, Attributes attrs) {
 420  13607
                 MetaModelElement type = trans.getType();
 421  
 
 422  131949
                 for (XMITrigger trigger : trans.getTriggerList()) {
 423  104735
                         if (trigger.type == ATTRVAL || trigger.type == XMI2ASSOC) {
 424  
                                 // Retrieve model element attribute from an XML attribute in the
 425  
                                 // XMI file. For example:
 426  
                                 // XMI Source: <ownedMember xmi:id='xmi.42' visibility='public'
 427  
                                 // classifier='xmi.43 xmi.44'/>
 428  
                                 // Triggers: <trigger name="classifiers" type="xmi2assoc"
 429  
                                 // attr="classifier" />
 430  
                                 // <trigger name="visible" type="attrval" attr="visibility" />
 431  44581
                                 String xmlAttrValue = attrs.getValue(trigger.attr);
 432  44581
                                 if (xmlAttrValue != null) {
 433  29150
                                         if (type.isSetAttribute(trigger.name)) {
 434  
                                                 // tokenize the XML attribute values and add each one
 435  1
                                                 StringTokenizer t = 
 436  1
                                                                 new StringTokenizer(xmlAttrValue);
 437  8
                                                 while (t.hasMoreTokens()) {
 438  6
                                                         String nextID = t.nextToken();
 439  6
                                                         setModelElementAttribute(element, nextID, trigger);
 440  
                                                 }
 441  1
                                         } else {
 442  29149
                                                 setModelElementAttribute(element, xmlAttrValue, trigger);
 443  
                                         }
 444  
                                 }
 445  29149
                         } else if (trigger.type == CONSTANT) {
 446  
                                 // Insert the constant defined in the XMI trigger as attribute
 447  
                                 // value
 448  15
                                 setModelAttributeValue(element, trigger.name, trigger.attr);
 449  
                         }
 450  
                 }
 451  13607
         }
 452  
 
 453  
         /**
 454  
          * Sets a cross-reference attribute value and registers the "linkbackattr"
 455  
          * on the "deferred relations" list, if defined.
 456  
          * 
 457  
          * @param sourceElement The source element of the cross-reference.
 458  
          * @param targetXMIID XMI ID of the target element.
 459  
          * @param trigger The trigger that produced the target element XMIID.
 460  
          */
 461  
         private void setModelElementAttribute(ModelElement sourceElement,
 462  
                         String targetXMIID, XMITrigger trigger) {
 463  33483
                 setModelAttributeValue(sourceElement, trigger.name, targetXMIID);
 464  33483
                 if (trigger.linkback != null) {
 465  941
                         DeferredRelation defRel = new DeferredRelation();
 466  941
                         defRel.sourceElement = sourceElement;
 467  941
                         defRel.targetXMIID = targetXMIID;
 468  941
                         defRel.linkBackAttr = trigger.linkback;
 469  941
                         deferredRelationsList.add(defRel);
 470  
                 }
 471  33483
         }
 472  
 
 473  
         /**
 474  
          * Processes "cattrval", "gcattrval", and "xmi2assoc" triggers of the owner
 475  
          * of a new model element.
 476  
          * 
 477  
          * @param xmlElement Name of the XML element defining the new model element.
 478  
          * @param xmiID XMI ID of the new model element.
 479  
          */
 480  
         private void processRelationTriggers(String xmlElement, String xmiID) {
 481  
                 // Process any matching "cattrval" or "xmi2assoc" triggers of the
 482  
                 // current model element
 483  13607
                 if ((nestingLevel == stack.getNestingLevel() + 1) && !stack.isEmpty()) {
 484  141
                         for (XMITrigger trigger : stack.getXMITransformation()
 485  20
                                         .getTriggerList()) {
 486  101
                                 if ((trigger.type == CATTRVAL && trigger.src.equals(xmlElement))
 487  101
                                                 || (trigger.type == XMI2ASSOC && trigger.attr
 488  27
                                                                 .equals(xmlElement))) {
 489  
                                         // Add relation to the new element from the owner
 490  
                                         // model element on the stack
 491  18
                                         setModelElementAttribute(stack.getModelElement(), xmiID,
 492  9
                                                         trigger);
 493  
                                 }
 494  
                         }
 495  
                 }
 496  
 
 497  
                 // process active "gcattr" trigger, if any, as long as they are not for
 498  
                 // the context attribute
 499  13607
                 if ((nestingLevel == stack.getNestingLevel() + 2)
 500  12833
                                 && stack.activeTriggerTypeEquals(GCATTRVAL)
 501  10
                                 && !MetaModelElement.CONTEXT
 502  10
                                                 .equals(stack.getActiveTrigger().name)) {
 503  5
                         processGCATTRElement(xmiID);
 504  
                 }
 505  13607
         }
 506  
 
 507  
         /**
 508  
          * Checks an XML child element of a model element for pertinent data.
 509  
          * 
 510  
          * @param xmlElement The name of the XML child element to process.
 511  
          * @param attrs The XML attributes of the XML child element.
 512  
          */
 513  
         private void processXMLFirstLevelChild(String xmlElement, Attributes attrs) {
 514  
 
 515  
                 // Go through list of triggers for current model element, checking if
 516  
                 // one is defined for the current XMI Element.
 517  105057
                 for (XMITrigger trigger : stack.getXMITransformation().getTriggerList()) {
 518  83369
                         if ((trigger.type == CATTRVAL && trigger.src.equals(xmlElement))
 519  83362
                                         || (trigger.type == XMI2ASSOC && trigger.attr
 520  38
                                                         .equals(xmlElement))) {
 521  
                                 // Process cattr or xmi2assoc trigger, for example:
 522  
                                 // XMI Source: <containedNode xmi:idref='xmi35'/>
 523  
                                 // Trigger: <trigger name="nodes" type="cattrval"
 524  
                                 // src="containedNode" attr="xmi:idref" />
 525  
                                 // or: <trigger name="nodes" type="xmi2assoc"
 526  
                                 // attr="containedNode" />
 527  
                                 int attrIndex;
 528  22
                                 if (trigger.type == XMI2ASSOC) {
 529  30
                                         attrIndex = findAttributeIndex(attrs, "xmi:idref",
 530  15
                                                         "xmi.idref");
 531  15
                                 } else {
 532  7
                                         attrIndex = attrs.getIndex(trigger.attr);
 533  
                                 }
 534  22
                                 if (attrIndex >= 0) {
 535  14
                                         String attrValue = attrs.getValue(attrIndex);
 536  28
                                         setModelElementAttribute(stack.getModelElement(),
 537  14
                                                         attrValue, trigger);
 538  
                                 }
 539  14
                         } else if ((trigger.type == GCATTRVAL || trigger.type == CTEXT 
 540  42204
                                         || trigger.type == REFLIST)        && trigger.src.equals(xmlElement)) {
 541  
                                 // Set current trigger for processing subsequent character text
 542  
                                 // or grandchild XML elements
 543  4662
                                 stack.setActiveTrigger(trigger);
 544  
                         }
 545  
                 }
 546  10844
         }
 547  
 
 548  
         /**
 549  
          * Checks an XML grandchild element of a model element for pertinent data.
 550  
          * 
 551  
          * @param attrs The XML attributes of the XML grandchild element.
 552  
          */
 553  
         private void processXMLSecondLevelChild(Attributes attrs) {
 554  8018
                 if (stack.activeTriggerTypeEquals(GCATTRVAL)) {
 555  4300
                         String attrValue = attrs.getValue(stack.getActiveTrigger().attr);
 556  4300
                         processGCATTRElement(attrValue);
 557  4300
                 } else if (stack.activeTriggerTypeEquals(REFLIST)) {
 558  4
                         processReflistElement(attrs);
 559  
                 }
 560  8018
         }
 561  
 
 562  
         /**
 563  
          * Completes the processing of the active "gcattrval" trigger.
 564  
          * 
 565  
          * @param attrValue Attribute value extracted by the trigger.
 566  
          */
 567  
         private void processGCATTRElement(String attrValue) {
 568  
                 // Example XMI Source:
 569  
                 // <UML:Partition' name='mySwimlane' xmi.id='xmi12'>
 570  
                 // <UML:Partition.contents>
 571  
                 // <UML:ModelElement xmi.idref='xmi35'/>
 572  
                 // Trigger: <trigger name="contents" type="gcattrval"
 573  
                 // src="UML:Partition.contents" attr="xmi.idref"/>
 574  4305
                 XMITrigger trigger = stack.getActiveTrigger();
 575  4305
                 MetaModelElement type = stack.getModelElement().getType();
 576  
 
 577  
                 // Insert attribute value of interest.
 578  4305
                 if (type.hasAttribute(trigger.name) && attrValue != null) {
 579  8610
                         setModelElementAttribute(stack.getModelElement(), attrValue,
 580  4305
                                         trigger);
 581  
                 }
 582  
                 // Done processing. If attribute is single-valued, expire trigger.
 583  4305
                 if (!type.isSetAttribute(trigger.name)) {
 584  4117
                         stack.setActiveTrigger(null);
 585  
                 }
 586  4305
         }
 587  
 
 588  
         /**
 589  
          * Processes an XML element that is a member of a "reflist". Creates a new
 590  
          * model element that is owned by the current model element on the top of
 591  
          * the stack.
 592  
          * <p>
 593  
          * Note: the reflist trigger is deprecated and only supported for backwards
 594  
          * compatibility. New XMITransformation files should use the
 595  
          * "[gc][c]relation" triggers.
 596  
          * 
 597  
          * @param attrs Attributes of the reflist element.
 598  
          * @return <code>true</code> if the XML element was processed successfully,
 599  
          *         <code>false</code> if the XML element was not suitable.
 600  
          */
 601  
         private boolean processReflistElement(Attributes attrs) {
 602  4
                 XMITrigger trigger = stack.getActiveTrigger();
 603  4
                 String attrval = attrs.getValue(trigger.attr);
 604  4
                 if (attrval == null) {
 605  0
                         return false;
 606  
                 }
 607  
 
 608  
                 // Get parent of new model element from top of the stack.
 609  4
                 ModelElement parent = stack.getModelElement();
 610  4
                 String parid = parent.getPlainAttribute(MetaModelElement.ID);
 611  
 
 612  
                 // Create the new model element.
 613  4
                 MetaModelElement nsetype = model.getMetaModel().getType(trigger.name);
 614  4
                 ModelElement nse = new ModelElement(nsetype);
 615  
 
 616  
                 // Set the owner of the new model element.
 617  4
                 setModelAttributeValue(nse, MetaModelElement.CONTEXT, parid);
 618  
 
 619  
                 // Set the cross-reference attribute to the referenced model element.
 620  4
                 setModelAttributeValue(nse, trigger.name, attrval);
 621  
 
 622  
                 // New model element has no name.
 623  4
                 setModelAttributeValue(nse, MetaModelElement.NAME, "");
 624  
 
 625  
                 // The XMI ID of the new model element is a concatenation of the
 626  
                 // referenced model element and the parent element.
 627  4
                 setModelAttributeValue(nse, MetaModelElement.ID, parid + attrval);
 628  
 
 629  
                 // Add new element to the model and return with success.
 630  4
                 addModelElement(nse.getPlainAttribute(MetaModelElement.ID), nse);
 631  4
                 return true;
 632  
         }
 633  
 
 634  
         /**
 635  
          * Sets the attribute value of a model element.
 636  
          * <p>
 637  
          * Replaces the string value with a cached copy if the value has been used
 638  
          * before.
 639  
          * 
 640  
          * @param element The model element
 641  
          * @param attributeName Name of the attribute to set
 642  
          * @param value Value for the attribute
 643  
          */
 644  
         private void setModelAttributeValue(ModelElement element,
 645  
                         String attributeName, String value) {
 646  53216
                 String cachedValue = stringCache.get(value);
 647  53216
                 if (cachedValue == null && value != null) {
 648  25805
                         cachedValue = value;
 649  25805
                         stringCache.put(value, value);
 650  
                 }
 651  
 
 652  53216
                 element.setAttribute(attributeName, cachedValue);
 653  53216
         }
 654  
 
 655  
         /**
 656  
          * Adds a new model element to the model.
 657  
          * 
 658  
          * @param xmiID XMI ID of the model element.
 659  
          * @param element The model element to add.
 660  
          */
 661  
         private void addModelElement(String xmiID, ModelElement element) {
 662  13611
                 element.setRunningID(elementsExtracted);
 663  13611
                 xmiIDMap.put(xmiID, element);
 664  13611
                 model.addElement(element);
 665  13611
                 elementsExtracted++;
 666  
 
 667  
                 // issue a progress message every 1000 elements
 668  13611
                 if (messageHandler != null) {
 669  1018
                         if ((elementsExtracted % 1000) == 0) {
 670  1
                                 messageHandler
 671  2
                                                 .reportXMIProgress("Reading UML model. Elements processed: "
 672  1
                                                                 + elementsExtracted);
 673  
                         }
 674  
                 }
 675  13611
         }
 676  
 
 677  
         /**
 678  
          * Processes the light and full extension references.
 679  
          */
 680  
         private void processProfileExtensions() {
 681  
                 // Iterate over all element types with an extension reference
 682  142
                 int extendingElementCount = 0;
 683  142
                 List<MetaModelElement> extendingTypes = new LinkedList<MetaModelElement>();
 684  2158
                 for (MetaModelElement type : model.getMetaModel()) {
 685  1874
                         String extRef = type.getExtensionReference();
 686  1874
                         if (extRef != null) {
 687  66
                                 extendingTypes.add(type);
 688  66
                                 extendingElementCount += model.getElements(type).size();
 689  
                         }
 690  
                 }
 691  
 
 692  142
                 if (extendingElementCount == 0) {
 693  136
                         return;
 694  
                 }
 695  
 
 696  6
                 if (messageHandler != null) {
 697  2
                         messageHandler.reportXMIProgress("Processing "
 698  1
                                         + extendingElementCount + " model extensions");
 699  
                 }
 700  
 
 701  
                 // Iterate over all element types with an extension reference
 702  30
                 for (MetaModelElement type : extendingTypes) {
 703  
                         // merge each extending element with its extended element,
 704  
                         // or assume the name and owner of the extended element
 705  18
                         String extRef = type.getExtensionReference();
 706  55
                         for (ModelElement extending : model.getElements(type)) {
 707  19
                                 String extendedID = extending.getPlainAttribute(extRef);
 708  19
                                 ModelElement extended = xmiIDMap.get(extendedID);
 709  19
                                 if (extended == null) {
 710  16
                                         continue;
 711  
                                 }
 712  3
                                 if (type.specializes(extended.getType())) {
 713  2
                                         extending.merge(extended);
 714  2
                                         model.removeElement(extended);
 715  2
                                         xmiIDMap.put(extendedID, extending);
 716  2
                                 } else {
 717  2
                                         extending.setAttribute(MetaModelElement.NAME,
 718  1
                                                         extended.getName());
 719  2
                                         extending.setAttribute(MetaModelElement.CONTEXT, extended
 720  1
                                                         .getPlainAttribute(MetaModelElement.CONTEXT));
 721  
                                 }
 722  
                         }
 723  
                 }
 724  6
         }
 725  
 
 726  
         /**
 727  
          * Replaces string-valued XMI IDs of cross-reference attribute values with
 728  
          * references to the respective model element instances. Called after all
 729  
          * model elements have been added.
 730  
          */
 731  
         private void doCrossreferencing() {
 732  142
                 if (messageHandler != null) {
 733  2
                         messageHandler.reportXMIProgress("Crossreferencing "
 734  1
                                         + elementsExtracted + " model elements");
 735  
                 }
 736  
 
 737  2158
                 for (MetaModelElement type : model.getMetaModel()) {
 738  12826
                         for (String attrName : type.getAttributeNames()) {
 739  9078
                                 if (type.isRefAttribute(attrName)) {
 740  4636
                                         crossReferenceAttribute(type, attrName);
 741  
                                 }
 742  
                         }
 743  
                 }
 744  
 
 745  
                 // don't need the XMI id mapping anymore
 746  142
                 xmiIDMap = null;
 747  142
         }
 748  
 
 749  
         /**
 750  
          * Performs the cross-referencing for a single attribute. 
 751  
          * @param type Type of the element with the cross-reference attribute.
 752  
          * @param attrName Name of the cross-reference attribute.
 753  
          */
 754  
         private void crossReferenceAttribute(MetaModelElement type, String attrName) {
 755  4636
                 boolean isMultivalued = type.isSetAttribute(attrName);
 756  41182
                 for (ModelElement element : model.getElements(type)) {
 757  31910
                         if (isMultivalued) {
 758  27576
                                 Collection<?> oldStringReferences = element
 759  13788
                                                 .getSetAttribute(attrName);
 760  13788
                                 if (oldStringReferences.isEmpty()) {
 761  13448
                                         continue;
 762  
                                 }
 763  680
                                 HashSet<ModelElement> newElementReferences = new HashSet<ModelElement>(
 764  340
                                                 oldStringReferences.size());
 765  1073
                                 for (Object xmiID : oldStringReferences) {
 766  393
                                         ModelElement referencedElement = xmiIDMap.get(xmiID);
 767  393
                                         if (referencedElement != null) {
 768  382
                                                 referencedElement.addRelation(attrName, element);
 769  382
                                                 newElementReferences.add(referencedElement);
 770  
                                         }
 771  
                                 }
 772  340
                                 element.setSetAttribute(attrName, newElementReferences);
 773  340
                         } else {
 774  36244
                                 ModelElement referencedElement = xmiIDMap
 775  18122
                                                 .get(element.getPlainAttribute(attrName));
 776  18122
                                 if (referencedElement != null) {
 777  17446
                                         referencedElement.addRelation(attrName, element);
 778  
                                 }
 779  18122
                                 element.setRefAttribute(attrName, referencedElement);
 780  
                         }
 781  
                 }
 782  4636
         }
 783  
 }