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 /** The vendor directory. */ 52 private static final String VENDOR_PATH = "./vendor/"; 53 54 /** 55 * Constructs a Configuration by traversing the directory tree, looking 56 * for .java and .jar files and identifying source roots. 57 */ Configuration()58 public Configuration() throws IOException { 59 this.toolDirectory = new File("development/tools/idegen"); 60 if (!toolDirectory.isDirectory()) { 61 // The wrapper script should have already verified this. 62 throw new AssertionError("Not in root directory."); 63 } 64 65 Stopwatch stopwatch = new Stopwatch(); 66 67 Excludes excludes = readExcludes(); 68 69 stopwatch.reset("Read excludes"); 70 71 List<File> jarFiles = new ArrayList<File>(500); 72 SortedSet<File> excludedDirs = new TreeSet<File>(); 73 SortedSet<File> sourceRoots = new TreeSet<File>(); 74 75 traverse(new File("."), sourceRoots, jarFiles, excludedDirs, excludes); 76 77 stopwatch.reset("Traversed tree"); 78 79 Log.debug(sourceRoots.size() + " source roots"); 80 Log.debug(jarFiles.size() + " jar files"); 81 Log.debug(excludedDirs.size() + " excluded dirs"); 82 83 this.sourceRoots = Collections.unmodifiableSortedSet(sourceRoots); 84 this.jarFiles = Collections.unmodifiableList(jarFiles); 85 this.excludedDirs = Collections.unmodifiableSortedSet(excludedDirs); 86 } 87 88 /** 89 * Reads excluded path files. 90 */ readExcludes()91 private Excludes readExcludes() throws IOException { 92 List<Pattern> patterns = new ArrayList<Pattern>(); 93 94 File globalExcludes = new File(toolDirectory, EXCLUDED_PATHS); 95 parseFile(globalExcludes, patterns); 96 97 // Traverse all vendor-specific directories 98 readVendorExcludes(patterns); 99 100 // Look for user-specific excluded-paths file in current directory. 101 File localExcludes = new File(EXCLUDED_PATHS); 102 if (localExcludes.exists()) { 103 parseFile(localExcludes, patterns); 104 } 105 106 return new Excludes(patterns); 107 } 108 109 /** 110 * Reads vendor excluded path files. 111 * @see #readExcludes() 112 */ readVendorExcludes(List<Pattern> out)113 private static void readVendorExcludes(List<Pattern> out) throws IOException { 114 File vendorDir = new File(VENDOR_PATH); 115 File[] vendorList; 116 if (!vendorDir.exists() || (vendorList = vendorDir.listFiles()) == null) return; 117 for (File vendor : vendorList) { 118 File vendorExcludes = new File(vendor, EXCLUDED_PATHS); 119 if (vendorExcludes.exists()) { 120 Log.info("Read vendor excludes: " + vendorExcludes.getPath()); 121 parseFile(vendorExcludes, out); 122 } 123 } 124 } 125 126 /** 127 * Recursively finds .java source roots, .jar files, and excluded 128 * directories. 129 */ traverse(File directory, Set<File> sourceRoots, Collection<File> jarFiles, Collection<File> excludedDirs, Excludes excludes)130 private static void traverse(File directory, Set<File> sourceRoots, 131 Collection<File> jarFiles, Collection<File> excludedDirs, 132 Excludes excludes) throws IOException { 133 /* 134 * Note it would be faster to stop traversing a source root as soon as 135 * we encounter the first .java file, but it appears we have nested 136 * source roots in our generated source directory (specifically, 137 * R.java files and aidl .java files don't share the same source 138 * root). 139 */ 140 141 boolean firstJavaFile = true; 142 File[] files = directory.listFiles(); 143 if (files == null) { 144 return; 145 } 146 for (File file : files) { 147 // Trim preceding "./" from path. 148 String path = file.getPath().substring(2); 149 150 // Skip nonexistent files/diretories, e.g. broken symlinks. 151 if (!file.exists()) { 152 Log.debug("Skipped nonexistent: " + path); 153 continue; 154 } 155 156 if (file.isDirectory()) { 157 // Traverse nested directories. 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 } else if (path.endsWith(".java")) { 167 // Keep track of source roots for .java files. 168 // Do not check excludes in this branch. 169 if (firstJavaFile) { 170 // Only parse one .java file per directory. 171 firstJavaFile = false; 172 173 File sourceRoot = rootOf(file); 174 if (sourceRoot != null) { 175 sourceRoots.add(sourceRoot); 176 } 177 } 178 } else if (path.endsWith(".jar")) { 179 // Keep track of .jar files. 180 if (excludes.exclude(path)) { 181 Log.debug("Skipped: " + file); 182 } else { 183 jarFiles.add(file); 184 } 185 } 186 } 187 } 188 189 /** 190 * Determines the source root for a given .java file. Returns null 191 * if the file doesn't have a package or if the file isn't in the 192 * correct directory structure. 193 */ rootOf(File javaFile)194 private static File rootOf(File javaFile) throws IOException { 195 String packageName = parsePackageName(javaFile); 196 if (packageName == null) { 197 // No package. 198 // TODO: Treat this as a source root? 199 return null; 200 } 201 202 String packagePath = packageName.replace('.', File.separatorChar); 203 File parent = javaFile.getParentFile(); 204 String parentPath = parent.getPath(); 205 if (!parentPath.endsWith(packagePath)) { 206 // Bad dir structure. 207 return null; 208 } 209 210 return new File(parentPath.substring( 211 0, parentPath.length() - packagePath.length())); 212 } 213 214 /** 215 * Reads a Java file and parses out the package name. Returns null if none 216 * found. 217 */ parsePackageName(File file)218 private static String parsePackageName(File file) throws IOException { 219 BufferedReader in = new BufferedReader(new FileReader(file)); 220 try { 221 String line; 222 while ((line = in.readLine()) != null) { 223 String trimmed = line.trim(); 224 if (trimmed.startsWith("package")) { 225 // TODO: Make this more robust. 226 // Assumes there's only once space after "package" and the 227 // line ends in a ";". 228 return trimmed.substring(8, trimmed.length() - 1); 229 } 230 } 231 232 return null; 233 } finally { 234 in.close(); 235 } 236 } 237 238 /** 239 * Picks out excluded directories that are under source roots. 240 */ excludesUnderSourceRoots()241 public SortedSet<File> excludesUnderSourceRoots() { 242 // TODO: Refactor this to share the similar logic in 243 // Eclipse.constructExcluding(). 244 SortedSet<File> picked = new TreeSet<File>(); 245 for (File sourceRoot : sourceRoots) { 246 String sourcePath = sourceRoot.getPath() + "/"; 247 SortedSet<File> tailSet = excludedDirs.tailSet(sourceRoot); 248 for (File file : tailSet) { 249 if (file.getPath().startsWith(sourcePath)) { 250 picked.add(file); 251 } else { 252 break; 253 } 254 } 255 } 256 return picked; 257 } 258 259 /** 260 * Reads a list of regular expressions from a file, one per line, and adds 261 * the compiled patterns to the given collection. Ignores lines starting 262 * with '#'. 263 * 264 * @param file containing regular expressions, one per line 265 * @param patterns collection to add compiled patterns from file to 266 */ parseFile(File file, Collection<Pattern> patterns)267 public static void parseFile(File file, Collection<Pattern> patterns) 268 throws IOException { 269 BufferedReader in = new BufferedReader(new FileReader(file)); 270 try { 271 String line; 272 while ((line = in.readLine()) != null) { 273 String trimmed = line.trim(); 274 if (trimmed.length() > 0 && !trimmed.startsWith("#")) { 275 patterns.add(Pattern.compile(trimmed)); 276 } 277 } 278 } finally { 279 in.close(); 280 } 281 } 282 } 283