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