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 }