001    package org.hackystat.telemetry.analyzer.reducer.impl;
002    
003    import java.util.List;
004    
005    import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
006    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
007    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.ConstructData;
008    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
009    import org.hackystat.telemetry.analyzer.model.TelemetryDataPoint;
010    import org.hackystat.telemetry.analyzer.model.TelemetryStream;
011    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
012    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducer;
013    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducerException;
014    import org.hackystat.telemetry.analyzer.reducer.util.IntervalUtility;
015    import org.hackystat.telemetry.service.server.ServerProperties;
016    import org.hackystat.utilities.time.interval.Interval;
017    import org.hackystat.utilities.time.period.Day;
018    import org.hackystat.utilities.tstamp.Tstamp;
019    
020    
021    /**
022     * Returns a single stream providing Coverage data.
023     * <p>
024     * Options:
025     * <ol>
026     * <li> mode: One of 'Percentage', 'NumCovered', or 'NumUncovered'. Default is 'Percentage'.
027     * <li> granularity: A string indicating the type of coverage, such as 'line', 'method', 'class'.
028     * Default is 'method'.
029     * </ol>
030     * 
031     * @author Philip Johnson, Cedric Zhang
032     */
033    public class CoverageReducer implements TelemetryReducer { 
034     
035      /** Possible mode values. */
036      public enum Mode { PERCENTAGE, NUMCOVERED, NUMUNCOVERED }
037      
038      /**
039       * Computes and returns the required telemetry streams object.
040       * 
041       * @param project The project.
042       * @param dpdClient The DPD Client.
043       * @param interval The interval.
044       * @param options The optional parameters.
045       * 
046       * @return Telemetry stream collection.
047       * @throws TelemetryReducerException If there is any error.
048       */
049      public TelemetryStreamCollection compute(Project project, DailyProjectDataClient dpdClient, 
050          Interval interval, String[] options) throws TelemetryReducerException {
051        Mode mode = Mode.PERCENTAGE;
052        String granularity = null;
053        // process options
054        if (options.length > 2) {
055          throw new TelemetryReducerException("Coverage reducer takes 2 optional parameters.");
056        }
057        if (options.length >= 1) {
058          try {
059            mode = Mode.valueOf(options[0].toUpperCase());
060          }
061          catch (Exception e) {
062            throw new TelemetryReducerException("Illegal mode value.", e);
063          }
064        }
065    
066        if (options.length >= 2) {
067          granularity = options[1];
068        }
069        
070        // Find out the DailyProjectData host, throw error if not found.
071        String dpdHost = System.getProperty(ServerProperties.DAILYPROJECTDATA_FULLHOST_KEY);
072        if (dpdHost == null) {
073          throw new TelemetryReducerException("Null DPD host in DevTimeReducer");
074        }
075    
076        // now get the telemetry stream.
077        try {
078          TelemetryStream telemetryStream = this.getStream(dpdClient, project, interval,  
079              mode, granularity, null);
080          TelemetryStreamCollection streams = new TelemetryStreamCollection(null, project, interval);
081          streams.add(telemetryStream);
082          return streams;
083        } 
084        catch (Exception e) {
085          throw new TelemetryReducerException(e);
086        }
087      }
088    
089      /**
090       * Gets the telemetry stream.
091       * 
092       * @param dpdClient The DailyProjectData client we will contact for the data.
093       * @param project The project.
094       * @param interval The interval.
095       * @param mode The mode (PERCENTAGE, NUMCOVERED, NUMUNCOVERED).
096       * @param granularity The type of coverage.
097       * @param streamTagValue The tag for the generated telemetry stream.
098       * 
099       * @return The telemetry stream as required.
100       * 
101       * @throws Exception If there is any error.
102       */
103      TelemetryStream getStream(DailyProjectDataClient dpdClient, 
104          Project project, Interval interval, Mode mode, 
105          String granularity, Object streamTagValue) 
106            throws Exception {
107        TelemetryStream telemetryStream = new TelemetryStream(streamTagValue);
108        List<IntervalUtility.Period> periods = IntervalUtility.getPeriods(interval);
109    
110        for (IntervalUtility.Period period : periods) {
111          Long value = this.getData(dpdClient, project, period.getStartDay(), period.getEndDay(),
112              mode, granularity);
113          telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), value));
114        }
115    
116        return telemetryStream;
117      }
118      
119      /**
120       * Returns a Coverage value for the specified time interval, or null if no SensorData. Note that
121       * we return a Long, so percentage is a Long ranging from 0 to 100. (There is no fractional
122       * coverage percentage.)
123       * 
124       * We work backward through the time interval, and return the Coverage value for the first day in
125       * the interval for which Coverage data exists.
126       * 
127       * @param dpdClient The DailyProjectData client we will use to get this data.
128       * @param project The project.
129       * @param startDay The start day (inclusive).
130       * @param endDay The end day (inclusive).
131       * @param mode The mode: PERCENTAGE, NUMCOVERED, or NUMUNCOVERED.
132       * @param granularity The type of coverage, such as 'line' or 'method'.
133       * @throws TelemetryReducerException If anything goes wrong.
134       * 
135       * @return The Coverage value, or null if there is no Coverage SensorData for that time period.
136       */
137      Long getData(DailyProjectDataClient dpdClient, Project project, Day startDay, Day endDay, 
138          Mode mode, String granularity) throws TelemetryReducerException {
139        try {
140          // Work backward through the interval, and return as soon as we get Coverage info.
141          for (Day day = endDay; day.compareTo(startDay) >= 0; day = day.inc(-1) ) {
142            // Get the DPD...
143            CoverageDailyProjectData dpdData = 
144              dpdClient.getCoverage(project.getOwner(), project.getName(), Tstamp.makeTimestamp(day),
145                  granularity);
146            // Go to the next day in the interval if we don't have anything for this day.
147            if ((dpdData.getConstructData() == null) || dpdData.getConstructData().isEmpty()) {
148              continue;
149            }
150            
151            // Otherwise we have coverage data, so get the total covered and uncovered values.
152            int totalCovered = 0;
153            int totalUncovered = 0;
154            for (ConstructData data : dpdData.getConstructData()) {
155              totalCovered += data.getNumCovered();
156              totalUncovered += data.getNumUncovered();
157            }
158            
159            // Now return the value based upon mode.
160            switch (mode) {
161            case NUMCOVERED:
162              return Long.valueOf(totalCovered);
163            case NUMUNCOVERED:
164              return Long.valueOf(totalUncovered);
165            case PERCENTAGE:
166              double total = totalCovered + totalUncovered;
167              int percent = (int)((totalCovered / total) * 100.0);
168              return Long.valueOf(percent);
169            default: 
170              throw new TelemetryReducerException("Unknown mode: " + mode);
171            }
172          }
173        }
174        catch (Exception ex) {
175          throw new TelemetryReducerException(ex);
176        }
177        // Never found appropriate coverage data in this interval, so return null.
178        return null;
179      }
180    
181    }