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 File[] files = directory.listFiles(); 127 if (files == null) { 128 return; 129 } 130 for (File file : files) { 131 // Trim preceding "./" from path. 132 String path = file.getPath().substring(2); 133 134 // Skip nonexistent files/diretories, e.g. broken symlinks. 135 if (!file.exists()) { 136 Log.debug("Skipped nonexistent: " + path); 137 continue; 138 } 139 140 if (file.isDirectory()) { 141 // Traverse nested directories. 142 if (excludes.exclude(path)) { 143 // Don't recurse into excluded dirs. 144 Log.debug("Excluding: " + path); 145 excludedDirs.add(file); 146 } else { 147 traverse(file, sourceRoots, jarFiles, excludedDirs, 148 excludes); 149 } 150 } else if (path.endsWith(".java")) { 151 // Keep track of source roots for .java files. 152 // Do not check excludes in this branch. 153 if (firstJavaFile) { 154 // Only parse one .java file per directory. 155 firstJavaFile = false; 156 157 File sourceRoot = rootOf(file); 158 if (sourceRoot != null) { 159 sourceRoots.add(sourceRoot); 160 } 161 } 162 } else if (path.endsWith(".jar")) { 163 // Keep track of .jar files. 164 if (excludes.exclude(path)) { 165 Log.debug("Skipped: " + file); 166 } else { 167 jarFiles.add(file); 168 } 169 } 170 } 171 } 172 173 /** 174 * Determines the source root for a given .java file. Returns null 175 * if the file doesn't have a package or if the file isn't in the 176 * correct directory structure. 177 */ rootOf(File javaFile)178 private static File rootOf(File javaFile) throws IOException { 179 String packageName = parsePackageName(javaFile); 180 if (packageName == null) { 181 // No package. 182 // TODO: Treat this as a source root? 183 return null; 184 } 185 186 String packagePath = packageName.replace('.', File.separatorChar); 187 File parent = javaFile.getParentFile(); 188 String parentPath = parent.getPath(); 189 if (!parentPath.endsWith(packagePath)) { 190 // Bad dir structure. 191 return null; 192 } 193 194 return new File(parentPath.substring( 195 0, parentPath.length() - packagePath.length())); 196 } 197 198 /** 199 * Reads a Java file and parses out the package name. Returns null if none 200 * found. 201 */ parsePackageName(File file)202 private static String parsePackageName(File file) throws IOException { 203 BufferedReader in = new BufferedReader(new FileReader(file)); 204 try { 205 String line; 206 while ((line = in.readLine()) != null) { 207 String trimmed = line.trim(); 208 if (trimmed.startsWith("package")) { 209 // TODO: Make this more robust. 210 // Assumes there's only once space after "package" and the 211 // line ends in a ";". 212 return trimmed.substring(8, trimmed.length() - 1); 213 } 214 } 215 216 return null; 217 } finally { 218 in.close(); 219 } 220 } 221 222 /** 223 * Picks out excluded directories that are under source roots. 224 */ excludesUnderSourceRoots()225 public SortedSet<File> excludesUnderSourceRoots() { 226 // TODO: Refactor this to share the similar logic in 227 // Eclipse.constructExcluding(). 228 SortedSet<File> picked = new TreeSet<File>(); 229 for (File sourceRoot : sourceRoots) { 230 String sourcePath = sourceRoot.getPath() + "/"; 231 SortedSet<File> tailSet = excludedDirs.tailSet(sourceRoot); 232 for (File file : tailSet) { 233 if (file.getPath().startsWith(sourcePath)) { 234 picked.add(file); 235 } else { 236 break; 237 } 238 } 239 } 240 return picked; 241 } 242 243 /** 244 * Reads a list of regular expressions from a file, one per line, and adds 245 * the compiled patterns to the given collection. Ignores lines starting 246 * with '#'. 247 * 248 * @param file containing regular expressions, one per line 249 * @param patterns collection to add compiled patterns from file to 250 */ parseFile(File file, Collection<Pattern> patterns)251 public static void parseFile(File file, Collection<Pattern> patterns) 252 throws IOException { 253 BufferedReader in = new BufferedReader(new FileReader(file)); 254 try { 255 String line; 256 while ((line = in.readLine()) != null) { 257 String trimmed = line.trim(); 258 if (trimmed.length() > 0 && !trimmed.startsWith("#")) { 259 patterns.add(Pattern.compile(trimmed)); 260 } 261 } 262 } finally { 263 in.close(); 264 } 265 } 266 } 267