KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > util > FileOracleFactory


1 /*
2  * Copyright 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.util;
17
18 import com.google.gwt.core.ext.TreeLogger;
19
20 import java.io.File JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23 import java.net.URI JavaDoc;
24 import java.net.URISyntaxException JavaDoc;
25 import java.net.URL JavaDoc;
26 import java.net.URLClassLoader JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Arrays JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Map JavaDoc;
34 import java.util.jar.JarEntry JavaDoc;
35 import java.util.jar.JarFile JavaDoc;
36
37 /**
38  * Creates a FileOracle based on a set of logical packages combined with either
39  * a URLClassLoader. For each specified package, the ClassLoader is searched for
40  * instances of that package as a directory. The results of this operation are
41  * merged together into a single list of URLs whose order is determined by the
42  * order of URLs in the ClassLoader. The relative order of different logical
43  * packages originating from the same URL in the ClassLoader is undefined.
44  *
45  * Once the sorted list of URLs is resolved, each URL is recursively searched to
46  * index all of its files (optionally, that pass the given FileOracleFilter).
47  * The results of this indexing are used to create the output FileOracle. Once
48  * the FileOracle is created, its index is fixed and no longer depends on the
49  * underlying URLClassLoader or file system. However, URLs returned from the
50  * FileOracle may become invalid if the contents of the file system change.
51  *
52  * Presently, only URLs beginning with <code>file:</code> and
53  * <code>jar:file:</code> can be inspected to index children. Any other types
54  * of URLs will generate a warning. The set of children indexed by
55  * <code>jar:file:</code> type URLs is fixed at creation time, but the set of
56  * children from <code>file:</code> type URLs will dynamically query the
57  * underlying file system.
58  */

59 public class FileOracleFactory {
60
61   /**
62    * Used to decide whether or not a resource name should be included in an
63    * enumeration.
64    */

65   public interface FileFilter {
66     boolean accept(String JavaDoc name);
67   }
68
69   /**
70    * Implementation of a FileOracle as an ordered (based on class path) list of
71    * abstract names (relative to some root), each mapped to a concrete URL.
72    */

73   private static final class FileOracleImpl extends FileOracle {
74
75     private final String JavaDoc[] logicalNames;
76
77     private final Map JavaDoc logicalToPhysical;
78
79     /**
80      * Creates a new FileOracle.
81      *
82      * @param logicalNames An ordered list of abstract path name strings.
83      * @param logicalToPhysical A map of every item in logicalNames onto a URL.
84      */

85     public FileOracleImpl(List JavaDoc logicalNames, Map JavaDoc logicalToPhysical) {
86       this.logicalNames = (String JavaDoc[]) logicalNames.toArray(new String JavaDoc[logicalNames.size()]);
87       this.logicalToPhysical = new HashMap JavaDoc(logicalToPhysical);
88     }
89
90     /*
91      * (non-Javadoc)
92      *
93      * @see com.google.gwt.dev.util.FileOracle#find(java.lang.String)
94      */

95     public URL JavaDoc find(String JavaDoc partialPath) {
96       return (URL JavaDoc) logicalToPhysical.get(partialPath);
97     }
98
99     /*
100      * (non-Javadoc)
101      *
102      * @see com.google.gwt.dev.util.FileOracle#getAllFiles()
103      */

104     public String JavaDoc[] getAllFiles() {
105       return logicalNames;
106     }
107
108     /*
109      * (non-Javadoc)
110      *
111      * @see com.google.gwt.dev.util.FileOracle#isEmpty()
112      */

113     public boolean isEmpty() {
114       return logicalNames.length == 0;
115     }
116   }
117
118   /**
119    * Given a set of logical packages, finds every occurrence of each of those
120    * packages within cl, and then sorts them relative to each other based on
121    * classPathUrlList.
122    *
123    * @param logger Logs the process.
124    * @param cl Provides the underlying class path.
125    * @param packageSet The input set of logical packages to search for and sort.
126    * @param classPathUrlList The order in which to sort the results.
127    * @param sortedUrls An output list to which urls are appended.
128    * @param sortedPackages An output list to which logical packages are appended
129    * exactly corresponding to appends made to sortedUrls.
130    * @param recordPackages If false, only empty strings are appended to
131    * sortedPackages.
132    */

133   private static void addPackagesInSortedOrder(TreeLogger logger,
134       URLClassLoader JavaDoc cl, Map JavaDoc packageMap, List JavaDoc classPathUrlList,
135       List JavaDoc sortedUrls, List JavaDoc sortedPackages, List JavaDoc sortedFilters,
136       boolean recordPackages) {
137
138     // Exhaustively find every package on the classpath in an unsorted fashion
139
//
140
List JavaDoc unsortedUrls = new ArrayList JavaDoc();
141     List JavaDoc unsortedPackages = new ArrayList JavaDoc();
142     List JavaDoc unsortedFilters = new ArrayList JavaDoc();
143     for (Iterator JavaDoc itPkg = packageMap.keySet().iterator(); itPkg.hasNext();) {
144       String JavaDoc curPkg = (String JavaDoc) itPkg.next();
145       FileFilter curFilter = (FileFilter) packageMap.get(curPkg);
146       try {
147         Enumeration JavaDoc found = cl.findResources(curPkg);
148         if (!recordPackages) {
149           curPkg = "";
150         }
151         while (found.hasMoreElements()) {
152           URL JavaDoc match = (URL JavaDoc) found.nextElement();
153           unsortedUrls.add(match);
154           unsortedPackages.add(curPkg);
155           unsortedFilters.add(curFilter);
156         }
157       } catch (IOException JavaDoc e) {
158         logger.log(TreeLogger.WARN, "Unexpected error searching classpath for "
159             + curPkg, e);
160       }
161     }
162
163     /*
164      * Now sort the collected list by the proper class path order. This is an
165      * O(N*M) operation, but it should be okay for what we're doing
166      */

167
168     // pre-convert the List of URL to String[] to speed up the inner loop below
169
int c = unsortedUrls.size();
170     String JavaDoc[] unsortedUrlStrings = new String JavaDoc[c];
171     for (int i = 0; i < c; ++i) {
172       unsortedUrlStrings[i] = unsortedUrls.get(i).toString();
173       // strip the jar prefix for text matching purposes
174
if (unsortedUrlStrings[i].startsWith("jar:")) {
175         unsortedUrlStrings[i] = unsortedUrlStrings[i].substring(4);
176       }
177     }
178
179     // now sort the URLs based on classPathUrlList
180
for (Iterator JavaDoc itCp = classPathUrlList.iterator(); itCp.hasNext();) {
181       URL JavaDoc curCpUrl = (URL JavaDoc) itCp.next();
182       String JavaDoc curUrlString = curCpUrl.toExternalForm();
183       // find all URLs that match this particular entry
184
for (int i = 0; i < c; ++i) {
185         if (unsortedUrlStrings[i].startsWith(curUrlString)) {
186           sortedUrls.add(unsortedUrls.get(i));
187           sortedPackages.add(unsortedPackages.get(i));
188           sortedFilters.add(unsortedFilters.get(i));
189         }
190       }
191     }
192   }
193
194   /**
195    * Index all the children of a particular folder (recursively).
196    *
197    * @param logger Logs the process.
198    * @param filter If non-null, filters out which files get indexed.
199    * @param stripBaseLen The number of characters to strip from the beginning of
200    * every child's file path when computing the logical name.
201    * @param curDir The directory to index.
202    * @param logicalNames An output List of Children found under this URL.
203    * @param logicalToPhysical An output Map of Children found under this URL
204    * mapped to their concrete URLs.
205    */

206   private static void indexFolder(TreeLogger logger, FileFilter filter,
207       int stripBaseLen, File JavaDoc curDir, List JavaDoc logicalNames, Map JavaDoc logicalToPhysical) {
208     File JavaDoc[] files = curDir.listFiles();
209     for (int i = 0; i < files.length; i++) {
210       File JavaDoc f = files[i];
211       if (f.exists()) {
212         if (f.isDirectory()) {
213           indexFolder(logger, filter, stripBaseLen, f, logicalNames,
214               logicalToPhysical);
215         } else if (f.isFile()) {
216           try {
217             String JavaDoc logicalName = f.getAbsolutePath().substring(stripBaseLen);
218             logicalName = logicalName.replace(File.separatorChar, '/');
219             if (logicalToPhysical.containsKey(logicalName)) {
220               // this logical name is shadowed
221
logger.log(TreeLogger.DEBUG, "Ignoring already-resolved "
222                   + logicalName, null);
223               continue;
224             }
225             if (filter != null && !filter.accept(logicalName)) {
226               // filtered out
227
logger.log(TreeLogger.SPAM, "Filtered out " + logicalName, null);
228               continue;
229             }
230             URL JavaDoc physicalUrl = f.toURL();
231             logicalToPhysical.put(logicalName, physicalUrl);
232             logicalNames.add(logicalName);
233             logger.log(TreeLogger.TRACE, "Found " + logicalName, null);
234           } catch (IOException JavaDoc e) {
235             logger.log(TreeLogger.WARN, "Unexpected error resolving " + f, e);
236           }
237         }
238       }
239     }
240   }
241
242   /**
243    * Index all the children in a particular folder of a jar.
244    *
245    * @param logger Logs the process.
246    * @param filter If non-null, filters out which files get indexed.
247    * @param jarUrl The URL of the containing jar file.
248    * @param jarFile The jarFile to index.
249    * @param basePath The sub tree within the jarFile to index.
250    * @param pkgBase If non-empty, causes the logical names of children to be
251    * shorter (rooting them higher in the tree).
252    * @param logicalNames An output List of Children found under this URL.
253    * @param logicalToPhysical An output Map of Children found under this URL
254    * mapped to their concrete URLs.
255    */

256   private static void indexJar(TreeLogger logger, FileFilter filter,
257       String JavaDoc jarUrl, JarFile JavaDoc jarFile, String JavaDoc basePath, String JavaDoc pkgBase,
258       List JavaDoc logicalNames, Map JavaDoc logicalToPhysical) {
259     int prefixCharsToStrip = basePath.length() - pkgBase.length();
260     for (Enumeration JavaDoc enumJar = jarFile.entries(); enumJar.hasMoreElements();) {
261       JarEntry JavaDoc jarEntry = (JarEntry JavaDoc) enumJar.nextElement();
262       String JavaDoc jarEntryName = jarEntry.getName();
263       if (jarEntryName.startsWith(basePath) && !jarEntry.isDirectory()) {
264         String JavaDoc logicalName = jarEntryName.substring(prefixCharsToStrip);
265         String JavaDoc physicalUrlString = jarUrl + "!/" + jarEntryName;
266         if (logicalToPhysical.containsKey(logicalName)) {
267           // this logical name is shadowed
268
logger.log(TreeLogger.DEBUG, "Ignoring already-resolved "
269               + logicalName, null);
270           continue;
271         }
272         if (filter != null && !filter.accept(logicalName)) {
273           // filtered out
274
logger.log(TreeLogger.SPAM, "Filtered out " + logicalName, null);
275           continue;
276         }
277         try {
278           URL JavaDoc physicalUrl = new URL JavaDoc(physicalUrlString);
279           logicalToPhysical.put(logicalName, physicalUrl);
280           logicalNames.add(logicalName);
281           logger.log(TreeLogger.TRACE, "Found " + logicalName, null);
282         } catch (MalformedURLException JavaDoc e) {
283           logger.log(TreeLogger.WARN, "Unexpected error resolving "
284               + physicalUrlString, e);
285         }
286       }
287     }
288   }
289
290   /**
291    * Finds all children of the specified URL and indexes them.
292    *
293    * @param logger Logs the process.
294    * @param filter If non-null, filters out which files get indexed.
295    * @param url The URL to index, must be <code>file:</code> or
296    * <code>jar:file:</code>
297    * @param pkgBase A prefix to exclude when indexing children.
298    * @param logicalNames An output List of Children found under this URL.
299    * @param logicalToPhysical An output Map of Children found under this URL
300    * mapped to their concrete URLs.
301    * @throws URISyntaxException if an unexpected error occurs.
302    * @throws IOException if an unexpected error occurs.
303    */

304   private static void indexURL(TreeLogger logger, FileFilter filter, URL JavaDoc url,
305       String JavaDoc pkgBase, List JavaDoc logicalNames, Map JavaDoc logicalToPhysical)
306       throws URISyntaxException JavaDoc, IOException JavaDoc {
307
308     String JavaDoc urlString = url.toString();
309     if (url.getProtocol().equals("file")) {
310       URI JavaDoc uri = new URI JavaDoc(urlString);
311       File JavaDoc f = new File JavaDoc(uri);
312       if (f.isDirectory()) {
313         int prefixCharsToStrip = f.getAbsolutePath().length() + 1
314             - pkgBase.length();
315         indexFolder(logger, filter, prefixCharsToStrip, f, logicalNames,
316             logicalToPhysical);
317       } else {
318         // We can't handle files here, only directories. If this is a jar
319
// reference, the url must come in as a "jar:file:<stuff>!/[stuff/]".
320
// Fall through.
321
logger.log(TreeLogger.WARN, "Unexpected error, " + f
322             + " is neither a file nor a jar", null);
323       }
324     } else if (url.getProtocol().equals("jar")) {
325       String JavaDoc path = url.getPath();
326       int pos = path.indexOf('!');
327       if (pos >= 0) {
328         String JavaDoc jarPath = path.substring(0, pos);
329         String JavaDoc dirPath = path.substring(pos + 2);
330         URL JavaDoc jarURL = new URL JavaDoc(jarPath);
331         if (jarURL.getProtocol().equals("file")) {
332           URI JavaDoc jarURI = new URI JavaDoc(jarURL.toString());
333           File JavaDoc f = new File JavaDoc(jarURI);
334           JarFile JavaDoc jarFile = new JarFile JavaDoc(f);
335           // From each child, strip off the leading classpath portion when
336
// determining the logical name (sans the pkgBase name we want!)
337
//
338
indexJar(logger, filter, "jar" + ":" + jarPath, jarFile, dirPath, pkgBase,
339               logicalNames, logicalToPhysical);
340         } else {
341           logger.log(TreeLogger.WARN, "Unexpected error, jar at " + jarURL
342               + " must be a file: type URL", null);
343         }
344       } else {
345         throw new URISyntaxException JavaDoc(path, "Cannot locate '!' separator");
346       }
347     } else {
348       logger.log(TreeLogger.WARN, "Unknown URL type for " + urlString, null);
349     }
350   }
351
352   /**
353    * The underlying classloader.
354    */

355   private final URLClassLoader JavaDoc classLoader;
356
357   /**
358    * A map of packages indexed from the root of the class path onto their
359    * corresponding FileFilters.
360    */

361   private final Map JavaDoc packages = new HashMap JavaDoc();
362
363   /**
364    * A map of packages that become their own roots (that is their children are
365    * indexed relative to them) onto their corresponding FileFilters.
366    */

367   private final Map JavaDoc rootPackages = new HashMap JavaDoc();
368
369   /**
370    * Creates a FileOracleFactory with the default URLClassLoader.
371    */

372   public FileOracleFactory() {
373     this((URLClassLoader JavaDoc) FileOracleFactory.class.getClassLoader());
374   }
375
376   /**
377    * Creates a FileOracleFactory.
378    *
379    * @param classLoader The underlying class path to use.
380    */

381   public FileOracleFactory(URLClassLoader JavaDoc classLoader) {
382     this.classLoader = classLoader;
383   }
384
385   /**
386    * Adds a logical package to the product FileOracle. All instances of this
387    * package that can be found in the underlying URLClassLoader will have their
388    * their children indexed, relative to the class path entry on which they are
389    * found.
390    *
391    * @param packageAsPath For example, "com/google/gwt/core/client".
392    */

393   public void addPackage(String JavaDoc packageAsPath, FileFilter filter) {
394     packageAsPath = ensureTrailingBackslash(packageAsPath);
395     packages.put(packageAsPath, filter);
396   }
397
398   /**
399    * Adds a logical root package to the product FileOracle. All instances of
400    * this package that can be found in the underlying URLClassLoader will have
401    * their their children indexed, relative to their location within
402    * packageAsPath. All root packages trump all non-root packages when
403    * determining the final precedence order.
404    *
405    * @param packageAsPath For example, "com/google/gwt/core/client".
406    */

407   public void addRootPackage(String JavaDoc packageAsPath, FileFilter filter) {
408     packageAsPath = ensureTrailingBackslash(packageAsPath);
409     rootPackages.put(packageAsPath, filter);
410   }
411
412   /**
413    * Creates the product FileOracle based on the logical packages previously
414    * added.
415    *
416    * @param logger Logs the process.
417    * @return a new FileOracle.
418    */

419   public FileOracle create(TreeLogger logger) {
420
421     // get the full expanded URL class path for sorting purposes
422
//
423
List JavaDoc classPathUrls = new ArrayList JavaDoc();
424     for (ClassLoader JavaDoc curCL = classLoader; curCL != null; curCL = curCL.getParent()) {
425       if (curCL instanceof URLClassLoader JavaDoc) {
426         URLClassLoader JavaDoc curURLCL = (URLClassLoader JavaDoc) curCL;
427         URL JavaDoc[] curURLs = curURLCL.getURLs();
428         classPathUrls.addAll(Arrays.asList(curURLs));
429       }
430     }
431
432     /*
433      * Collect a sorted list of URLs corresponding to all of the logical
434      * packages mapped onto the
435      */

436
437     // The list of
438
List JavaDoc urls = new ArrayList JavaDoc();
439     List JavaDoc pkgNames = new ArrayList JavaDoc();
440     List JavaDoc filters = new ArrayList JavaDoc();
441
442     // don't record package names for root packages, they are rebased
443
addPackagesInSortedOrder(logger, classLoader, rootPackages, classPathUrls,
444         urls, pkgNames, filters, false);
445     // record package names for non-root packages
446
addPackagesInSortedOrder(logger, classLoader, packages, classPathUrls,
447         urls, pkgNames, filters, true);
448
449     // We have a complete sorted list of mapped URLs with package prefixes
450

451     // Setup data collectors
452
List JavaDoc logicalNames = new ArrayList JavaDoc();
453     Map JavaDoc logicalToPhysical = new HashMap JavaDoc();
454
455     for (int i = 0, c = urls.size(); i < c; ++i) {
456       try {
457         URL JavaDoc url = (URL JavaDoc) urls.get(i);
458         String JavaDoc pkgName = (String JavaDoc) pkgNames.get(i);
459         FileFilter filter = (FileFilter) filters.get(i);
460         TreeLogger branch = logger.branch(TreeLogger.TRACE, url.toString(),
461             null);
462         indexURL(branch, filter, url, pkgName, logicalNames, logicalToPhysical);
463       } catch (URISyntaxException JavaDoc e) {
464         logger.log(TreeLogger.WARN,
465             "Unexpected error searching " + urls.get(i), e);
466       } catch (IOException JavaDoc e) {
467         logger.log(TreeLogger.WARN,
468             "Unexpected error searching " + urls.get(i), e);
469       }
470     }
471
472     return new FileOracleImpl(logicalNames, logicalToPhysical);
473   }
474
475   /**
476    * Helper method to regularize packages.
477    *
478    * @param packageAsPath For exmaple, "com/google/gwt/core/client"
479    * @return For example, "com/google/gwt/core/client/"
480    */

481   private String JavaDoc ensureTrailingBackslash(String JavaDoc packageAsPath) {
482     if (packageAsPath.endsWith("/")) {
483       return packageAsPath;
484     } else {
485       return packageAsPath + "/";
486     }
487   }
488 }
489
Popular Tags