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.coupling.jaxb.CouplingDailyProjectData;
007    import org.hackystat.dailyprojectdata.resource.coupling.jaxb.CouplingData;
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 a Coupling value.
023     * <p>
024     * Options:
025     * <ol>
026     * <li> coupling: One of 'Afferent', 'Efferent', or 'All'.  Defaults to 'All'.
027     * <li> mode: One of 'Total', 'Average', or 'TotalInstancesAboveThreshold'. Defaults to 'Average'.
028     * Note that the value returned depends upon the value selected for coupling.  
029     * <li> type: A string indicating the type of coupling. Defaults to 'class'. Could also be 
030     * 'package'.
031     * <li> threshold: A string indicating the threshold value.  Defaults to '10'.
032     * This parameter is ignored unless the mode is 
033     * 'TotalAboveThreshold', in which case it must be parsed to an integer.
034     * <li> tool: The tool whose sensor data is to be used to calculate the coupling information.
035     * Defaults to 'DependencyFinder'. 
036     * </ol>
037     * 
038     * @author Philip Johnson
039     */
040    public class CouplingReducer implements TelemetryReducer { 
041      
042      /** Possible mode values. */
043      public enum Coupling {
044        /** Afferent coupling (measure of responsibility). */
045        AFFERENT,
046        /** Efferent coupling (measure of independence). */
047        EFFERENT, 
048        /** Both Afferent and Efferent coupling.*/
049        ALL  }
050     
051      /** Possible mode values. */
052      public enum Mode {
053        /** Aggregate total of all couplings. */
054        TOTAL,
055        /** Average number of couplings.. */
056        AVERAGE,
057        /** Total number of instances with a coupling value above the threshold. */
058        TOTALINSTANCESABOVETHRESHOLD }
059      
060      /**
061       * Computes and returns the required telemetry streams object.
062       * 
063       * @param project The project.
064       * @param dpdClient The DPD Client.
065       * @param interval The interval.
066       * @param options The optional parameters.
067       * 
068       * @return Telemetry stream collection.
069       * @throws TelemetryReducerException If there is any error.
070       */
071      public TelemetryStreamCollection compute(Project project, DailyProjectDataClient dpdClient, 
072          Interval interval, String[] options) throws TelemetryReducerException {
073        Coupling coupling = Coupling.ALL;
074        Mode mode = Mode.AVERAGE;
075        String type = "class";
076        String thresholdString = null;
077        String tool = "DependencyFinder";
078        // process options
079        if (options.length > 5) {
080          throw new TelemetryReducerException("Coupling reducer needs only 5 parameters.");
081        }
082        if (options.length >= 1) {
083          try {
084            coupling = Coupling.valueOf(options[0].toUpperCase());
085          }
086          catch (Exception e) {
087            throw new TelemetryReducerException("Illegal coupling value: " + options[0], e);
088          }
089        }
090        
091        if (options.length >= 2) {
092          try {
093            mode = Mode.valueOf(options[1].toUpperCase());
094          }
095          catch (Exception e) {
096            throw new TelemetryReducerException("Illegal mode value: " + options[1], e);
097          }
098        }
099    
100        if (options.length >= 3) {
101          type = options[2];
102        }
103    
104        if (options.length >= 4) {
105          thresholdString = options[3];
106        }
107        
108        if (options.length >= 5) {
109          tool = options[4];
110        }
111    
112        // Make sure threshold is a number if mode is TotalAboveThreshold.
113        int threshold = 10;
114        if (mode == Mode.TOTALINSTANCESABOVETHRESHOLD) {
115          try {
116            threshold = Integer.valueOf(thresholdString);
117          }
118          catch (Exception e) {
119            throw new TelemetryReducerException("Illegal threshold value: " + options[3], e);
120          }
121        }
122        
123        
124        // Find out the DailyProjectData host, throw error if not found.
125        String dpdHost = System.getProperty(ServerProperties.DAILYPROJECTDATA_FULLHOST_KEY);
126        if (dpdHost == null) {
127          throw new TelemetryReducerException("Null DPD host in CouplingReducer");
128        }
129    
130        // Now get the telemetry stream.
131        try {
132          TelemetryStream telemetryStream = this.getStream(dpdClient, project, interval,  
133              coupling, mode, type, threshold, tool, null);
134          TelemetryStreamCollection streams = new TelemetryStreamCollection(null, project, interval);
135          streams.add(telemetryStream);
136          return streams;
137        } 
138        catch (Exception e) {
139          throw new TelemetryReducerException(e);
140        }
141      }
142    
143      /**
144       * Gets the telemetry stream.
145       * 
146       * @param dpdClient The DailyProjectData client we will contact for the data.
147       * @param project The project.
148       * @param interval The interval.
149       * @param coupling The coupling.
150       * @param mode The mode.
151       * @param type The type.
152       * @param threshold The threshold (if mode is TOTALABOVETHRESHOLD).
153       * @param tool The tool whose sensor data will be used. 
154       * @param streamTagValue The tag for the generated telemetry stream.
155       * 
156       * @return The telemetry stream as required.
157       * 
158       * @throws Exception If there is any error.
159       */
160      TelemetryStream getStream(DailyProjectDataClient dpdClient, 
161          Project project, Interval interval, Coupling coupling, Mode mode, String type,
162          int threshold, String tool, Object streamTagValue) 
163            throws Exception {
164        TelemetryStream telemetryStream = new TelemetryStream(streamTagValue);
165        List<IntervalUtility.Period> periods = IntervalUtility.getPeriods(interval);
166    
167        for (IntervalUtility.Period period : periods) {
168          Double value = this.getData(dpdClient, project, period.getStartDay(), period.getEndDay(),
169              coupling, mode, type, threshold, tool);
170          telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), value));
171        }
172        return telemetryStream;
173      }
174      
175      /**
176       * Returns a Coupling value for the specified time interval, or null if no SensorData. 
177       * 
178       * We work backward through the time interval, and return a Coupling value for the first day in
179       * the interval for which Coupling data exists.
180       * 
181       * @param dpdClient The DailyProjectData client we will use to get this data.
182       * @param project The project.
183       * @param startDay The start day (inclusive).
184       * @param endDay The end day (inclusive).
185       * @param coupling The coupling.
186       * @param mode The mode.
187       * @param type The type.
188       * @param threshold The threshold, if mode is TOTALINSTANCESABOVETHRESHOLD.
189       * @param tool The tool whose sensor data will be used. 
190       * @throws TelemetryReducerException If anything goes wrong.
191       * 
192       * @return The coupling value, or null if there is no coupling SensorData in that period.
193       */
194      Double getData(DailyProjectDataClient dpdClient, Project project, Day startDay, Day endDay, 
195          Coupling coupling, Mode mode, String type, int threshold, String tool) 
196      throws TelemetryReducerException {
197        try {
198          // Work backward through the interval, and return as soon as we get Complexity info.
199          for (Day day = endDay; day.compareTo(startDay) >= 0; day = day.inc(-1) ) {
200            // Get the DPD...
201            CouplingDailyProjectData dpdData = 
202              dpdClient.getCoupling(project.getOwner(), project.getName(), Tstamp.makeTimestamp(day),
203                  type, tool);
204            // Go to the next day in the interval if we don't have anything for this day.
205            if ((dpdData.getCouplingData() == null) || dpdData.getCouplingData().isEmpty()) {
206              continue;
207            }
208            
209            // Otherwise we have complexity data, so calculate the desired values.
210            double totalAfferent = 0;
211            double totalEfferent = 0;
212            double totalUnits = 0;
213            double totalAboveThreshold = 0;
214            for (CouplingData data : dpdData.getCouplingData()) {
215              totalUnits++;
216              totalAfferent += data.getAfferent().intValue();
217              totalEfferent += data.getEfferent().intValue();
218              if ((mode.equals(Mode.TOTALINSTANCESABOVETHRESHOLD)) &&
219                  (coupling.equals(Coupling.AFFERENT)) &&
220                  (data.getAfferent().intValue() > threshold)) {
221                totalAboveThreshold++;
222              }
223              if ((mode.equals(Mode.TOTALINSTANCESABOVETHRESHOLD)) &&
224                  (coupling.equals(Coupling.EFFERENT)) &&
225                  (data.getEfferent().intValue() > threshold)) {
226                totalAboveThreshold++;
227              }
228              if ((mode.equals(Mode.TOTALINSTANCESABOVETHRESHOLD)) &&
229                  (coupling.equals(Coupling.ALL)) &&
230                  (data.getAfferent().intValue() + data.getEfferent().intValue() > threshold)) {
231                totalAboveThreshold++;
232              }
233            }
234    
235            // Now return the value based upon mode and coupling.
236            switch (mode) { //NOPMD
237            case TOTAL:
238              switch (coupling) {
239              case AFFERENT: 
240                return Double.valueOf(totalAfferent);
241              case EFFERENT:
242                return Double.valueOf(totalEfferent);
243              case ALL:
244                return Double.valueOf((totalEfferent + totalAfferent));
245              default: 
246                throw new TelemetryReducerException("Unknown coupling: " + coupling);
247              }
248            case AVERAGE:
249              switch (coupling) {
250              case AFFERENT: 
251                return Double.valueOf(totalAfferent / totalUnits);
252              case EFFERENT:
253                return Double.valueOf(totalEfferent / totalUnits);
254              case ALL:
255                return Double.valueOf((totalEfferent + totalAfferent) / totalUnits);
256              default: 
257                throw new TelemetryReducerException("Unknown coupling: " + coupling);
258              }
259            case TOTALINSTANCESABOVETHRESHOLD:
260              return Double.valueOf(totalAboveThreshold);
261            default: 
262              throw new TelemetryReducerException("Unknown mode: " + mode);
263            }
264          }
265        }
266        catch (Exception ex) {
267          throw new TelemetryReducerException(ex);
268        }
269        // Never found appropriate coupling data in this interval, so return null.
270        return null;
271      }
272    }