1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import java.io.File; 18 import java.io.IOException; 19 import java.io.BufferedReader; 20 import java.io.FileReader; 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Set; 26 import java.util.TreeSet; 27 import java.util.SortedSet; 28 import java.util.regex.Pattern; 29 30 /** 31 * Immutable representation of an IDE configuration. Assumes that the current 32 * directory is the project's root directory. 33 */ 34 public class Configuration { 35 36 /** Java source tree roots. */ 37 public final SortedSet<File> sourceRoots; 38 39 /** Found .jar files (that weren't excluded). */ 40 public final List<File> jarFiles; 41 42 /** Excluded directories which may or may not be under a source root. */ 43 public final SortedSet<File> excludedDirs; 44 45 /** The root directory for this tool. */ 46 public final File toolDirectory; 47 48 /** File name used for excluded path files. */ 49 private static final String EXCLUDED_PATHS = "excluded-paths"; 50 51 /** 52 * Constructs a Configuration by traversing the directory tree, looking 53 * for .java and .jar files and identifying source roots. 54 */ Configuration()55 public Configuration() throws IOException { 56 this.toolDirectory = new File("development/tools/idegen"); 57 if (!toolDirectory.isDirectory()) { 58 // The wrapper script should have already verified this. 59 throw new AssertionError("Not in root directory."); 60 } 61 62 Stopwatch stopwatch = new Stopwatch(); 63 64 Excludes excludes = readExcludes(); 65 66 stopwatch.reset("Read excludes"); 67 68 List<File> jarFiles = new ArrayList<File>(500); 69 SortedSet<File> excludedDirs = new TreeSet<File>(); 70 SortedSet<File> sourceRoots = new TreeSet<File>(); 71 72 traverse(new File("."), sourceRoots, jarFiles, excludedDirs, excludes); 73 74 stopwatch.reset("Traversed tree"); 75 76 Log.debug(sourceRoots.size() + " source roots"); 77 Log.debug(jarFiles.size() + " jar files"); 78 Log.debug(excludedDirs.size() + " excluded dirs"); 79 80 this.sourceRoots = Collections.unmodifiableSortedSet(sourceRoots); 81 this.jarFiles = Collections.unmodifiableList(jarFiles); 82 this.excludedDirs = Collections.unmodifiableSortedSet(excludedDirs); 83 } 84 85 /** 86 * Reads excluded path files. 87 */ readExcludes()88 private Excludes readExcludes() throws IOException { 89 List<Pattern> patterns = new ArrayList<Pattern>(); 90 91 File globalExcludes = new File(toolDirectory, EXCLUDED_PATHS); 92 parseFile(globalExcludes, patterns); 93 94 // Look for Google-specific excludes. 95 // TODO: Traverse all vendor-specific directories. 96 File googleExcludes = new File("./vendor/google/" + EXCLUDED_PATHS); 97 if (googleExcludes.exists()) { 98 parseFile(googleExcludes, patterns); 99 } 100 101 // Look for user-specific excluded-paths file in current directory. 102 File localExcludes = new File(EXCLUDED_PATHS); 103 if (localExcludes.exists()) { 104 parseFile(localExcludes, patterns); 105 } 106 107 return new Excludes(patterns); 108 } 109 110 /** 111 * Recursively finds .java source roots, .jar files, and excluded 112 * directories. 113 */ traverse(File directory, Set<File> sourceRoots, Collection<File> jarFiles, Collection<File> excludedDirs, Excludes excludes)114 private static void traverse(File directory, Set<File> sourceRoots, 115 Collection<File> jarFiles, Collection<File> excludedDirs, 116 Excludes excludes) throws IOException { 117 /* 118 * Note it would be faster to stop traversing a source root as soon as 119 * we encounter the first .java file, but it appears we have nested 120 * source roots in our generated source directory (specifically, 121 * R.java files and aidl .java files don't share the same source 122 * root). 123 */ 124 125 boolean firstJavaFile = true; 126 for (File file : directory.listFiles()) { 127 // Trim preceding "./" from path. 128 String path = file.getPath().substring(2); 129 130 // Keep track of source roots for .java files. 131 if (path.endsWith(".java")) { 132 if (firstJavaFile) { 133 // Only parse one .java file per directory. 134 firstJavaFile = false; 135 136 File sourceRoot = rootOf(file); 137 if (sourceRoot != null) { 138 sourceRoots.add(sourceRoot); 139 } 140 } 141 142 continue; 143 } 144 145 // Keep track of .jar files. 146 if (path.endsWith(".jar")) { 147 if (!excludes.exclude(path)) { 148 jarFiles.add(file); 149 } else { 150 Log.debug("Skipped: " + file); 151 } 152 153 continue; 154 } 155 156 // Traverse nested directories. 157 if (file.isDirectory()) { 158 if (excludes.exclude(path)) { 159 // Don't recurse into excluded dirs. 160 Log.debug("Excluding: " + path); 161 excludedDirs.add(file); 162 } else { 163 traverse(file, sourceRoots, jarFiles, excludedDirs, 164 excludes); 165 } 166 } 167 } 168 } 169 170 /** 171 * Determines the source root for a given .java file. Returns null 172 * if the file doesn't have a package or if the file isn't in the 173 * correct directory structure. 174 */ rootOf(File javaFile)175 private static File rootOf(File javaFile) throws IOException { 176 String packageName = parsePackageName(javaFile); 177 if (packageName == null) { 178 // No package. 179 // TODO: Treat this as a source root? 180 return null; 181 } 182 183 String packagePath = packageName.replace('.', File.separatorChar); 184 File parent = javaFile.getParentFile(); 185 String parentPath = parent.getPath(); 186 if (!parentPath.endsWith(packagePath)) { 187 // Bad dir structure. 188 return null; 189 } 190 191 return new File(parentPath.substring( 192 0, parentPath.length() - packagePath.length())); 193 } 194 195 /** 196 * Reads a Java file and parses out the package name. Returns null if none 197 * found. 198 */ parsePackageName(File file)199 private static String parsePackageName(File file) throws IOException { 200 BufferedReader in = new BufferedReader(new FileReader(file)); 201 try { 202 String line; 203 while ((line = in.readLine()) != null) { 204 String trimmed = line.trim(); 205 if (trimmed.startsWith("package")) { 206 // TODO: Make this more robust. 207 // Assumes there's only once space after "package" and the 208 // line ends in a ";". 209 return trimmed.substring(8, trimmed.length() - 1); 210 } 211 } 212 213 return null; 214 } finally { 215 in.close(); 216 } 217 } 218 219 /** 220 * Picks out excluded directories that are under source roots. 221 */ excludesUnderSourceRoots()222 public SortedSet<File> excludesUnderSourceRoots() { 223 // TODO: Refactor this to share the similar logic in 224 // Eclipse.constructExcluding(). 225 SortedSet<File> picked = new TreeSet<File>(); 226 for (File sourceRoot : sourceRoots) { 227 String sourcePath = sourceRoot.getPath() + "/"; 228 SortedSet<File> tailSet = excludedDirs.tailSet(sourceRoot); 229 for (File file : tailSet) { 230 if (file.getPath().startsWith(sourcePath)) { 231 picked.add(file); 232 } else { 233 break; 234 } 235 } 236 } 237 return picked; 238 } 239 240 /** 241 * Reads a list of regular expressions from a file, one per line, and adds 242 * the compiled patterns to the given collection. Ignores lines starting 243 * with '#'. 244 * 245 * @param file containing regular expressions, one per line 246 * @param patterns collection to add compiled patterns from file to 247 */ parseFile(File file, Collection<Pattern> patterns)248 public static void parseFile(File file, Collection<Pattern> patterns) 249 throws IOException { 250 BufferedReader in = new BufferedReader(new FileReader(file)); 251 try { 252 String line; 253 while ((line = in.readLine()) != null) { 254 String trimmed = line.trim(); 255 if (trimmed.length() > 0 && !trimmed.startsWith("#")) { 256 patterns.add(Pattern.compile(trimmed)); 257 } 258 } 259 } finally { 260 in.close(); 261 } 262 } 263 } 264