001    package org.hackystat.sensor.ant.javancss;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.HashMap;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.Set;
009    
010    import org.hackystat.sensor.ant.javancss.jaxb.Function;
011    import org.hackystat.sensor.ant.javancss.jaxb.Functions;
012    
013    /**
014     * A data structure that takes as input the list of Java files that were processed by JavaNCSS
015     * and the Functions instance that JavaNCSS produced as a result of processing. Constructs
016     * a data structure that maps the list of Java files to a string containing a list of 
017     * comma-separated integers, each one representing a CCN value for one of the methods in that class.
018     * If no CCN data was found for that file, null is returned.
019     * 
020     * @author Philip Johnson
021     */
022    public class CcnData {
023      
024      /** Maps the fully qualified Java file to a list of ints indicating its methods' ccn's. */
025      private Map<File, List<Integer>> file2Ccns = new HashMap<File, List<Integer>>();
026      private Map<File, Integer> file2TotalLines = new HashMap<File, Integer>();
027      
028      /**
029       * Constructs the File2CcnList, which is a mapping from a Java file path to a list of integers
030       * representing the cyclometric complexity values found for all of its interior methods. 
031       * @param files The Java files whose CCN numbers are to be found.
032       * @param functions The Functions object contains CCN data. 
033       */
034      public CcnData(List<File> files, Functions functions) {
035        for (Function function : functions.getFunction()) {
036          String methodSignature = function.getName();
037          File javaFile = findJavaFile(files, methodSignature);
038          // Update our data structure only if we found a Java file corresponding to the method sig.
039          if (javaFile != null) {
040            // First, add the found ccn value to our list of ccn values. 
041            if (!file2Ccns.containsKey(javaFile)) {
042              file2Ccns.put(javaFile, new ArrayList<Integer>());
043              file2TotalLines.put(javaFile, 0);
044            }
045            // Second, update the mapping from file to TotalLines.
046            file2Ccns.get(javaFile).add(function.getCcn().intValue());
047            int lines = 0;
048            try {
049              lines = Integer.valueOf(function.getNcss());
050            }
051            catch (Exception e) {
052              System.out.println("Warning: could not make an integer from: " +
053                  function.getNcss() + ". Ignoring this value for NCSS");
054            }
055            int newTotalLines = file2TotalLines.get(javaFile) + lines;
056            file2TotalLines.put(javaFile, newTotalLines);
057          }
058        }
059      }
060    
061      /**
062       * Takes a methodSignature, and returns the Java file associated with it, or null if no 
063       * corresponding Java file could be found.
064       * 
065       * Method signatures look like:
066       * <pre>
067       * org.hackystat.sensor.xmldata.MessageDelegate.MessageDelegate(XmlDataController)
068       * MessageDelegate.MessageDelegate(XmlDataController)
069       * </pre>
070       * @param files The list of all Files. 
071       * @param methodSignature The method signature of interest.
072       * @return The Java file, if one matches the method signature. 
073       */
074      private File findJavaFile(List<File> files, String methodSignature) {
075        // Find the '.' that's just before the start of the method name and signature.
076        int index = methodSignature.lastIndexOf('.');
077        // Now slice off the method name and signature. 
078        String fullyQualifiedClassName = methodSignature.substring(0, index);
079        // Now create a path by replacing the remaining '.' with file.separator.
080        String filePath = fullyQualifiedClassName.replace(".", System.getProperty("file.separator"));
081        // Add the .java
082        String javaPath = filePath + ".java";
083        // Now we hopefully have something like: org/hackystat/sensor/xmldata/MessageDelegate.java
084        // So see if any of our files ends with this string. If so, we've found a match and return it.
085        for (File file : files) {
086          if (file.getAbsolutePath().endsWith(javaPath)) {
087            return file;
088          }
089        }
090        // We didn't find it, so return null.
091        return null;
092      }
093      
094      /**
095       * Returns a string containing a comma-separated list of CCN values for the given file, or null
096       * if no CCN data is present. 
097       * @param file The file whose CCN data is to be retrieved.
098       * @return The CCN data string, or null.
099       */
100      public String getCcnData (File file) {
101        List<Integer> ccnValues = file2Ccns.get(file);
102        if (ccnValues == null) {
103          return null;
104        }
105        // Otherwise we have data, so construct the string.
106        StringBuilder ccnData = new StringBuilder();
107        for (Integer value : ccnValues) {
108          ccnData.append(value).append(",");
109        }
110        // strip off the final ",". 
111        return ccnData.substring(0, ccnData.length() - 1);
112      }
113      
114      /**
115       * Returns the totallines value for file, or null if not found.
116       * @param file The file whose total lines we're interested in. 
117       * @return The number of lines, or null. 
118       */
119      public int getTotalLines(File file) {
120        return file2TotalLines.get(file);
121      }
122      
123      /**
124       * Returns a set containing the java file paths in this CcnData instance. 
125       * @return The Java file paths with CcnData. 
126       */
127      public Set<File> getFiles () {
128        return file2Ccns.keySet();
129      }
130    }