Coverage Report - com.sdmetrics.metrics.MetricsEngine - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
MetricsEngine
98%
126/128
98%
49/50
4,444
MetricsEngine$MetricValuesCache
100%
25/25
100%
8/8
4,444
 
 1  
 /*
 2  
  * SDMetrics Open Core for UML design measurement
 3  
  * Copyright (c) Juergen Wuest
 4  
  * To contact the author, see <http://www.sdmetrics.com/Contact.html>.
 5  
  * 
 6  
  * This file is part of the SDMetrics Open Core.
 7  
  * 
 8  
  * SDMetrics Open Core is free software: you can redistribute it and/or modify
 9  
  * it under the terms of the GNU Affero General Public License as
 10  
  * published by the Free Software Foundation, either version 3 of the
 11  
  * License, or (at your option) any later version.
 12  
     
 13  
  * SDMetrics Open Core is distributed in the hope that it will be useful,
 14  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16  
  * GNU Affero General Public License for more details.
 17  
  *
 18  
  * You should have received a copy of the GNU Affero General Public License
 19  
  * along with SDMetrics Open Core.  If not, see <http://www.gnu.org/licenses/>.
 20  
  *
 21  
  */
 22  
 package com.sdmetrics.metrics;
 23  
 
 24  
 import java.util.Collection;
 25  
 import java.util.Collections;
 26  
 import java.util.HashMap;
 27  
 
 28  
 import com.sdmetrics.math.ExpressionNode;
 29  
 import com.sdmetrics.model.MetaModel;
 30  
 import com.sdmetrics.model.Model;
 31  
 import com.sdmetrics.model.ModelElement;
 32  
 
 33  
 /**
 34  
  * Calculates metrics and sets.
 35  
  * 
 36  
  * The metrics engine offers methods to retrieve a metric value (
 37  
  * {@link #getMetricValue getMetricValue()}) or set ({@link #getSet getSet()})
 38  
  * for a particular model element.
 39  
  * <p>
 40  
  * 
 41  
  * The engine uses a "lazy calculation" strategy: if a set or metric for a model
 42  
  * element is requested for the first time, the set or metric is calculated,
 43  
  * stored in a cache, and returned to the caller. On subsequent requests for the
 44  
  * same set or metric, the value is simply retrieved from the cache.
 45  
  */
 46  
 
 47  
 public class MetricsEngine {
 48  
         /** The definitions of the metrics and sets. */
 49  319
         private MetricStore metrics;
 50  
         /** The model with the elements for which to calculate metrics. */
 51  
         private Model model;
 52  
         /** Cache to store all measurement values. */
 53  
         private MetricValuesCache metricCache;
 54  
         /** Cache to keep the metric calculation procedures for reuse. */
 55  
         private MetricProcedureCache metricProcedures;
 56  
         /** Cache to keep the set calculation procedures for reuse. */
 57  
         private SetProcedureCache setProcedures;
 58  
 
 59  
         /**
 60  
          * Initializes a new metrics engine.
 61  
          * 
 62  
          * @param metrics The definitions of the sets and metrics to calculate.
 63  
          * @param model The model on which to operate. Must use the same metamodel
 64  
          *        as the metric definitions.
 65  
          */
 66  119
         public MetricsEngine(MetricStore metrics, Model model) {
 67  119
                 if (metrics.getMetaModel() != model.getMetaModel())
 68  2
                         throw new IllegalArgumentException(
 69  1
                                         "Metamodel of metrics and elements do no match.");
 70  
 
 71  118
                 this.metrics = metrics;
 72  118
                 this.model = model;
 73  118
                 this.metricProcedures = metrics.getMetricProcedures();
 74  118
                 this.setProcedures = metrics.getSetProcedures();
 75  118
                 this.metricCache = new MetricValuesCache();
 76  118
         }
 77  
 
 78  
         /**
 79  
          * Retrieves the metamodel on which this metrics engine is based.
 80  
          * 
 81  
          * @return Metamodel of this engine.
 82  
          */
 83  
         public MetaModel getMetaModel() {
 84  1336
                 return metrics.getMetaModel();
 85  
         }
 86  
 
 87  
         /**
 88  
          * Retrieves the metric definitions used by this metrics engine.
 89  
          * 
 90  
          * @return Metric definitions of this engine.
 91  
          */
 92  
         public MetricStore getMetricStore() {
 93  136
                 return metrics;
 94  
         }
 95  
 
 96  
         /**
 97  
          * Retrieves the model on which this metrics engine operates.
 98  
          * 
 99  
          * @return Model of this engine.
 100  
          */
 101  
         public Model getModel() {
 102  9
                 return model;
 103  
         }
 104  
 
 105  
         /**
 106  
          * Retrieves the value of a metric for a model element.
 107  
          * <p>
 108  
          * Returns the cached value if the metric has been calculated before for the
 109  
          * element. Otherwise, the value is calculated and cached.
 110  
          * 
 111  
          * @param element Model element to retrieve the metric value for.
 112  
          * @param metric The metric to retrieve. Must be taken from the metric store
 113  
          *        of this engine.
 114  
          * @return Value of the metric for the specified element.
 115  
          * @throws SDMetricsException An error occurred during the metric
 116  
          *         calculation.
 117  
          */
 118  
         public Object getMetricValue(ModelElement element, Metric metric)
 119  
                         throws SDMetricsException {
 120  
                 // Check if the metric value has already been calculated.
 121  194
                 Object value = metricCache.getMetricValue(element, metric);
 122  194
                 if (value != null)
 123  23
                         return value;
 124  
 
 125  
                 // preliminarily set the metric value to 0 in the cache, to preempt
 126  
                 // any infinite recursion.
 127  171
                 metricCache.setMetricValue(element, metric, MetricTools.ZERO);
 128  
 
 129  
                 // calculate the metric value.
 130  
                 try {
 131  
                         // Obtain the procedure to calculate the metrics
 132  171
                         String procedureName = metric.getProcedureName();
 133  342
                         MetricProcedure procedure = metricProcedures
 134  171
                                         .getProcedure(procedureName);
 135  171
                         procedure.setMetricsEngine(this);
 136  
 
 137  
                         // Perform the calculation and return the procedure for reuse
 138  171
                         value = procedure.calculate(element, metric);
 139  167
                         metricProcedures.returnProcedure(procedure);
 140  167
                 } catch (SDMetricsException ex) {
 141  3
                         ex.fillInPerpetrators(element, metric);
 142  3
                         throw ex;
 143  1
                 } catch (RuntimeException ex) {
 144  
                         // wrap exceptions in an SDMetricsException so we know
 145  
                         // what metric/element is to blame
 146  1
                         throw new SDMetricsException(element, metric, ex);
 147  
                 }
 148  
 
 149  
                 // Cache and return the final metric value.
 150  167
                 metricCache.setMetricValue(element, metric, value);
 151  167
                 return value;
 152  
         }
 153  
 
 154  
         /**
 155  
          * Retrieves the contents of a set for a model element.
 156  
          * <p>
 157  
          * Returns the cached set if the set has been calculated before for the
 158  
          * element. Otherwise, the set is calculated and cached.
 159  
          * <p>
 160  
          * Sets typically contain either model elements, numbers (mix of instances
 161  
          * of Integer and Float), or strings.
 162  
          * 
 163  
          * @param element Model element to retrieve the set for.
 164  
          * @param set The set to retrieve. Must be taken from the metric store of
 165  
          *        this engine.
 166  
          * @return Contents of the set for the specified element.
 167  
          * @throws SDMetricsException An error occurred during the metric
 168  
          *         calculation.
 169  
          */
 170  
         public Collection<?> getSet(ModelElement element, Set set)
 171  
                         throws SDMetricsException {
 172  
                 // Check if the set has already been calculated.
 173  200
                 Collection<?> result = metricCache.getSet(element, set);
 174  200
                 if (result != null)
 175  7
                         return result;
 176  
 
 177  
                 // preliminarily set the contents to be the empty set to preempt
 178  
                 // any infinite recursion
 179  193
                 metricCache.setSet(element, set, Collections.EMPTY_SET);
 180  
 
 181  
                 try {
 182  193
                         result = computeSet(element, set);
 183  191
                 } catch (SDMetricsException ex) {
 184  1
                         ex.fillInPerpetrators(element, set);
 185  1
                         throw ex;
 186  1
                 } catch (RuntimeException ex) {
 187  
                         // wrap exceptions in an SDMetricsException so we know
 188  
                         // what set/element is to blame
 189  1
                         throw new SDMetricsException(element, set, ex);
 190  
                 }
 191  191
                 metricCache.setSet(element, set, result);
 192  191
                 return result;
 193  
         }
 194  
 
 195  
         /**
 196  
          * Calculates a set. Does not use the cache. The set definition need not be
 197  
          * taken from the metric store of the engine.
 198  
          * 
 199  
          * @param element Model element to calculate the set for.
 200  
          * @param set Metric object with the set definition to calculate.
 201  
          * @return The contents of the resulting set.
 202  
          * @throws SDMetricsException if an error occurred during calculation of the
 203  
          *         set.
 204  
          */
 205  
         Collection<?> computeSet(ModelElement element, Set set)
 206  
                         throws SDMetricsException {
 207  
                 // Obtain the procedure to calculate the set
 208  228
                 String procedureName = set.getProcedureName();
 209  228
                 SetProcedure procedure = setProcedures.getProcedure(procedureName);
 210  
                 // Perform the calculation, return the procedure for reuse
 211  228
                 procedure.setMetricsEngine(this);
 212  228
                 Collection<?> result = procedure.calculate(element, set);
 213  226
                 setProcedures.returnProcedure(procedure);
 214  226
                 return result;
 215  
         }
 216  
 
 217  
         /**
 218  
          * Evaluates a metric expression. Metric expressions return a scalar value
 219  
          * (a number, string, or model element).
 220  
          * 
 221  
          * @param element The model element for which to calculate the metric
 222  
          *        expression.
 223  
          * @param node Root node of the metric expression operator tree.
 224  
          * @param vars Variables for the expression evaluation
 225  
          * @return The result of the metric expression.
 226  
          * @throws SDMetricsException An error occurred evaluating the expression.
 227  
          */
 228  
         Object evalExpression(ModelElement element, ExpressionNode node,
 229  
                         Variables vars) throws SDMetricsException {
 230  
                 // Check the node type (operation, identifier, or constant), and act
 231  
                 // accordingly.
 232  3330
                 if (node.isOperation())
 233  1214
                         return evalOperationExpression(element, node, vars);
 234  
 
 235  2116
                 if (node.isNumberConstant())
 236  334
                         return Float.valueOf(node.getValue());
 237  
 
 238  1782
                 if (node.isStringConstant())
 239  139
                         return node.getValue();
 240  
 
 241  1643
                 if (node.isIdentifier())
 242  1643
                         return evalIdentifier(element, node.getValue(), vars);
 243  
 
 244  0
                 throw new SDMetricsException(element, null,
 245  0
                                 "Unknown expression node type.");
 246  
         }
 247  
 
 248  
         /**
 249  
          * Evaluate a metric expression that returns a model element.
 250  
          * 
 251  
          * @param element The model element for which to calculate the metric
 252  
          *        expression.
 253  
          * @param node Root node of the metric expression operator tree.
 254  
          * @param vars Variables for the expression evaluation
 255  
          * @return The resulting model element, or <code>null</code> if the
 256  
          *         expression did not produce a model element.
 257  
          * @throws SDMetricsException An error occurred evaluating the metric
 258  
          *         expression.
 259  
          */
 260  
         ModelElement evalModelElementExpression(ModelElement element,
 261  
                         ExpressionNode node, Variables vars) throws SDMetricsException {
 262  148
                 Object obj = evalExpression(element, node, vars);
 263  148
                 if (!(obj instanceof ModelElement))
 264  11
                         return null;
 265  137
                 return (ModelElement) obj;
 266  
         }
 267  
 
 268  
         /**
 269  
          * Evaluates a condition expression. Condition expressions return a boolean
 270  
          * value <code>true</code> or <code>false</code>.
 271  
          * 
 272  
          * @param element The model element for which to calculate the condition
 273  
          *        expression.
 274  
          * @param node Root node of the condition expression operator tree.
 275  
          * @param vars Variables for the expression evaluation
 276  
          * @return The result of the condition expression.
 277  
          * @throws SDMetricsException An error occurred evaluating the expression.
 278  
          */
 279  
         boolean evalBooleanExpression(ModelElement element, ExpressionNode node,
 280  
                         Variables vars) throws SDMetricsException {
 281  481
                 String operator = node.getValue();
 282  962
                 ProcedureCache<BooleanOperation> scops = metrics
 283  481
                                 .getExpressionOperations().getBooleanOperations();
 284  
 
 285  481
                 BooleanOperation op = scops.getProcedure(operator);
 286  480
                 op.setMetricsEngine(this);
 287  480
                 boolean result = op.calculateValue(element, node, vars);
 288  477
                 scops.returnProcedure(op);
 289  477
                 return result;
 290  
         }
 291  
 
 292  
         /**
 293  
          * Evaluates a scalar operator in a metric expression. Scalar operators
 294  
          * return numbers, strings, or model elements.
 295  
          * 
 296  
          * @param element The model element for which to calculate the operation.
 297  
          * @param node Node with the operator.
 298  
          * @param vars Variables for the expression evaluation
 299  
          * @return The result of the operation.
 300  
          * @throws SDMetricsException An error occurred evaluating the operation.
 301  
          */
 302  
         private Object evalOperationExpression(ModelElement element,
 303  
                         ExpressionNode node, Variables vars) throws SDMetricsException {
 304  
 
 305  1214
                 String operator = node.getValue();
 306  2428
                 ProcedureCache<ScalarOperation> scops = metrics
 307  1214
                                 .getExpressionOperations().getScalarOperations();
 308  
 
 309  1214
                 ScalarOperation op = scops.getProcedure(operator);
 310  1212
                 op.setMetricsEngine(this);
 311  1212
                 Object result = op.calculateValue(element, node, vars);
 312  1206
                 scops.returnProcedure(op);
 313  1206
                 return result;
 314  
         }
 315  
 
 316  
         /**
 317  
          * Evaluates an identifier in a metric expression.
 318  
          * <p>
 319  
          * The identifier may refer to a metric, an attribute, or a variable
 320  
          * (including the implicit variable _self). The type of identifier is
 321  
          * checked in that order.
 322  
          * 
 323  
          * @param element The model element for which to evaluate the identifier.
 324  
          * @param identifier The identifier.
 325  
          * @param vars Variables for the expression evaluation
 326  
          * @return The value of the identifier.
 327  
          * @throws SDMetricsException The identifier could not be resolved.
 328  
          */
 329  
         private Object evalIdentifier(ModelElement element, String identifier,
 330  
                         Variables vars) throws SDMetricsException {
 331  
                 // check if identifier is a metric
 332  1643
                 Metric metric = metrics.getMetric(element.getType(), identifier);
 333  1643
                 if (metric != null)
 334  118
                         return getMetricValue(element, metric);
 335  
 
 336  
                 // check if identifier is a single-valued attribute
 337  1525
                 if (element.getType().hasAttribute(identifier)) {
 338  791
                         if (!element.getType().isSetAttribute(identifier)) {
 339  790
                                 if (element.getType().isRefAttribute(identifier)) {
 340  163
                                         ModelElement ref = element.getRefAttribute(identifier);
 341  163
                                         if (ref == null)
 342  14
                                                 return ""; // no element referenced -> empty string
 343  149
                                         return ref;
 344  
                                 }
 345  
                                 // plain data attribute, return its value
 346  627
                                 return element.getPlainAttribute(identifier);
 347  
                         }
 348  
                 }
 349  
 
 350  
                 // _self returns the current model element; also accept the
 351  
                 // deprecated "self"
 352  735
                 if ("_self".equals(identifier) || "self".equals(identifier))
 353  95
                         return element;
 354  
 
 355  
                 // check variables in the value map
 356  640
                 if (vars != null)
 357  636
                         if (vars.hasVariable(identifier))
 358  635
                                 return vars.getVariable(identifier);
 359  
 
 360  10
                 throw new SDMetricsException(element, null,
 361  10
                                 "No metric or single-valued attribute '" + identifier
 362  5
                                                 + "' for elements of type '"
 363  5
                                                 + element.getType().getName() + "'.");
 364  
         }
 365  
 
 366  
         /**
 367  
          * Evaluates a set expression. Set expressions return sets.
 368  
          * 
 369  
          * @param element The model element for which to calculate the set
 370  
          *        expression.
 371  
          * @param node Root node of the set expression operator tree.
 372  
          * @param vars Variables for the expression evaluation
 373  
          * @return The contents of the resulting set.
 374  
          * @throws SDMetricsException An error occurred evaluating the set
 375  
          *         expression.
 376  
          */
 377  
         Collection<?> evalSetExpression(ModelElement element, ExpressionNode node,
 378  
                         Variables vars) throws SDMetricsException {
 379  
 
 380  
                 // The root of a set expression must be an operator or identifier
 381  
                 // accordingly.
 382  204
                 if (node.isIdentifier())
 383  186
                         return evalSetIdentifier(element, node.getValue(), vars);
 384  
 
 385  
                 // Make sure we have a binary operator
 386  18
                 if (!node.isOperation())
 387  2
                         throw new SDMetricsException(element, null,
 388  1
                                         "Illegal set expression.");
 389  
 
 390  
                 // Process the operator
 391  17
                 String operator = node.getValue();
 392  34
                 ProcedureCache<SetOperation> scops = metrics.getExpressionOperations()
 393  17
                                 .getSetOperations();
 394  17
                 SetOperation op = scops.getProcedure(operator);
 395  15
                 op.setMetricsEngine(this);
 396  15
                 Collection<?> result = op.calculateValue(element, node, vars);
 397  15
                 scops.returnProcedure(op);
 398  15
                 return result;
 399  
         }
 400  
 
 401  
         /**
 402  
          * Evaluates an identifier in a set expression.
 403  
          * 
 404  
          * @param element The model element for which to evaluate the set
 405  
          *        identifier.
 406  
          * @param identifier The set identifier.
 407  
          * @param vars Variables for the expression evaluation
 408  
          * @return The the set specified by the identifier.
 409  
          * @throws SDMetricsException The identifier could not be resolved.
 410  
          */
 411  
         private Collection<?> evalSetIdentifier(ModelElement element,
 412  
                         String identifier, Variables vars) throws SDMetricsException {
 413  
                 // Check if there is a set of that name for the element
 414  186
                 Set set = metrics.getSet(element.getType(), identifier);
 415  186
                 if (set != null)
 416  143
                         return getSet(element, set);
 417  
 
 418  
                 // Check multi-valued attributes of the element
 419  43
                 if (element.getType().hasAttribute(identifier)) {
 420  2
                         if (element.getType().isSetAttribute(identifier)) {
 421  1
                                 return element.getSetAttribute(identifier);
 422  
                         }
 423  
                 }
 424  
 
 425  
                 // Check the variables
 426  42
                 if (vars != null) {
 427  40
                         Object o = vars.getVariable(identifier);
 428  40
                         if (o != null) {
 429  39
                                 if (o instanceof Collection)
 430  38
                                         return (Collection<?>) o;
 431  
 
 432  2
                                 throw new SDMetricsException(element, null, "Variable '"
 433  1
                                                 + identifier + "' is not a set.");
 434  
                         }
 435  
                 }
 436  
 
 437  6
                 throw new SDMetricsException(element, null, "Unknown set identifier '"
 438  3
                                 + identifier + "' for elements of type '"
 439  3
                                 + element.getType().getName() + "'.");
 440  
         }
 441  
 
 442  
         /** Cache to store all measurement values. */
 443  118
         class MetricValuesCache {
 444  
                 /** Metric value cache. */
 445  118
                 private final HashMap<ModelElement, Object[]> metricValues = new HashMap<ModelElement, Object[]>();
 446  
                 /** Set value cache. */
 447  118
                 private final HashMap<ModelElement, Collection<?>[]> setValues = new HashMap<ModelElement, Collection<?>[]>();
 448  
 
 449  
                 /**
 450  
                  * Sets the value for a metric of an element.
 451  
                  * 
 452  
                  * @param element the model element
 453  
                  * @param metric the metric
 454  
                  * @param value the value of the metric
 455  
                  */
 456  
                 void setMetricValue(ModelElement element, Metric metric, Object value) {
 457  338
                         Object[] values = metricValues.get(element);
 458  338
                         if (values == null) {
 459  264
                                 values = new Object[metrics.getMetrics(element.getType())
 460  132
                                                 .size()];
 461  132
                                 metricValues.put(element, values);
 462  
                         }
 463  338
                         values[metric.getID()] = value;
 464  338
                 }
 465  
 
 466  
                 /**
 467  
                  * Sets the value of a set for a model element.
 468  
                  * 
 469  
                  * @param element the model element
 470  
                  * @param set the set
 471  
                  * @param value the value of the metric
 472  
                  */
 473  
                 void setSet(ModelElement element, Set set, Collection<?> value) {
 474  384
                         Collection<?>[] collections = setValues.get(element);
 475  384
                         if (collections == null) {
 476  374
                                 collections = new Collection<?>[metrics.getSets(element.getType())
 477  187
                                                 .size()];
 478  187
                                 setValues.put(element, collections);
 479  
                         }
 480  384
                         collections[set.getID()] = value;
 481  384
                 }
 482  
 
 483  
                 /**
 484  
                  * Retrieves the value of a metric for a model element.
 485  
                  * 
 486  
                  * @param element the model element
 487  
                  * @param metric the metric
 488  
                  * @return Value of the metric, <code>null</code> if the value has not
 489  
                  *         yet been cached
 490  
                  */
 491  
                 Object getMetricValue(ModelElement element, Metric metric) {
 492  194
                         Object[] result = metricValues.get(element);
 493  194
                         if (result != null) {
 494  62
                                 return result[metric.getID()];
 495  
                         }
 496  132
                         return null;
 497  
                 }
 498  
 
 499  
                 /**
 500  
                  * Retrieves the value of a set for a model element.
 501  
                  * 
 502  
                  * @param element the model element
 503  
                  * @param set the set
 504  
                  * @return Value of the set, <code>null</code> if the value has not yet
 505  
                  *         been cached
 506  
                  */
 507  
                 Collection<?> getSet(ModelElement element, Set set) {
 508  200
                         Collection<?>[] result = setValues.get(element);
 509  200
                         if (result != null) {
 510  13
                                 return result[set.getID()];
 511  
                         }
 512  187
                         return null;
 513  
                 }
 514  
         }
 515  
 }