001    package org.hackystat.telemetry.analyzer.evaluator;
002    
003    import org.hackystat.telemetry.analyzer.function.TelemetryFunctionManager;
004    import org.hackystat.telemetry.analyzer.language.ast.ChartReference;
005    import org.hackystat.telemetry.analyzer.language.ast.Constant;
006    import org.hackystat.telemetry.analyzer.language.ast.Expression;
007    import org.hackystat.telemetry.analyzer.language.ast.FunctionCall;
008    import org.hackystat.telemetry.analyzer.language.ast.NumberConstant;
009    import org.hackystat.telemetry.analyzer.language.ast.ReducerCall;
010    import org.hackystat.telemetry.analyzer.language.ast.StreamsReference;
011    import org.hackystat.telemetry.analyzer.language.ast.StringConstant;
012    import org.hackystat.telemetry.analyzer.language.ast.TelemetryChartDefinition;
013    import org.hackystat.telemetry.analyzer.language.ast.TelemetryChartYAxisDefinition;
014    import org.hackystat.telemetry.analyzer.language.ast.TelemetryReportDefinition;
015    import org.hackystat.telemetry.analyzer.language.ast.TelemetryStreamsDefinition;
016    import org.hackystat.telemetry.analyzer.language.ast.Variable;
017    import org.hackystat.telemetry.analyzer.language.ast.YAxisReference;
018    import org.hackystat.telemetry.analyzer.model.TelemetryStream;
019    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
020    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducerManager;
021    import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
022    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
023    import org.hackystat.utilities.time.interval.Interval;
024    
025    /**
026     * Provides an evaluation function for Telemetry, in which a definition is evaluated with 
027     * respect to a set of variables and their values, a Project, and an Interval. 
028     * 
029     * @author (Cedric) Qin ZHANG
030     */
031    public class TelemetryEvaluator {
032    
033      /**
034       * Private constructor to prevent this class from being instantiated.
035       */
036      private TelemetryEvaluator() {
037      }
038      
039      /**
040       * Evaluates a telemetry streams definition to produce a 
041       * <code>TelemetryStreamsObject</code> object.
042       * 
043       * @param streamsDefinition The telemetry streams definition.
044       * @param variableResolver The variable resolver.
045       * @param project The project.
046       * @param dpdClient The DPD Client.
047       * @param interval The interval.
048       * 
049       * @return An instance of a <code>TelemetryStreamsObject</code> object.  
050       * 
051       * @throws TelemetryEvaluationException If there is any error during the evaluation process.
052       */
053      public static TelemetryStreamsObject evaluate(TelemetryStreamsDefinition streamsDefinition, 
054          VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient,
055          Interval interval)
056          throws TelemetryEvaluationException {
057        
058        Object result = resolveExpression(streamsDefinition.getExpression(), 
059                                          variableResolver, project, dpdClient, interval);
060        if (! (result instanceof TelemetryStreamCollection)) {
061          throw new TelemetryEvaluationException("Telemetry streams " + streamsDefinition.getName()
062              + " does not evaluate to a TelemetryStreamCollection. "
063              + "Is there at least one reducer in the expression?");
064        }
065        
066        // telemetry_streams_definition_name[<parameter, ..., parameter>][:individual_stream_tag].
067        StringBuffer categorySeriesNamePrefix = new StringBuffer(streamsDefinition.getName());
068        Variable[] variables = streamsDefinition.getVariables();
069        if (variables.length > 0) {
070          categorySeriesNamePrefix.append('<');
071          categorySeriesNamePrefix.append(variableResolver.resolve(variables[0]).getValueString());
072          for (int i = 1; i < variables.length; i++) {
073            categorySeriesNamePrefix.append(", ");
074            categorySeriesNamePrefix.append(variableResolver.resolve(variables[i]).getValueString());
075          }
076          categorySeriesNamePrefix.append('>');
077        }
078        
079        TelemetryStreamsObject telemetryStreamsObject = new TelemetryStreamsObject(streamsDefinition);
080        for (TelemetryStream stream : ((TelemetryStreamCollection) result)) {
081          Object streamTag = stream.getTag();
082          String streamName = streamTag == null ? categorySeriesNamePrefix.toString()
083              : categorySeriesNamePrefix.toString() + ':' + streamTag.toString();
084          telemetryStreamsObject.addStream(new TelemetryStreamsObject.Stream(streamName, stream));
085        }
086        return telemetryStreamsObject;
087      }
088    
089      /**
090       * Evaluates a telemetry chart definition to <code>TeemetryChartObject</code> object.
091       * 
092       * @param chartDefinition The telemetry chart definition.
093       * @param telemetryDefinitionResolver The telemetry definition resolver.
094       * @param variableResolver The variable resolver.
095       * @param project The project.
096       * @param dpdClient The DPD Client.
097       * @param interval The interval.
098       * 
099       * @return An instance of <code>TelemetryChartObject</code> object.  
100       * 
101       * @throws TelemetryEvaluationException If there is any error during the evaluation process.
102       */
103      public static TelemetryChartObject evaluate(TelemetryChartDefinition chartDefinition, 
104          TelemetryDefinitionResolver telemetryDefinitionResolver, VariableResolver variableResolver, 
105          Project project, DailyProjectDataClient dpdClient, Interval interval) 
106      throws TelemetryEvaluationException {
107        
108        TelemetryChartObject telemetryChartObject = new TelemetryChartObject(chartDefinition);
109        
110        for (TelemetryChartDefinition.SubChartDefinition subChart : chartDefinition.getSubCharts()) {
111          
112          //find the TelmetryStreamsDefinition object
113          StreamsReference streamsRef = subChart.getStreamsReference();
114          TelemetryStreamsDefinition streamsDef 
115              = telemetryDefinitionResolver.resolveStreamsDefinition(streamsRef.getName());     
116          //prepares a variable resolver for the TelemetryStreamsDefinition object
117          Expression[] streamsRefParameters = streamsRef.getParameters();
118          Variable[] streamsVariables = streamsDef.getVariables();
119          if (streamsVariables.length != streamsRefParameters.length) {
120            throw new TelemetryEvaluationException("Error in chart definition detected. The streams '"
121              + streamsDef.getName() + "' the chart relies on has " + streamsVariables.length
122              + " parameter(s), but the chart has only supplied " + streamsRefParameters.length 
123              + " parameter value(s).");
124          }
125          VariableResolver streamsVariableResolver = new VariableResolver();
126          for (int j = 0; j < streamsVariables.length; j++) {
127            Expression streamsRefParameter = streamsRefParameters[j];
128            if (streamsRefParameter instanceof Constant) {
129              streamsVariableResolver.add(streamsVariables[j], (Constant) streamsRefParameter);
130            }
131            else if (streamsRefParameter instanceof Variable) {
132              Constant constant = variableResolver.resolve((Variable) streamsRefParameter);
133              streamsVariableResolver.add(streamsVariables[j], constant);          
134            }
135            else {
136              throw new RuntimeException("Unknow parameter type.");          
137            }
138          }
139          //get TelemetryStreamsObject
140          TelemetryStreamsObject streamsObject 
141              = TelemetryEvaluator.evaluate(streamsDef, streamsVariableResolver, project, dpdClient, 
142                  interval);
143        
144          
145          //find the YAxisDefinition object
146          YAxisReference yAxisRef = subChart.getYAxisReference();
147          TelemetryChartYAxisDefinition yAxisDef
148              = telemetryDefinitionResolver.resolveYAxisDefinition(yAxisRef.getName());
149          //prepares a variable resolver for the YAxisDefinition object
150          Expression[] yAxisRefParameters = yAxisRef.getParameters();
151          Variable[] yAxisVariables = yAxisDef.getVariables();
152          if (yAxisVariables.length != yAxisRefParameters.length) {
153            throw new TelemetryEvaluationException("Error in chart definition detected. The y-axis '"
154              + yAxisDef.getName() + "' the chart relies on has " + yAxisVariables.length
155              + " parameter(s), but the chart has only supplied " + yAxisRefParameters.length 
156              + " parameter value(s).");
157          }
158          VariableResolver yAxisVariableResolver = new VariableResolver();
159          for (int j = 0; j < yAxisVariables.length; j++) {
160            Expression yAxisRefParameter = yAxisRefParameters[j];
161            if (yAxisRefParameter instanceof Constant) {
162              yAxisVariableResolver.add(yAxisVariables[j], (Constant) yAxisRefParameter);
163            }
164            else if (yAxisRefParameter instanceof Variable) {
165              Constant constant = variableResolver.resolve((Variable) yAxisRefParameter);
166              yAxisVariableResolver.add(yAxisVariables[j], constant);          
167            }
168            else {
169              throw new RuntimeException("Unsupported parameter type in y-axis " + yAxisDef.getName());
170            }
171          }
172          //get y-axis label value
173          String yAxisLabelValue = null;
174          Expression yAxisDefLabelParam = yAxisDef.getLabelParameter();
175          if (yAxisDefLabelParam instanceof StringConstant) {
176            yAxisLabelValue = ((StringConstant) yAxisDefLabelParam).getValue();
177          }
178          else if (yAxisDefLabelParam instanceof Variable) {
179            Constant constant = yAxisVariableResolver.resolve((Variable) yAxisDefLabelParam);
180            if (constant instanceof StringConstant) {
181              yAxisLabelValue = ((StringConstant) constant).getValue();
182            }
183            else {
184              throw new TelemetryEvaluationException("Y-axis '" + yAxisDef.getName()
185                  + "' variable '" + ((Variable) yAxisDefLabelParam).getName() 
186                  + "' does not resolve to a string.");
187            }
188          }
189          else {
190            throw new RuntimeException("Unsupported parameter type in y-axis " + yAxisDef.getName());
191          }
192    
193          //add the sub-chart
194          boolean isYAxisInteger = TelemetryChartYAxisDefinition.NUMBER_TYPE_INTEGER.equals(
195              yAxisDef.getNumberType());
196          Number lowerBound = null;
197          Number upperBound = null;
198          if (! yAxisDef.isAutoScale()) {
199            lowerBound = yAxisDef.getLowerBound();
200            upperBound = yAxisDef.getUpperBound();
201          }
202          TelemetryChartObject.YAxis yAxisObject = new TelemetryChartObject.YAxis(yAxisLabelValue,
203              isYAxisInteger, lowerBound, upperBound);
204          telemetryChartObject.addSubChart(new TelemetryChartObject.SubChart(
205              streamsObject, yAxisObject));
206        }
207        
208        return telemetryChartObject;
209      }
210      
211      /**
212       * Evaluates a telemetry report definition to <code>TelemetryReportObject</code> object.
213       * 
214       * @param reportDefinition The telemetry report definition.
215       * @param telemetryDefinitionResolver The telemetry definition resolver.
216       * @param variableResolver The variable resolver.
217       * @param project The project.
218       * @param dpdClient The DPD Client.
219       * @param interval The interval.
220       * 
221       * @return An instance of <code>TelemetryReportObject</code> object.  
222       * 
223       * @throws TelemetryEvaluationException If there is any error during the evaluation process.
224       */
225      public static TelemetryReportObject evaluate(TelemetryReportDefinition reportDefinition, 
226          TelemetryDefinitionResolver telemetryDefinitionResolver, VariableResolver variableResolver, 
227          Project project, DailyProjectDataClient dpdClient, Interval interval) 
228      throws TelemetryEvaluationException {
229        
230        TelemetryReportObject telemetryReportObject = new TelemetryReportObject(reportDefinition);
231        for (ChartReference chartRef : reportDefinition.getChartReferences()) {
232          //find the TelmetryChartDefinition object
233          TelemetryChartDefinition chartDef 
234              = telemetryDefinitionResolver.resolveChartDefinition(chartRef.getName());
235          
236          //prepares a variable resolver for the TelemetryChartDefinition object
237          Expression[] chartRefParameters = chartRef.getParameters();
238          Variable[] chartVariables = chartDef.getVariables();
239          if (chartVariables.length != chartRefParameters.length) {
240            throw new TelemetryEvaluationException("Error in report definition detected. The chart '"
241              + chartDef.getName() + "' the chart relies on has " + chartVariables.length
242              + " parameter(s), but the chart has only supplied " + chartRefParameters.length 
243              + " parameter value(s).");
244          }
245          VariableResolver chartVariableResolver = new VariableResolver();
246          for (int j = 0; j < chartVariables.length; j++) {
247            Expression chartRefParameter = chartRefParameters[j];
248            if (chartRefParameter instanceof Constant) {
249              chartVariableResolver.add(chartVariables[j], (Constant) chartRefParameter);
250            }
251            else if (chartRefParameter instanceof Variable) {
252              Constant constant = variableResolver.resolve((Variable) chartRefParameter);
253              chartVariableResolver.add(chartVariables[j], constant);          
254            }
255            else {
256              throw new RuntimeException("Unknow parameter type.");          
257            }
258          }
259        
260          //generate telemetry chart object
261          TelemetryChartObject chartObject = TelemetryEvaluator.evaluate(chartDef, 
262              telemetryDefinitionResolver, chartVariableResolver, project, dpdClient, interval);  
263          telemetryReportObject.addChartObject(chartObject);
264        }
265        
266        return telemetryReportObject;
267      }
268      
269      /**
270       * Resolves an expression to an instance of <code>TelemetryStreamCollection</code>
271       * or <code>Number</code>.
272       * 
273       * @param expression The telemetry expression.
274       * @param variableResolver The variable resolver.
275       * @param project The project.
276       * @param dpdClient The DPD Client.
277       * @param interval The interval.
278       * 
279       * @return The resulting instance of type either <code>TelemetryStreamCollection</code>
280       *         or <code>Number</code>.
281       * 
282       * @throws TelemetryEvaluationException If the expression call cannot be resolved.
283       */
284      static Object resolveExpression(Expression expression, VariableResolver variableResolver,
285          Project project, DailyProjectDataClient dpdClient, Interval interval) 
286      throws TelemetryEvaluationException {
287        try {
288          FunctionCall idempotent = new FunctionCall("idempotent", new Expression[]{expression});
289          return resolveFunctionCall(idempotent, variableResolver, project, dpdClient, interval);
290        }
291        catch (Exception ex) {
292          throw new TelemetryEvaluationException(ex);
293        }
294      }
295     
296      /**
297       * Resolves a function call to an instance of <code>TelemetryStreamCollection</code>
298       * or <code>Number</code>.
299       * 
300       * @param functionCall The <code>FunctionCall</code> instance.
301       * @param variableResolver The variable resolver.
302       * @param project The project.
303       * @param dpdClient The DPD Client.
304       * @param interval The interval.
305       * 
306       * @return The result. It's an instance of type either <code>TelemetryStreamCollection</code>
307       *         or <code>Number</code>.
308       * 
309       * @throws Exception If the function call cannot be resolved.
310       */
311      private static Object resolveFunctionCall(FunctionCall functionCall, 
312          VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient, 
313          Interval interval) throws Exception {
314        
315        Expression[] parameters = functionCall.getParameters();
316        
317        //Only 3 types of objects are valid: String, Number, TelemetryStreamCollection.
318        Object[] parameterValues = new Object[parameters.length];
319        
320        for (int i = 0; i < parameters.length; i++) {
321          Expression param = parameters[i];
322          //resolve variable first
323          if (param instanceof Variable) {
324            param = variableResolver.resolve((Variable) param);
325          }
326          //fill parameterValues next
327          if (param instanceof ReducerCall) {
328            parameterValues[i] 
329                = resolveReducerCall((ReducerCall) param, variableResolver, project, dpdClient, 
330                    interval);
331          }
332          else if (param instanceof FunctionCall) {
333            parameterValues[i] 
334                = resolveFunctionCall((FunctionCall) param, variableResolver, project, dpdClient, 
335                    interval);
336          }
337          else if (param instanceof NumberConstant) {
338            parameterValues[i] = ((NumberConstant) param).getValue();
339          }
340          else if (param instanceof StringConstant) {
341            parameterValues[i] = ((StringConstant) param).getValue();
342          }
343          else {
344            throw new TelemetryEvaluationException("Function " + functionCall.getFunctionName() 
345                + " does not accept parameter of type " + param.getClass().getName());
346          }  
347        }  
348        return TelemetryFunctionManager.getInstance().compute(functionCall.getFunctionName(),
349            parameterValues);
350      }
351      
352      
353      /**
354       * Resolves a reducer call to an instance of <code>TelemetryStreamCollection</code>.
355       * 
356       * @param reducerCall The <code>ReducerCall</code> instance.
357       * @param variableResolver The variable resolver.
358       * @param project The project.
359       * @param dpdClient The DPD Client.
360       * @param interval The interval.
361       * 
362       * @return The resulting instance of <code>TelemetryStreamCollection</code>.
363       * 
364       * @throws Exception If the reducer call cannot be resolved.
365       */
366      private static TelemetryStreamCollection resolveReducerCall(ReducerCall reducerCall, 
367          VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient, 
368          Interval interval) throws Exception {
369        
370        Expression[] parameters = reducerCall.getParameters();
371        
372        //Only objects of type String is valid.
373        String[] parameterValues = new String[parameters.length];
374        
375        for (int i = 0; i < parameters.length; i++) {
376          Expression param = parameters[i];
377          //resolve variable first
378          if (param instanceof Variable) {
379            param = variableResolver.resolve((Variable) param); 
380          }
381          //fill parameterValues next
382          if (param instanceof Constant) {
383            parameterValues[i] = ((Constant) param).getValueString();
384          }
385          else {
386            throw new TelemetryEvaluationException("Reducer " + reducerCall.getReducerName() 
387                + " does not accept parameter of type " + param.getClass().getName());
388          }
389        }
390        return TelemetryReducerManager.getInstance().compute(reducerCall.getReducerName(),
391            dpdClient, project, interval, parameterValues);
392      }
393    }