001    package org.hackystat.telemetry.analyzer.function.impl;
002    
003    import java.util.List;
004    
005    import org.hackystat.telemetry.analyzer.function.TelemetryFunctionException;
006    import org.hackystat.telemetry.analyzer.model.TelemetryDataPoint;
007    import org.hackystat.telemetry.analyzer.model.TelemetryStream;
008    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
009    import org.hackystat.utilities.time.period.TimePeriod;
010    
011    /**
012     * Helper class to perform binary operations on <code>TelemetryStreamCollection</code> objects.
013     * 
014     * @author (Cedric) Qin ZHANG, Philip Johnson
015     */
016    class BinaryOperationUtility {
017      
018      /**
019       * Binary operator interface.
020       * 
021       * @author (Cedric) Qin ZHANG
022       */
023      interface BinaryOperator {
024        /**
025         * Performs binary operation.
026         * 
027         * @param a Number 1.
028         * @param b Number 2.
029         * @return The result.
030         */
031        Number computes(Number a, Number b);
032      }
033    
034      /**
035       * Applies binary operation to two <code>TelemetryStreamCollection</code> objects, 
036       * and generates a new <code>TelemetryStreamCollection</code> object.
037       * 
038       * @param operator The binary operator.
039       * @param streamCollection1 The first <code>TelemetryStreamCollection</code> object.
040       * @param streamCollection2 The second <code>TelemetryStreamCollection</code> object.
041       * @return A new <code>TelemetryStreamCollection</code> object after applying the operator.
042       * @throws TelemetryFunctionException If there is anything wrong.
043       */
044      static TelemetryStreamCollection computes(BinaryOperator operator,
045          TelemetryStreamCollection streamCollection1, TelemetryStreamCollection streamCollection2) 
046          throws TelemetryFunctionException {
047    
048        // check whether the streams in the two collections can be matched.
049        if (!streamCollection1.getProject().equals(streamCollection2.getProject())) {
050          throw new TelemetryFunctionException("Two stream collections are for different projects.");
051        }
052        if (!streamCollection1.getInterval().equals(streamCollection2.getInterval())) {
053          throw new TelemetryFunctionException("Two stream collections are for different intervals.");
054        }
055        if (streamCollection1.getTelemetryStreams().size() //NOPMD
056            != streamCollection2.getTelemetryStreams().size()) {
057          throw new TelemetryFunctionException(
058              "Two stream collections have different number of streams.");
059        }
060        else {
061          for (TelemetryStream stream1 : streamCollection1) {
062            if (streamCollection2.get(stream1.getTag()) == null) {
063              throw new TelemetryFunctionException("Two stream collections do not match.");
064            }
065          }
066        }
067    
068        //do computation
069        try {
070          TelemetryStreamCollection resultStreams = new TelemetryStreamCollection(null,
071              streamCollection1.getProject(), streamCollection2.getInterval());
072          for (TelemetryStream stream1 : streamCollection1) {
073            TelemetryStream stream2 = streamCollection2.get(stream1.getTag());
074            resultStreams.add(computes(operator, stream1, stream2));
075          }
076          return resultStreams;
077        }
078        catch (Exception ex) {
079          throw new TelemetryFunctionException(ex.getMessage(), ex);
080        }
081      }
082      
083      /**
084       * Applies binary operation to two telemetry streams and generates a new stream.
085       * The two source streams must:
086       * <ul>
087       * <li>Have the same tag.</li>
088       * <li>All time periods in the data points must match.</li>
089       * </ul>
090       * If one of the values in a data point is null, then a null will be put in the
091       * generated data point.
092       * 
093       * @param operator The binary operator.
094       * @param stream1 Source telemetry stream 1. Order may be important in certain
095       *        operations.
096       * @param stream2 Source telemetry stream 2. Order may be important in certain
097       *        operations.
098       * @return The new generated telemetry stream after applying the operator.
099       * @throws TelemetryFunctionException If there is any error.
100       */
101      private static TelemetryStream computes(BinaryOperator operator,
102          TelemetryStream stream1, TelemetryStream stream2) throws TelemetryFunctionException {
103        // check tag match
104        Object tag1 = stream1.getTag();
105        Object tag2 = stream2.getTag();
106        if (tag1 == null) {
107          if (tag2 != null) {
108            throw new TelemetryFunctionException(
109                "The tags of the two telemetry streams does not match.");
110          }
111        }
112        else {
113          if (!tag1.equals(tag2)) {
114            throw new TelemetryFunctionException(
115                "The tags of the two telemetry streams does not match.");
116          }
117        }
118        // check number of data points in the two streams
119        List<TelemetryDataPoint> dataPointSeries1 = stream1.getDataPoints();
120        List<TelemetryDataPoint> dataPointSeries2 = stream2.getDataPoints();
121        if (dataPointSeries1.size() != dataPointSeries2.size()) {
122          throw new TelemetryFunctionException("Two telemetry streams are of different size.");
123        }
124    
125        //do computation
126        TelemetryStream resultStream = new TelemetryStream(tag1);
127        try {
128          int size = dataPointSeries1.size();
129          for (int i = 0; i < size; i++) {
130            TelemetryDataPoint dataPoint1 = dataPointSeries1.get(i);
131            TelemetryDataPoint dataPoint2 = dataPointSeries2.get(i);
132            TimePeriod timePeriod = dataPoint1.getPeriod();
133            if (!timePeriod.equals(dataPoint2.getPeriod())) {
134              throw new TelemetryFunctionException(
135                  "Different time periods detected in the two streams.");
136            }
137            Number value1 = dataPoint1.getValue();
138            Number value2 = dataPoint2.getValue();
139            Number resultValue = null;
140            if (value1 != null && value2 != null) {
141              resultValue = operator.computes(value1, value2);
142            }
143            resultStream.addDataPoint(new TelemetryDataPoint(timePeriod, resultValue));
144          }
145        }
146        catch (Exception ex) {
147          throw new TelemetryFunctionException(ex.getMessage(), ex);
148        }
149        return resultStream;
150      }
151    }