Coverage Report - com.sdmetrics.metrics.MetricStore - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
MetricStore
100%
55/55
100%
4/4
2,5
MetricStore$MetricParser
100%
262/262
97%
125/128
2,5
MetricStore$MetricParser$1
100%
3/3
N/A
2,5
MetricStore$MetricParser$2
100%
3/3
N/A
2,5
MetricStore$MetricParser$3
100%
3/3
N/A
2,5
MetricStore$MetricParser$InheritanceProcessor
100%
10/10
100%
6/6
2,5
MetricStore$WordList
100%
12/12
100%
4/4
2,5
 
 1  1958
 /*
 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.metrics;
 23  
 
 24  
 import java.util.ArrayList;
 25  
 import java.util.Collection;
 26  
 import java.util.HashMap;
 27  
 import java.util.HashSet;
 28  
 import java.util.LinkedHashMap;
 29  
 import java.util.List;
 30  
 import java.util.Locale;
 31  
 import java.util.Map;
 32  
 import java.util.StringTokenizer;
 33  
 
 34  
 import org.xml.sax.Attributes;
 35  
 import org.xml.sax.SAXException;
 36  
 import org.xml.sax.helpers.DefaultHandler;
 37  
 
 38  
 import com.sdmetrics.math.ExpressionNode;
 39  
 import com.sdmetrics.math.ExpressionParser;
 40  
 import com.sdmetrics.model.MetaModel;
 41  
 import com.sdmetrics.model.MetaModelElement;
 42  
 import com.sdmetrics.util.SAXHandler;
 43  
 
 44  
 /**
 45  
  * Reads a metric definition file and provides access to metrics, sets, and
 46  
  * rules defined therein.
 47  
  * <p>
 48  
  * The class also maintains the matrices, literature references, glossary
 49  
  * entries, and word lists defined in the file, as well as custom defined
 50  
  * metric, set, and rule procedures, and custom scalar, boolean, or set
 51  
  * operations.
 52  
  */
 53  
 public class MetricStore {
 54  
 
 55  
         /** The name of the top level XML element in the metric definition file. */
 56  
         public static final String TL_ELEMENT = "sdmetrics";
 57  
 
 58  
         /** Metamodel on which the metric, set, and rule definitions are based. */
 59  9187
         private final MetaModel metaModel;
 60  
 
 61  
         /** The standard and custom defined metric calculation procedures. */
 62  152
         private final MetricProcedureCache metricProcedures;
 63  
         /** The standard and custom defined set calculation procedures. */
 64  152
         private final SetProcedureCache setProcedures;
 65  
         /** The standard and custom defined rule calculation procedures. */
 66  151
         private final RuleProcedureCache ruleProcedures;
 67  
         /** The standard and custom defined expression operations. */
 68  1074
         private final ExpressionOperations exprOps;
 69  
 
 70  
         /**
 71  
          * Container for the metrics. Metrics are stored by model element type. For
 72  
          * each type there is one mapping of metric name (key) to the associated
 73  
          * metric.
 74  
          */
 75  6359
         private final HashMap<MetaModelElement, LinkedHashMap<String, Metric>> metrics;
 76  
 
 77  
         /** Container for the sets. Same organization as for metrics. */
 78  3747
         private final HashMap<MetaModelElement, LinkedHashMap<String, Set>> sets;
 79  
 
 80  
         /** Container for the sets. Same organization as for metrics. */
 81  3613
         private final HashMap<MetaModelElement, LinkedHashMap<String, Rule>> rules;
 82  
 
 83  
         /** The relation matrices. */
 84  834
         private final ArrayList<Matrix> matrices;
 85  
         /** The literature references. Key is the reference tag (such as "CK94") */
 86  135
         private final LinkedHashMap<String, Reference> references;
 87  
         /** The glossary. Key is the glossary term. */
 88  135
         private final LinkedHashMap<String, Glossary> glossary;
 89  
         /** The word lists. Key is the name of the list. */
 90  839
         private final HashMap<String, WordList> wordLists;
 91  
 
 92  
         /** The element type with information about rule exemptions. */
 93  304
         private MetaModelElement ruleExemptionType;
 94  
         /**
 95  
          * Name of the attribute or metric that contains the rule exemption
 96  
          * information.
 97  
          */
 98  173
         private String ruleExemptionTag;
 99  
 
 100  
         /** Indicates if metrics/sets/rule should be inheritable by default. */
 101  8216
         private boolean defaultInheritability;
 102  
 
 103  
         /**
 104  
          * Creates a new metric store.
 105  
          * 
 106  
          * @param metaModel Metamodel on which the metrics are based.
 107  
          */
 108  174
         public MetricStore(MetaModel metaModel) {
 109  174
                 this.metaModel = metaModel;
 110  
 
 111  174
                 metricProcedures = new MetricProcedureCache();
 112  174
                 setProcedures = new SetProcedureCache();
 113  174
                 ruleProcedures = new RuleProcedureCache();
 114  174
                 exprOps = new ExpressionOperations();
 115  
 
 116  174
                 metrics = new HashMap<MetaModelElement, LinkedHashMap<String, Metric>>();
 117  174
                 sets = new HashMap<MetaModelElement, LinkedHashMap<String, Set>>();
 118  174
                 rules = new HashMap<MetaModelElement, LinkedHashMap<String, Rule>>();
 119  2722
                 for (MetaModelElement type : metaModel) {
 120  2374
                         metrics.put(type, new LinkedHashMap<String, Metric>());
 121  2374
                         sets.put(type, new LinkedHashMap<String, Set>());
 122  2374
                         rules.put(type, new LinkedHashMap<String, Rule>());
 123  
                 }
 124  
 
 125  174
                 matrices = new ArrayList<Matrix>();
 126  174
                 references = new LinkedHashMap<String, Reference>();
 127  174
                 glossary = new LinkedHashMap<String, Glossary>();
 128  174
                 wordLists = new HashMap<String, WordList>();
 129  174
         }
 130  
 
 131  
         /**
 132  
          * Gets a SAX handler to parse a metric definition file and store its
 133  
          * contents with this object.
 134  
          * 
 135  
          * @return SAX handler to parse metrics definition file
 136  
          */
 137  
         public DefaultHandler getSAXParserHandler() {
 138  174
                 return new MetricParser();
 139  
         }
 140  
 
 141  
         /**
 142  
          * Gets the metamodel on which the metrics are based.
 143  
          * 
 144  
          * @return the metamodel for this metric store
 145  
          */
 146  
         public MetaModel getMetaModel() {
 147  1460
                 return metaModel;
 148  
         }
 149  
 
 150  
         /**
 151  
          * Gets the cache holding the metric procedure definitions for this metric
 152  
          * store.
 153  
          * 
 154  
          * @return the metric procedure definitions for this store.
 155  
          */
 156  
         MetricProcedureCache getMetricProcedures() {
 157  119
                 return metricProcedures;
 158  
         }
 159  
 
 160  
         /**
 161  
          * Gets the cache holding the set procedure definitions for this metric
 162  
          * store.
 163  
          * 
 164  
          * @return the set procedure definitions for this store.
 165  
          */
 166  
         SetProcedureCache getSetProcedures() {
 167  119
                 return setProcedures;
 168  
         }
 169  
 
 170  
         /**
 171  
          * Gets the cache holding the rule procedure definitions for this metric
 172  
          * store.
 173  
          * 
 174  
          * @return the rule procedure definitions for this store.
 175  
          */
 176  
         RuleProcedureCache getRuleProcedures() {
 177  119
                 return ruleProcedures;
 178  
         }
 179  
 
 180  
         /**
 181  
          * Gets the cache holding the scalar, boolean, and set operations for the
 182  
          * metric expressions in this metric store.
 183  
          * 
 184  
          * @return metric expressions in this store.
 185  
          */
 186  
         ExpressionOperations getExpressionOperations() {
 187  1830
                 return exprOps;
 188  
         }
 189  
 
 190  
         /**
 191  
          * Gets the metric for a given element type by its name.
 192  
          * 
 193  
          * @param type Element type for which the metric is defined.
 194  
          * @param name Name of the metric.
 195  
          * @return the specified metric, or <code>null</code> if no such metric was
 196  
          *         found.
 197  
          */
 198  
         public Metric getMetric(MetaModelElement type, String name) {
 199  6178
                 return metrics.get(type).get(name);
 200  
         }
 201  
 
 202  
         /**
 203  
          * Gets the set for a given element type by its name.
 204  
          * 
 205  
          * @param type Element type for which the set is defined.
 206  
          * @param name Name of the set.
 207  
          * @return the specified set, or <code>null</code> if no such set was found.
 208  
          */
 209  
         public Set getSet(MetaModelElement type, String name) {
 210  2061
                 return sets.get(type).get(name);
 211  
         }
 212  
 
 213  
         /**
 214  
          * Gets the rule for a given element type by its name.
 215  
          * 
 216  
          * @param type Element type for which the rule is defined.
 217  
          * @param name Name of the rule.
 218  
          * @return the specified rule, or <code>null</code> if no such rule was
 219  
          *         found.
 220  
          * @since 2.3
 221  
          */
 222  
         public Rule getRule(MetaModelElement type, String name) {
 223  1728
                 return rules.get(type).get(name);
 224  
         }
 225  
 
 226  
         /**
 227  
          * Gets the list of all metrics for a given element type.
 228  
          * 
 229  
          * @param type Element type for which to return the metrics.
 230  
          * @return The metrics for the type. Iteration over this collection
 231  
          *         preserves the order in which the metrics are defined in the
 232  
          *         metric definition file.
 233  
          */
 234  
         public Collection<Metric> getMetrics(MetaModelElement type) {
 235  132
                 return metrics.get(type).values();
 236  
         }
 237  
 
 238  
         /**
 239  
          * Gets the list of all sets for a given element type.
 240  
          * 
 241  
          * @param type Element type for which to return the sets.
 242  
          * @return The sets for the type.
 243  
          */
 244  
         public Collection<Set> getSets(MetaModelElement type) {
 245  188
                 return sets.get(type).values();
 246  
         }
 247  
 
 248  
         /**
 249  
          * Gets the list of all rules for a given element type.
 250  
          * 
 251  
          * @param type Element type for which to return the rules.
 252  
          * @return The rules for the type. Iteration over this collection preserves
 253  
          *         the order in which the rules are defined in the metric definition
 254  
          *         file.
 255  
          */
 256  
         public Collection<Rule> getRules(MetaModelElement type) {
 257  79
                 return rules.get(type).values();
 258  
         }
 259  
 
 260  
         /**
 261  
          * Gets the list of all relationship matrices defined in the metric
 262  
          * definition file.
 263  
          * 
 264  
          * @return Random access list containing the matrices in the order they
 265  
          *         appear in the metric definition file.
 266  
          */
 267  
         public List<Matrix> getMatrices() {
 268  4
                 return matrices;
 269  
         }
 270  
 
 271  
         /**
 272  
          * Gets the list of all literature references defined in the metric
 273  
          * definition file.
 274  
          * 
 275  
          * @return The literature references. Iteration over this collection
 276  
          *         preserves the order in which the references are defined in the
 277  
          *         metric definition file.
 278  
          */
 279  
         public Collection<Reference> getLiteratureReferences() {
 280  1
                 return references.values();
 281  
         }
 282  
 
 283  
         /**
 284  
          * Gets the list of all glossary terms defined in the metric definition
 285  
          * file.
 286  
          * 
 287  
          * @return The glossary terms. Iteration over this collection preserves the
 288  
          *         order in which the glossary terms are defined in the metric
 289  
          *         definition file.
 290  
          */
 291  
         public Collection<Glossary> getGlossary() {
 292  1
                 return glossary.values();
 293  
         }
 294  
 
 295  
         /**
 296  
          * Returns the element type that contains information about rule exemptions.
 297  
          * This type is defined by the "ruleexemption" attribute of the top level
 298  
          * XML element in the metric definition file.
 299  
          * 
 300  
          * @return element type with rule exemption information, or
 301  
          *         <code>null</code> if no such type was specified.
 302  
          */
 303  
         MetaModelElement getRuleExemptionType() {
 304  120
                 return ruleExemptionType;
 305  
         }
 306  
 
 307  
         /**
 308  
          * Returns the name of the attribute or metric that yields the rule
 309  
          * exemption information. This name is defined by the "ruleexemption"
 310  
          * attribute of the top level XML element in the metric definition file.
 311  
          * 
 312  
          * @return Name of the rule exemption attribute or metric, or
 313  
          *         <code>null</code> if none was specified.
 314  
          */
 315  
         String getRuleExemptionTag() {
 316  120
                 return ruleExemptionTag;
 317  
         }
 318  
 
 319  
         /**
 320  
          * Checks if a specified word is on a word list.
 321  
          * 
 322  
          * @param word The word to search for.
 323  
          * @param listName The name of the list to search.
 324  
          * @return <code>true</code> if the specified list contains the word.
 325  
          * @throws SDMetricsException The specified list does not exist.
 326  
          */
 327  
         boolean isWordOnList(String word, String listName)
 328  
                         throws SDMetricsException {
 329  15
                 WordList list = wordLists.get(listName);
 330  15
                 if (list == null) {
 331  2
                         throw new SDMetricsException(null, null, listName
 332  1
                                         + ": No such word list.");
 333  
                 }
 334  14
                 return list.hasWord(word);
 335  
         }
 336  
 
 337  
         /** Represents a word list in the metric definition file. */
 338  
         static class WordList {
 339  
                 /** Indicates if string comparisons should be case-sensitive. */
 340  
                 private final boolean caseSensitive;
 341  
                 /** The set of words on the list. */
 342  
                 private final HashSet<String> words;
 343  
 
 344  
                 /**
 345  
                  * Construct a new word list.
 346  
                  * 
 347  
                  * @param isCaseSensitive Indicates if string comparisons should be case
 348  
                  *        sensitive or not.
 349  
                  */
 350  419
                 WordList(boolean isCaseSensitive) {
 351  419
                         caseSensitive = isCaseSensitive;
 352  419
                         words = new HashSet<String>();
 353  419
                 }
 354  
 
 355  
                 /**
 356  
                  * Adds a word to the word list.
 357  
                  * 
 358  
                  * @param word The word to add.
 359  
                  */
 360  
                 void addWord(String word) {
 361  1437
                         if (caseSensitive) {
 362  1305
                                 words.add(word);
 363  1305
                         } else {
 364  132
                                 words.add(word.toUpperCase(Locale.ENGLISH));
 365  
                         }
 366  1437
                 }
 367  
 
 368  
                 /**
 369  
                  * Tests if a word is on the word list.
 370  
                  * 
 371  
                  * @param word The word to test.
 372  
                  * @return <code>true</code> if the word is on the list
 373  
                  */
 374  
                 boolean hasWord(String word) {
 375  14
                         if (caseSensitive) {
 376  11
                                 return words.contains(word);
 377  
                         }
 378  3
                         return words.contains(word.toUpperCase(Locale.ENGLISH));
 379  
                 }
 380  
         }
 381  
 
 382  
         /**
 383  
          * SAX handler to parse a metric definition file.
 384  
          */
 385  348
         private class MetricParser extends SAXHandler {
 386  
 
 387  
                 // XML element and attribute names in the metric definition file
 388  
                 private static final String METRICPROCEDURE = "metricprocedure";
 389  
                 private static final String SETPROCEDURE = "setprocedure";
 390  
                 private static final String RULEPROCEDURE = "ruleprocedure";
 391  
                 private static final String SCALAROPERATION = "scalaroperationdefinition";
 392  
                 private static final String SETOPERATION = "setoperationdefinition";
 393  
                 private static final String BOOLEANOPERATION = "booleanoperationdefinition";
 394  
 
 395  
                 private static final String METRICENTRY = "metric";
 396  
                 private static final String SETENTRY = "set";
 397  
                 private static final String MATRIXENTRY = "matrix";
 398  
                 private static final String RULEENTRY = "rule";
 399  
                 private static final String WORDLIST = "wordlist";
 400  
                 private static final String WORDLISTENTRY = "entry";
 401  
                 private static final String REFERENCEENTRY = "reference";
 402  
                 private static final String GLOSSARYENTRY = "term";
 403  
                 private static final String DESCRIPTION = "description";
 404  
                 private static final String INHERITABLE = "inheritable";
 405  
 
 406  174
                 private MetricEntry currentEntry = null;
 407  174
                 private String currentEntryType = null;
 408  174
                 private String currentEntryName = null;
 409  
                 private WordList currentWordList;
 410  174
                 private boolean inDescription = false;
 411  174
                 private ExpressionParser ep = exprOps.getExpressionParser();
 412  
 
 413  
                 /**
 414  
                  * Process an XML element in the metric definition file.
 415  
                  * 
 416  
                  * @throws SAXException An error occurred processing an XML element (bad
 417  
                  *         definition file).
 418  
                  */
 419  
                 @Override
 420  
                 public void startElement(String uri, String local, String raw,
 421  
                                 Attributes attrs) throws SAXException {
 422  20856
                         if (MetricStore.TL_ELEMENT.equals(raw)) {
 423  174
                                 checkVersion(attrs, null);
 424  174
                                 String exemptionTypeString = attrs.getValue("ruleexemption");
 425  174
                                 if (exemptionTypeString != null) {
 426  152
                                         ruleExemptionType = metaModel.getType(exemptionTypeString);
 427  152
                                         if (ruleExemptionType == null) {
 428  2
                                                 reportError("Unknown rule exemption type '" 
 429  1
                                                                 + exemptionTypeString + "'.");
 430  
                                         }
 431  
                                 }
 432  173
                                 ruleExemptionTag = attrs.getValue("exemptiontag");
 433  173
                                 defaultInheritability = isAttributeValueTrue(attrs, INHERITABLE);
 434  173
                         } else if (DESCRIPTION.equals(raw)) {
 435  853
                                 inDescription = true;
 436  853
                         } else if (METRICPROCEDURE.equals(raw)) {
 437  152
                                 handleProcedureDefinition(metricProcedures, raw, attrs);
 438  150
                         } else if (SETPROCEDURE.equals(raw)) {
 439  152
                                 handleProcedureDefinition(setProcedures, raw, attrs);
 440  151
                         } else if (RULEPROCEDURE.equals(raw)) {
 441  151
                                 handleProcedureDefinition(ruleProcedures, raw, attrs);
 442  150
                         } else if (SCALAROPERATION.equals(raw)) {
 443  300
                                 String opName = handleProcedureDefinition(
 444  150
                                                 exprOps.getScalarOperations(), raw, attrs);
 445  150
                                 exprOps.addCustomFunctionName(opName);
 446  150
                         } else if (SETOPERATION.equals(raw)) {
 447  300
                                 String opName = handleProcedureDefinition(
 448  150
                                                 exprOps.getSetOperations(), raw, attrs);
 449  150
                                 exprOps.addCustomFunctionName(opName);
 450  150
                         } else if (BOOLEANOPERATION.equals(raw)) {
 451  300
                                 String opName = handleProcedureDefinition(
 452  150
                                                 exprOps.getBooleanOperations(), raw, attrs);
 453  150
                                 exprOps.addCustomFunctionName(opName);
 454  150
                         } else if (METRICENTRY.equals(raw)) {
 455  4471
                                 currentEntryType = raw;
 456  4471
                                 currentEntry = handleMetricDefinition(attrs);
 457  4467
                         } else if (SETENTRY.equals(raw)) {
 458  1856
                                 currentEntryType = raw;
 459  1856
                                 currentEntry = handleSetDefinition(attrs);
 460  1855
                         } else if (MATRIXENTRY.equals(raw)) {
 461  418
                                 currentEntryType = raw;
 462  418
                                 currentEntry = handleMatrixDefinition(attrs);
 463  417
                         } else if (RULEENTRY.equals(raw)) {
 464  1722
                                 currentEntryType = raw;
 465  1722
                                 currentEntry = handleRuleDefinition(attrs);
 466  1721
                         } else if (REFERENCEENTRY.equals(raw)) {
 467  69
                                 currentEntryType = raw;
 468  69
                                 inDescription = true;
 469  69
                                 currentEntry = handleReferenceDefinition(attrs);
 470  67
                         } else if (GLOSSARYENTRY.equals(raw)) {
 471  68
                                 currentEntryType = raw;
 472  68
                                 inDescription = true;
 473  68
                                 currentEntry = handleGlossaryDefinition(attrs);
 474  67
                         } else if (WORDLIST.equals(raw)) {
 475  420
                                 currentEntryType = raw;
 476  420
                                 currentWordList = handleWordListDefinition(attrs);
 477  419
                         } else if (WORDLISTENTRY.equals(raw)) {
 478  1439
                                 handleWordListEntry(attrs);
 479  1437
                         } else {
 480  8461
                                 handleComputationDefinition(raw, attrs);
 481  
                         }
 482  20835
                 }
 483  
 
 484  
                 /**
 485  
                  * Adds text to the description of the current entry.
 486  
                  */
 487  
                 @Override
 488  
                 public void characters(char[] ch, int start, int length) {
 489  30928
                         if (inDescription && currentEntry != null && length > 0) {
 490  1219
                                 currentEntry.addDescription(ch, start, length);
 491  
                         }
 492  30928
                 }
 493  
 
 494  
                 /**
 495  
                  * Performs consistency checks at the end of an XML element.
 496  
                  */
 497  
                 @Override
 498  
                 public void endElement(String uri, String local, String raw)
 499  
                                 throws SAXException {
 500  20810
                         if (raw.equals(DESCRIPTION) || raw.equals(GLOSSARYENTRY)
 501  19891
                                         || raw.equals(REFERENCEENTRY)) {
 502  986
                                 inDescription = false;
 503  986
                                 if (!raw.equals(DESCRIPTION)) {
 504  134
                                         currentEntry = null;
 505  
                                 }
 506  134
                         } else if (raw.equals(METRICENTRY) || raw.equals(SETENTRY)
 507  13504
                                         || raw.equals(RULEENTRY) || raw.equals(MATRIXENTRY)) {
 508  8458
                                 if (currentEntry.getAttributes() == null) {
 509  2
                                         reportError("No calculation procedure specified for " + raw
 510  1
                                                         + " '" + currentEntry.getName() + "'.");
 511  
                                 }
 512  8457
                                 currentEntry = null;
 513  8457
                         } else if (raw.equals(WORDLIST)) {
 514  418
                                 currentWordList = null;
 515  
                         }
 516  20809
                 }
 517  
 
 518  
                 /**
 519  
                  * Calculates metric/set/rule inheritance after all transformations have
 520  
                  * been read.
 521  
                  */
 522  
                 @Override
 523  
                 public void endDocument() {
 524  152
                         HashSet<MetaModelElement> processedTypes = new HashSet<MetaModelElement>();
 525  2348
                         for (MetaModelElement type : metaModel) {
 526  2044
                                 processInheritance(type, processedTypes);
 527  
                         }
 528  152
                 }
 529  
 
 530  
                 /**
 531  
                  * Recursively adds the metrics/sets/rules that the type inherits from
 532  
                  * its parent type.
 533  
                  * 
 534  
                  * @param trans The type to process.
 535  
                  * @param processedTransformations The set of types already processed
 536  
                  */
 537  
                 private void processInheritance(MetaModelElement type,
 538  
                                 HashSet<MetaModelElement> processedTypes) {
 539  
                         // make sure each type is processed only once
 540  3936
                         if (processedTypes.contains(type)) {
 541  1892
                                 return;
 542  
                         }
 543  2044
                         processedTypes.add(type);
 544  
 
 545  
                         // Recursively process the parent type first
 546  2044
                         MetaModelElement parentType = type.getParent();
 547  2044
                         if (parentType == null) {
 548  152
                                 return;
 549  
                         }
 550  1892
                         processInheritance(parentType, processedTypes);
 551  
 
 552  
                         // Process metric inheritance for the type
 553  1892
                         new InheritanceProcessor<Metric>() {
 554  
                                 @Override
 555  
                                 Metric createCopy(Metric original, MetaModelElement newType) {
 556  183
                                         return new Metric(original, newType);
 557  
                                 }
 558  1892
                         }.processInheritance(metrics, parentType, type);
 559  
 
 560  
                         // process set inheritance for the type
 561  1892
                         new InheritanceProcessor<Set>() {
 562  
                                 @Override
 563  
                                 Set createCopy(Set original, MetaModelElement newType) {
 564  66
                                         return new Set(original, newType);
 565  
                                 }
 566  1892
                         }.processInheritance(sets, parentType, type);
 567  
 
 568  
                         // process rule inheritance for the type
 569  1892
                         new InheritanceProcessor<Rule>() {
 570  
                                 @Override
 571  
                                 Rule createCopy(Rule original, MetaModelElement newType) {
 572  66
                                         return new Rule(original, newType);
 573  
                                 }
 574  1892
                         }.processInheritance(rules, parentType, type);
 575  1892
                 }
 576  
 
 577  
                 /**
 578  
                  * Helper class to copy inherited metrics/sets/rules from parent to
 579  
                  * child type.
 580  
                  * 
 581  
                  * @param <T> The type of elements to copy
 582  
                  */
 583  11352
                 private abstract class InheritanceProcessor<T extends MetricEntry> {
 584  
                         abstract T createCopy(T original, MetaModelElement newType);
 585  
 
 586  
                         void processInheritance(
 587  
                                         HashMap<MetaModelElement, LinkedHashMap<String, T>> entries,
 588  
                                         MetaModelElement parentType, MetaModelElement type) {
 589  
 
 590  5676
                                 Map<String, T> parentEntries = entries.get(parentType);
 591  5676
                                 Map<String, T> childEntries = entries.get(type);
 592  13284
                                 for (Map.Entry<String, T> entry : parentEntries.entrySet()) {
 593  1932
                                         T metricEntry = entry.getValue();
 594  
                                         // if entry is inheritable, and this type does not have an
 595  
                                         // entry under that name, copy the entry for this type
 596  1932
                                         if (metricEntry.isInheritable()
 597  348
                                                         && !childEntries.containsKey(entry.getKey())) {
 598  315
                                                 T copy = createCopy(metricEntry, type);
 599  315
                                                 addEntry(childEntries, copy);
 600  
                                         }
 601  
                                 }
 602  5676
                         }
 603  
                 }
 604  
 
 605  
                 /* Process a metric definition in the metric definition file. */
 606  
                 private Metric handleMetricDefinition(Attributes attrs)
 607  
                                 throws SAXException {
 608  4471
                         String name = getEntryName(attrs);
 609  4470
                         MetaModelElement type = getTypeName(attrs, "domain");
 610  
 
 611  4468
                         if (getMetric(type, name) != null) {
 612  2
                                 reportError("Duplicate definition of metric '" + name
 613  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 614  
                         }
 615  
 
 616  4467
                         String category = getOptionalAttribute(attrs, "category");
 617  
 
 618  4467
                         Metric metric = new Metric(name, type, category);
 619  4467
                         metric.setInternal(isAttributeValueTrue(attrs, "internal"));
 620  4467
                         setLocation(metric);
 621  4467
                         setInheritability(metric, attrs);
 622  
 
 623  
                         // add the metric to the store
 624  4467
                         addEntry(metrics.get(type), metric);
 625  4467
                         return metric;
 626  
                 }
 627  
 
 628  
                 private String getEntryName(Attributes attrs) throws SAXException {
 629  8955
                         currentEntryName = attrs.getValue("name");
 630  8955
                         if (currentEntryName == null) {
 631  1
                                 reportError("No name specified for " + currentEntryType + ".");
 632  
                         }
 633  8954
                         return currentEntryName;
 634  
                 }
 635  
 
 636  
                 private MetaModelElement getTypeName(Attributes attrs,
 637  
                                 String domAttribute) throws SAXException {
 638  8884
                         String typeName = attrs.getValue(domAttribute);
 639  8884
                         if (typeName == null) {
 640  2
                                 reportError("No domain specified for " + currentEntryType
 641  1
                                                 + " '" + currentEntryName + "'.");
 642  
                         }
 643  8883
                         MetaModelElement type = metaModel.getType(typeName);
 644  8883
                         if (type == null) {
 645  2
                                 reportError("Unknown domain '" + typeName + "' for "
 646  1
                                                 + currentEntryType + " '" + currentEntryName 
 647  1
                                                 + "'.");
 648  
                         }
 649  8882
                         return type;
 650  
                 }
 651  
 
 652  
                 private String getOptionalAttribute(Attributes attrs, String attrName) {
 653  7909
                         String value = attrs.getValue(attrName);
 654  7909
                         if (value == null) {
 655  7190
                                 return "";
 656  
                         }
 657  719
                         return value;
 658  
                 }
 659  
 
 660  
                 private boolean isAttributeValueTrue(Attributes attrs, String attrName) {
 661  9001
                         return "true".equals(attrs.getValue(attrName));
 662  
                 }
 663  
 
 664  
                 private void setLocation(MetricEntry entry) {
 665  8461
                         if (locator != null) {
 666  8461
                                 entry.setLocation(locator.getLineNumber());
 667  
                         }
 668  8461
                 }
 669  
 
 670  
                 private void setInheritability(MetricEntry entry, Attributes attrs) {
 671  8043
                         boolean inheritable = defaultInheritability;
 672  8043
                         if (attrs.getValue(INHERITABLE) != null) {
 673  366
                                 inheritable = isAttributeValueTrue(attrs, INHERITABLE);
 674  
                         }
 675  8043
                         entry.setInheritable(inheritable);
 676  8043
                 }
 677  
 
 678  315
                 private <T extends MetricEntry> void addEntry(Map<String, T> map,
 679  
                                 T entry) {
 680  8358
                         entry.setId(map.size());
 681  8358
                         map.put(entry.getName(), entry);
 682  8358
                 }
 683  
 
 684  
                 /* Process a set definition in the metric definition file. */
 685  
                 private Set handleSetDefinition(Attributes attrs) throws SAXException {
 686  1856
                         String name = getEntryName(attrs);
 687  1856
                         MetaModelElement type = getTypeName(attrs, "domain");
 688  
 
 689  1856
                         if (getSet(type, name) != null) {
 690  2
                                 reportError("Duplicate definition of set '" + name
 691  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 692  
                         }
 693  
 
 694  1855
                         Set set = new Set(name, type);
 695  1855
                         setLocation(set);
 696  1855
                         setInheritability(set, attrs);
 697  
 
 698  1855
                         set.setMultiSet(isAttributeValueTrue(attrs, "multiset"));
 699  
 
 700  
                         // assign metric ID to the metric, and add it to the store
 701  1855
                         addEntry(sets.get(type), set);
 702  1855
                         return set;
 703  
                 }
 704  
 
 705  
                 /* Process a relation matrix definition. */
 706  
                 private Matrix handleMatrixDefinition(Attributes attrs)
 707  
                                 throws SAXException {
 708  418
                         String name = getEntryName(attrs);
 709  418
                         MetaModelElement rowType = getTypeName(attrs, "from_row_type");
 710  418
                         MetaModelElement columnType = getTypeName(attrs, "to_col_type");
 711  
 
 712  418
                         Matrix matrix = new Matrix(name, rowType, columnType);
 713  418
                         setLocation(matrix);
 714  
 
 715  418
                         matrix.setRowCondition(parseExpression(attrs, "row_condition"));
 716  417
                         matrix.setColumnCondition(parseExpression(attrs, "col_condition"));
 717  
 
 718  
                         // assign ID to the matrix, add it to the store
 719  417
                         matrix.setId(matrices.size());
 720  417
                         matrices.add(matrix);
 721  
 
 722  417
                         return matrix;
 723  
                 }
 724  
 
 725  
                 private ExpressionNode parseExpression(Attributes attrs, String attrName)
 726  
                                 throws SAXException {
 727  21663
                         ExpressionNode result = null;
 728  21663
                         String expr = attrs.getValue(attrName);
 729  21663
                         if (expr != null) {
 730  21129
                                 result = ep.parseExpression(expr);
 731  21129
                                 if (result == null) {
 732  2
                                         reportError("Error parsing " + attrName + "='" + expr
 733  1
                                                         + "' for " + currentEntryType + " '"
 734  1
                                                         + currentEntryName + "':\n" + ep.getErrorInfo());
 735  
                                 }
 736  
                         }
 737  21662
                         return result;
 738  
                 }
 739  
 
 740  
                 /* Process a rule definition in the metric definition file. */
 741  
                 private Rule handleRuleDefinition(Attributes attrs) throws SAXException {
 742  1722
                         String name = getEntryName(attrs);
 743  
 
 744  1722
                         MetaModelElement type = getTypeName(attrs, "domain");
 745  1722
                         if (getRule(type, name) != null) {
 746  2
                                 reportError("Duplicate definition of rule '" + name
 747  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 748  
                         }
 749  
 
 750  1721
                         String category = getOptionalAttribute(attrs, "category");
 751  1721
                         String severity = getOptionalAttribute(attrs, "severity");
 752  1721
                         boolean enabled = !isAttributeValueTrue(attrs, "disabled");
 753  
 
 754  1721
                         ArrayList<String> applicationList = null;
 755  1721
                         String appliesTo = attrs.getValue("applies_to");
 756  1721
                         if (appliesTo != null) {
 757  885
                                 applicationList = new ArrayList<String>();
 758  1770
                                 StringTokenizer st = new StringTokenizer(appliesTo,
 759  885
                                                 " ,\t\n\r\f");
 760  2871
                                 while (st.hasMoreTokens()) {
 761  1101
                                         applicationList.add(st.nextToken());
 762  
                                 }
 763  885
                                 if (applicationList.isEmpty()) {
 764  33
                                         applicationList = null;
 765  
                                 }
 766  
                         }
 767  
 
 768  3442
                         Rule rule = new Rule(name, type, category, severity,
 769  1721
                                         applicationList, enabled);
 770  1721
                         setLocation(rule);
 771  1721
                         setInheritability(rule, attrs);
 772  
 
 773  
                         // assign ID to the rule, add it to the store
 774  1721
                         addEntry(rules.get(type), rule);
 775  1721
                         return rule;
 776  
                 }
 777  
 
 778  
                 /** Process a literature reference in the metric definition file. */
 779  
                 private MetricEntry handleReferenceDefinition(Attributes attrs)
 780  
                                 throws SAXException {
 781  69
                         String tag = attrs.getValue("tag");
 782  69
                         if (tag == null) {
 783  1
                                 reportError("Reference is missing its tag.");
 784  
                         }
 785  68
                         if (references.containsKey(tag)) {
 786  1
                                 reportError("Duplicate definition of reference '" + tag + "'.");
 787  
                         }
 788  67
                         Reference ref = new Reference(tag);
 789  67
                         references.put(tag, ref);
 790  67
                         return ref;
 791  
                 }
 792  
 
 793  
                 /* Process a glossary entry in the metric definition file. */
 794  
                 private Glossary handleGlossaryDefinition(Attributes attrs)
 795  
                                 throws SAXException {
 796  68
                         String name = getEntryName(attrs);
 797  68
                         if (glossary.containsKey(name)) {
 798  2
                                 reportError("Duplicate definition of glossary term '" + name
 799  1
                                                 + "'.");
 800  
                         }
 801  67
                         Glossary item = new Glossary(name);
 802  67
                         glossary.put(name, item);
 803  67
                         return item;
 804  
                 }
 805  
 
 806  
                 /* Process the definition of a word list in the metric definition file. */
 807  
                 private WordList handleWordListDefinition(Attributes attrs)
 808  
                                 throws SAXException {
 809  420
                         String listName = getEntryName(attrs);
 810  420
                         if (wordLists.containsKey(listName)) {
 811  2
                                 reportError("Duplicate definition of word list '" + listName
 812  1
                                                 + "'.");
 813  
                         }
 814  419
                         boolean caseSensitive = !isAttributeValueTrue(attrs, "ignorecase");
 815  419
                         WordList list = new WordList(caseSensitive);
 816  419
                         wordLists.put(listName, list);
 817  419
                         return list;
 818  
                 }
 819  
 
 820  
                 /* Process an entry in a word list. */
 821  
                 private void handleWordListEntry(Attributes attrs) throws SAXException {
 822  1439
                         if (currentWordList == null) {
 823  1
                                 reportError("Wordlist entry outside of a word list.");
 824  
                         }
 825  1438
                         String word = attrs.getValue("word");
 826  1438
                         if (word == null) {
 827  1
                                 reportError("Wordlist entry missing 'word' attribute.");
 828  
                         }
 829  1437
                         currentWordList.addWord(word);
 830  1437
                 }
 831  
 
 832  
                 /**
 833  
                  * Handles the computation procedure for the current metric, set,
 834  
                  * matrix, or rule. Compiles the expressions of the attributes and adds
 835  
                  * them to the computation procedure.
 836  
                  * 
 837  
                  * @param procedureName Name of the computation procedure
 838  
                  * @param attrs The attributes for the computation procedure.
 839  
                  * @throws SAXException There was an error parsing an attribute of the
 840  
                  *         computation procedure.
 841  
                  */
 842  
                 private void handleComputationDefinition(String procedureName,
 843  
                                 Attributes attrs) throws SAXException {
 844  8461
                         if (inDescription) {
 845  2
                                 reportError("Unexpected XML element '" + procedureName
 846  1
                                                 + "' in description.");
 847  
                         }
 848  
 
 849  8460
                         if (currentEntry == null) {
 850  2
                                 reportError("Unexpected XML element '" + procedureName
 851  1
                                                 + "' at this point.");
 852  
                         }
 853  
 
 854  8459
                         if (currentEntry.getAttributes() != null) {
 855  2
                                 reportError("Unexpected XML element '" + procedureName + "' - "
 856  1
                                                 + currentEntryType + " '" + currentEntryName
 857  1
                                                 + "' already has a calculation procedure.");
 858  
                         }
 859  8458
                         currentEntry.setProcedureName(procedureName);
 860  
 
 861  8458
                         ProcedureAttributes attributes = new ProcedureAttributes();
 862  8458
                         int attnum = attrs.getLength();
 863  29286
                         for (int i = 0; i < attnum; i++) {
 864  
                                 // Compile expression and store the operator tree.
 865  20828
                                 String attrName = attrs.getQName(i);
 866  20828
                                 ExpressionNode root = parseExpression(attrs, attrName);
 867  
                                 // check for old "_exp" suffix from versions 1.2 and earlier,
 868  
                                 // remove it from the attribute name to maintain backwards
 869  
                                 // compatibility
 870  20828
                                 if (attrName.endsWith("_exp")) {
 871  33
                                         attrName = attrName.substring(0, attrName.length() - 4);
 872  
                                 }
 873  20828
                                 attributes.setExpressionNode(attrName, root);
 874  
                         }
 875  8458
                         currentEntry.setAttributes(attributes);
 876  8458
                 }
 877  
 
 878  
                 /**
 879  
                  * Handles the definition of a custom defined metric, set, or rule
 880  
                  * procedure. Makes sure the procedure class can be loaded and
 881  
                  * instantiated.
 882  
                  * 
 883  
                  * @param store The applicable procedure store
 884  
                  * @param element the name of the XML element defining the procedure
 885  
                  * @param attrs the XML attributes of the element
 886  
                  * @return Name of the procedure
 887  
                  * @throws SAXException Missing attributes or procedure class could not
 888  
                  *         be loaded
 889  
                  */
 890  
                 private String handleProcedureDefinition(ProcedureCache<?> store,
 891  
                                 String element, Attributes attrs) throws SAXException {
 892  905
                         String procedureName = attrs.getValue("name");
 893  905
                         if (procedureName == null) {
 894  2
                                 reportError("The " + element
 895  1
                                                 + " definition requires a 'name' attribute.");
 896  
                         }
 897  
 
 898  904
                         if (store.hasProcedure(procedureName)) {
 899  2
                                 reportError("The " + element + " '" + procedureName
 900  1
                                                 + "' is already defined.");
 901  
                         }
 902  
 
 903  903
                         String className = attrs.getValue("class");
 904  903
                         if (className == null) {
 905  2
                                 reportError("No class defined for " + element + " definition '"
 906  1
                                                 + procedureName + "'.");
 907  
                         }
 908  
 
 909  
                         try {
 910  902
                                 store.addProcedureClass(procedureName, className);
 911  901
                         } catch (SDMetricsException ex) {
 912  1
                                 reportError(ex.getMessage());
 913  
                         }
 914  901
                         return procedureName;
 915  
                 }
 916  
         }
 917  
 }