Coverage Report - com.sdmetrics.metrics.FilterAttributeProcessor - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
FilterAttributeProcessor
98%
94/95
98%
104/106
5,933
FilterAttributeProcessor$1
100%
5/5
N/A
5,933
FilterAttributeProcessor$2
100%
5/5
N/A
5,933
FilterAttributeProcessor$FilteringIterator
94%
35/37
100%
16/16
5,933
 
 1  176
 /*
 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.Iterator;
 26  
 import java.util.NoSuchElementException;
 27  
 
 28  
 import com.sdmetrics.math.ExpressionNode;
 29  
 import com.sdmetrics.model.MetaModelElement;
 30  
 import com.sdmetrics.model.ModelElement;
 31  
 
 32  
 /**
 33  
  * Processes the standard filter attributes for individual model elements or
 34  
  * entire sets. The filter attributes are the attributes "target",
 35  
  * "targetcondition", "element", "eltype", "condition", and "scope".
 36  
  */
 37  
 public class FilterAttributeProcessor {
 38  
         // The names of the filter attributes
 39  
         private static final String ATTR_TARGET = "target";
 40  
         private static final String ATTR_TARGETCOND = "targetcondition";
 41  
         private static final String ATTR_ELEMENT = "element";
 42  
         private static final String ATTR_ELTYPE = "eltype";
 43  
         private static final String ATTR_CONDEXP = "condition";
 44  
         private static final String ATTR_SCOPE = "scope";
 45  
 
 46  
         // The admissible values for the 'scope' attribute
 47  
         private static final String IDEM = "idem";
 48  
         private static final String NOTIDEM = "notidem";
 49  
         private static final String CONTAINEDIN = "containedin";
 50  
         private static final String NOTCONTAINEDIN = "notcontainedin";
 51  
         private static final String SAME = "same";
 52  
         private static final String OTHER = "other";
 53  
         private static final String HIGHER = "higher";
 54  
         private static final String LOWER = "lower";
 55  
         private static final String SAMEORHIGHER = "sameorhigher";
 56  
         private static final String SAMEORLOWER = "sameorlower";
 57  
         private static final String NOTHIGHER = "nothigher";
 58  
         private static final String NOTLOWER = "notlower";
 59  
         private static final String SAMEBRANCH = "samebranch";
 60  
         private static final String NOTSAMEBRANCH = "notsamebranch";
 61  
 
 62  
         /** The metrics engine for all calculations. */
 63  
         private final MetricsEngine engine;
 64  
         /** Expression of the "target" attribute. */
 65  
         private final ExpressionNode targetExpr;
 66  
         /** Expression of the "targetCondition". */
 67  
         private final ExpressionNode targetConditionExpr;
 68  
         /** Expression of the "element" attribute. */
 69  
         private final ExpressionNode elementExpr;
 70  
         /** Expression of the "eltype" attribute. */
 71  
         private final ExpressionNode eltypeExprt;
 72  
         /** Expression of the "condition" attribute. */
 73  
         private final ExpressionNode conditionExpr;
 74  
         /** Value of the "scope" attribute. */
 75  
         private final String scope;
 76  
         /**
 77  
          * The validity of the most recent element to which the filter attributes
 78  
          * were applied.
 79  
          */
 80  
         private boolean valid;
 81  
 
 82  
         /**
 83  
          * Constructor.
 84  
          * @param engine Metric engine to evaluate the filter expressions
 85  
          * @param attributes metric or set calculation procedure definition with the
 86  
          *        filter attributes to apply.
 87  
          * @throws SDMetricsException The "scope" attribute is set but does not
 88  
          *         contain a string.
 89  
          */
 90  282
         public FilterAttributeProcessor(MetricsEngine engine,
 91  
                         ProcedureAttributes attributes) throws SDMetricsException {
 92  282
                 this.engine = engine;
 93  282
                 targetExpr = attributes.getExpression(ATTR_TARGET);
 94  282
                 targetConditionExpr = attributes.getExpression(ATTR_TARGETCOND);
 95  282
                 elementExpr = attributes.getExpression(ATTR_ELEMENT);
 96  282
                 eltypeExprt = attributes.getExpression(ATTR_ELTYPE);
 97  282
                 conditionExpr = attributes.getExpression(ATTR_CONDEXP);
 98  282
                 scope = attributes.getStringValue(ATTR_SCOPE);
 99  282
         }
 100  
 
 101  
         /**
 102  
          * Applies the filter attributes to a candidate element.
 103  
          * <p>
 104  
          * Returns the element produced by the "element" filter attribute, or
 105  
          * <code>null</code> if the "element" filter attribute yields no model
 106  
          * element. If the "element" filter attribute is not set, the candidate
 107  
          * element itself is returned.
 108  
          * <p>
 109  
          * In either case, after the call to this method, method {@link #isValid()}
 110  
          * indicates if the returned element satisfies the conditions of the other
 111  
          * filter attributes.
 112  
          * 
 113  
          * @param principal The model element for which the metric or set is
 114  
          *        calculated.
 115  
          * @param candidate The candidate model element.
 116  
          * @param vars Variables for the evaluation of the condition expressions
 117  
          * @return The result from applying the "element" filter attribute, if
 118  
          *         specified, otherwise the candidate model element.
 119  
          * @throws SDMetricsException An error occurred evaluating one of the filter
 120  
          *         attributes.
 121  
          */
 122  
         public ModelElement applyFilters(ModelElement principal,
 123  
                         ModelElement candidate, Variables vars) throws SDMetricsException {
 124  
                 // process "target" attribute
 125  1386
                 valid = checkType(candidate.getType(), targetExpr);
 126  
 
 127  
                 // check "targetCondition"
 128  1385
                 if (valid && targetConditionExpr != null) {
 129  4
                         valid = engine.evalBooleanExpression(candidate,
 130  2
                                         targetConditionExpr, vars);
 131  
                 }
 132  
 
 133  1385
                 ModelElement result = candidate;
 134  
                 // process "element" and "eltype" attributes
 135  1385
                 if (valid && elementExpr != null) {
 136  280
                         result = engine.evalModelElementExpression(candidate, elementExpr,
 137  140
                                         vars);
 138  140
                         if (result == null) {
 139  9
                                 return null;
 140  
                         }
 141  
 
 142  131
                         valid = checkType(result.getType(), eltypeExprt);
 143  
                 }
 144  
 
 145  1376
                 if (valid && conditionExpr != null) {
 146  24
                         valid = engine.evalBooleanExpression(result, conditionExpr, vars);
 147  
                 }
 148  
 
 149  1376
                 if (valid && scope != null) {
 150  34
                         valid = checkScope(principal, result);
 151  
                 }
 152  
 
 153  1375
                 return result;
 154  
         }
 155  
 
 156  
         /**
 157  
          * Tests if the resulting model element from the most recent application of
 158  
          * filter attributes fulfills all filter conditions.
 159  
          * <p>
 160  
          * Filters are applied by calling method {@link #applyFilters} or methods
 161  
          * hasNext() and next() on the iterators produced by methods
 162  
          * {@link #fullIteration} or {@link #validIteration(Collection, Variables)}.
 163  
          * 
 164  
          * @return <code>true</code> if the model element returned by the most
 165  
          *         recent filter application fulfills all filter attribute
 166  
          *         conditions.
 167  
          */
 168  
         public boolean isValid() {
 169  1374
                 return valid;
 170  
         }
 171  
 
 172  
         /**
 173  
          * Evaluates element filter attributes "target" and "eltype".
 174  
          * <p>
 175  
          * These filter attributes contain an expression of the form
 176  
          * [+]type_1[|[+]type_2|...|[+]type_n] that lists the name of all admissible
 177  
          * element types. For element types prefixed by a "+", the type itself or
 178  
          * any of its subtypes are admissible.
 179  
          * 
 180  
          * @param type metamodel element type to check
 181  
          * @param typeTree Operator tree of the "target" or "eltype" expression.
 182  
          * @return <code>true</code> if the typeTree is <code>null</code>
 183  
          *         ("empty tree", admits all model element types), or the tree
 184  
          *         contains the specified element type. <code>false</code> if the
 185  
          *         typeTree is not empty and does not contain the specified type.
 186  
          * @throws SDMetricsException typeTree contains an unknown model element
 187  
          *         type
 188  
          */
 189  
         private boolean checkType(MetaModelElement type, ExpressionNode typeTree)
 190  
                         throws SDMetricsException {
 191  1528
                 if (typeTree == null) {
 192  199
                         return true;
 193  
                 }
 194  
 
 195  1332
                 boolean isPlusOperator = typeTree.isOperation()
 196  10
                                 && "+".equals(typeTree.getValue())
 197  3
                                 && typeTree.getOperandCount() == 1;
 198  
 
 199  1329
                 if (typeTree.isOperation() && !isPlusOperator) {
 200  7
                         if (checkType(type, typeTree.getLeftNode())) {
 201  2
                                 return true;
 202  
                         }
 203  5
                         if (typeTree.getRightNode() != null) {
 204  4
                                 return checkType(type, typeTree.getRightNode());
 205  
                         }
 206  1
                         return false;
 207  
                 }
 208  
 
 209  1322
                 ExpressionNode typeNameNode = typeTree;
 210  1322
                 if (isPlusOperator) {
 211  3
                         typeNameNode = typeTree.getLeftNode();
 212  
                 }
 213  
 
 214  2644
                 MetaModelElement candidate = engine.getMetaModel().getType(
 215  1322
                                 typeNameNode.getValue());
 216  1322
                 if (candidate == null) {
 217  2
                         throw new SDMetricsException(null, null,
 218  2
                                         "Unknown model element type '" + typeNameNode.getValue()
 219  1
                                                         + "'.");
 220  
                 }
 221  
 
 222  1321
                 if (isPlusOperator) {
 223  2
                         return type.specializes(candidate);
 224  
                 }
 225  1319
                 return type == candidate;
 226  
         }
 227  
 
 228  
         /**
 229  
          * Evaluates filter attribute "scope".
 230  
          * 
 231  
          * @param principal The model element for which the set or metric is
 232  
          *        calculated.
 233  
          * @param candidate The model element whose scope to test.
 234  
          * @return <code>true</code> if the scope condition is satisfied
 235  
          * @throws SDMetricsException scope attribute value is invalid
 236  
          */
 237  
         private boolean checkScope(ModelElement principal, ModelElement canidate)
 238  
                         throws SDMetricsException {
 239  34
                 if (scope.equals(IDEM)) {
 240  2
                         return (principal == canidate);
 241  
                 }
 242  32
                 if (scope.equals(NOTIDEM)) {
 243  2
                         return !(principal == canidate);
 244  
                 }
 245  30
                 if (scope.equals(CONTAINEDIN)) {
 246  3
                         return contains(principal, canidate);
 247  
                 }
 248  27
                 if (scope.equals(NOTCONTAINEDIN)) {
 249  2
                         return !contains(principal, canidate);
 250  
                 }
 251  
 
 252  25
                 ModelElement parent1 = principal.getOwner();
 253  25
                 ModelElement parent2 = canidate.getOwner();
 254  
 
 255  25
                 boolean same = (parent1 == parent2);
 256  25
                 if (scope.equals(SAME)) {
 257  2
                         return same;
 258  
                 }
 259  23
                 if (scope.equals(OTHER)) {
 260  2
                         return !same;
 261  
                 }
 262  
 
 263  21
                 boolean lower = contains(parent1, parent2);
 264  21
                 if (scope.equals(LOWER)) {
 265  2
                         return lower;
 266  
                 }
 267  19
                 if (scope.equals(NOTLOWER)) {
 268  2
                         return !lower;
 269  
                 }
 270  17
                 if (scope.equals(SAMEORLOWER)) {
 271  3
                         return (same || lower);
 272  
                 }
 273  
 
 274  14
                 boolean higher = contains(parent2, parent1);
 275  14
                 if (scope.equals(HIGHER)) {
 276  2
                         return higher;
 277  
                 }
 278  12
                 if (scope.equals(NOTHIGHER)) {
 279  2
                         return !higher;
 280  
                 }
 281  10
                 if (scope.equals(SAMEORHIGHER)) {
 282  3
                         return (same || higher);
 283  
                 }
 284  
 
 285  7
                 boolean sameBranch = (same || lower || higher);
 286  7
                 if (scope.equals(SAMEBRANCH)) {
 287  4
                         return sameBranch;
 288  
                 }
 289  3
                 if (scope.equals(NOTSAMEBRANCH)) {
 290  2
                         return !sameBranch;
 291  
                 }
 292  
 
 293  2
                 throw new SDMetricsException(null, null, "Illegal scope criterion '"
 294  1
                                 + scope + "'.");
 295  
         }
 296  
 
 297  
         /**
 298  
          * Checks if a model element directly or indirectly owns another model
 299  
          * element.
 300  
          * 
 301  
          * @param containing The containing model element.
 302  
          * @param contained The candidate contained model element.
 303  
          * @return <code>true</code> if "containing" element directly or indirectly
 304  
          *         owns the "contained" element.
 305  
          */
 306  
         private boolean contains(ModelElement containing, ModelElement contained) {
 307  40
                 if (contained == null) {
 308  0
                         return false;
 309  
                 }
 310  40
                 ModelElement context = contained.getOwner();
 311  153
                 while (context != null) {
 312  86
                         if (context == containing) {
 313  13
                                 return true;
 314  
                         }
 315  73
                         context = context.getOwner();
 316  
                 }
 317  27
                 return false;
 318  
         }
 319  
 
 320  
         /**
 321  
          * Applies element filters and filter attributes to an element set and
 322  
          * returns an iteration over the resulting elements.
 323  
          * <p>
 324  
          * <ul>
 325  
          * <li>Elements in the input set that should be ignored according to element
 326  
          * filter settings are immediately dismissed.
 327  
          * <li>To each remaining element, the filter attributes are applied (see
 328  
          * {@link #applyFilters}.
 329  
          * <li>The resulting elements are returned as values of the iteration.
 330  
          * <li>Method {@link #isValid()} indicates if the most recently returned
 331  
          * element of the iteration fulfills the filter attribute conditions.
 332  
          * </ul>
 333  
          * 
 334  
          * @param set Element set, typically the result of a "relation" or "relset"
 335  
          *        attribute in a projection-like metric.
 336  
          * @param vars Variables for the evaluation of expressions
 337  
          * @return Iteration over the resulting elements
 338  
          * @throws SDMetricsException Error evaluating the filter attributes
 339  
          */
 340  
         public Iterable<ModelElement> fullIteration(
 341  
                         final Collection<ModelElement> set, final Variables vars)
 342  
                         throws SDMetricsException {
 343  
 
 344  176
                 return new Iterable<ModelElement>() {
 345  
                         @Override
 346  
                         public Iterator<ModelElement> iterator() {
 347  176
                                 FilteringIterator result = new FilteringIterator(set, vars);
 348  176
                                 result.returnValidsOnly = false;
 349  176
                                 return result;
 350  
                         }
 351  
                 };
 352  
         }
 353  
 
 354  
         /**
 355  
          * Applies element filters and filter attributes to an element set and
 356  
          * returns an iteration over the valid elements.
 357  
          * <p>
 358  
          * <ul>
 359  
          * <li>Elements in the input set that should be ignored according to element
 360  
          * filter settings are immediately dismissed.
 361  
          * <li>To each remaining element, the filter attributes are applied (see
 362  
          * {@link #applyFilters}.
 363  
          * <li>If the resulting element also fulfills the filter attribute
 364  
          * conditions, it is returned as values of the iteration. Otherwise, the
 365  
          * element is dismissed.
 366  
          * </ul>
 367  
          * 
 368  
          * @param set Element set, typically the result of a "relation" or "relset"
 369  
          *        attribute in a projection-like metric.
 370  
          * @param vars Variables for the evaluation of expressions
 371  
          * @return Iteration over the resulting elements
 372  
          * @throws SDMetricsException Error evaluating the filter attributes
 373  
          */
 374  
         public Iterable<ModelElement> validIteration(
 375  
                         final Collection<ModelElement> set, final Variables vars)
 376  
                         throws SDMetricsException {
 377  
 
 378  94
                 return new Iterable<ModelElement>() {
 379  
                         @Override
 380  
                         public Iterator<ModelElement> iterator() {
 381  94
                                 FilteringIterator result = new FilteringIterator(set, vars);
 382  94
                                 result.returnValidsOnly = true;
 383  94
                                 return result;
 384  
                         }
 385  
                 };
 386  
         }
 387  
 
 388  
         private class FilteringIterator implements Iterator<ModelElement> {
 389  
                 private final Iterator<ModelElement> it;
 390  
                 private final Variables variables;
 391  
                 private final ModelElement principal;
 392  
                 
 393  270
                 private boolean hasNext = false;
 394  270
                 private ModelElement next = null;
 395  270
                 private boolean nextKnown = false;
 396  270
                 boolean returnValidsOnly = false;
 397  
 
 398  
                 /**
 399  
                  * Creates a new filtering iterator.
 400  
                  * 
 401  
                  * @param set Element set to iterate.
 402  
                  * @param vars Variables for the evaluation of filter expressions
 403  
                  */
 404  270
                 FilteringIterator(Collection<ModelElement> set, Variables vars) {
 405  270
                         this.variables = vars;
 406  270
                         principal = vars.getPrincipal();
 407  270
                         it = set.iterator();
 408  270
                 }
 409  
 
 410  
                 /**
 411  
                  * Tests if the iteration has more elements.
 412  
                  * 
 413  
                  * @return <tt>true</tt> if the iterator has still more elements.
 414  
                  */
 415  
                 @Override
 416  
                 public boolean hasNext() {
 417  1231
                         if (!nextKnown) {
 418  1229
                                 findNext();
 419  
                         }
 420  1231
                         return hasNext;
 421  
                 }
 422  
 
 423  
                 /**
 424  
                  * Returns the next element in the iteration.
 425  
                  * 
 426  
                  * @return Next element in the iteration.
 427  
                  */
 428  
                 @Override
 429  
                 public ModelElement next() {
 430  965
                         if (!nextKnown) {
 431  2
                                 findNext();
 432  
                         }
 433  
 
 434  965
                         if (!hasNext) {
 435  1
                                 throw new NoSuchElementException();
 436  
                         }
 437  
 
 438  964
                         nextKnown = false;
 439  964
                         return next;
 440  
                 }
 441  
 
 442  
                 private void findNext() throws SDMetricsException {
 443  1231
                         nextKnown = true;
 444  2891
                         while (it.hasNext()) {
 445  1393
                                 ModelElement candidate = it.next();
 446  
                                 // Dismiss the element if links to the element should be ignored
 447  
                                 // as per the element filter settings
 448  1393
                                 if (candidate.getLinksIgnored()) {
 449  63
                                         continue;
 450  
                                 }
 451  
 
 452  
                                 // Apply the filter attributes and dismiss elements that do not
 453  
                                 // return a result
 454  1330
                                 candidate = applyFilters(principal, candidate, variables);
 455  1330
                                 if (candidate == null) {
 456  7
                                         continue;
 457  
                                 }
 458  
 
 459  
                                 // Optionally dismiss elements that do not fulfill the filter
 460  
                                 // attribute conditions
 461  1323
                                 if (returnValidsOnly && !isValid()) {
 462  359
                                         continue;
 463  
                                 }
 464  
 
 465  964
                                 hasNext = true;
 466  964
                                 next = candidate;
 467  964
                                 return;
 468  
                         }
 469  
 
 470  
                         // end of the iteration has been reached
 471  267
                         hasNext = false;
 472  267
                         next = null;
 473  267
                 }
 474  
 
 475  
                 @Override
 476  
                 public void remove() {
 477  0
                         it.remove();
 478  0
                 }
 479  
         }
 480  
 }