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