Contents > 9 Extending the Metrics and Rule Engine > 9.6 Set Functions

9.6 Set Functions

A set function is used in set expressions (see Section 8.5 "Expression Terms") and yields an element set or a value set. To illustrate the implementation of set functions, we'll define one to calculate the symmetric difference of two sets.

The symmetric difference of two sets A and B is the set of elements contained in either A or B, but not both. For regular sets, we could express the symmetric difference in terms of the existing set operations as (A+B)-(A*B) (i.e., the union of the sets without the intersection of the sets, see Section 8.5.3 "Set Expressions"). For multisets, however, we must take the cardinality of elements into account: the cardinality of an element in the symmetric difference is the absolute difference of the cardinality of the element in sets A and B. For example, if the cardinality of element e is five in set A and three in set B, the cardinality of element e in the symmetric difference is two. The formula (A+B)-(A*B) would yield cardinality (5+3)-3=5 for element e and therefore cannot be used for multisets.

The following implementation handles both regular and multisets.

   packacke com.acme;
   import java.util.Collection;
   import java.util.Iterator;

   import com.sdmetrics.math.ExpressionNode;
   import com.sdmetrics.metrics.MetricTools;
   import com.sdmetrics.metrics.SDMetricsException;
   import com.sdmetrics.metrics.SetOperation;
   import com.sdmetrics.metrics.Variables;
   import com.sdmetrics.model.ModelElement;

01 public class SetOperationSymmDiff extends SetOperation {

   @Override
02 public Collection<?> calculateValue(ModelElement element,
      ExpressionNode node, Variables vars) throws SDMetricsException {

03   Collection<?> left = evalSetExpression(element, node.getOperand(0),
         vars);
04   Collection<?> right = evalSetExpression(element, node.getOperand(1),
         vars);

05   boolean isMultiSet = MetricTools.isMultiSet(right)
         || MetricTools.isMultiSet(left);
06   Collection<?> result = MetricTools.createHashSet(isMultiSet);

     // process elements from the first set
07   Iterator<?> it = MetricTools.getFlatIterator(left);
08   while (it.hasNext()) {
09     processElement(it.next(), result, left, right);
     }

     // process additional elements from the second set
10   it = MetricTools.getFlatIterator(right);
11   while (it.hasNext()) {
12     Object o = it.next();
13     if (!left.contains(o)) {
14       processElement(o, result, left, right);
       }
     }
15   return result;
   }

   @SuppressWarnings({ "unchecked", "rawtypes" })
16 private void processElement(Object o, Collection col, 
        Collection<?> left, Collection<?> right) {
17   int leftCount = MetricTools.elementCount(left, o);
18   int rightCount = MetricTools.elementCount(right, o);
19   int count = Math.abs(leftCount - rightCount);
20   for (int i = 0; i < count; i++)
21     col.add(o);
    }
}
Once more, we discuss the salient features of this implementation, line by line. To use the set function, we register it with the metrics engine as follows:
<setoperationdefinition name="symmdiff" 
   class="com.acme.SetOperationSymmDiff" />
Again, we deploy the class file of the class in the "bin" folder of our SDMetrics installation (path com/acme/SetOperationSymmDiff.class). After that, we can write set expressions using the new function. For example:
<metric name="FooBar" domain="package">
<compoundmetric term="size(symmdiff(FooBarClassesSet, FooBazClassesSet))" />
</metric>