Home | History | Annotate | Download | only in src
      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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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