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 }