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%
260/260
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%
11/11
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  1833
                 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  6183
                 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  133
                 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  14
                 return list.hasWord(word);
 334  
         }
 335  
 
 336  
         /** Represents a word list in the metric definition file. */
 337  
         static class WordList {
 338  
                 /** Indicates if string comparisons should be case-sensitive. */
 339  
                 private final boolean caseSensitive;
 340  
                 /** The set of words on the list. */
 341  
                 private final HashSet<String> words;
 342  
 
 343  
                 /**
 344  
                  * Construct a new word list.
 345  
                  * 
 346  
                  * @param isCaseSensitive Indicates if string comparisons should be case
 347  
                  *        sensitive or not.
 348  
                  */
 349  419
                 WordList(boolean isCaseSensitive) {
 350  419
                         caseSensitive = isCaseSensitive;
 351  419
                         words = new HashSet<String>();
 352  419
                 }
 353  
 
 354  
                 /**
 355  
                  * Adds a word to the word list.
 356  
                  * 
 357  
                  * @param word The word to add.
 358  
                  */
 359  
                 void addWord(String word) {
 360  1437
                         if (caseSensitive)
 361  1305
                                 words.add(word);
 362  
                         else
 363  132
                                 words.add(word.toUpperCase(Locale.ENGLISH));
 364  1437
                 }
 365  
 
 366  
                 /**
 367  
                  * Tests if a word is on the word list.
 368  
                  * 
 369  
                  * @param word The word to test.
 370  
                  * @return <code>true</code> if the word is on the list
 371  
                  */
 372  
                 boolean hasWord(String word) {
 373  14
                         if (caseSensitive)
 374  11
                                 return words.contains(word);
 375  3
                         return words.contains(word.toUpperCase(Locale.ENGLISH));
 376  
                 }
 377  
         }
 378  
 
 379  
         /**
 380  
          * SAX handler to parse a metric definition file.
 381  
          */
 382  348
         private class MetricParser extends SAXHandler {
 383  
 
 384  
                 // XML element and attribute names in the metric definition file
 385  
                 private static final String METRICPROCEDURE = "metricprocedure";
 386  
                 private static final String SETPROCEDURE = "setprocedure";
 387  
                 private static final String RULEPROCEDURE = "ruleprocedure";
 388  
                 private static final String SCALAROPERATION = "scalaroperationdefinition";
 389  
                 private static final String SETOPERATION = "setoperationdefinition";
 390  
                 private static final String BOOLEANOPERATION = "booleanoperationdefinition";
 391  
 
 392  
                 private static final String METRICENTRY = "metric";
 393  
                 private static final String SETENTRY = "set";
 394  
                 private static final String MATRIXENTRY = "matrix";
 395  
                 private static final String RULEENTRY = "rule";
 396  
                 private static final String WORDLIST = "wordlist";
 397  
                 private static final String WORDLISTENTRY = "entry";
 398  
                 private static final String REFERENCEENTRY = "reference";
 399  
                 private static final String GLOSSARYENTRY = "term";
 400  
                 private static final String DESCRIPTION = "description";
 401  
                 private static final String INHERITABLE = "inheritable";
 402  
 
 403  174
                 private MetricEntry currentEntry = null;
 404  174
                 private String currentEntryType = null;
 405  174
                 private String currentEntryName = null;
 406  
                 private WordList currentWordList;
 407  174
                 private boolean inDescription = false;
 408  174
                 private ExpressionParser ep = exprOps.getExpressionParser();
 409  
 
 410  
                 /**
 411  
                  * Process an XML element in the metric definition file.
 412  
                  * 
 413  
                  * @throws SAXException An error occurred processing an XML element (bad
 414  
                  *         definition file).
 415  
                  */
 416  
                 @Override
 417  
                 public void startElement(String uri, String local, String raw,
 418  
                                 Attributes attrs) throws SAXException {
 419  20856
                         if (MetricStore.TL_ELEMENT.equals(raw)) {
 420  174
                                 checkVersion(attrs, null);
 421  174
                                 String exemptionTypeString=attrs.getValue("ruleexemption");
 422  174
                                 if (exemptionTypeString != null) {
 423  152
                                         ruleExemptionType = metaModel.getType(exemptionTypeString);
 424  152
                                         if (ruleExemptionType == null) {
 425  1
                                                 reportError("Unknown rule exemption type '" + exemptionTypeString + "'.");
 426  
                                         }
 427  
                                 }
 428  173
                                 ruleExemptionTag = attrs.getValue("exemptiontag");
 429  173
                                 defaultInheritability = isAttributeValueTrue(attrs, INHERITABLE);
 430  173
                         } else if (DESCRIPTION.equals(raw)) {
 431  853
                                 inDescription = true;
 432  853
                         } else if (METRICPROCEDURE.equals(raw)) {
 433  152
                                 handleProcedureDefinition(metricProcedures, raw, attrs);
 434  150
                         } else if (SETPROCEDURE.equals(raw)) {
 435  152
                                 handleProcedureDefinition(setProcedures, raw, attrs);
 436  151
                         } else if (RULEPROCEDURE.equals(raw)) {
 437  151
                                 handleProcedureDefinition(ruleProcedures, raw, attrs);
 438  150
                         } else if (SCALAROPERATION.equals(raw)) {
 439  300
                                 String opName = handleProcedureDefinition(
 440  150
                                                 exprOps.getScalarOperations(), raw, attrs);
 441  150
                                 exprOps.addCustomFunctionName(opName);
 442  150
                         } else if (SETOPERATION.equals(raw)) {
 443  300
                                 String opName = handleProcedureDefinition(
 444  150
                                                 exprOps.getSetOperations(), raw, attrs);
 445  150
                                 exprOps.addCustomFunctionName(opName);
 446  150
                         } else if (BOOLEANOPERATION.equals(raw)) {
 447  300
                                 String opName = handleProcedureDefinition(
 448  150
                                                 exprOps.getBooleanOperations(), raw, attrs);
 449  150
                                 exprOps.addCustomFunctionName(opName);
 450  150
                         } else if (METRICENTRY.equals(raw)) {
 451  4471
                                 currentEntryType = raw;
 452  4471
                                 currentEntry = handleMetricDefinition(attrs);
 453  4467
                         } else if (SETENTRY.equals(raw)) {
 454  1856
                                 currentEntryType = raw;
 455  1856
                                 currentEntry = handleSetDefinition(attrs);
 456  1855
                         } else if (MATRIXENTRY.equals(raw)) {
 457  418
                                 currentEntryType = raw;
 458  418
                                 currentEntry = handleMatrixDefinition(attrs);
 459  417
                         } else if (RULEENTRY.equals(raw)) {
 460  1722
                                 currentEntryType = raw;
 461  1722
                                 currentEntry = handleRuleDefinition(attrs);
 462  1721
                         } else if (REFERENCEENTRY.equals(raw)) {
 463  69
                                 currentEntryType = raw;
 464  69
                                 inDescription = true;
 465  69
                                 currentEntry = handleReferenceDefinition(attrs);
 466  67
                         } else if (GLOSSARYENTRY.equals(raw)) {
 467  68
                                 currentEntryType = raw;
 468  68
                                 inDescription = true;
 469  68
                                 currentEntry = handleGlossaryDefinition(attrs);
 470  67
                         } else if (WORDLIST.equals(raw)) {
 471  420
                                 currentEntryType = raw;
 472  420
                                 currentWordList = handleWordListDefinition(attrs);
 473  419
                         } else if (WORDLISTENTRY.equals(raw)) {
 474  1439
                                 handleWordListEntry(attrs);
 475  1437
                         } else {
 476  8461
                                 handleComputationDefinition(raw, attrs);
 477  
                         }
 478  20835
                 }
 479  
 
 480  
                 /**
 481  
                  * Adds text to the description of the current entry.
 482  
                  */
 483  
                 @Override
 484  
                 public void characters(char ch[], int start, int length) {
 485  30928
                         if (inDescription && currentEntry != null && length > 0) {
 486  1219
                                 currentEntry.addDescription(ch, start, length);
 487  
                         }
 488  30928
                 }
 489  
 
 490  
                 /**
 491  
                  * Performs consistency checks at the end of an XML element.
 492  
                  */
 493  
                 @Override
 494  
                 public void endElement(String uri, String local, String raw)
 495  
                                 throws SAXException {
 496  20810
                         if (raw.equals(DESCRIPTION) || raw.equals(GLOSSARYENTRY)
 497  19891
                                         || raw.equals(REFERENCEENTRY)) {
 498  986
                                 inDescription = false;
 499  986
                                 if (!raw.equals(DESCRIPTION)) {
 500  134
                                         currentEntry = null;
 501  
                                 }
 502  134
                         } else if (raw.equals(METRICENTRY) || raw.equals(SETENTRY)
 503  13504
                                         || raw.equals(RULEENTRY) || raw.equals(MATRIXENTRY)) {
 504  8458
                                 if (currentEntry.getAttributes() == null) {
 505  2
                                         reportError("No calculation procedure specified for " + raw
 506  1
                                                         + " '" + currentEntry.getName() + "'.");
 507  
                                 }
 508  8457
                                 currentEntry = null;
 509  8457
                         } else if (raw.equals(WORDLIST)) {
 510  418
                                 currentWordList = null;
 511  
                         }
 512  20809
                 }
 513  
 
 514  
                 /**
 515  
                  * Calculates metric/set/rule inheritance after all transformations have
 516  
                  * been read.
 517  
                  */
 518  
                 @Override
 519  
                 public void endDocument() {
 520  152
                         HashSet<MetaModelElement> processedTypes = new HashSet<MetaModelElement>();
 521  2348
                         for (MetaModelElement type : metaModel)
 522  2044
                                 processInheritance(type, processedTypes);
 523  152
                 }
 524  
 
 525  
                 /**
 526  
                  * Recursively adds the metrics/sets/rules that the type inherits from
 527  
                  * its parent type.
 528  
                  * 
 529  
                  * @param trans The type to process.
 530  
                  * @param processedTransformations The set of types already processed
 531  
                  */
 532  
                 private void processInheritance(MetaModelElement type,
 533  
                                 HashSet<MetaModelElement> processedTypes) {
 534  
                         // make sure each type is processed only once
 535  3936
                         if (processedTypes.contains(type))
 536  1892
                                 return;
 537  2044
                         processedTypes.add(type);
 538  
 
 539  
                         // Recursively process the parent type first
 540  2044
                         MetaModelElement parentType = type.getParent();
 541  2044
                         if (parentType == null)
 542  152
                                 return;
 543  1892
                         processInheritance(parentType, processedTypes);
 544  
 
 545  
                         // Process metric inheritance for the type
 546  1892
                         new InheritanceProcessor<Metric>() {
 547  
                                 @Override
 548  
                                 Metric createCopy(Metric original, MetaModelElement newType) {
 549  183
                                         return new Metric(original, newType);
 550  
                                 }
 551  1892
                         }.processInheritance(metrics, parentType, type);
 552  
 
 553  
                         // process set inheritance for the type
 554  1892
                         new InheritanceProcessor<Set>() {
 555  
                                 @Override
 556  
                                 Set createCopy(Set original, MetaModelElement newType) {
 557  66
                                         return new Set(original, newType);
 558  
                                 }
 559  1892
                         }.processInheritance(sets, parentType, type);
 560  
 
 561  
                         // process rule inheritance for the type
 562  1892
                         new InheritanceProcessor<Rule>() {
 563  
                                 @Override
 564  
                                 Rule createCopy(Rule original, MetaModelElement newType) {
 565  66
                                         return new Rule(original, newType);
 566  
                                 }
 567  1892
                         }.processInheritance(rules, parentType, type);
 568  1892
                 }
 569  
 
 570  
                 /**
 571  
                  * Helper class to copy inherited metrics/sets/rules from parent to
 572  
                  * child type.
 573  
                  * 
 574  
                  * @param <T> The type of elements to copy
 575  
                  */
 576  11352
                 private abstract class InheritanceProcessor<T extends MetricEntry> {
 577  
                         abstract T createCopy(T original, MetaModelElement newType);
 578  
 
 579  
                         void processInheritance(
 580  
                                         HashMap<MetaModelElement, LinkedHashMap<String, T>> entries,
 581  
                                         MetaModelElement parentType, MetaModelElement type) {
 582  
 
 583  5676
                                 Map<String, T> parentEntries = entries.get(parentType);
 584  5676
                                 Map<String, T> childEntries = entries.get(type);
 585  13284
                                 for (Map.Entry<String, T> entry : parentEntries.entrySet()) {
 586  1932
                                         T metricEntry = entry.getValue();
 587  
                                         // if entry is inheritable, and this type does not have an
 588  
                                         // entry under that name, copy the entry for this type
 589  1932
                                         if (metricEntry.isInheritable()
 590  348
                                                         && !childEntries.containsKey(entry.getKey())) {
 591  315
                                                 T copy = createCopy(metricEntry, type);
 592  315
                                                 addEntry(childEntries, copy);
 593  
                                         }
 594  
                                 }
 595  5676
                         }
 596  
                 }
 597  
 
 598  
                 /* Process a metric definition in the metric definition file. */
 599  
                 private Metric handleMetricDefinition(Attributes attrs)
 600  
                                 throws SAXException {
 601  4471
                         String name = getEntryName(attrs);
 602  4470
                         MetaModelElement type = getTypeName(attrs, "domain");
 603  
 
 604  4468
                         if (getMetric(type, name) != null)
 605  2
                                 reportError("Duplicate definition of metric '" + name
 606  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 607  
 
 608  4467
                         String category = getOptionalAttribute(attrs, "category");
 609  
 
 610  4467
                         Metric metric = new Metric(name, type, category);
 611  4467
                         metric.setInternal(isAttributeValueTrue(attrs, "internal"));
 612  4467
                         setLocation(metric);
 613  4467
                         setInheritability(metric, attrs);
 614  
 
 615  
                         // add the metric to the store
 616  4467
                         addEntry(metrics.get(type), metric);
 617  4467
                         return metric;
 618  
                 }
 619  
 
 620  
                 private String getEntryName(Attributes attrs) throws SAXException {
 621  8955
                         currentEntryName = attrs.getValue("name");
 622  8955
                         if (currentEntryName == null)
 623  1
                                 reportError("No name specified for " + currentEntryType + ".");
 624  8954
                         return currentEntryName;
 625  
                 }
 626  
 
 627  
                 private MetaModelElement getTypeName(Attributes attrs,
 628  
                                 String domAttribute) throws SAXException {
 629  8884
                         String typeName = attrs.getValue(domAttribute);
 630  8884
                         if (typeName == null)
 631  2
                                 reportError("No domain specified for " + currentEntryType
 632  1
                                                 + " '" + currentEntryName + "'.");
 633  8883
                         MetaModelElement type = metaModel.getType(typeName);
 634  8883
                         if (type == null)
 635  2
                                 reportError("Unknown domain '" + typeName + "' for "
 636  1
                                                 + currentEntryType + " '" + currentEntryName + "'.");
 637  8882
                         return type;
 638  
                 }
 639  
 
 640  
                 private String getOptionalAttribute(Attributes attrs, String attrName) {
 641  7909
                         String value = attrs.getValue(attrName);
 642  7909
                         if (value == null)
 643  7190
                                 return "";
 644  719
                         return value;
 645  
                 }
 646  
 
 647  
                 private boolean isAttributeValueTrue(Attributes attrs, String attrName) {
 648  9001
                         return "true".equals(attrs.getValue(attrName));
 649  
                 }
 650  
 
 651  
                 private void setLocation(MetricEntry entry) {
 652  8461
                         if (locator != null) {
 653  8461
                                 entry.setLocation(locator.getLineNumber());
 654  
                         }
 655  8461
                 }
 656  
 
 657  
                 private void setInheritability(MetricEntry entry, Attributes attrs) {
 658  8043
                         boolean inheritable = defaultInheritability;
 659  8043
                         if (attrs.getValue(INHERITABLE) != null) {
 660  366
                                 inheritable = isAttributeValueTrue(attrs, INHERITABLE);
 661  
                         }
 662  8043
                         entry.setInheritable(inheritable);
 663  8043
                 }
 664  
 
 665  315
                 private <T extends MetricEntry> void addEntry(Map<String, T> map,
 666  
                                 T entry) {
 667  8358
                         entry.setId(map.size());
 668  8358
                         map.put(entry.getName(), entry);
 669  8358
                 }
 670  
 
 671  
                 /* Process a set definition in the metric definition file. */
 672  
                 private Set handleSetDefinition(Attributes attrs) throws SAXException {
 673  1856
                         String name = getEntryName(attrs);
 674  1856
                         MetaModelElement type = getTypeName(attrs, "domain");
 675  
 
 676  1856
                         if (getSet(type, name) != null)
 677  2
                                 reportError("Duplicate definition of set '" + name
 678  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 679  
 
 680  1855
                         Set set = new Set(name, type);
 681  1855
                         setLocation(set);
 682  1855
                         setInheritability(set, attrs);
 683  
 
 684  1855
                         set.setMultiSet(isAttributeValueTrue(attrs, "multiset"));
 685  
 
 686  
                         // assign metric ID to the metric, and add it to the store
 687  1855
                         addEntry(sets.get(type), set);
 688  1855
                         return set;
 689  
                 }
 690  
 
 691  
                 /* Process a relation matrix definition. */
 692  
                 private Matrix handleMatrixDefinition(Attributes attrs)
 693  
                                 throws SAXException {
 694  418
                         String name = getEntryName(attrs);
 695  418
                         MetaModelElement rowType = getTypeName(attrs, "from_row_type");
 696  418
                         MetaModelElement columnType = getTypeName(attrs, "to_col_type");
 697  
 
 698  418
                         Matrix matrix = new Matrix(name, rowType, columnType);
 699  418
                         setLocation(matrix);
 700  
 
 701  418
                         matrix.setRowCondition(parseExpression(attrs, "row_condition"));
 702  417
                         matrix.setColumnCondition(parseExpression(attrs, "col_condition"));
 703  
 
 704  
                         // assign ID to the matrix, add it to the store
 705  417
                         matrix.setId(matrices.size());
 706  417
                         matrices.add(matrix);
 707  
 
 708  417
                         return matrix;
 709  
                 }
 710  
 
 711  
                 private ExpressionNode parseExpression(Attributes attrs, String attrName)
 712  
                                 throws SAXException {
 713  21663
                         ExpressionNode result = null;
 714  21663
                         String expr = attrs.getValue(attrName);
 715  21663
                         if (expr != null) {
 716  21129
                                 result = ep.parseExpression(expr);
 717  21129
                                 if (result == null)
 718  2
                                         reportError("Error parsing " + attrName + "='" + expr
 719  1
                                                         + "' for " + currentEntryType + " '"
 720  1
                                                         + currentEntryName + "':\n" + ep.getErrorInfo());
 721  
                         }
 722  21662
                         return result;
 723  
                 }
 724  
 
 725  
                 /* Process a rule definition in the metric definition file. */
 726  
                 private Rule handleRuleDefinition(Attributes attrs) throws SAXException {
 727  1722
                         String name = getEntryName(attrs);
 728  
 
 729  1722
                         MetaModelElement type = getTypeName(attrs, "domain");
 730  1722
                         if (getRule(type, name) != null)
 731  2
                                 reportError("Duplicate definition of rule '" + name
 732  1
                                                 + "' for elements of type '" + type.getName() + "'.");
 733  
 
 734  1721
                         String category = getOptionalAttribute(attrs, "category");
 735  1721
                         String severity = getOptionalAttribute(attrs, "severity");
 736  1721
                         boolean enabled = !isAttributeValueTrue(attrs, "disabled");
 737  
 
 738  1721
                         ArrayList<String> applicationList = null;
 739  1721
                         String appliesTo = attrs.getValue("applies_to");
 740  1721
                         if (appliesTo != null) {
 741  885
                                 applicationList = new ArrayList<String>();
 742  1770
                                 StringTokenizer st = new StringTokenizer(appliesTo,
 743  885
                                                 " ,\t\n\r\f");
 744  2871
                                 while (st.hasMoreTokens())
 745  1101
                                         applicationList.add(st.nextToken());
 746  885
                                 if (applicationList.isEmpty())
 747  33
                                         applicationList = null;
 748  
                         }
 749  
 
 750  3442
                         Rule rule = new Rule(name, type, category, severity,
 751  1721
                                         applicationList, enabled);
 752  1721
                         setLocation(rule);
 753  1721
                         setInheritability(rule, attrs);
 754  
 
 755  
                         // assign ID to the rule, add it to the store
 756  1721
                         addEntry(rules.get(type), rule);
 757  1721
                         return rule;
 758  
                 }
 759  
 
 760  
                 /** Process a literature reference in the metric definition file. */
 761  
                 private MetricEntry handleReferenceDefinition(Attributes attrs)
 762  
                                 throws SAXException {
 763  69
                         String tag = attrs.getValue("tag");
 764  69
                         if (tag == null)
 765  1
                                 reportError("Reference is missing its tag.");
 766  68
                         if (references.containsKey(tag))
 767  1
                                 reportError("Duplicate definition of reference '" + tag + "'.");
 768  67
                         Reference ref = new Reference(tag);
 769  67
                         references.put(tag, ref);
 770  67
                         return ref;
 771  
                 }
 772  
 
 773  
                 /* Process a glossary entry in the metric definition file. */
 774  
                 private Glossary handleGlossaryDefinition(Attributes attrs)
 775  
                                 throws SAXException {
 776  68
                         String name = getEntryName(attrs);
 777  68
                         if (glossary.containsKey(name))
 778  2
                                 reportError("Duplicate definition of glossary term '" + name
 779  1
                                                 + "'.");
 780  67
                         Glossary item = new Glossary(name);
 781  67
                         glossary.put(name, item);
 782  67
                         return item;
 783  
                 }
 784  
 
 785  
                 /* Process the definition of a word list in the metric definition file. */
 786  
                 private WordList handleWordListDefinition(Attributes attrs)
 787  
                                 throws SAXException {
 788  420
                         String listName = getEntryName(attrs);
 789  420
                         if (wordLists.containsKey(listName))
 790  2
                                 reportError("Duplicate definition of word list '" + listName
 791  1
                                                 + "'.");
 792  419
                         boolean caseSensitive = !isAttributeValueTrue(attrs, "ignorecase");
 793  419
                         WordList list = new WordList(caseSensitive);
 794  419
                         wordLists.put(listName, list);
 795  419
                         return list;
 796  
                 }
 797  
 
 798  
                 /* Process an entry in a word list. */
 799  
                 private void handleWordListEntry(Attributes attrs) throws SAXException {
 800  1439
                         if (currentWordList == null)
 801  1
                                 reportError("Wordlist entry outside of a word list.");
 802  1438
                         String word = attrs.getValue("word");
 803  1438
                         if (word == null)
 804  1
                                 reportError("Wordlist entry missing 'word' attribute.");
 805  1437
                         currentWordList.addWord(word);
 806  1437
                 }
 807  
 
 808  
                 /**
 809  
                  * Handles the computation procedure for the current metric, set,
 810  
                  * matrix, or rule. Compiles the expressions of the attributes and adds
 811  
                  * them to the computation procedure.
 812  
                  * 
 813  
                  * @param procedureName Name of the computation procedure
 814  
                  * @param attrs The attributes for the computation procedure.
 815  
                  * @throws SAXException There was an error parsing an attribute of the
 816  
                  *         computation procedure.
 817  
                  */
 818  
                 private void handleComputationDefinition(String procedureName,
 819  
                                 Attributes attrs) throws SAXException {
 820  8461
                         if (inDescription)
 821  2
                                 reportError("Unexpected XML element '" + procedureName
 822  1
                                                 + "' in description.");
 823  
 
 824  8460
                         if (currentEntry == null)
 825  2
                                 reportError("Unexpected XML element '" + procedureName
 826  1
                                                 + "' at this point.");
 827  
 
 828  8459
                         if (currentEntry.getAttributes() != null)
 829  2
                                 reportError("Unexpected XML element '" + procedureName + "' - "
 830  1
                                                 + currentEntryType + " '" + currentEntryName
 831  1
                                                 + "' already has a calculation procedure.");
 832  8458
                         currentEntry.setProcedureName(procedureName);
 833  
 
 834  8458
                         ProcedureAttributes attributes = new ProcedureAttributes();
 835  8458
                         int attnum = attrs.getLength();
 836  29286
                         for (int i = 0; i < attnum; i++) {
 837  
                                 // Compile expression and store the operator tree.
 838  20828
                                 String attrName = attrs.getQName(i);
 839  20828
                                 ExpressionNode root = parseExpression(attrs, attrName);
 840  
                                 // check for old "_exp" suffix from versions 1.2 and earlier,
 841  
                                 // remove it from the attribute name to maintain backwards
 842  
                                 // compatibility
 843  20828
                                 if (attrName.endsWith("_exp"))
 844  33
                                         attrName = attrName.substring(0, attrName.length() - 4);
 845  20828
                                 attributes.setExpressionNode(attrName, root);
 846  
                         }
 847  8458
                         currentEntry.setAttributes(attributes);
 848  8458
                 }
 849  
 
 850  
                 /**
 851  
                  * Handles the definition of a custom defined metric, set, or rule
 852  
                  * procedure. Makes sure the procedure class can be loaded and
 853  
                  * instantiated.
 854  
                  * 
 855  
                  * @param store The applicable procedure store
 856  
                  * @param element the name of the XML element defining the procedure
 857  
                  * @param attrs the XML attributes of the element
 858  
                  * @return Name of the procedure
 859  
                  * @throws SAXException Missing attributes or procedure class could not
 860  
                  *         be loaded
 861  
                  */
 862  
                 private String handleProcedureDefinition(ProcedureCache<?> store,
 863  
                                 String element, Attributes attrs) throws SAXException {
 864  905
                         String procedureName = attrs.getValue("name");
 865  905
                         if (procedureName == null)
 866  2
                                 reportError("The " + element
 867  1
                                                 + " definition requires a 'name' attribute.");
 868  
 
 869  904
                         if (store.hasProcedure(procedureName))
 870  2
                                 reportError("The " + element + " '" + procedureName
 871  1
                                                 + "' is already defined.");
 872  
 
 873  903
                         String className = attrs.getValue("class");
 874  903
                         if (className == null)
 875  2
                                 reportError("No class defined for " + element + " definition '"
 876  1
                                                 + procedureName + "'.");
 877  
 
 878  
                         try {
 879  902
                                 store.addProcedureClass(procedureName, className);
 880  901
                         } catch (SDMetricsException ex) {
 881  1
                                 reportError(ex.getMessage());
 882  
                         }
 883  901
                         return procedureName;
 884  
                 }
 885  
         }
 886  
 }