001    package org.hackystat.sensor.ant.util;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.Collection;
006    import java.util.HashMap;
007    import java.util.List;
008    import java.util.Map;
009    import java.util.Set;
010    
011    /**
012     * Provides a facility for mapping fully qualified Java class names to their corresponding
013     * fully qualified Java source files.  Also allows retrieval of the source directory 
014     * corresponding to a package name.
015     * 
016     * @author (Cedric) Qin Zhang
017     */
018    public class JavaClass2FilePathMapper {
019    
020      /** Used to create an internal list of fully qualified source files with the same separator. */
021      private static final char CANONICAL_SEPARATOR = '/';
022      /** The initial file names, with any separator. */
023      private List<String> originalFileNames;
024      /** The canonical file names, all with the '/' separator. */
025      private List<String> canonicalfileNames;
026    
027      /**
028       * A map with the edited package path as the key and the original package path
029       * as the value.
030       */
031      private Map<String, String> packagePathMap = new HashMap<String, String>();
032    
033    
034      
035      /**
036       * This constructor accepts a list of file names, and processes this list to
037       * build two parallel arrays. The first array contains all of the .java file
038       * names found in the passed list, and the second contains these same file
039       * names with the path separator replaced by a canonical separator.
040       * 
041       * @param fullyQualifiedFileNames A collection of string representing fully
042       * qualified file paths or directory path for java files.
043       */
044      public JavaClass2FilePathMapper(Collection<File> fullyQualifiedFileNames) {
045        this.originalFileNames = new ArrayList<String>(fullyQualifiedFileNames.size());
046        this.canonicalfileNames = new ArrayList<String>(fullyQualifiedFileNames.size());
047        for (File file : fullyQualifiedFileNames) {
048          // Allow either 'real' files or file names ending with .java that don't
049          // currently exist.
050          if (file.isFile() || file.getAbsolutePath().endsWith(".java")) {
051            addFile(file.getAbsolutePath());
052          }
053          else if (file.isDirectory()) {
054            traverseDirectory(file);
055          }
056          // splits the path string into the parent path without a slash char at
057          // the end
058          // ex. C:\home\austen or /home/austen
059          String[] originalPath = file.getAbsolutePath().split(".\\w+\\.java");
060          // adds the parent path to a hashmap
061          String editedPath = originalPath[0].replace('\\', CANONICAL_SEPARATOR);
062          this.packagePathMap.put(editedPath, originalPath[0]);
063        }
064      }
065      
066      /**
067       * This constructor accepts a list of file names, and processes this list to
068       * build two parallel arrays. The first array contains all of the .java file
069       * names found in the passed list, and the second contains these same file
070       * names with the path separator replaced by a canonical separator.
071       * 
072       * @param fullyQualifiedFileNames A collection of string representing fully
073       * qualified file paths or directory path for java files.
074       */
075      public JavaClass2FilePathMapper(Set<String> fullyQualifiedFileNames) {
076        this.originalFileNames = new ArrayList<String>(fullyQualifiedFileNames.size());
077        this.canonicalfileNames = new ArrayList<String>(fullyQualifiedFileNames.size());
078        for (String file : fullyQualifiedFileNames) {
079          addFile(file);
080          // splits the path string into the parent path without a slash char at the end
081          // ex. C:\home\austen or /home/austen
082          String[] originalPath = file.split(".\\w+\\.java");
083          // adds the parent path to a hashmap
084          String editedPath = originalPath[0].replace('\\', CANONICAL_SEPARATOR);
085          this.packagePathMap.put(editedPath, originalPath[0]);
086        }
087      }
088        
089    
090      /**
091       * Recursively traverses the passed directory, invoking "addFile" on all files
092       * found.
093       * 
094       * @param dir The directory to traverse.
095       */
096      private void traverseDirectory(File dir) {
097        File[] files = dir.listFiles();
098        for (int i = 0; i < files.length; i++) {
099          File file = files[i];
100          if (file.isFile()) {
101            addFile(file.getAbsolutePath());
102          }
103          else if (file.isDirectory()) {
104            traverseDirectory(file);
105          }
106        }
107      }
108    
109      /**
110       * Updates our parallel arrays if the passed File is a Java file.
111       * 
112       * @param fileName A file name, which might be a Java file.
113       */
114      private void addFile(String fileName) {
115        try {
116          if (fileName.endsWith(".java")) {
117            String canonicalFileName = fileName.substring(0, fileName.length() - 5).replace('\\',
118                CANONICAL_SEPARATOR);
119            this.originalFileNames.add(fileName);
120            this.canonicalfileNames.add(canonicalFileName);
121          }
122        }
123        catch (Exception e) {
124          // do nothing
125          return;
126        }
127      }
128    
129      /**
130       * Returns a string containing the java file name corresponding to the passed
131       * class name.
132       * 
133       * @param fullyQualifiedClassName The java class name.
134       * @return The java file name, or null if there is no mapping information.
135       */
136      public String getFilePath(String fullyQualifiedClassName) {
137        // Note that this uses linear search. Improve if this is too slow.
138        String searchString = fullyQualifiedClassName.replace('.', CANONICAL_SEPARATOR);
139        int indexOfFirstDollarSign = searchString.indexOf('$');
140        if (indexOfFirstDollarSign >= 0) {
141          searchString = searchString.substring(0, indexOfFirstDollarSign);
142        }
143        int size = this.canonicalfileNames.size();
144        for (int i = 0; i < size; i++) {
145          String canonicalFileName = this.canonicalfileNames.get(i);
146          if (canonicalFileName.endsWith(searchString)) {
147            return this.originalFileNames.get(i);
148          }
149        }
150        return null;
151      }
152    
153      /**
154       * Returns the canonical file names array.
155       * 
156       * @return The array of canonical file names.
157       */
158      @Override
159      public String toString() {
160        return this.originalFileNames.toString();
161      }
162    
163      /**
164       * Returns the path associated with a package name. If a path is not found, an
165       * empty string is returned.
166       * 
167       * @param packageName the name of the package.
168       * @return the path associated with the package name. An empty string is
169       * returned if a path is not found.
170       */
171      public String getPackagePath(String packageName) {
172        String tempPackageName = packageName.replace('.', CANONICAL_SEPARATOR);
173        for (String path : this.packagePathMap.keySet()) {
174          if (path.endsWith(tempPackageName)) {
175            return this.packagePathMap.get(path);
176          }
177        }
178        return "";
179      }
180    }