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