Coverage Report - com.sdmetrics.model.XMIReader - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
XMIReader
99%
259/260
95%
165/172
5,043
XMIReader$DeferredRelation
100%
1/1
N/A
5,043
XMIReader$ProgressMessageHandler
N/A
N/A
5,043
 
 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  1868
         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  140
         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  
          * @param trans The XMI transformations to use.
 133  
          * @param model The model that will contain the extracted model elements.
 134  
          */
 135  140
         public XMIReader(XMITransformations trans, Model model) {
 136  140
                 this.model = model;
 137  140
                 this.transformations = trans;
 138  140
         }
 139  
 
 140  
         /**
 141  
          * Registers a message handler for the XMI reader to report progress to.
 142  
          * 
 143  
          * @param handler The message handler.
 144  
          */
 145  
         public void setProgressMessageHandler(ProgressMessageHandler handler) {
 146  1
                 messageHandler = handler;
 147  1
         }
 148  
 
 149  
         /**
 150  
          * Gets the number of elements that have been extracted.
 151  
          * 
 152  
          * @return number of elements extracted from the XMI file
 153  
          */
 154  
         public int getNumberOfElements() {
 155  1
                 return elementsExtracted;
 156  
         }
 157  
 
 158  
         // SAX Parser callbacks
 159  
 
 160  
         /** Prepare parser for new XMI file. */
 161  
         @Override
 162  
         public void startDocument() {
 163  140
                 stack = new XMIContextStack();
 164  140
                 deferredRelationsList = new LinkedList<DeferredRelation>();
 165  140
                 xmiIDMap = new HashMap<String, ModelElement>(2048);
 166  140
                 stringCache = new HashMap<String, String>(2048);
 167  140
                 elementsExtracted = 0;
 168  140
                 nestingLevel = 0;
 169  140
                 artificialIDCounter = 0;
 170  140
         }
 171  
 
 172  
         /**
 173  
          * Processes the opening tag of an XML element in the XMI source file.
 174  
          * Depending on the parser state, this either creates a new model element,
 175  
          * or adds information from a child or grandchild node of the XMI tree to
 176  
          * the current model element.
 177  
          * 
 178  
          * @throws SAXException An error occurred during the evaluation of a
 179  
          *         conditional XMI transformation.
 180  
          */
 181  
         @Override
 182  
         public void startElement(String uri, String local, String raw,
 183  
                         Attributes attrs) throws SAXException {
 184  38048
                 nestingLevel++;
 185  
 
 186  38048
                 if (stack.isAcceptingNewElements()) {
 187  
                         // Check if this is the beginning of a new model element
 188  28818
                         if (checkAndProcessNewElement(raw, attrs))
 189  13590
                                 return;
 190  
                 }
 191  
 
 192  24458
                 if (stack.isEmpty()) {
 193  1104
                         return;
 194  23354
                 } else if (nestingLevel == stack.getNestingLevel() + 1) {
 195  10805
                         processXMLFirstLevelChild(raw, attrs);
 196  10805
                 } else if (nestingLevel == stack.getNestingLevel() + 2) {
 197  7996
                         processXMLSecondLevelChild(attrs);
 198  
                 }
 199  23354
         }
 200  
 
 201  
         /**
 202  
          * Processes text between XML element tags.
 203  
          */
 204  
         @Override
 205  
         public void characters(char ch[], int start, int length) {
 206  60563
                 if (stack.activeTriggerTypeEquals(CTEXT)) {
 207  486
                         ctextBuffer.append(ch, start, length);
 208  
                 }
 209  60563
         }
 210  
 
 211  
         /**
 212  
          * Processes the closing tag of an XML element. If the end of the definition
 213  
          * of the model element on the top of the stack has been reached, add this
 214  
          * element to the {@link Model}.
 215  
          */
 216  
         @Override
 217  
         public void endElement(String uri, String local, String raw) {
 218  
                 // Check for end of XML tag relevant to "ctext" triggers
 219  38048
                 if (nestingLevel == stack.getNestingLevel() + 1) {
 220  10943
                         if (stack.activeTriggerTypeEquals(CTEXT)) {
 221  
                                 // add the character data to the current model element
 222  368
                                 ModelElement element = stack.getModelElement();
 223  736
                                 setModelAttributeValue(element, stack.getActiveTrigger().name,
 224  368
                                                 ctextBuffer.toString());
 225  368
                                 ctextBuffer.setLength(0);
 226  
                         }
 227  
                         // any currently active trigger is expired by now.
 228  10943
                         stack.setActiveTrigger(null);
 229  
                 }
 230  
 
 231  
                 // Check if a model element has completed and add that
 232  
                 // element to the model
 233  38048
                 if (nestingLevel == stack.getNestingLevel()) {
 234  13590
                         ModelElement element = stack.pop();
 235  
 
 236  
                         // if element is its own "owner" (Visio XMI exporter), delete its
 237  
                         // context info
 238  27180
                         String contextID = element
 239  13590
                                         .getPlainAttribute(MetaModelElement.CONTEXT);
 240  13590
                         if (contextID.equals(element.getXMIID())) {
 241  10
                                 contextID = "";
 242  
                         }
 243  
 
 244  
                         // if element has no context info, insert id of owner (element below
 245  
                         // on the stack)
 246  13590
                         if (contextID.length() == 0) {
 247  13585
                                 ModelElement parent = stack.getModelElement();
 248  13585
                                 if (parent != null)
 249  13424
                                         contextID = parent.getXMIID();
 250  27170
                                 setModelAttributeValue(element, MetaModelElement.CONTEXT,
 251  13585
                                                 contextID);
 252  
                         }
 253  
 
 254  
                         // now we can add the element to the model
 255  13590
                         addModelElement(element.getXMIID(), element);
 256  
                 }
 257  
 
 258  38048
                 nestingLevel--;
 259  38048
         }
 260  
 
 261  
         /**
 262  
          * Performs the post-processing after the entire XMI file has been read.
 263  
          * Process the elements on the deferredRelations list, and cross-references
 264  
          * all model element links.
 265  
          */
 266  
         @Override
 267  
         public void endDocument() {
 268  
                 // Now that all model elements are known, we can process deferred
 269  
                 // relations
 270  1214
                 for (DeferredRelation dRel : deferredRelationsList) {
 271  934
                         ModelElement target = xmiIDMap.get(dRel.targetXMIID);
 272  934
                         if (target == null)
 273  5
                                 continue; // referenced element does not exist
 274  
 
 275  
                         // set attribute of target element to point back to the source
 276  929
                         if (target.getType().hasAttribute(dRel.linkBackAttr))
 277  358
                                 setModelAttributeValue(target, dRel.linkBackAttr,
 278  179
                                                 dRel.sourceElement.getXMIID());
 279  
                 }
 280  
 
 281  
                 // The string cache and deferred relations list have served their
 282  
                 // purpose and can be gc'ed
 283  140
                 stringCache = null;
 284  140
                 deferredRelationsList = null;
 285  
 
 286  
                 // Process extension attributes
 287  140
                 processProfileExtensions();
 288  
 
 289  
                 // Now we can do the cross-referencing of model elements
 290  140
                 doCrossreferencing();
 291  140
         }
 292  
 
 293  
         /**
 294  
          * Checks if an XML element in the XMI file defines a new model element, and
 295  
          * process it accordingly.
 296  
          * 
 297  
          * @param xmlElement Name of the XML element.
 298  
          * @param attrs The attributes of the XML element.
 299  
          * @return <code>true</code> if a new model element was created, else
 300  
          *         <code>false</code>.
 301  
          * @throws SAXException An error occurred evaluating the condition
 302  
          *         expression of a conditional XMI transformation.
 303  
          */
 304  
         private boolean checkAndProcessNewElement(String xmlElement,
 305  
                         Attributes attrs) throws SAXException {
 306  57636
                 XMITransformation trans = transformations.getTransformation(xmlElement,
 307  28818
                                 attrs);
 308  
 
 309  28818
                 if (trans == null) {
 310  
                         // XML element defines xmi:type or xsi:type attribute, e.g.:
 311  
                         // <someRelation xmi:id='12' xmi:type="uml:Property" ... />
 312  
                         // Use its value to find an XMI transformation for the element
 313  12515
                         int xmiTypeIndex = findAttributeIndex(attrs, "xmi:type", "xsi:type");
 314  12515
                         if (xmiTypeIndex >= 0) {
 315  19
                                 String xmiPattern = attrs.getValue(xmiTypeIndex);
 316  19
                                 trans = transformations.getTransformation(xmiPattern, attrs);
 317  
                         }
 318  
                 }
 319  
 
 320  28818
                 if (trans == null) {
 321  
                         // Check if current element has a "xmi2assoc" trigger where
 322  
                         // xmlElement==trigger.attr, for example:
 323  
                         // XMI Source: <ownedAttribute xmi:id='xmi.43' name='attr1'
 324  
                         // type='xmi.2001'/>
 325  
                         // Candidate Trigger: <trigger name="properties" type="xmi2assoc"
 326  
                         // attr="ownedAttribute" src="uml:Property"/>
 327  12506
                         XMITransformation currentTrans = stack.getXMITransformation();
 328  12506
                         if (currentTrans != null) {
 329  105666
                                 for (XMITrigger trigger : currentTrans.getTriggerList()) {
 330  82853
                                         if (trigger.type == XMI2ASSOC && trigger.src != null
 331  20
                                                         && trigger.attr.equals(xmlElement)) {
 332  10
                                                 trans = transformations.getTransformation(trigger.src,
 333  5
                                                                 attrs);
 334  5
                                                 break;
 335  
                                         }
 336  
                                 }
 337  
                         }
 338  
                 }
 339  
 
 340  28818
                 if (trans == null)
 341  12504
                         return false;
 342  
 
 343  16314
                 if (trans.requiresXMIID()) {
 344  
                         // Test if XML element has an XMI id specified.
 345  
                         // If not, return false to ignore the element.
 346  15833
                         if (findAttributeIndex(attrs, "xmi.id", "xmi:id") < 0)
 347  2663
                                 return false;
 348  
                 } else {
 349  
                         // Test if XML element has an XMI idref specified. If so, ignore the
 350  
                         // element.
 351  481
                         if (findAttributeIndex(attrs, "xmi.idref", "xmi:idref") >= 0)
 352  61
                                 return false;
 353  
                 }
 354  
 
 355  13590
                 processNewElement(trans, xmlElement, attrs);
 356  13590
                 return true;
 357  
         }
 358  
 
 359  
         /**
 360  
          * Finds the index of an XML attribute that can be specified using one of
 361  
          * two alternative names.
 362  
          * 
 363  
          * @param attrs SAX attributes of an XML element
 364  
          * @param attr1 First name used for the attribute.
 365  
          * @param attr2 Alternative name used for the attribute.
 366  
          * @return Index of the attribute, or -1 if the attribute is not contained
 367  
          *         under either name
 368  
          */
 369  
         private int findAttributeIndex(Attributes attrs, String attr1, String attr2) {
 370  28844
                 int idindex = attrs.getIndex(attr1);
 371  28844
                 return (idindex < 0) ? attrs.getIndex(attr2) : idindex;
 372  
         }
 373  
 
 374  
         /**
 375  
          * Processes a new model element in the XMI file. Creates a new
 376  
          * {@link ModelElement}, processes the XML elements, and pushes the new
 377  
          * model element on the context stack.
 378  
          * 
 379  
          * @param trans XMI transformations to apply for the model element.
 380  
          * @param xmlElement The name of the XML element in the XMI file.
 381  
          * @param attrs The XML attributes of the XML element.
 382  
          */
 383  
         private void processNewElement(XMITransformation trans, String xmlElement,
 384  
                         Attributes attrs) {
 385  13590
                 ModelElement element = new ModelElement(trans.getType());
 386  13590
                 processAttrTriggers(element, trans, attrs);
 387  
 
 388  
                 // if element has no XMI ID yet, add one
 389  13590
                 String xmiID = element.getPlainAttribute(MetaModelElement.ID);
 390  13590
                 if (xmiID.length() == 0) {
 391  5545
                         xmiID = "SDMetricsID." + (artificialIDCounter++);
 392  5545
                         setModelAttributeValue(element, MetaModelElement.ID, xmiID);
 393  
                 }
 394  
 
 395  13590
                 processRelationTriggers(xmlElement, xmiID);
 396  
 
 397  13590
                 stack.push(element, trans, nestingLevel);
 398  13590
         }
 399  
 
 400  
         /**
 401  
          * Processes triggers "attrval", "xmi2assoc" and "constant" for a new model
 402  
          * element. These triggers can be determined from the attributes of the XML
 403  
          * element that defines the model element.
 404  
          * 
 405  
          * @param element The new model element.
 406  
          * @param trans XMI transformation to use for the new model element.
 407  
          * @param attrs XML attributes defining the model element.
 408  
          */
 409  
         private void processAttrTriggers(ModelElement element,
 410  
                         XMITransformation trans, Attributes attrs) {
 411  13590
                 MetaModelElement type = trans.getType();
 412  
 
 413  131788
                 for (XMITrigger trigger : trans.getTriggerList()) {
 414  104608
                         if (trigger.type == ATTRVAL || trigger.type == XMI2ASSOC) {
 415  
                                 // Retrieve model element attribute from an XML attribute in the
 416  
                                 // XMI file. For example:
 417  
                                 // XMI Source: <ownedMember xmi:id='xmi.42' visibility='public'
 418  
                                 // classifier='xmi.43 xmi.44'/>
 419  
                                 // Triggers: <trigger name="classifiers" type="xmi2assoc"
 420  
                                 // attr="classifier" />
 421  
                                 // <trigger name="visible" type="attrval" attr="visibility" />
 422  44521
                                 String xmlAttrValue = attrs.getValue(trigger.attr);
 423  44521
                                 if (xmlAttrValue != null) {
 424  29116
                                         if (type.isSetAttribute(trigger.name)) {
 425  
                                                 // tokenize the XML attribute values and add each one
 426  1
                                                 StringTokenizer t = new StringTokenizer(xmlAttrValue);
 427  8
                                                 while (t.hasMoreTokens()) {
 428  6
                                                         String nextID = t.nextToken();
 429  6
                                                         setModelElementAttribute(element, nextID, trigger);
 430  
                                                 }
 431  1
                                         } else {
 432  29115
                                                 setModelElementAttribute(element, xmlAttrValue, trigger);
 433  
                                         }
 434  
                                 }
 435  29115
                         } else if (trigger.type == CONSTANT) {
 436  
                                 // Insert the constant defined in the XMI trigger as attribute
 437  
                                 // value
 438  14
                                 setModelAttributeValue(element, trigger.name, trigger.attr);
 439  
                         }
 440  
                 }
 441  13590
         }
 442  
 
 443  
         /**
 444  
          * Sets a cross-reference attribute value and registers the "linkbackattr"
 445  
          * on the "deferred relations" list, if defined.
 446  
          * 
 447  
          * @param sourceElement The source element of the cross-reference.
 448  
          * @param targetXMIID XMI ID of the target element.
 449  
          * @param trigger The trigger that produced the target element XMIID.
 450  
          */
 451  
         private void setModelElementAttribute(ModelElement sourceElement,
 452  
                         String targetXMIID, XMITrigger trigger) {
 453  33436
                 setModelAttributeValue(sourceElement, trigger.name, targetXMIID);
 454  33436
                 if (trigger.linkback != null) {
 455  934
                         DeferredRelation dRel = new DeferredRelation();
 456  934
                         dRel.sourceElement = sourceElement;
 457  934
                         dRel.targetXMIID = targetXMIID;
 458  934
                         dRel.linkBackAttr = trigger.linkback;
 459  934
                         deferredRelationsList.add(dRel);
 460  
                 }
 461  33436
         }
 462  
 
 463  
         /**
 464  
          * Processes "cattrval", "gcattrval", and "xmi2assoc" triggers of the owner
 465  
          * of a new model element.
 466  
          * 
 467  
          * @param xmlElement Name of the XML element defining the new model element.
 468  
          * @param xmiID XMI ID of the new model element.
 469  
          */
 470  
         private void processRelationTriggers(String xmlElement, String xmiID) {
 471  
                 // Process any matching "cattrval" or "xmi2assoc" triggers of the
 472  
                 // current model element
 473  13590
                 if ((nestingLevel == stack.getNestingLevel() + 1) && !stack.isEmpty()) {
 474  141
                         for (XMITrigger trigger : stack.getXMITransformation()
 475  20
                                         .getTriggerList()) {
 476  101
                                 if ((trigger.type == CATTRVAL && trigger.src.equals(xmlElement))
 477  101
                                                 || (trigger.type == XMI2ASSOC && trigger.attr
 478  27
                                                                 .equals(xmlElement))) {
 479  
                                         // Add relation to the new element from the owner
 480  
                                         // model element on the stack
 481  18
                                         setModelElementAttribute(stack.getModelElement(), xmiID,
 482  9
                                                         trigger);
 483  
                                 }
 484  
                         }
 485  
                 }
 486  
 
 487  
                 // process active "gcattr" trigger, if any, as long as they are not for
 488  
                 // the context attribute
 489  13590
                 if ((nestingLevel == stack.getNestingLevel() + 2)
 490  12819
                                 && stack.activeTriggerTypeEquals(GCATTRVAL)
 491  10
                                 && !MetaModelElement.CONTEXT
 492  10
                                                 .equals(stack.getActiveTrigger().name)) {
 493  5
                         processGCATTRElement(xmiID);
 494  
                 }
 495  13590
         }
 496  
 
 497  
         /**
 498  
          * Checks an XML child element of a model element for pertinent data.
 499  
          * 
 500  
          * @param xmlElement The name of the XML child element to process.
 501  
          * @param attrs The XML attributes of the XML child element.
 502  
          */
 503  
         private void processXMLFirstLevelChild(String xmlElement, Attributes attrs) {
 504  
 
 505  
                 // Go through list of triggers for current model element, checking if
 506  
                 // one is defined for the current XMI Element.
 507  104699
                 for (XMITrigger trigger : stack.getXMITransformation().getTriggerList()) {
 508  83089
                         if ((trigger.type == CATTRVAL && trigger.src.equals(xmlElement))
 509  83084
                                         || (trigger.type == XMI2ASSOC && trigger.attr
 510  38
                                                         .equals(xmlElement))) {
 511  
                                 // Process cattr or xmi2assoc trigger, for example:
 512  
                                 // XMI Source: <containedNode xmi:idref='xmi35'/>
 513  
                                 // Trigger: <trigger name="nodes" type="cattrval"
 514  
                                 // src="containedNode" attr="xmi:idref" />
 515  
                                 // or: <trigger name="nodes" type="xmi2assoc"
 516  
                                 // attr="containedNode" />
 517  
                                 int attrIndex;
 518  20
                                 if (trigger.type == XMI2ASSOC) {
 519  30
                                         attrIndex = findAttributeIndex(attrs, "xmi:idref",
 520  15
                                                         "xmi.idref");
 521  15
                                 } else
 522  5
                                         attrIndex = attrs.getIndex(trigger.attr);
 523  20
                                 if (attrIndex >= 0) {
 524  12
                                         String attrValue = attrs.getValue(attrIndex);
 525  24
                                         setModelElementAttribute(stack.getModelElement(),
 526  12
                                                         attrValue, trigger);
 527  
                                 }
 528  12
                         } else if ((trigger.type == GCATTRVAL || trigger.type == CTEXT || trigger.type == REFLIST)
 529  41060
                                         && trigger.src.equals(xmlElement)) {
 530  
                                 // Set current trigger for processing subsequent character text
 531  
                                 // or grandchild XML elements
 532  4651
                                 stack.setActiveTrigger(trigger);
 533  
                         }
 534  
                 }
 535  10805
         }
 536  
 
 537  
         /**
 538  
          * Checks an XML grandchild element of a model element for pertinent data.
 539  
          * 
 540  
          * @param attrs The XML attributes of the XML grandchild element.
 541  
          */
 542  
         private void processXMLSecondLevelChild(Attributes attrs) {
 543  7996
                 if (stack.activeTriggerTypeEquals(GCATTRVAL)) {
 544  4289
                         String attrValue = attrs.getValue(stack.getActiveTrigger().attr);
 545  4289
                         processGCATTRElement(attrValue);
 546  4289
                 } else if (stack.activeTriggerTypeEquals(REFLIST))
 547  4
                         processReflistElement(attrs);
 548  7996
         }
 549  
 
 550  
         /**
 551  
          * Completes the processing of the active "gcattrval" trigger.
 552  
          * 
 553  
          * @param attrValue Attribute value extracted by the trigger.
 554  
          */
 555  
         private void processGCATTRElement(String attrValue) {
 556  
                 // Example XMI Source:
 557  
                 // <UML:Partition' name='mySwimlane' xmi.id='xmi12'>
 558  
                 // <UML:Partition.contents>
 559  
                 // <UML:ModelElement xmi.idref='xmi35'/>
 560  
                 // Trigger: <trigger name="contents" type="gcattrval"
 561  
                 // src="UML:Partition.contents" attr="xmi.idref"/>
 562  4294
                 XMITrigger trigger = stack.getActiveTrigger();
 563  4294
                 MetaModelElement type = stack.getModelElement().getType();
 564  
 
 565  
                 // Insert attribute value of interest.
 566  4294
                 if (type.hasAttribute(trigger.name) && attrValue != null) {
 567  8588
                         setModelElementAttribute(stack.getModelElement(), attrValue,
 568  4294
                                         trigger);
 569  
                 }
 570  
                 // Done processing. If attribute is single-valued, expire trigger.
 571  4294
                 if (!type.isSetAttribute(trigger.name))
 572  4110
                         stack.setActiveTrigger(null);
 573  4294
         }
 574  
 
 575  
         /**
 576  
          * Processes an XML element that is a member of a "reflist". Creates a new
 577  
          * model element that is owned by the current model element on the top of
 578  
          * the stack.
 579  
          * <p>
 580  
          * Note: the reflist trigger is deprecated and only supported for backwards
 581  
          * compatibility. New XMITransformation files should use the
 582  
          * "[gc][c]relation" triggers.
 583  
          * 
 584  
          * @param attrs Attributes of the reflist element.
 585  
          * @return <code>true</code> if the XML element was processed successfully,
 586  
          *         <code>false</code> if the XML element was not suitable.
 587  
          */
 588  
         private boolean processReflistElement(Attributes attrs) {
 589  4
                 XMITrigger trigger = stack.getActiveTrigger();
 590  4
                 String attrval = attrs.getValue(trigger.attr);
 591  4
                 if (attrval == null)
 592  0
                         return false;
 593  
 
 594  
                 // Get parent of new model element from top of the stack.
 595  4
                 ModelElement parent = stack.getModelElement();
 596  4
                 String parid = parent.getPlainAttribute(MetaModelElement.ID);
 597  
 
 598  
                 // Create the new model element.
 599  4
                 MetaModelElement nsetype = model.getMetaModel().getType(trigger.name);
 600  4
                 ModelElement nse = new ModelElement(nsetype);
 601  
 
 602  
                 // Set the owner of the new model element.
 603  4
                 setModelAttributeValue(nse, MetaModelElement.CONTEXT, parid);
 604  
 
 605  
                 // Set the cross-reference attribute to the referenced model element.
 606  4
                 setModelAttributeValue(nse, trigger.name, attrval);
 607  
 
 608  
                 // New model element has no name.
 609  4
                 setModelAttributeValue(nse, MetaModelElement.NAME, "");
 610  
 
 611  
                 // The XMI ID of the new model element is a concatenation of the
 612  
                 // referenced model element and the parent element.
 613  4
                 setModelAttributeValue(nse, MetaModelElement.ID, parid + attrval);
 614  
 
 615  
                 // Add new element to the model and return with success.
 616  4
                 addModelElement(nse.getPlainAttribute(MetaModelElement.ID), nse);
 617  4
                 return true;
 618  
         }
 619  
 
 620  
         /**
 621  
          * Sets the attribute value of a model element.
 622  
          * <p>
 623  
          * Replaces the string value with a cached copy if the value has been used
 624  
          * before.
 625  
          * 
 626  
          * @param element The model element
 627  
          * @param attributeName Name of the attribute to set
 628  
          * @param value Value for the attribute
 629  
          */
 630  
         private void setModelAttributeValue(ModelElement element,
 631  
                         String attributeName, String value) {
 632  53143
                 String cachedValue = stringCache.get(value);
 633  53143
                 if (cachedValue == null && value != null) {
 634  25770
                         cachedValue = value;
 635  25770
                         stringCache.put(value, value);
 636  
                 }
 637  
 
 638  53143
                 element.setAttribute(attributeName, cachedValue);
 639  53143
         }
 640  
 
 641  
         /**
 642  
          * Adds a new model element to the model.
 643  
          * 
 644  
          * @param xmiID XMI ID of the model element.
 645  
          * @param element The model element to add.
 646  
          */
 647  
         private void addModelElement(String xmiID, ModelElement element) {
 648  13594
                 element.setRunningID(elementsExtracted);
 649  13594
                 xmiIDMap.put(xmiID, element);
 650  13594
                 model.addElement(element);
 651  13594
                 elementsExtracted++;
 652  
 
 653  
                 // issue a progress message every 1000 elements
 654  13594
                 if (messageHandler != null) {
 655  1018
                         if ((elementsExtracted % 1000) == 0)
 656  1
                                 messageHandler
 657  2
                                                 .reportXMIProgress("Reading UML model. Elements processed: "
 658  1
                                                                 + elementsExtracted);
 659  
                 }
 660  13594
         }
 661  
 
 662  
         /**
 663  
          * Processes the light and full extension references.
 664  
          */
 665  
         private void processProfileExtensions() {
 666  
                 // Iterate over all element types with an extension reference
 667  140
                 int extendingElementCount = 0;
 668  140
                 List<MetaModelElement> extendingTypes = new LinkedList<MetaModelElement>();
 669  2135
                 for (MetaModelElement type : model.getMetaModel()) {
 670  1855
                         String extRef = type.getExtensionReference();
 671  1855
                         if (extRef != null) {
 672  63
                                 extendingTypes.add(type);
 673  63
                                 extendingElementCount += model.getElements(type).size();
 674  
                         }
 675  
                 }
 676  
 
 677  140
                 if (extendingElementCount == 0)
 678  134
                         return;
 679  
 
 680  6
                 if (messageHandler != null)
 681  2
                         messageHandler.reportXMIProgress("Processing "
 682  1
                                         + extendingElementCount + " model extensions");
 683  
 
 684  
                 // Iterate over all element types with an extension reference
 685  30
                 for (MetaModelElement type : extendingTypes) {
 686  
                         // merge each extending element with its extended element,
 687  
                         // or assume the name and owner of the extended element
 688  18
                         String extRef = type.getExtensionReference();
 689  55
                         for (ModelElement extending : model.getElements(type)) {
 690  19
                                 String extendedID = extending.getPlainAttribute(extRef);
 691  19
                                 ModelElement extended = xmiIDMap.get(extendedID);
 692  19
                                 if (extended == null)
 693  16
                                         continue;
 694  3
                                 if (type.specializes(extended.getType())) {
 695  2
                                         extending.merge(extended);
 696  2
                                         model.removeElement(extended);
 697  2
                                         xmiIDMap.put(extendedID, extending);
 698  2
                                 } else {
 699  2
                                         extending.setAttribute(MetaModelElement.NAME,
 700  1
                                                         extended.getName());
 701  2
                                         extending.setAttribute(MetaModelElement.CONTEXT, extended
 702  1
                                                         .getPlainAttribute(MetaModelElement.CONTEXT));
 703  
                                 }
 704  
                         }
 705  
                 }
 706  6
         }
 707  
 
 708  
         /**
 709  
          * Replaces string-valued XMI IDs of cross-reference attribute values with
 710  
          * references to the respective model element instances. Called after all
 711  
          * model elements have been added.
 712  
          */
 713  
         private void doCrossreferencing() {
 714  140
                 if (messageHandler != null)
 715  2
                         messageHandler.reportXMIProgress("Crossreferencing "
 716  1
                                         + elementsExtracted + " model elements");
 717  
 
 718  2135
                 for (MetaModelElement type : model.getMetaModel()) {
 719  12695
                         for (String attrName : type.getAttributeNames()) {
 720  8985
                                 if (type.isRefAttribute(attrName)) {
 721  4593
                                         boolean isMultivalued = type.isSetAttribute(attrName);
 722  41053
                                         for (ModelElement element : model.getElements(type)) {
 723  31867
                                                 if (isMultivalued) {
 724  27540
                                                         Collection<?> oldStringReferences = element
 725  13770
                                                                         .getSetAttribute(attrName);
 726  13770
                                                         if (oldStringReferences.isEmpty())
 727  13436
                                                                 continue;
 728  668
                                                         HashSet<ModelElement> newElementReferences = new HashSet<ModelElement>(
 729  334
                                                                         oldStringReferences.size());
 730  1053
                                                         for (Object xmiID : oldStringReferences) {
 731  770
                                                                 ModelElement referencedElement = xmiIDMap
 732  385
                                                                                 .get(xmiID);
 733  385
                                                                 if (referencedElement != null) {
 734  748
                                                                         referencedElement.addRelation(attrName,
 735  374
                                                                                         element);
 736  374
                                                                         newElementReferences.add(referencedElement);
 737  
                                                                 }
 738  
                                                         }
 739  668
                                                         element.setSetAttribute(attrName,
 740  334
                                                                         newElementReferences);
 741  334
                                                 } else {
 742  36194
                                                         ModelElement referencedElement = xmiIDMap
 743  18097
                                                                         .get(element.getPlainAttribute(attrName));
 744  18097
                                                         if (referencedElement != null)
 745  17425
                                                                 referencedElement
 746  17425
                                                                                 .addRelation(attrName, element);
 747  18097
                                                         element.setRefAttribute(attrName, referencedElement);
 748  
                                                 }
 749  
                                         }
 750  
                                 }
 751  
                         }
 752  
                 }
 753  
 
 754  
                 // don't need the XMI id mapping anymore
 755  140
                 xmiIDMap = null;
 756  140
         }
 757  
 }