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%
9/9
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%
12/12
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%
11/11
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  1365
                 return scalarOperations;
 196  
         }
 197  
 
 198  
         /**
 199  
          * Provides access to the Boolean operations.
 200  
          * 
 201  
          * @return The Boolean operations.
 202  
          */
 203  
         ProcedureCache<BooleanOperation> getBooleanOperations() {
 204  632
                 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  1
                         return Integer.valueOf(c.size());
 267  
                 }
 268  
         }
 269  
 
 270  
         /**
 271  
          * Calculates the length of a string.
 272  
          */
 273  13
         public static class LengthFunction extends ScalarOperation {
 274  
 
 275  
                 @Override
 276  
                 public Integer calculateValue(ModelElement element,
 277  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 278  371
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 279  371
                         return Integer.valueOf(leftValue.toString().length());
 280  
                 }
 281  
         }
 282  
 
 283  
         /**
 284  
          * Converts all of the characters in a string to lower case. The default
 285  
          * locale applies.
 286  
          */
 287  1
         public static class ToLowerFunction extends ScalarOperation {
 288  
 
 289  
                 @Override
 290  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 291  
                                 Variables vars) throws SDMetricsException {
 292  1
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 293  1
                         return leftValue.toString().toLowerCase();
 294  
                 }
 295  
         }
 296  
 
 297  
         /**
 298  
          * Returns the type name of a model element as a string.
 299  
          */
 300  2
         public static class TypeOfFunction extends ScalarOperation {
 301  
 
 302  
                 @Override
 303  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 304  
                                 Variables vars) throws SDMetricsException {
 305  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 306  4
                         if (leftValue instanceof ModelElement) {
 307  1
                                 ModelElement elem = (ModelElement) leftValue;
 308  1
                                 return elem.getType().getName();
 309  
                         }
 310  3
                         if (MetricTools.isEmptyElement(leftValue))
 311  1
                                 return "";
 312  4
                         throw new SDMetricsException(element, null, "Operator '"
 313  2
                                         + node.getValue()
 314  2
                                         + "' can only be applied to model elements.");
 315  
                 }
 316  
         }
 317  
 
 318  
         /**
 319  
          * Returns the fully qualified name of a model element as a string.
 320  
          */
 321  2
         public static class QualifiedNameFunction extends ScalarOperation {
 322  
                 @Override
 323  
                 public String calculateValue(ModelElement element, ExpressionNode node,
 324  
                                 Variables vars) throws SDMetricsException {
 325  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 326  4
                         if (leftValue instanceof ModelElement) {
 327  1
                                 ModelElement elem = (ModelElement) leftValue;
 328  1
                                 return elem.getFullName();
 329  
                         }
 330  3
                         if (MetricTools.isEmptyElement(leftValue))
 331  1
                                 return "";
 332  4
                         throw new SDMetricsException(element, null, "Operator '"
 333  2
                                         + node.getValue()
 334  2
                                         + "' can only be applied to model elements.");
 335  
                 }
 336  
         }
 337  
 
 338  
         /**
 339  
          * Processes the dot operator in metric expressions.
 340  
          */
 341  10
         public static class DotOperator extends ScalarOperation {
 342  
                 @Override
 343  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 344  
                                 Variables vars) throws SDMetricsException {
 345  
                         // Suppresses any errors and return empty string
 346  
                         // if something cannot be evaluated as it should.
 347  583
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 348  583
                         if (!(leftValue instanceof ModelElement))
 349  1
                                 return "";
 350  
                         try {
 351  1164
                                 return evalExpression((ModelElement) leftValue,
 352  582
                                                 node.getRightNode(), vars);
 353  2
                         } catch (Exception ex) {
 354  2
                                 return "";
 355  
                         }
 356  
                 }
 357  
         }
 358  
 
 359  
         /**
 360  
          * Processes the "upto" operator.
 361  
          */
 362  1
         public static class UpToOperator extends ScalarOperation {
 363  
                 @Override
 364  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 365  
                                 Variables vars) throws SDMetricsException {
 366  2
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 367  11
                         while (leftValue != null) {
 368  8
                                 if (leftValue instanceof ModelElement) {
 369  7
                                         ModelElement elem = (ModelElement) leftValue;
 370  7
                                         if (evalBooleanExpression(elem, node.getRightNode(), vars))
 371  1
                                                 return elem;
 372  6
                                         leftValue = evalExpression(elem, node.getLeftNode(), vars);
 373  6
                                 } else
 374  1
                                         leftValue = null;
 375  
                         }
 376  1
                         return "";
 377  
                 }
 378  
         }
 379  
 
 380  
         /**
 381  
          * Processes the "topmost" operator.
 382  
          */
 383  1
         public static class TopMostOperator extends ScalarOperation {
 384  
                 @Override
 385  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 386  
                                 Variables vars) throws SDMetricsException {
 387  1
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 388  1
                         Object result = "";
 389  8
                         while (leftValue != null) {
 390  6
                                 if (leftValue instanceof ModelElement) {
 391  5
                                         ModelElement elem = (ModelElement) leftValue;
 392  5
                                         if (evalBooleanExpression(elem, node.getRightNode(), vars))
 393  3
                                                 result = elem;
 394  5
                                         leftValue = evalExpression(elem, node.getLeftNode(), vars);
 395  5
                                 } else
 396  1
                                         leftValue = null;
 397  
                         }
 398  1
                         return result;
 399  
                 }
 400  
         }
 401  
 
 402  
         /**
 403  
          * Determines the cardinality of an element in a set.
 404  
          */
 405  1
         public static class InOperator extends ScalarOperation {
 406  
                 @Override
 407  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 408  
                                 Variables vars) throws SDMetricsException {
 409  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 410  3
                         ExpressionNode rightNode = node.getRightNode();
 411  3
                         Collection<?> c = evalSetExpression(element, rightNode, vars);
 412  3
                         return Integer.valueOf(MetricTools.elementCount(c, leftValue));
 413  
                 }
 414  
         }
 415  
 
 416  
         /**
 417  
          * Calculates the unary and binary plus operator for metric expressions.
 418  
          * Performs numerical addition or string concatenation, depending on the
 419  
          * types of the elements.
 420  
          */
 421  7
         public static class PlusOperator extends ScalarOperation {
 422  
                 @Override
 423  
                 public Object calculateValue(ModelElement element, ExpressionNode node,
 424  
                                 Variables vars) throws SDMetricsException {
 425  212
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 426  
 
 427  
                         // process unary operator
 428  212
                         if (node.getOperandCount() == 1)
 429  1
                                 return leftValue;
 430  
 
 431  
                         // String concatenation if left hand side is not a number
 432  211
                         if (!(leftValue instanceof Number)) {
 433  418
                                 Object rightValue = evalExpression(element,
 434  209
                                                 node.getRightNode(), vars);
 435  209
                                 if (rightValue instanceof Number)
 436  2
                                         return leftValue.toString()
 437  2
                                                         + java.text.NumberFormat.getInstance().format(
 438  1
                                                                         rightValue);
 439  208
                                 return leftValue.toString() + rightValue.toString();
 440  
                         }
 441  
 
 442  
                         // numerical addition
 443  2
                         float left = getFloatValue(leftValue, element);
 444  2
                         float right = getFloatValue(element, node.getRightNode(), vars);
 445  2
                         return Float.valueOf(left + right);
 446  
                 }
 447  
         }
 448  
 
 449  
         /**
 450  
          * Calculates the unary and binary minus operator for metric expressions.
 451  
          */
 452  4
         public static class MinusOperator extends ScalarOperation {
 453  
 
 454  
                 @Override
 455  
                 public Float calculateValue(ModelElement element, ExpressionNode node,
 456  
                                 Variables vars) throws SDMetricsException {
 457  6
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 458  6
                         if (node.getOperandCount() == 1)
 459  5
                                 return Float.valueOf(-left);
 460  1
                         float right = getFloatValue(element, node.getRightNode(), vars);
 461  1
                         return Float.valueOf(left - right);
 462  
                 }
 463  
         }
 464  
 
 465  
         /**
 466  
          * Base class for binary metric operators that operate on numerical values
 467  
          * only.
 468  
          */
 469  4
         static abstract class BinaryNumericalOperator extends ScalarOperation {
 470  
                 @Override
 471  
                 final public Float calculateValue(ModelElement element,
 472  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 473  5
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 474  5
                         float right = getFloatValue(element, node.getRightNode(), vars);
 475  4
                         return Float.valueOf(calculateValue(left, right));
 476  
                 }
 477  
 
 478  
                 /**
 479  
                  * Calculates the numerical value of the operation.
 480  
                  * 
 481  
                  * @param left The value of the left hand side operand.
 482  
                  * @param right The value of the right hand side operand.
 483  
                  * @return Value of the operation.
 484  
                  */
 485  
                 protected abstract float calculateValue(float left, float right);
 486  
         }
 487  
 
 488  
         /**
 489  
          * Calculates the product of two numbers.
 490  
          */
 491  1
         public static class MultiplicationOperator extends BinaryNumericalOperator {
 492  
                 @Override
 493  
                 protected float calculateValue(float left, float right) {
 494  1
                         return left * right;
 495  
                 }
 496  
         }
 497  
 
 498  
         /**
 499  
          * Calculates the quotient of two numbers.
 500  
          */
 501  2
         public static class DivisionOperator extends BinaryNumericalOperator {
 502  
                 @Override
 503  
                 protected float calculateValue(float left, float right) {
 504  2
                         return left / right;
 505  
                 }
 506  
         }
 507  
 
 508  
         /**
 509  
          * Exponentiates two numbers.
 510  
          */
 511  1
         public static class ExponentiationOperator extends BinaryNumericalOperator {
 512  
                 @Override
 513  
                 protected float calculateValue(float left, float right) {
 514  1
                         return (float) java.lang.Math.pow(left, right);
 515  
                 }
 516  
         }
 517  
 
 518  
         /**
 519  
          * Base class for unary metric operators that operate on numerical values
 520  
          * only.
 521  
          */
 522  8
         static abstract class UnaryNumericalOperator extends ScalarOperation {
 523  
                 @Override
 524  
                 final public Float calculateValue(ModelElement element,
 525  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 526  9
                         float left = getFloatValue(element, node.getLeftNode(), vars);
 527  9
                         return Float.valueOf(calculateValue(left));
 528  
                 }
 529  
 
 530  
                 /**
 531  
                  * Calculates the numerical value of the operation.
 532  
                  * 
 533  
                  * @param operand The value of the single operand.
 534  
                  * @return Value of the operation.
 535  
                  */
 536  
                 protected abstract float calculateValue(float operand);
 537  
         }
 538  
 
 539  
         /**
 540  
          * Calculates the natural logarithm (base <code>e</code> of a value.
 541  
          */
 542  2
         public static class LogarithmFunction extends UnaryNumericalOperator {
 543  
                 @Override
 544  
                 protected float calculateValue(float left) {
 545  2
                         return (float) Math.log(left);
 546  
                 }
 547  
         }
 548  
 
 549  
         /**
 550  
          * Calculates the <code>e</code> raised to the power of a value.
 551  
          */
 552  1
         public static class ExponentiationFunction extends UnaryNumericalOperator {
 553  
                 @Override
 554  
                 protected float calculateValue(float left) {
 555  1
                         return (float) Math.exp(left);
 556  
                 }
 557  
         }
 558  
 
 559  
         /**
 560  
          * Calculates the square root of a value.
 561  
          */
 562  1
         public static class SquareRootFunction extends UnaryNumericalOperator {
 563  
                 @Override
 564  
                 protected float calculateValue(float left) {
 565  1
                         return (float) Math.sqrt(left);
 566  
                 }
 567  
         }
 568  
 
 569  
         /**
 570  
          * Calculates the absolute value of value.
 571  
          */
 572  1
         public static class AbsoluteFunction extends UnaryNumericalOperator {
 573  
                 @Override
 574  
                 protected float calculateValue(float left) {
 575  1
                         return Math.abs(left);
 576  
                 }
 577  
         }
 578  
 
 579  
         /**
 580  
          * Rounds to the next lower integer (towards -infinity).
 581  
          */
 582  1
         public static class FloorFunction extends UnaryNumericalOperator {
 583  
                 @Override
 584  
                 protected float calculateValue(float left) {
 585  1
                         return (float) Math.floor(left);
 586  
                 }
 587  
         }
 588  
 
 589  
         /**
 590  
          * Rounds to the next higher integer (towards +infinity).
 591  
          */
 592  1
         public static class CeilingFunction extends UnaryNumericalOperator {
 593  
                 @Override
 594  
                 protected float calculateValue(float left) {
 595  1
                         return (float) Math.ceil(left);
 596  
                 }
 597  
         }
 598  
 
 599  
         /**
 600  
          * Rounds to the nearest integer ("half up").
 601  
          */
 602  1
         public static class RoundFunction extends UnaryNumericalOperator {
 603  
                 @Override
 604  
                 protected float calculateValue(float left) {
 605  2
                         return (float) Math.floor(left + 0.5);
 606  
                 }
 607  
         }
 608  
 
 609  
         /**
 610  
          * Returns the numerical value of a string containing a number. Assumes
 611  
          * Java-style formatting for decimal numbers.
 612  
          * 
 613  
          * @since 2.31
 614  
          */
 615  2
         public static class ParseNumberFunction extends ScalarOperation {
 616  
 
 617  
                 @Override
 618  
                 public Number calculateValue(ModelElement element, ExpressionNode node,
 619  
                                 Variables vars) throws SDMetricsException {
 620  10
                         String str = evalExpression(element, node.getOperand(0), vars)
 621  5
                                         .toString();
 622  
                         try {
 623  5
                                 float value = Float.parseFloat(str);
 624  4
                                 return MetricTools.getNumber(value);
 625  1
                         } catch (NumberFormatException ex) {
 626  2
                                 throw new SDMetricsException(element, null,
 627  1
                                                 "Could not parse as number: '" + str + "'");
 628  
                         }
 629  
                 }
 630  
         }
 631  
 
 632  
         // Boolean operations
 633  
 
 634  
         /**
 635  
          * Calculates the "and" operator. Short-circuits if possible.
 636  
          */
 637  1
         public static class AndOperator extends BooleanOperation {
 638  
                 @Override
 639  
                 public boolean calculateValue(ModelElement element,
 640  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 641  3
                         if (evalBooleanExpression(element, node.getLeftNode(), vars))
 642  2
                                 return evalBooleanExpression(element, node.getRightNode(), vars);
 643  1
                         return false;
 644  
                 }
 645  
         }
 646  
 
 647  
         /**
 648  
          * Calculates the "or" operator. Short-circuits if possible.
 649  
          */
 650  2
         public static class OrOperator extends BooleanOperation {
 651  
                 @Override
 652  
                 public boolean calculateValue(ModelElement element,
 653  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 654  6
                         if (!evalBooleanExpression(element, node.getLeftNode(), vars))
 655  5
                                 return evalBooleanExpression(element, node.getRightNode(), vars);
 656  1
                         return true;
 657  
                 }
 658  
         }
 659  
 
 660  
         /**
 661  
          * Calculates the "not" operator.
 662  
          */
 663  2
         public static class NotOperator extends BooleanOperation {
 664  
                 @Override
 665  
                 public boolean calculateValue(ModelElement element,
 666  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 667  8
                         return !evalBooleanExpression(element, node.getLeftNode(), vars);
 668  
                 }
 669  
         }
 670  
 
 671  
         /**
 672  
          * Checks if all elements in a multiset have cardinality 1.
 673  
          */
 674  1
         public static class IsUniqueFunction extends BooleanOperation {
 675  
 
 676  
                 @Override
 677  
                 public boolean calculateValue(ModelElement element,
 678  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 679  6
                         Collection<?> c = evalSetExpression(element, node.getLeftNode(),
 680  3
                                         vars);
 681  3
                         if (!(MetricTools.isMultiSet(c)))
 682  1
                                 return true;
 683  
 
 684  2
                         return c.size() == ((HashMultiSet<?>) c).flatSetSize();
 685  
                 }
 686  
         }
 687  
 
 688  
         /**
 689  
          * Checks if a string starts with a capital (upper case) letter.
 690  
          */
 691  1
         public static class StartsWithCapitalFunction extends BooleanOperation {
 692  
 
 693  
                 @Override
 694  
                 public boolean calculateValue(ModelElement element,
 695  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 696  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 697  3
                         String s = leftValue.toString();
 698  3
                         if (s.length() < 1)
 699  1
                                 return true;
 700  2
                         return s.charAt(0) == Character.toUpperCase(s.charAt(0));
 701  
                 }
 702  
         }
 703  
 
 704  
         /**
 705  
          * Checks if a string starts with a lower case letter.
 706  
          */
 707  1
         public static class StartsWithLowerCaseFunction 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  2
                         return s.charAt(0) == Character.toLowerCase(s.charAt(0));
 717  
                 }
 718  
         }
 719  
 
 720  
         /**
 721  
          * Checks that a string does not contain any capital letters.
 722  
          */
 723  1
         public static class IsLowerCaseFunction extends BooleanOperation {
 724  
 
 725  
                 @Override
 726  
                 public boolean calculateValue(ModelElement element,
 727  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 728  3
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 729  3
                         String s = leftValue.toString();
 730  3
                         return s.equals(s.toLowerCase());
 731  
                 }
 732  
         }
 733  
 
 734  
         /**
 735  
          * Checks that a model element has a particular type or one of its
 736  
          * sub-types.
 737  
          */
 738  2
         public static class InstanceOfFunction extends BooleanOperation {
 739  
                 @Override
 740  
                 public boolean calculateValue(ModelElement element,
 741  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 742  
 
 743  
                         // Candidate element is either "self" (if there is only one argument
 744  
                         // to the function), or the first argument of the function
 745  6
                         ModelElement candidate = element;
 746  6
                         if (node.getOperandCount() == 2) {
 747  3
                                 Object o = evalExpression(element, node.getOperand(0), vars);
 748  3
                                 if (MetricTools.isEmptyElement(o))
 749  1
                                         return false;
 750  2
                                 if (!(o instanceof ModelElement))
 751  2
                                         throw new SDMetricsException(element, null,
 752  2
                                                         "First argument of binary function "
 753  1
                                                                         + node.getValue()
 754  1
                                                                         + " must yield a model element");
 755  1
                                 candidate = (ModelElement) o;
 756  
                         }
 757  
 
 758  
                         // Type to check is the last argument of the function
 759  8
                         Object base = evalExpression(element,
 760  4
                                         node.getOperand(node.getOperandCount() - 1), vars);
 761  4
                         MetaModelElement baseType = null;
 762  4
                         if (base instanceof ModelElement) {
 763  1
                                 baseType = ((ModelElement) base).getType();
 764  1
                         } else {
 765  3
                                 baseType = getMetaModel().getType(base.toString());
 766  
                         }
 767  
 
 768  4
                         if (baseType == null) {
 769  2
                                 throw new SDMetricsException(element, null,
 770  1
                                                 "Unknown base type '" + base.toString() + "'");
 771  
                         }
 772  
 
 773  
                         // Check if model element is an instance of the base type
 774  3
                         return candidate.getType().specializes(baseType);
 775  
                 }
 776  
         }
 777  
 
 778  
         /**
 779  
          * Checks if a set contains a particular model element or value.
 780  
          */
 781  1
         public static class InOperatorBool extends BooleanOperation {
 782  
 
 783  
                 @Override
 784  
                 public boolean calculateValue(ModelElement element,
 785  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 786  4
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 787  8
                         Collection<?> c = evalSetExpression(element, node.getRightNode(),
 788  4
                                         vars);
 789  4
                         return c.contains(leftValue);
 790  
                 }
 791  
         }
 792  
 
 793  
         /**
 794  
          * Checks if a word list contains a particular word.
 795  
          */
 796  2
         public static class OnListOperator extends BooleanOperation {
 797  
 
 798  
                 @Override
 799  
                 public boolean calculateValue(ModelElement element,
 800  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 801  9
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 802  9
                         ExpressionNode rhsNode = node.getRightNode();
 803  
                         String wordListName;
 804  9
                         if (rhsNode.isIdentifier() || rhsNode.isStringConstant())
 805  8
                                 wordListName = rhsNode.getValue();
 806  
                         else
 807  2
                                 wordListName = evalExpression(element, rhsNode, vars)
 808  1
                                                 .toString();
 809  18
                         return getMetricsEngine().getMetricStore().isWordOnList(
 810  9
                                         leftValue.toString(), wordListName);
 811  
                 }
 812  
         }
 813  
 
 814  
         /**
 815  
          * Base class for binary comparison operators. The operators perform
 816  
          * numerical comparisons when both arguments are numbers, and string or
 817  
          * object comparisons otherwise.
 818  
          */
 819  38
         static abstract class BooleanComparator extends BooleanOperation {
 820  
                 @Override
 821  
                 public boolean calculateValue(ModelElement element,
 822  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 823  421
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 824  840
                         Object rightValue = evalExpression(element, node.getRightNode(),
 825  420
                                         vars);
 826  
 
 827  420
                         if (leftValue instanceof Number)
 828  383
                                 if (rightValue instanceof Number) {
 829  382
                                         float lf = ((Number) leftValue).floatValue();
 830  382
                                         float rf = ((Number) rightValue).floatValue();
 831  
 
 832  382
                                         return compare(lf, rf);
 833  
                                 }
 834  
 
 835  38
                         return compare(leftValue, rightValue);
 836  
                 }
 837  
 
 838  
                 /**
 839  
                  * Performs a numerical comparison.
 840  
                  * 
 841  
                  * @param left left hand side operand to compare
 842  
                  * @param right right hand side operand to compare
 843  
                  * @return result of the comparison
 844  
                  */
 845  
                 protected abstract boolean compare(float left, float right);
 846  
 
 847  
                 /**
 848  
                  * Performs an object or string comparison.
 849  
                  * 
 850  
                  * @param left left hand side operand to compare
 851  
                  * @param right right hand side operand to compare
 852  
                  * @return result of the comparison
 853  
                  */
 854  
                 protected abstract boolean compare(Object left, Object right);
 855  
 
 856  
         }
 857  
 
 858  
         /**
 859  
          * Compares for equality. Object identity is determined by the equals()
 860  
          * method.
 861  
          */
 862  9
         public static class EqualsOperator extends BooleanComparator {
 863  
                 @Override
 864  
                 protected boolean compare(float left, float right) {
 865  29
                         return left == right;
 866  
                 }
 867  
 
 868  
                 @Override
 869  
                 protected boolean compare(Object left, Object right) {
 870  13
                         return left.equals(right);
 871  
                 }
 872  
         }
 873  
 
 874  
         /**
 875  
          * Compares for non-equality. Object identity is determined by the equals()
 876  
          * method.
 877  
          */
 878  6
         public static class NotEqualsOperator extends BooleanComparator {
 879  
                 @Override
 880  
                 protected boolean compare(float left, float right) {
 881  32
                         return left != right;
 882  
                 }
 883  
 
 884  
                 @Override
 885  
                 protected boolean compare(Object left, Object right) {
 886  17
                         return !left.equals(right);
 887  
                 }
 888  
         }
 889  
 
 890  
         /** Performs "greater than" comparisons for numerical or string values. */
 891  7
         public static class GreaterThanOperator extends BooleanComparator {
 892  
                 @Override
 893  
                 protected boolean compare(float left, float right) {
 894  8
                         return left > right;
 895  
                 }
 896  
 
 897  
                 @Override
 898  
                 protected boolean compare(Object left, Object right) {
 899  2
                         int comp = left.toString().compareTo(right.toString());
 900  2
                         return comp > 0;
 901  
                 }
 902  
         }
 903  
 
 904  
         /** Performs "greater or equal" comparisons for numerical or string values. */
 905  2
         public static class GreaterOrEqualOperator extends BooleanComparator {
 906  
                 @Override
 907  
                 protected boolean compare(float left, float right) {
 908  2
                         return left >= right;
 909  
                 }
 910  
 
 911  
                 @Override
 912  
                 protected boolean compare(Object left, Object right) {
 913  2
                         int comp = left.toString().compareTo(right.toString());
 914  2
                         return comp >= 0;
 915  
                 }
 916  
         }
 917  
 
 918  
         /** Performs "less than" comparisons for numerical or string values. */
 919  11
         public static class LessThanOperator extends BooleanComparator {
 920  
                 @Override
 921  
                 protected boolean compare(float left, float right) {
 922  262
                         return left < right;
 923  
                 }
 924  
 
 925  
                 @Override
 926  
                 protected boolean compare(Object left, Object right) {
 927  2
                         int comp = left.toString().compareTo(right.toString());
 928  2
                         return comp < 0;
 929  
                 }
 930  
         }
 931  
 
 932  
         /** Performs "greater or equal" comparisons for numerical or string values. */
 933  3
         public static class LessOrEqualOperator extends BooleanComparator {
 934  
                 @Override
 935  
                 protected boolean compare(float left, float right) {
 936  49
                         return left <= right;
 937  
                 }
 938  
 
 939  
                 @Override
 940  
                 protected boolean compare(Object left, Object right) {
 941  2
                         int comp = left.toString().compareTo(right.toString());
 942  2
                         return comp <= 0;
 943  
                 }
 944  
         }
 945  
 
 946  
         /**
 947  
          * Checks if a string value starts with a specified prefix.
 948  
          */
 949  2
         public static class StartsWithOperator extends BooleanOperation {
 950  
 
 951  
                 @Override
 952  
                 public boolean calculateValue(ModelElement element,
 953  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 954  7
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 955  14
                         Object rightValue = evalExpression(element, node.getRightNode(),
 956  7
                                         vars);
 957  7
                         return leftValue.toString().startsWith(rightValue.toString());
 958  
                 }
 959  
         }
 960  
 
 961  
         /**
 962  
          * Checks if a string value ends with a specified prefix.
 963  
          */
 964  1
         public static class EndsWithOperator extends BooleanOperation {
 965  
 
 966  
                 @Override
 967  
                 public boolean calculateValue(ModelElement element,
 968  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 969  2
                         Object leftValue = evalExpression(element, node.getLeftNode(), vars);
 970  4
                         Object rightValue = evalExpression(element, node.getRightNode(),
 971  2
                                         vars);
 972  2
                         return leftValue.toString().endsWith(rightValue.toString());
 973  
                 }
 974  
         }
 975  
 
 976  
         // Set operations
 977  
 
 978  
         /**
 979  
          * Calculates the dot operator for set expressions.
 980  
          */
 981  1
         public static class DotOperatorSet extends SetOperation {
 982  
                 @Override
 983  
                 public Collection<?> calculateValue(ModelElement element,
 984  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 985  
                         // Suppresses any errors and returns empty set
 986  
                         // if something cannot be evaluated as it should.
 987  3
                         Object lhsObj = evalExpression(element, node.getLeftNode(), vars);
 988  3
                         if (!(lhsObj instanceof ModelElement))
 989  1
                                 return Collections.EMPTY_SET;
 990  
                         try {
 991  4
                                 return evalSetExpression((ModelElement) lhsObj,
 992  2
                                                 node.getRightNode(), vars);
 993  1
                         } catch (Exception ex) {
 994  1
                                 return Collections.EMPTY_SET;
 995  
                         }
 996  
                 }
 997  
         }
 998  
 
 999  
         /**
 1000  
          * Base class for binary set operations where both operands are sets.
 1001  
          */
 1002  4
         static abstract class BinarySetOperation extends SetOperation {
 1003  
                 @Override
 1004  
                 public Collection<?> calculateValue(ModelElement element,
 1005  
                                 ExpressionNode node, Variables vars) throws SDMetricsException {
 1006  
 
 1007  
                         // evaluate the left and right hand side operands
 1008  20
                         Collection<?> leftValue = evalSetExpression(element,
 1009  10
                                         node.getLeftNode(), vars);
 1010  20
                         Collection<?> rightValue = evalSetExpression(element,
 1011  10
                                         node.getRightNode(), vars);
 1012  
 
 1013  10
                         return calculateValue(leftValue, rightValue);
 1014  
                 }
 1015  
 
 1016  
                 /**
 1017  
                  * Calculates the numerical value of the set operation.
 1018  
                  * 
 1019  
                  * @param left The value of the left hand side operand.
 1020  
                  * @param right The value of the right hand side operand.
 1021  
                  * @return Result set for the operation.
 1022  
                  */
 1023  
                 public abstract Collection<?> calculateValue(Collection<?> left,
 1024  
                                 Collection<?> right);
 1025  
         }
 1026  
 
 1027  
         /**
 1028  
          * Calculates the union of two sets. Result is a multiset if any of the
 1029  
          * operands is a multiset.
 1030  
          */
 1031  1
         public static class UnionOperation extends BinarySetOperation {
 1032  
                 @SuppressWarnings("unchecked")
 1033  
                 @Override
 1034  
                 public Collection<?> calculateValue(Collection<?> left,
 1035  
                                 Collection<?> right) {
 1036  5
                         boolean isMultiSet = MetricTools.isMultiSet(right)
 1037  2
                                         || MetricTools.isMultiSet(left);
 1038  
                         @SuppressWarnings("rawtypes")
 1039  4
                         Collection result = MetricTools.createHashSet(isMultiSet, left);
 1040  4
                         result.addAll(right);
 1041  4
                         return result;
 1042  
                 }
 1043  
         }
 1044  
 
 1045  
         /**
 1046  
          * Calculates the difference of two sets. The result is of the same type as
 1047  
          * the base (left hand side) set.
 1048  
          */
 1049  1
         public static class DifferenceOperation extends BinarySetOperation {
 1050  
                 @Override
 1051  
                 public Collection<?> calculateValue(Collection<?> left,
 1052  
                                 Collection<?> right) {
 1053  2
                         Collection<?> result = MetricTools.createHashSet(
 1054  2
                                         MetricTools.isMultiSet(left), left);
 1055  2
                         result.removeAll(right);
 1056  2
                         return result;
 1057  
                 }
 1058  
         }
 1059  
 
 1060  
         /**
 1061  
          * Calculates the intersection of two sets. Result is a multiset if both
 1062  
          * sets are multisets. Otherwise, the result is a regular set.
 1063  
          */
 1064  2
         public static class IntersectionOperation extends BinarySetOperation {
 1065  
                 @Override
 1066  
                 public Collection<?> calculateValue(Collection<?> left,
 1067  
                                 Collection<?> right) {
 1068  5
                         boolean isMultiSet = MetricTools.isMultiSet(right)
 1069  2
                                         && MetricTools.isMultiSet(left);
 1070  4
                         Collection<?> result = MetricTools.createHashSet(isMultiSet, left);
 1071  4
                         result.retainAll(right);
 1072  4
                         return result;
 1073  
                 }
 1074  
         }
 1075  
 }