Coverage Report - com.sdmetrics.metrics.ExpressionOperations - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
ExpressionOperations
100%
22/22
N/A
1,863
ExpressionOperations$AbsoluteFunction
100%
2/2
N/A
1,863
ExpressionOperations$AndOperator
100%
4/4
100%
2/2
1,863
ExpressionOperations$BinaryNumericalOperator
100%
5/5
N/A
1,863
ExpressionOperations$BinarySetOperation
100%
6/6
N/A
1,863
ExpressionOperations$BooleanComparator
100%
10/10
100%
4/4
1,863
ExpressionOperations$BooleanOperationCache
100%
30/30
N/A
1,863
ExpressionOperations$CeilingFunction
100%
2/2
N/A
1,863
ExpressionOperations$DifferenceOperation
100%
6/6
N/A
1,863
ExpressionOperations$DivisionOperator
100%
2/2
N/A
1,863
ExpressionOperations$DotOperator
100%
8/8
100%
2/2
1,863
ExpressionOperations$DotOperatorSet
100%
8/8
100%
2/2
1,863
ExpressionOperations$EndsWithOperator
100%
5/5
N/A
1,863
ExpressionOperations$EqualsOperator
100%
4/4
100%
2/2
1,863
ExpressionOperations$ExponentiationFunction
100%
2/2
N/A
1,863
ExpressionOperations$ExponentiationOperator
100%
2/2
N/A
1,863
ExpressionOperations$FlatSizeFunction
100%
7/7
100%
2/2
1,863
ExpressionOperations$FloorFunction
100%
2/2
N/A
1,863
ExpressionOperations$GreaterOrEqualOperator
100%
5/5
100%
4/4
1,863
ExpressionOperations$GreaterThanOperator
100%
5/5
100%
4/4
1,863
ExpressionOperations$InOperator
100%
5/5
N/A
1,863
ExpressionOperations$InOperatorBool
100%
5/5
N/A
1,863
ExpressionOperations$InstanceOfFunction
100%
23/23
100%
10/10
1,863
ExpressionOperations$IntersectionOperation
100%
7/7
100%
4/4
1,863
ExpressionOperations$IsLowerCaseFunction
100%
4/4
N/A
1,863
ExpressionOperations$IsUniqueFunction
100%
6/6
100%
4/4
1,863
ExpressionOperations$LengthFunction
100%
4/4
N/A
1,863
ExpressionOperations$LessOrEqualOperator
100%
5/5
100%
4/4
1,863
ExpressionOperations$LessThanOperator
100%
5/5
100%
4/4
1,863
ExpressionOperations$LogarithmFunction
100%
2/2
N/A
1,863
ExpressionOperations$MinusOperator
100%
7/7
100%
2/2
1,863
ExpressionOperations$MultiplicationOperator
100%
2/2
N/A
1,863
ExpressionOperations$NotEqualsOperator
100%
4/4
100%
4/4
1,863
ExpressionOperations$NotOperator
100%
2/2
100%
2/2
1,863
ExpressionOperations$OnListOperator
100%
10/10
100%
4/4
1,863
ExpressionOperations$OrOperator
100%
4/4
100%
2/2
1,863
ExpressionOperations$ParseNumberFunction
100%
9/9
N/A
1,863
ExpressionOperations$PlusOperator
100%
15/15
100%
6/6
1,863
ExpressionOperations$QualifiedNameFunction
100%
11/11
100%
4/4
1,863
ExpressionOperations$RoundFunction
100%
2/2
N/A
1,863
ExpressionOperations$ScalarOperationCache
100%
34/34
N/A
1,863
ExpressionOperations$SetOperationCache
100%
8/8
N/A
1,863
ExpressionOperations$SizeFunction
100%
5/5
N/A
1,863
ExpressionOperations$SquareRootFunction
100%
2/2
N/A
1,863
ExpressionOperations$StartsWithCapitalFunction
100%
6/6
100%
4/4
1,863
ExpressionOperations$StartsWithLowerCaseFunction
100%
6/6
100%
4/4
1,863
ExpressionOperations$StartsWithOperator
100%
5/5
N/A
1,863
ExpressionOperations$ToLowerFunction
100%
4/4
N/A
1,863
ExpressionOperations$TopMostOperator
100%
13/13
100%
6/6
1,863
ExpressionOperations$TypeOfFunction
100%
11/11
100%
4/4
1,863
ExpressionOperations$UnaryNumericalOperator
100%
4/4
N/A
1,863
ExpressionOperations$UnionOperation
100%
7/7
100%
4/4
1,863
ExpressionOperations$UpToOperator
100%
12/12
100%
6/6
1,863
 
 1  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.HashSet;
 27  
 
 28  
 import com.sdmetrics.math.ExpressionNode;
 29  
 import com.sdmetrics.math.ExpressionParser;
 30  
 import com.sdmetrics.math.HashMultiSet;
 31  
 import com.sdmetrics.model.MetaModelElement;
 32  
 import com.sdmetrics.model.ModelElement;
 33  
 
 34  
 /**
 35  
  * Holds the expression operations for metric, set, and condition expressions.
 36  
  */
 37  
 class ExpressionOperations {
 38  
 
 39  
         private final ScalarOperationCache scalarOperations;
 40  
         private final BooleanOperationCache booleanOperations;
 41  
         private final SetOperationCache setOperations;
 42  
 
 43  3306
         private final HashSet<String> functionNames;
 44  522
         private final HashSet<String> highPrecedenceRelationNames;
 45  348
         private final HashSet<String> lowPrecedenceRelationNames;
 46  
 
 47  
         /**
 48  
          * Constructor.
 49  
          */
 50  174
         ExpressionOperations() {
 51  174
                 functionNames = new HashSet<String>();
 52  174
                 highPrecedenceRelationNames = new HashSet<String>();
 53  174
                 lowPrecedenceRelationNames = new HashSet<String>();
 54  
 
 55  174
                 scalarOperations = new ScalarOperationCache();
 56  174
                 booleanOperations = new BooleanOperationCache();
 57  174
                 setOperations = new SetOperationCache();
 58  174
         }
 59  
 
 60  
         /**
 61  
          * Cache for scalar operations.
 62  
          */
 63  
         class ScalarOperationCache extends ProcedureCache<ScalarOperation> {
 64  
 
 65  
                 /**
 66  
                  * Creates a new cache and registers the standard scalar operations.
 67  
                  */
 68  174
                 ScalarOperationCache() {
 69  174
                         super("operation");
 70  174
                         addProcedure(this, "size", SizeFunction.class, functionNames);
 71  348
                         addProcedure(this, "flatsize", FlatSizeFunction.class,
 72  174
                                         functionNames);
 73  174
                         addProcedure(this, "length", LengthFunction.class, functionNames);
 74  348
                         addProcedure(this, "tolowercase", ToLowerFunction.class,
 75  174
                                         functionNames);
 76  174
                         addProcedure(this, "typeof", TypeOfFunction.class, functionNames);
 77  348
                         addProcedure(this, "qualifiedname", QualifiedNameFunction.class,
 78  174
                                         functionNames);
 79  174
                         addProcedureClass(".", DotOperator.class);
 80  348
                         addProcedure(this, "upto", UpToOperator.class,
 81  174
                                         lowPrecedenceRelationNames);
 82  348
                         addProcedure(this, "topmost", TopMostOperator.class,
 83  174
                                         lowPrecedenceRelationNames);
 84  174
                         addProcedureClass("->", InOperator.class);
 85  174
                         addProcedureClass("+", PlusOperator.class);
 86  174
                         addProcedureClass("-", MinusOperator.class);
 87  174
                         addProcedureClass("*", MultiplicationOperator.class);
 88  174
                         addProcedureClass("/", DivisionOperator.class);
 89  174
                         addProcedureClass("^", ExponentiationOperator.class);
 90  174
                         addProcedure(this, "ln", LogarithmFunction.class, functionNames);
 91  348
                         addProcedure(this, "exp", ExponentiationFunction.class,
 92  174
                                         functionNames);
 93  174
                         addProcedure(this, "sqrt", SquareRootFunction.class, functionNames);
 94  174
                         addProcedure(this, "abs", AbsoluteFunction.class, functionNames);
 95  174
                         addProcedure(this, "floor", FloorFunction.class, functionNames);
 96  174
                         addProcedure(this, "ceil", CeilingFunction.class, functionNames);
 97  174
                         addProcedure(this, "round", RoundFunction.class, functionNames);
 98  348
                         addProcedure(this, "parsenumber", ParseNumberFunction.class,
 99  174
                                         functionNames);
 100  174
                 }
 101  
 
 102  
                 @Override
 103  
                 protected Class<? extends ScalarOperation> loadClass(String className)
 104  
                                 throws ClassNotFoundException {
 105  150
                         return Class.forName(className).asSubclass(ScalarOperation.class);
 106  
                 }
 107  
         }
 108  
 
 109  
         /**
 110  
          * Adds an operation and registers its name to be recognized by the
 111  
          * expression parser.
 112  
          */
 113  4176
         private <T extends AbstractProcedure> void addProcedure(
 114  
                         ProcedureCache<T> cache, String name, Class<? extends T> cls,
 115  
                         HashSet<String> type) {
 116  4176
                 cache.addProcedureClass(name, cls);
 117  4176
                 type.add(name);
 118  4176
         }
 119  
 
 120  
         /**
 121  
          * Cache for Boolean operations.
 122  
          */
 123  
         class BooleanOperationCache extends ProcedureCache<BooleanOperation> {
 124  
 
 125  
                 /**
 126  
                  * Creates a new cache and registers the standard Boolean operations.
 127  
                  */
 128  174
                 BooleanOperationCache() {
 129  174
                         super("boolean operation");
 130  174
                         addProcedureClass("&", AndOperator.class);
 131  174
                         addProcedureClass("|", OrOperator.class);
 132  174
                         addProcedureClass("!", NotOperator.class);
 133  348
                         addProcedure(this, "isunique", IsUniqueFunction.class,
 134  174
                                         functionNames);
 135  348
                         addProcedure(this, "startswithcapital",
 136  174
                                         StartsWithCapitalFunction.class, functionNames);
 137  348
                         addProcedure(this, "startswithlowercase",
 138  174
                                         StartsWithLowerCaseFunction.class, functionNames);
 139  348
                         addProcedure(this, "islowercase", IsLowerCaseFunction.class,
 140  174
                                         functionNames);
 141  348
                         addProcedure(this, "instanceof", InstanceOfFunction.class,
 142  174
                                         functionNames);
 143  174
                         addProcedureClass("->", InOperatorBool.class);
 144  348
                         addProcedure(this, "onlist", OnListOperator.class,
 145  174
                                         highPrecedenceRelationNames);
 146  174
                         addProcedureClass("=", EqualsOperator.class);
 147  174
                         addProcedureClass("!=", NotEqualsOperator.class);
 148  174
                         addProcedureClass(">", GreaterThanOperator.class);
 149  174
                         addProcedureClass(">=", GreaterOrEqualOperator.class);
 150  174
                         addProcedureClass("<", LessThanOperator.class);
 151  174
                         addProcedureClass("<=", LessOrEqualOperator.class);
 152  348
                         addProcedure(this, "startswith", StartsWithOperator.class,
 153  174
                                         highPrecedenceRelationNames);
 154  348
                         addProcedure(this, "endswith", EndsWithOperator.class,
 155  174
                                         highPrecedenceRelationNames);
 156  174
                 }
 157  
 
 158  
                 @Override
 159  
                 protected Class<? extends BooleanOperation> loadClass(String className)
 160  
                                 throws ClassNotFoundException {
 161  150
                         return Class.forName(className).asSubclass(BooleanOperation.class);
 162  
                 }
 163  
 
 164  
         }
 165  
 
 166  
         /**
 167  
          * Cache for Boolean operations.
 168  
          */
 169  
         class SetOperationCache extends ProcedureCache<SetOperation> {
 170  
 
 171  
                 /**
 172  
                  * Creates a new cache and registers the standard Boolean operations.
 173  
                  */
 174  174
                 SetOperationCache() {
 175  174
                         super("set operation");
 176  174
                         addProcedureClass(".", DotOperatorSet.class);
 177  174
                         addProcedureClass("+", UnionOperation.class);
 178  174
                         addProcedureClass("-", DifferenceOperation.class);
 179  174
                         addProcedureClass("*", IntersectionOperation.class);
 180  174
                 }
 181  
 
 182  
                 @Override
 183  
                 protected Class<? extends SetOperation> loadClass(String className)
 184  
                                 throws ClassNotFoundException {
 185  150
                         return Class.forName(className).asSubclass(SetOperation.class);
 186  
                 }
 187  
         }
 188  
 
 189  
         /**
 190  
          * Provides access to the scalar operations.
 191  
          * 
 192  
          * @return The scalar operations.
 193  
          */
 194  
         ProcedureCache<ScalarOperation> getScalarOperations() {
 195  1363
                 return scalarOperations;
 196  
         }
 197  
 
 198  
         /**
 199  
          * Provides access to the Boolean operations.
 200  
          * 
 201  
          * @return The Boolean operations.
 202  
          */
 203  
         ProcedureCache<BooleanOperation> getBooleanOperations() {
 204  631
                 return booleanOperations;
 205  
         }
 206  
 
 207  
         /**
 208  
          * Provides access to the Boolean operations.
 209  
          * 
 210  
          * @return The Boolean operations.
 211  
          */
 212  
         ProcedureCache<SetOperation> getSetOperations() {
 213  168
                 return setOperations;
 214  
         }
 215  
 
 216  
         /**
 217  
          * Registers the name of a custom function to be recognized by the
 218  
          * expression parser.
 219  
          * 
 220  
          * @param name The name of the custom function
 221  
          */
 222  
         void addCustomFunctionName(String name) {
 223  450
                 functionNames.add(name);
 224  450
         }
 225  
 
 226  
         /**
 227  
          * Creates a new expression parser that recognizes all the operations
 228  
          * registered with this object.
 229  
          * 
 230  
          * @return Expression parser
 231  
          */
 232  
         ExpressionParser getExpressionParser() {
 233  584
                 return new ExpressionParser(functionNames, highPrecedenceRelationNames,
 234  292
                                 lowPrecedenceRelationNames);
 235  
         }
 236  
 
 237  
         // Scalar Operations
 238  
 
 239  
         /**
 240  
          * Calculates the number of elements in a set.
 241  
          */
 242  1
         public static class SizeFunction extends ScalarOperation {
 243  
 
 244  
                 @Override
 245  
                 public Integer calculateValue(ModelElement element,
 246  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 247  4
                         Collection<?> c = evalSetExpression(element, node.getLeftNode(),
 248  2
                                         vars);
 249  2
                         return Integer.valueOf(c.size());
 250  
                 }
 251  
         }
 252  
 
 253  
         /**
 254  
          * Calculates the number of elements in a set, ignoring the cardinality of
 255  
          * elements for multisets.
 256  
          */
 257  1
         public static class FlatSizeFunction extends ScalarOperation {
 258  
 
 259  
                 @Override
 260  
                 public Integer calculateValue(ModelElement element,
 261  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 262  4
                         Collection<?> c = evalSetExpression(element, node.getLeftNode(),
 263  2
                                         vars);
 264  2
                         if (MetricTools.isMultiSet(c)) {
 265  1
                                 return Integer.valueOf(((HashMultiSet<?>) c).flatSetSize());
 266  
                         }
 267  1
                         return Integer.valueOf(c.size());
 268  
                 }
 269  
         }
 270  
 
 271  
         /**
 272  
          * Calculates the length of a string.
 273  
          */
 274  13
         public static class LengthFunction extends ScalarOperation {
 275  
 
 276  
                 @Override
 277  
                 public Integer calculateValue(ModelElement element,
 278  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 279  371
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 280  371
                         return Integer.valueOf(leftValue.toString().length());
 281  
                 }
 282  
         }
 283  
 
 284  
         /**
 285  
          * Converts all of the characters in a string to lower case. The default
 286  
          * locale applies.
 287  
          */
 288  1
         public static class ToLowerFunction extends ScalarOperation {
 289  
 
 290  
                 @Override
 291  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 292  
                                 Variables vars) throws SDMetricsException {
 293  1
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 294  1
                         return leftValue.toString().toLowerCase();
 295  
                 }
 296  
         }
 297  
 
 298  
         /**
 299  
          * Returns the type name of a model element as a string.
 300  
          */
 301  2
         public static class TypeOfFunction extends ScalarOperation {
 302  
 
 303  
                 @Override
 304  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 305  
                                 Variables vars) throws SDMetricsException {
 306  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 307  4
                         if (leftValue instanceof ModelElement) {
 308  1
                                 ModelElement elem = (ModelElement) leftValue;
 309  1
                                 return elem.getType().getName();
 310  
                         }
 311  3
                         if (MetricTools.isEmptyElement(leftValue)) {
 312  1
                                 return "";
 313  
                         }
 314  4
                         throw new SDMetricsException(element, null, "Operator '"
 315  2
                                         + node.getValue()
 316  2
                                         + "' can only be applied to model elements.");
 317  
                 }
 318  
         }
 319  
 
 320  
         /**
 321  
          * Returns the fully qualified name of a model element as a string.
 322  
          */
 323  2
         public static class QualifiedNameFunction extends ScalarOperation {
 324  
                 @Override
 325  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 326  
                                 Variables vars) throws SDMetricsException {
 327  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 328  4
                         if (leftValue instanceof ModelElement) {
 329  1
                                 ModelElement elem = (ModelElement) leftValue;
 330  1
                                 return elem.getFullName();
 331  
                         }
 332  3
                         if (MetricTools.isEmptyElement(leftValue)) {
 333  1
                                 return "";
 334  
                         }
 335  4
                         throw new SDMetricsException(element, null, "Operator '"
 336  2
                                         + node.getValue()
 337  2
                                         + "' can only be applied to model elements.");
 338  
                 }
 339  
         }
 340  
 
 341  
         /**
 342  
          * Processes the dot operator in metric expressions.
 343  
          */
 344  10
         public static class DotOperator extends ScalarOperation {
 345  
                 @Override
 346  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 347  
                                 Variables vars) throws SDMetricsException {
 348  
                         // Suppresses any errors and return empty string
 349  
                         // if something cannot be evaluated as it should.
 350  581
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 351  581
                         if (!(leftValue instanceof ModelElement)) {
 352  1
                                 return "";
 353  
                         }
 354  
                         try {
 355  1160
                                 return evalExpression((ModelElement) leftValue,
 356  580
                                                 node.getRightNode(), vars);
 357  2
                         } catch (Exception ex) {
 358  2
                                 return "";
 359  
                         }
 360  
                 }
 361  
         }
 362  
 
 363  
         /**
 364  
          * Processes the "upto" operator.
 365  
          */
 366  1
         public static class UpToOperator extends ScalarOperation {
 367  
                 @Override
 368  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 369  
                                 Variables vars) throws SDMetricsException {
 370  2
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 371  11
                         while (leftValue != null) {
 372  8
                                 if (leftValue instanceof ModelElement) {
 373  7
                                         ModelElement elem = (ModelElement) leftValue;
 374  14
                                         if (evalBooleanExpression(elem, 
 375  14
                                                         node.getRightNode(), vars)) {
 376  1
                                                 return elem;
 377  
                                         }
 378  6
                                         leftValue = evalExpression(elem, node.getLeftNode(), vars);
 379  6
                                 } else {
 380  1
                                         leftValue = null;
 381  
                                 }
 382  
                         }
 383  1
                         return "";
 384  
                 }
 385  
         }
 386  
 
 387  
         /**
 388  
          * Processes the "topmost" operator.
 389  
          */
 390  1
         public static class TopMostOperator extends ScalarOperation {
 391  
                 @Override
 392  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 393  
                                 Variables vars) throws SDMetricsException {
 394  1
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 395  1
                         Object result = "";
 396  8
                         while (leftValue != null) {
 397  6
                                 if (leftValue instanceof ModelElement) {
 398  5
                                         ModelElement elem = (ModelElement) leftValue;
 399  10
                                         if (evalBooleanExpression(elem, 
 400  10
                                                         node.getRightNode(), vars)) {
 401  3
                                                 result = elem;
 402  
                                         }
 403  5
                                         leftValue = evalExpression(elem, node.getLeftNode(), vars);
 404  5
                                 } else {
 405  1
                                         leftValue = null;
 406  
                                 }
 407  
                         }
 408  1
                         return result;
 409  
                 }
 410  
         }
 411  
 
 412  
         /**
 413  
          * Determines the cardinality of an element in a set.
 414  
          */
 415  1
         public static class InOperator extends ScalarOperation {
 416  
                 @Override
 417  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 418  
                                 Variables vars) throws SDMetricsException {
 419  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 420  3
                         ExpressionNode rightNode = node.getRightNode();
 421  3
                         Collection<?> c = evalSetExpression(element, rightNode, vars);
 422  3
                         return Integer.valueOf(MetricTools.elementCount(c, leftValue));
 423  
                 }
 424  
         }
 425  
 
 426  
         /**
 427  
          * Calculates the unary and binary plus operator for metric expressions.
 428  
          * Performs numerical addition or string concatenation, depending on the
 429  
          * types of the elements.
 430  
          */
 431  7
         public static class PlusOperator extends ScalarOperation {
 432  
                 @Override
 433  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 434  
                                 Variables vars) throws SDMetricsException {
 435  212
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 436  
 
 437  
                         // process unary operator
 438  212
                         if (node.getOperandCount() == 1) {
 439  1
                                 return leftValue;
 440  
                         }
 441  
 
 442  
                         // String concatenation if left hand side is not a number
 443  211
                         if (!(leftValue instanceof Number)) {
 444  418
                                 Object rightValue = evalExpression(element,
 445  209
                                                 node.getRightNode(), vars);
 446  209
                                 if (rightValue instanceof Number) {
 447  2
                                         return leftValue.toString()
 448  2
                                                         + java.text.NumberFormat.getInstance().format(
 449  1
                                                                         rightValue);
 450  
                                 }
 451  208
                                 return leftValue.toString() + rightValue.toString();
 452  
                         }
 453  
 
 454  
                         // numerical addition
 455  2
                         float left = getFloatValue(leftValue, element);
 456  2
                         float right = getFloatValue(element, node.getRightNode(), vars);
 457  2
                         return Float.valueOf(left + right);
 458  
                 }
 459  
         }
 460  
 
 461  
         /**
 462  
          * Calculates the unary and binary minus operator for metric expressions.
 463  
          */
 464  4
         public static class MinusOperator extends ScalarOperation {
 465  
 
 466  
                 @Override
 467  
                 public Float calculateValue(ModelElement element, ExpressionNode node,
 468  
                                 Variables vars) throws SDMetricsException {
 469  6
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 470  6
                         if (node.getOperandCount() == 1) {
 471  5
                                 return Float.valueOf(-left);
 472  
                         }
 473  1
                         float right = getFloatValue(element, node.getRightNode(), vars);
 474  1
                         return Float.valueOf(left - right);
 475  
                 }
 476  
         }
 477  
 
 478  
         /**
 479  
          * Base class for binary metric operators that operate on numerical values
 480  
          * only.
 481  
          */
 482  4
         abstract static class BinaryNumericalOperator extends ScalarOperation {
 483  
                 @Override
 484  
                 public final Float calculateValue(ModelElement element,
 485  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 486  5
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 487  5
                         float right = getFloatValue(element, node.getRightNode(), vars);
 488  4
                         return Float.valueOf(calculateValue(left, right));
 489  
                 }
 490  
 
 491  
                 /**
 492  
                  * Calculates the numerical value of the operation.
 493  
                  * 
 494  
                  * @param left The value of the left hand side operand.
 495  
                  * @param right The value of the right hand side operand.
 496  
                  * @return Value of the operation.
 497  
                  */
 498  
                 protected abstract float calculateValue(float left, float right);
 499  
         }
 500  
 
 501  
         /**
 502  
          * Calculates the product of two numbers.
 503  
          */
 504  1
         public static class MultiplicationOperator extends BinaryNumericalOperator {
 505  
                 @Override
 506  
                 protected float calculateValue(float left, float right) {
 507  1
                         return left * right;
 508  
                 }
 509  
         }
 510  
 
 511  
         /**
 512  
          * Calculates the quotient of two numbers.
 513  
          */
 514  2
         public static class DivisionOperator extends BinaryNumericalOperator {
 515  
                 @Override
 516  
                 protected float calculateValue(float left, float right) {
 517  2
                         return left / right;
 518  
                 }
 519  
         }
 520  
 
 521  
         /**
 522  
          * Exponentiates two numbers.
 523  
          */
 524  1
         public static class ExponentiationOperator extends BinaryNumericalOperator {
 525  
                 @Override
 526  
                 protected float calculateValue(float left, float right) {
 527  1
                         return (float) java.lang.Math.pow(left, right);
 528  
                 }
 529  
         }
 530  
 
 531  
         /**
 532  
          * Base class for unary metric operators that operate on numerical values
 533  
          * only.
 534  
          */
 535  8
         abstract static class UnaryNumericalOperator extends ScalarOperation {
 536  
                 @Override
 537  
                 public final Float calculateValue(ModelElement element,
 538  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 539  9
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 540  9
                         return Float.valueOf(calculateValue(left));
 541  
                 }
 542  
 
 543  
                 /**
 544  
                  * Calculates the numerical value of the operation.
 545  
                  * 
 546  
                  * @param operand The value of the single operand.
 547  
                  * @return Value of the operation.
 548  
                  */
 549  
                 protected abstract float calculateValue(float operand);
 550  
         }
 551  
 
 552  
         /**
 553  
          * Calculates the natural logarithm (base <code>e</code> of a value.
 554  
          */
 555  2
         public static class LogarithmFunction extends UnaryNumericalOperator {
 556  
                 @Override
 557  
                 protected float calculateValue(float left) {
 558  2
                         return (float) Math.log(left);
 559  
                 }
 560  
         }
 561  
 
 562  
         /**
 563  
          * Calculates the <code>e</code> raised to the power of a value.
 564  
          */
 565  1
         public static class ExponentiationFunction extends UnaryNumericalOperator {
 566  
                 @Override
 567  
                 protected float calculateValue(float left) {
 568  1
                         return (float) Math.exp(left);
 569  
                 }
 570  
         }
 571  
 
 572  
         /**
 573  
          * Calculates the square root of a value.
 574  
          */
 575  1
         public static class SquareRootFunction extends UnaryNumericalOperator {
 576  
                 @Override
 577  
                 protected float calculateValue(float left) {
 578  1
                         return (float) Math.sqrt(left);
 579  
                 }
 580  
         }
 581  
 
 582  
         /**
 583  
          * Calculates the absolute value of value.
 584  
          */
 585  1
         public static class AbsoluteFunction extends UnaryNumericalOperator {
 586  
                 @Override
 587  
                 protected float calculateValue(float left) {
 588  1
                         return Math.abs(left);
 589  
                 }
 590  
         }
 591  
 
 592  
         /**
 593  
          * Rounds to the next lower integer (towards -infinity).
 594  
          */
 595  1
         public static class FloorFunction extends UnaryNumericalOperator {
 596  
                 @Override
 597  
                 protected float calculateValue(float left) {
 598  1
                         return (float) Math.floor(left);
 599  
                 }
 600  
         }
 601  
 
 602  
         /**
 603  
          * Rounds to the next higher integer (towards +infinity).
 604  
          */
 605  1
         public static class CeilingFunction extends UnaryNumericalOperator {
 606  
                 @Override
 607  
                 protected float calculateValue(float left) {
 608  1
                         return (float) Math.ceil(left);
 609  
                 }
 610  
         }
 611  
 
 612  
         /**
 613  
          * Rounds to the nearest integer ("half up").
 614  
          */
 615  1
         public static class RoundFunction extends UnaryNumericalOperator {
 616  
                 @Override
 617  
                 protected float calculateValue(float left) {
 618  2
                         return (float) Math.floor(left + 0.5);
 619  
                 }
 620  
         }
 621  
 
 622  
         /**
 623  
          * Returns the numerical value of a string containing a number. Assumes
 624  
          * Java-style formatting for decimal numbers.
 625  
          * 
 626  
          * @since 2.31
 627  
          */
 628  2
         public static class ParseNumberFunction extends ScalarOperation {
 629  
 
 630  
                 @Override
 631  
                 public Number calculateValue(ModelElement element, ExpressionNode node,
 632  
                                 Variables vars) throws SDMetricsException {
 633  10
                         String str = evalExpression(element, node.getOperand(0), vars)
 634  5
                                         .toString();
 635  
                         try {
 636  5
                                 float value = Float.parseFloat(str);
 637  4
                                 return MetricTools.getNumber(value);
 638  1
                         } catch (NumberFormatException ex) {
 639  2
                                 throw new SDMetricsException(element, null,
 640  1
                                                 "Could not parse as number: '" + str + "'");
 641  
                         }
 642  
                 }
 643  
         }
 644  
 
 645  
         // Boolean operations
 646  
 
 647  
         /**
 648  
          * Calculates the "and" operator. Short-circuits if possible.
 649  
          */
 650  1
         public static class AndOperator extends BooleanOperation {
 651  
                 @Override
 652  
                 public boolean calculateValue(ModelElement element,
 653  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 654  3
                         if (evalBooleanExpression(element, node.getLeftNode(), vars)) {
 655  2
                                 return evalBooleanExpression(element, node.getRightNode(), vars);
 656  
                         }
 657  1
                         return false;
 658  
                 }
 659  
         }
 660  
 
 661  
         /**
 662  
          * Calculates the "or" operator. Short-circuits if possible.
 663  
          */
 664  2
         public static class OrOperator extends BooleanOperation {
 665  
                 @Override
 666  
                 public boolean calculateValue(ModelElement element,
 667  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 668  6
                         if (!evalBooleanExpression(element, node.getLeftNode(), vars)) {
 669  5
                                 return evalBooleanExpression(element, node.getRightNode(), vars);
 670  
                         }
 671  1
                         return true;
 672  
                 }
 673  
         }
 674  
 
 675  
         /**
 676  
          * Calculates the "not" operator.
 677  
          */
 678  2
         public static class NotOperator extends BooleanOperation {
 679  
                 @Override
 680  
                 public boolean calculateValue(ModelElement element,
 681  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 682  8
                         return !evalBooleanExpression(element, node.getLeftNode(), vars);
 683  
                 }
 684  
         }
 685  
 
 686  
         /**
 687  
          * Checks if all elements in a multiset have cardinality 1.
 688  
          */
 689  1
         public static class IsUniqueFunction extends BooleanOperation {
 690  
 
 691  
                 @Override
 692  
                 public boolean calculateValue(ModelElement element,
 693  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 694  6
                         Collection<?> c = evalSetExpression(element, node.getLeftNode(),
 695  3
                                         vars);
 696  3
                         if (!(MetricTools.isMultiSet(c))) {
 697  1
                                 return true;
 698  
                         }
 699  
 
 700  2
                         return c.size() == ((HashMultiSet<?>) c).flatSetSize();
 701  
                 }
 702  
         }
 703  
 
 704  
         /**
 705  
          * Checks if a string starts with a capital (upper case) letter.
 706  
          */
 707  1
         public static class StartsWithCapitalFunction extends BooleanOperation {
 708  
 
 709  
                 @Override
 710  
                 public boolean calculateValue(ModelElement element,
 711  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 712  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 713  3
                         String s = leftValue.toString();
 714  3
                         if (s.length() < 1) {
 715  1
                                 return true;
 716  
                         }
 717  2
                         return s.charAt(0) == Character.toUpperCase(s.charAt(0));
 718  
                 }
 719  
         }
 720  
 
 721  
         /**
 722  
          * Checks if a string starts with a lower case letter.
 723  
          */
 724  1
         public static class StartsWithLowerCaseFunction extends BooleanOperation {
 725  
 
 726  
                 @Override
 727  
                 public boolean calculateValue(ModelElement element,
 728  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 729  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 730  3
                         String s = leftValue.toString();
 731  3
                         if (s.length() < 1) {
 732  1
                                 return true;
 733  
                         }
 734  2
                         return s.charAt(0) == Character.toLowerCase(s.charAt(0));
 735  
                 }
 736  
         }
 737  
 
 738  
         /**
 739  
          * Checks that a string does not contain any capital letters.
 740  
          */
 741  1
         public static class IsLowerCaseFunction extends BooleanOperation {
 742  
 
 743  
                 @Override
 744  
                 public boolean calculateValue(ModelElement element,
 745  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 746  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 747  3
                         String s = leftValue.toString();
 748  3
                         return s.equals(s.toLowerCase());
 749  
                 }
 750  
         }
 751  
 
 752  
         /**
 753  
          * Checks that a model element has a particular type or one of its
 754  
          * sub-types.
 755  
          */
 756  2
         public static class InstanceOfFunction extends BooleanOperation {
 757  
                 @Override
 758  
                 public boolean calculateValue(ModelElement element,
 759  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 760  
 
 761  
                         // Candidate element is either "self" (if there is only one argument
 762  
                         // to the function), or the first argument of the function
 763  6
                         ModelElement candidate = element;
 764  6
                         if (node.getOperandCount() == 2) {
 765  3
                                 Object o = evalExpression(element, node.getOperand(0), vars);
 766  3
                                 if (MetricTools.isEmptyElement(o)) {
 767  1
                                         return false;
 768  
                                 }
 769  2
                                 if (!(o instanceof ModelElement)) {
 770  2
                                         throw new SDMetricsException(element, null,
 771  2
                                                         "First argument of binary function "
 772  1
                                                                         + node.getValue()
 773  1
                                                                         + " must yield a model element");
 774  
                                 }
 775  1
                                 candidate = (ModelElement) o;
 776  
                         }
 777  
 
 778  
                         // Type to check is the last argument of the function
 779  8
                         Object base = evalExpression(element,
 780  4
                                         node.getOperand(node.getOperandCount() - 1), vars);
 781  4
                         MetaModelElement baseType = null;
 782  4
                         if (base instanceof ModelElement) {
 783  1
                                 baseType = ((ModelElement) base).getType();
 784  1
                         } else {
 785  3
                                 baseType = getMetaModel().getType(base.toString());
 786  
                         }
 787  
 
 788  4
                         if (baseType == null) {
 789  2
                                 throw new SDMetricsException(element, null,
 790  1
                                                 "Unknown base type '" + base.toString() + "'");
 791  
                         }
 792  
 
 793  
                         // Check if model element is an instance of the base type
 794  3
                         return candidate.getType().specializes(baseType);
 795  
                 }
 796  
         }
 797  
 
 798  
         /**
 799  
          * Checks if a set contains a particular model element or value.
 800  
          */
 801  1
         public static class InOperatorBool extends BooleanOperation {
 802  
 
 803  
                 @Override
 804  
                 public boolean calculateValue(ModelElement element,
 805  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 806  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 807  8
                         Collection<?> c = evalSetExpression(element, node.getRightNode(),
 808  4
                                         vars);
 809  4
                         return c.contains(leftValue);
 810  
                 }
 811  
         }
 812  
 
 813  
         /**
 814  
          * Checks if a word list contains a particular word.
 815  
          */
 816  2
         public static class OnListOperator extends BooleanOperation {
 817  
 
 818  
                 @Override
 819  
                 public boolean calculateValue(ModelElement element,
 820  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 821  9
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 822  9
                         ExpressionNode rhsNode = node.getRightNode();
 823  
                         String wordListName;
 824  9
                         if (rhsNode.isIdentifier() || rhsNode.isStringConstant()) {
 825  8
                                 wordListName = rhsNode.getValue();
 826  8
                         } else {
 827  2
                                 wordListName = evalExpression(element, rhsNode, vars)
 828  1
                                                 .toString();
 829  
                         }
 830  18
                         return getMetricsEngine().getMetricStore().isWordOnList(
 831  9
                                         leftValue.toString(), wordListName);
 832  
                 }
 833  
         }
 834  
 
 835  
         /**
 836  
          * Base class for binary comparison operators. The operators perform
 837  
          * numerical comparisons when both arguments are numbers, and string or
 838  
          * object comparisons otherwise.
 839  
          */
 840  38
         abstract static class BooleanComparator extends BooleanOperation {
 841  
                 @Override
 842  
                 public boolean calculateValue(ModelElement element,
 843  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 844  420
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 845  838
                         Object rightValue = evalExpression(element, node.getRightNode(),
 846  419
                                         vars);
 847  
 
 848  419
                         if (leftValue instanceof Number) {
 849  382
                                 if (rightValue instanceof Number) {
 850  381
                                         float lf = ((Number) leftValue).floatValue();
 851  381
                                         float rf = ((Number) rightValue).floatValue();
 852  
 
 853  381
                                         return compare(lf, rf);
 854  
                                 }
 855  
                         }
 856  
 
 857  38
                         return compare(leftValue, rightValue);
 858  
                 }
 859  
 
 860  
                 /**
 861  
                  * Performs a numerical comparison.
 862  
                  * 
 863  
                  * @param left left hand side operand to compare
 864  
                  * @param right right hand side operand to compare
 865  
                  * @return result of the comparison
 866  
                  */
 867  
                 protected abstract boolean compare(float left, float right);
 868  
 
 869  
                 /**
 870  
                  * Performs an object or string comparison.
 871  
                  * 
 872  
                  * @param left left hand side operand to compare
 873  
                  * @param right right hand side operand to compare
 874  
                  * @return result of the comparison
 875  
                  */
 876  
                 protected abstract boolean compare(Object left, Object right);
 877  
 
 878  
         }
 879  
 
 880  
         /**
 881  
          * Compares for equality. Object identity is determined by the equals()
 882  
          * method.
 883  
          */
 884  9
         public static class EqualsOperator extends BooleanComparator {
 885  
                 @Override
 886  
                 protected boolean compare(float left, float right) {
 887  29
                         return left == right;
 888  
                 }
 889  
 
 890  
                 @Override
 891  
                 protected boolean compare(Object left, Object right) {
 892  13
                         return left.equals(right);
 893  
                 }
 894  
         }
 895  
 
 896  
         /**
 897  
          * Compares for non-equality. Object identity is determined by the equals()
 898  
          * method.
 899  
          */
 900  6
         public static class NotEqualsOperator extends BooleanComparator {
 901  
                 @Override
 902  
                 protected boolean compare(float left, float right) {
 903  32
                         return left != right;
 904  
                 }
 905  
 
 906  
                 @Override
 907  
                 protected boolean compare(Object left, Object right) {
 908  17
                         return !left.equals(right);
 909  
                 }
 910  
         }
 911  
 
 912  
         /** Performs "greater than" comparisons for numerical or string values. */
 913  7
         public static class GreaterThanOperator extends BooleanComparator {
 914  
                 @Override
 915  
                 protected boolean compare(float left, float right) {
 916  8
                         return left > right;
 917  
                 }
 918  
 
 919  
                 @Override
 920  
                 protected boolean compare(Object left, Object right) {
 921  2
                         int comp = left.toString().compareTo(right.toString());
 922  2
                         return comp > 0;
 923  
                 }
 924  
         }
 925  
 
 926  
         /** Performs "greater or equal" comparisons for numerical or string values. */
 927  2
         public static class GreaterOrEqualOperator extends BooleanComparator {
 928  
                 @Override
 929  
                 protected boolean compare(float left, float right) {
 930  2
                         return left >= right;
 931  
                 }
 932  
 
 933  
                 @Override
 934  
                 protected boolean compare(Object left, Object right) {
 935  2
                         int comp = left.toString().compareTo(right.toString());
 936  2
                         return comp >= 0;
 937  
                 }
 938  
         }
 939  
 
 940  
         /** Performs "less than" comparisons for numerical or string values. */
 941  11
         public static class LessThanOperator extends BooleanComparator {
 942  
                 @Override
 943  
                 protected boolean compare(float left, float right) {
 944  261
                         return left < right;
 945  
                 }
 946  
 
 947  
                 @Override
 948  
                 protected boolean compare(Object left, Object right) {
 949  2
                         int comp = left.toString().compareTo(right.toString());
 950  2
                         return comp < 0;
 951  
                 }
 952  
         }
 953  
 
 954  
         /** Performs "greater or equal" comparisons for numerical or string values. */
 955  3
         public static class LessOrEqualOperator extends BooleanComparator {
 956  
                 @Override
 957  
                 protected boolean compare(float left, float right) {
 958  49
                         return left <= right;
 959  
                 }
 960  
 
 961  
                 @Override
 962  
                 protected boolean compare(Object left, Object right) {
 963  2
                         int comp = left.toString().compareTo(right.toString());
 964  2
                         return comp <= 0;
 965  
                 }
 966  
         }
 967  
 
 968  
         /**
 969  
          * Checks if a string value starts with a specified prefix.
 970  
          */
 971  2
         public static class StartsWithOperator extends BooleanOperation {
 972  
 
 973  
                 @Override
 974  
                 public boolean calculateValue(ModelElement element,
 975  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 976  7
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 977  14
                         Object rightValue = evalExpression(element, node.getRightNode(),
 978  7
                                         vars);
 979  7
                         return leftValue.toString().startsWith(rightValue.toString());
 980  
                 }
 981  
         }
 982  
 
 983  
         /**
 984  
          * Checks if a string value ends with a specified prefix.
 985  
          */
 986  1
         public static class EndsWithOperator extends BooleanOperation {
 987  
 
 988  
                 @Override
 989  
                 public boolean calculateValue(ModelElement element,
 990  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 991  2
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 992  4
                         Object rightValue = evalExpression(element, node.getRightNode(),
 993  2
                                         vars);
 994  2
                         return leftValue.toString().endsWith(rightValue.toString());
 995  
                 }
 996  
         }
 997  
 
 998  
         // Set operations
 999  
 
 1000  
         /**
 1001  
          * Calculates the dot operator for set expressions.
 1002  
          */
 1003  1
         public static class DotOperatorSet extends SetOperation {
 1004  
                 @Override
 1005  
                 public Collection<?> calculateValue(ModelElement element,
 1006  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 1007  
                         // Suppresses any errors and returns empty set
 1008  
                         // if something cannot be evaluated as it should.
 1009  3
                         Object lhsObj = evalExpression(element, node.getLeftNode(), vars);
 1010  3
                         if (!(lhsObj instanceof ModelElement)) {
 1011  1
                                 return Collections.EMPTY_SET;
 1012  
                         }
 1013  
                         try {
 1014  4
                                 return evalSetExpression((ModelElement) lhsObj,
 1015  2
                                                 node.getRightNode(), vars);
 1016  1
                         } catch (Exception ex) {
 1017  1
                                 return Collections.EMPTY_SET;
 1018  
                         }
 1019  
                 }
 1020  
         }
 1021  
 
 1022  
         /**
 1023  
          * Base class for binary set operations where both operands are sets.
 1024  
          */
 1025  4
         abstract static class BinarySetOperation extends SetOperation {
 1026  
                 @Override
 1027  
                 public Collection<?> calculateValue(ModelElement element,
 1028  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 1029  
 
 1030  
                         // evaluate the left and right hand side operands
 1031  20
                         Collection<?> leftValue = evalSetExpression(element,
 1032  10
                                         node.getLeftNode(), vars);
 1033  20
                         Collection<?> rightValue = evalSetExpression(element,
 1034  10
                                         node.getRightNode(), vars);
 1035  
 
 1036  10
                         return calculateValue(leftValue, rightValue);
 1037  
                 }
 1038  
 
 1039  
                 /**
 1040  
                  * Calculates the numerical value of the set operation.
 1041  
                  * 
 1042  
                  * @param left The value of the left hand side operand.
 1043  
                  * @param right The value of the right hand side operand.
 1044  
                  * @return Result set for the operation.
 1045  
                  */
 1046  
                 public abstract Collection<?> calculateValue(Collection<?> left,
 1047  
                                 Collection<?> right);
 1048  
         }
 1049  
 
 1050  
         /**
 1051  
          * Calculates the union of two sets. Result is a multiset if any of the
 1052  
          * operands is a multiset.
 1053  
          */
 1054  1
         public static class UnionOperation extends BinarySetOperation {
 1055  
                 @SuppressWarnings("unchecked")
 1056  
                 @Override
 1057  
                 public Collection<?> calculateValue(Collection<?> left,
 1058  
                                 Collection<?> right) {
 1059  5
                         boolean isMultiSet = MetricTools.isMultiSet(right)
 1060  2
                                         || MetricTools.isMultiSet(left);
 1061  
                         @SuppressWarnings("rawtypes")
 1062  4
                         Collection result = MetricTools.createHashSet(isMultiSet, left);
 1063  4
                         result.addAll(right);
 1064  4
                         return result;
 1065  
                 }
 1066  
         }
 1067  
 
 1068  
         /**
 1069  
          * Calculates the difference of two sets. The result is of the same type as
 1070  
          * the base (left hand side) set.
 1071  
          */
 1072  1
         public static class DifferenceOperation extends BinarySetOperation {
 1073  
                 @Override
 1074  
                 public Collection<?> calculateValue(Collection<?> left,
 1075  
                                 Collection<?> right) {
 1076  2
                         Collection<?> result = MetricTools.createHashSet(
 1077  2
                                         MetricTools.isMultiSet(left), left);
 1078  2
                         result.removeAll(right);
 1079  2
                         return result;
 1080  
                 }
 1081  
         }
 1082  
 
 1083  
         /**
 1084  
          * Calculates the intersection of two sets. Result is a multiset if both
 1085  
          * sets are multisets. Otherwise, the result is a regular set.
 1086  
          */
 1087  2
         public static class IntersectionOperation extends BinarySetOperation {
 1088  
                 @Override
 1089  
                 public Collection<?> calculateValue(Collection<?> left,
 1090  
                                 Collection<?> right) {
 1091  5
                         boolean isMultiSet = MetricTools.isMultiSet(right)
 1092  2
                                         && MetricTools.isMultiSet(left);
 1093  4
                         Collection<?> result = MetricTools.createHashSet(isMultiSet, left);
 1094  4
                         result.retainAll(right);
 1095  4
                         return result;
 1096  
                 }
 1097  
         }
 1098  
 }