1 /*
2  * Copyright (C) 2012 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 package com.android.idegen;
18 
19 import com.google.common.base.Preconditions;
20 import com.google.common.base.Predicate;
21 import com.google.common.collect.Collections2;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.Sets;
24 
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.FilenameFilter;
28 import java.io.IOException;
29 import java.net.URISyntaxException;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 
37 /**
38  * Find directories utility.
39  */
40 public class DirectorySearch {
41 
42     private static final Logger logger = Logger.getLogger(DirectorySearch.class.getName());
43 
44     public static final HashSet<String> SOURCE_DIRS = Sets.newHashSet();
45 
46     static {
47         SOURCE_DIRS.add("src");
48         SOURCE_DIRS.add("java");
49     }
50 
51     private static final Pattern EXCLUDE_PATTERN = Pattern.compile("values-..(-.*)*");
52 
53     private static File repoRoot = null;
54     public static final String REL_TEMPLATE_DIR = "templates";
55     public static final String REL_TEMPLATE_PATH_FROM_ROOT = "development/tools/idegen/"
56             + REL_TEMPLATE_DIR;
57 
58     /**
59      * Returns the previously initialized repo root.
60      */
getRepoRoot()61     public static File getRepoRoot() {
62         Preconditions.checkNotNull(repoRoot, "repoRoot has not been initialized yet.  Call "
63                 + "findAndInitRepoRoot() first.");
64         return repoRoot;
65     }
66 
67     /**
68      * Find the repo root.  This is the root branch directory of a full repo checkout.
69      *
70      * @param file any file inside the root.
71      * @return the root directory.
72      */
findAndInitRepoRoot(File file)73     public static void findAndInitRepoRoot(File file) {
74         Preconditions.checkNotNull(file);
75         if (repoRoot != null) {
76             return;
77         }
78 
79         if (file.isDirectory()) {
80             File[] files = file.listFiles(new FilenameFilter() {
81                 @Override
82                 public boolean accept(File dir, String name) {
83                     return ".repo".equals(name);
84                 }
85             });
86             if (files.length > 0) {
87                 repoRoot = file;
88             }
89         }
90         File parent = file.getParentFile();
91         if (parent == null) {
92             throw new IllegalStateException("Repo root not found from starting point " +
93                     file.getPath());
94         }
95         findAndInitRepoRoot(parent);
96     }
97 
98     /**
99      * Searches up the parent chain to find the closes module root directory. A module root is one
100      * with an Android.mk file in it. <p> For example, the module root for directory
101      * <code>package/apps/Contacts/src</code> is <code>packages/apps/Contacts</code>
102      *
103      * @return the module root.
104      * @throws IOException when module root is not found.
105      */
findModuleRoot(File path)106     public static File findModuleRoot(File path) throws IOException {
107         Preconditions.checkNotNull(path);
108         File dir;
109         if (path.isFile()) {
110             dir = path.getParentFile();
111         } else {
112             dir = path;
113         }
114         while (dir != null) {
115             File makeFile = new File(dir, "Android.mk");
116             if (makeFile.exists()) {
117                 return dir;
118             } else {
119                 dir = dir.getParentFile();
120             }
121         }
122         // At this point, there are no parents and we have not found a module. Error.
123         throw new IOException("Module root not found for path " + path.getCanonicalPath());
124     }
125 
126     /**
127      * Find all source directories from a given root file.
128      *
129      * If the root file is a file, the directory of that file will be used as the starting
130      * location.
131      *
132      * @param file The starting location. Can be a file or directory.
133      * @return List of
134      */
findSourceDirs(File file)135     public static ImmutableList<File> findSourceDirs(File file) {
136         Preconditions.checkNotNull(file);
137         if (!file.exists()) {
138             return ImmutableList.of();
139         }
140         if (!file.isDirectory()) {
141             file = file.getParentFile();
142         }
143         ImmutableList.Builder<File> builder = ImmutableList.builder();
144         File[] children = file.listFiles();
145         for (File child : children) {
146             if (child.isDirectory()) {
147                 // Recurse further down the tree first to cover case of:
148                 //
149                 // src/java
150                 //   or
151                 // java/src
152                 //   or
153                 // src/main/java/java
154                 //
155                 // In first two cases, we don't want the parent.
156                 // In third case we want the parent of the last "java", the last "java" is actually
157                 // part of namespace.
158                 ImmutableList<File> dirs = findSourceDirs(child);
159                 // filter out the third case.
160                 Collection<File> filteredDirs = Collections2.filter(dirs, new Predicate<File>() {
161                     @Override
162                     public boolean apply(File input) {
163                         return !input.getAbsolutePath().endsWith("java/java");
164                     }
165                 });
166                 if (filteredDirs.isEmpty()) {
167                     if (SOURCE_DIRS.contains(child.getName())) {
168                         builder.add(child);
169                     }
170                 } else {
171                     builder.addAll(filteredDirs);
172                 }
173             }
174         }
175 
176         return builder.build();
177     }
178 
findExcludeDirs(File file)179     public static ImmutableList<File> findExcludeDirs(File file) {
180         Preconditions.checkNotNull(file);
181         if (!file.exists()) {
182             return ImmutableList.of();
183         }
184         if (!file.isDirectory()) {
185             file = file.getParentFile();
186         }
187         ImmutableList.Builder<File> builder = ImmutableList.builder();
188         // Go into the res folder
189         File resFile = new File(file, "res");
190         if (resFile.exists()) {
191 
192             File[] children = resFile.listFiles();
193             for (File child : children) {
194                 if (child.isDirectory()) {
195                     Matcher matcher = EXCLUDE_PATTERN.matcher(child.getName());
196                     if (matcher.matches()) {
197                         // Exclude internationalization language folders.
198                         // ex: values-zh
199                         // But don't exclude values-land.  Assume all language folders are two
200                         // letters.
201                         builder.add(child);
202                     }
203                 }
204             }
205         }
206 
207         return builder.build();
208     }
209 
210     private static File templateDirCurrent = null;
211     private static File templateDirRoot = null;
212 
findTemplateDir()213     public static File findTemplateDir() throws IOException {
214         // Cache optimization.
215         if (templateDirCurrent != null && templateDirCurrent.exists()) {
216             return templateDirCurrent;
217         }
218         if (templateDirRoot != null && templateDirRoot.exists()) {
219             return templateDirRoot;
220         }
221 
222         File currentDir = null;
223         try {
224             currentDir = new File(
225                     IntellijProject.class.getProtectionDomain().getCodeSource().getLocation()
226                             .toURI().getPath()).getParentFile();
227         } catch (URISyntaxException e) {
228             logger.log(Level.SEVERE, "Could not get jar location.", e);
229             return null;
230         }
231         // Support for program execution in intellij.
232         if (currentDir.getPath().endsWith("out/production")) {
233             return new File(currentDir.getParentFile().getParentFile(), REL_TEMPLATE_DIR);
234         }
235         // First check relative to current run directory.
236         templateDirCurrent = new File(currentDir, REL_TEMPLATE_DIR);
237         if (templateDirCurrent.exists()) {
238             return templateDirCurrent;
239         } else {
240             // Then check relative to root directory.
241             templateDirRoot = new File(repoRoot, REL_TEMPLATE_PATH_FROM_ROOT);
242             if (templateDirRoot.exists()) {
243                 return templateDirRoot;
244             }
245         }
246         throw new FileNotFoundException(
247                 "Unable to find template dir. Tried the following locations:\n" +
248                         templateDirCurrent.getCanonicalPath() + "\n" +
249                         templateDirRoot.getCanonicalPath());
250     }
251 }
252