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 }