001    package org.hackystat.sensor.ant.dependencyfinder;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.Date;
006    import java.util.HashMap;
007    import java.util.List;
008    import java.util.Map;
009    
010    import javax.xml.bind.JAXBContext;
011    import javax.xml.bind.Unmarshaller;
012    import javax.xml.datatype.XMLGregorianCalendar;
013    
014    import org.apache.tools.ant.BuildException;
015    import org.hackystat.sensor.ant.dependencyfinder.jaxb.Dependencies;
016    import org.hackystat.sensor.ant.dependencyfinder.jaxb.Package;
017    import org.hackystat.sensor.ant.dependencyfinder.jaxb.Class;
018    import org.hackystat.sensor.ant.task.HackystatSensorTask;
019    import org.hackystat.sensor.ant.util.JavaClass2FilePathMapper;
020    import org.hackystat.utilities.tstamp.Tstamp;
021    
022    /**
023     * Implements an Ant task that parses the XML files generated by DependencyFinder. 
024     * The Ant Task sends the Coupling data to a Hackystat server.
025     * 
026     * @author Philip Johnson
027     */
028    public class DependencyFinderSensor extends HackystatSensorTask {
029    
030      /** The name of this tool. */
031      private static String tool = "DependencyFinder";
032    
033      /** Initialize a new instance of this sensor. */
034      public DependencyFinderSensor() {
035        super(tool);
036      }
037    
038      /**
039       * Initialize a new instance of this sensor for testing purposes.
040       * 
041       * @param host The SensorBase host URL.
042       * @param email The SensorBase email to use.
043       * @param password The SensorBase password to use.
044       */
045      public DependencyFinderSensor(String host, String email, String password) {
046        super(host, email, password, tool);
047      }
048    
049      /**
050       * Parses the tool's XML file and sends the resulting data to the SensorBase server.
051       * 
052       * @throws BuildException If there is an error.
053       */
054      @Override
055      public void executeInternal() throws BuildException {
056        this.setupSensorShell();
057        int numberOfEntries = 0;
058        Date startTime = new Date();
059        for (File dataFile : getDataFiles()) {
060          try {
061            verboseInfo("Processing DependencyFinder file: " + dataFile);
062            numberOfEntries += this.processDependencyFinderXmlFile(dataFile);
063          }
064          catch (Exception e) {
065            signalError("Failure processing: " + dataFile, e);
066          }
067        }
068        // We've collected the data, now send it. 
069        this.sendAndQuit();
070        summaryInfo(startTime, "Coupling", numberOfEntries);
071      }
072    
073      /**
074       * Processes a single DependencyFinder XML data file, generating sensor data.
075       * 
076       * @param xmlFile The file containing the DependencyFinder data.
077       * @return The number of Coupling instances generated.
078       * @throws BuildException If problems occur.
079       */
080      int processDependencyFinderXmlFile(File xmlFile) throws BuildException {
081        // The start time for all entries will be approximated by the XML file's last mod time.
082        // Use the TstampSet to make it unique.
083        long startTime = xmlFile.lastModified();
084        int count = 0;
085        try {
086          JAXBContext context = JAXBContext
087          .newInstance(org.hackystat.sensor.ant.dependencyfinder.jaxb.ObjectFactory.class);
088          Unmarshaller unmarshaller = context.createUnmarshaller();
089    
090          // DependencyFinder XML report.
091          Dependencies dependencies = (Dependencies) unmarshaller.unmarshal(xmlFile);
092          // Construct a mapper from class names to their file path.
093          JavaClass2FilePathMapper mapper = new JavaClass2FilePathMapper(getSourceFiles());
094          List<Package> packages = new ArrayList<Package>();
095          if (dependencies.getPackage() != null) { 
096            packages = dependencies.getPackage();
097          }
098    
099          for (Package packageElement : packages) {
100            List<Class> classList = new ArrayList<Class>();
101            if (packageElement.getClazz() != null) {
102              classList = packageElement.getClazz();
103            }
104            for (Class classElement : classList) {
105              String resource = mapper.getFilePath(classElement.getName());
106    
107              if (resource != null) {
108                long tstamp = this.tstampSet.getUniqueTstamp(startTime);
109                XMLGregorianCalendar tstampXml = Tstamp.makeTimestamp(tstamp);
110                XMLGregorianCalendar runtimeXml = Tstamp.makeTimestamp(this.runtime);
111                // Create the sensor data instance key/value map.
112                Map<String, String> keyValMap = new HashMap<String, String>();
113                // Required for all sensor data
114                keyValMap.put("Tool", tool);
115                keyValMap.put("SensorDataType", "Coupling");
116                keyValMap.put("Runtime", runtimeXml.toString());
117                keyValMap.put("Timestamp", tstampXml.toString());
118                keyValMap.put("Resource", resource);
119                // Expected for "Coupling" sensor data. 
120                keyValMap.put("Type", "class");
121                keyValMap.put("Afferent", String.valueOf(getAfferent(classElement))); 
122                keyValMap.put("Efferent", String.valueOf(getEfferent(classElement)));
123                // add data to sensorshell
124                this.sensorShell.add(keyValMap);
125                count++;
126              }
127            }
128          }
129        }
130        catch (Throwable e) {
131          throw new BuildException(errMsgPrefix + "Failure: " + e.getMessage(), e);
132        }
133        return count;
134      }
135    
136      /**
137       * Gets the Afferent (inbound) number of couplings.
138       * @param classElement The class element to count.
139       * @return The integer number of afferent links.
140       */
141      private int getAfferent(Class classElement) {
142        return (classElement.getInbound() == null) ? 0 : classElement.getInbound().size();
143      }
144      
145      /**
146       * Gets the Efferent (outbound) number of couplings.
147       * @param classElement The class element to count.
148       * @return The integer number of efferent links.
149       */
150      private int getEfferent(Class classElement) {
151        return (classElement.getOutbound() == null) ? 0 : classElement.getOutbound().size();
152      }
153    }