KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > api > java > classpath > ClassPath


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.api.java.classpath;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.beans.PropertyChangeListener JavaDoc;
24 import java.beans.PropertyChangeSupport JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.lang.ref.Reference JavaDoc;
28 import java.lang.ref.SoftReference JavaDoc;
29 import java.lang.ref.WeakReference JavaDoc;
30 import java.net.URI JavaDoc;
31 import java.net.URISyntaxException JavaDoc;
32 import java.net.URL JavaDoc;
33 import java.text.MessageFormat JavaDoc;
34 import java.util.ArrayList JavaDoc;
35 import java.util.Arrays JavaDoc;
36 import java.util.Collection JavaDoc;
37 import java.util.Collections JavaDoc;
38 import java.util.HashSet JavaDoc;
39 import java.util.LinkedHashSet JavaDoc;
40 import java.util.List JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.Set JavaDoc;
43 import java.util.WeakHashMap JavaDoc;
44 import org.netbeans.modules.java.classpath.ClassPathAccessor;
45 import org.netbeans.spi.java.classpath.ClassPathImplementation;
46 import org.netbeans.spi.java.classpath.ClassPathProvider;
47 import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation;
48 import org.netbeans.spi.java.classpath.PathResourceImplementation;
49 import org.openide.ErrorManager;
50 import org.openide.filesystems.FileAttributeEvent;
51 import org.openide.filesystems.FileChangeListener;
52 import org.openide.filesystems.FileEvent;
53 import org.openide.filesystems.FileObject;
54 import org.openide.filesystems.FileRenameEvent;
55 import org.openide.filesystems.FileStateInvalidException;
56 import org.openide.filesystems.FileSystem;
57 import org.openide.filesystems.FileUtil;
58 import org.openide.filesystems.URLMapper;
59 import org.openide.util.Lookup;
60 import org.openide.util.Utilities;
61
62 /**
63  * ClassPath objects should be used to access contents of the ClassPath, searching
64  * for resources, objects reachable using the ClassPath at runtime. It is intended
65  * to replace some of the functionality of <link>org.openide.filesystems.Repository</link>.
66  * <BR>
67  * ClassPath instances should be used to map from Java-style resource names
68  * to FileObject (NetBeans-style resource) and vice versa. It should be also used
69  * whenever the operation requires inspection of development or runtime project
70  * environment instead. The service supports either searching in the classpath
71  * resource space, properly hiding resources as the ClassLoader would do at runtime.
72  * It can effectively say whether a FileObject is within the reach of a ClassPath
73  * or whether it is <I>reachable</I> (visible to a ClassLoader). One can translate
74  * filenames to resource names and vice versa.
75  * <P>
76  * A client may obtain a ClassPath instance using
77  * <code>ClassPath.getClassPath(id)</code> static method, where the ID is an
78  * abstract name for the classpath wanted. There are some predefined classpath
79  * names predefined as symbolic constants, following individual types of services
80  * (compiler, debugger, executor). Names are not limited to the listed ones; an extension
81  * module might add its own private classpath type.
82  */

83 public final class ClassPath {
84
85     static {
86         ClassPathAccessor.DEFAULT = new ClassPathAccessor() {
87             public ClassPath createClassPath(ClassPathImplementation spiClasspath) {
88                 return new ClassPath(spiClasspath);
89             }
90             public ClassPathImplementation getClassPathImpl(ClassPath cp) {
91                 return cp == null ? null : cp.impl;
92             }
93         };
94     }
95
96     /**
97      * Classpath setting for executing things. This type can be used to learn
98      * runtime time classpath for execution of the file in question.
99      * <p class="nonnormative">
100      * It corresponds to the <code>-classpath</code> option to <code>java</code>
101      * (the Java launcher): i.e. all compiled classes outside the JRE that
102      * will be needed to run the program, or at least to load a certain class.
103      * It may also be thought of as corresponding to the list of URLs in a
104      * <code>URLClassLoader</code> (plus URLs present in parent class loaders
105      * but excluding the bootstrap and extension class loaders).
106      * </p>
107      */

108     public static final String JavaDoc EXECUTE = "classpath/execute";
109
110     /**
111      * Classpath for debugging things
112      * @deprecated Probably useless.
113      */

114     @Deprecated JavaDoc
115     public static final String JavaDoc DEBUG = "classpath/debug";
116
117     /**
118      * ClassPath for compiling things. This type can be used to learn
119      * compilation time classpath for the file in question.
120      * <p class="nonnormative">
121      * It corresponds to the <code>-classpath</code> option to <code>javac</code>:
122      * i.e. already-compiled classes which some new sources need to compile against,
123      * besides what is already in the JRE.
124      * </p>
125      */

126     public static final String JavaDoc COMPILE = "classpath/compile";
127
128     /**
129      * ClassPath for project sources. This type can be used to learn
130      * package root of the file in question.
131      * <div class="nonnormative">
132      * <p>
133      * It is similar to the <code>-sourcepath</code> option of <code>javac</code>.
134      * </p>
135      * <p>
136      * For typical source files, the sourcepath will consist of one element:
137      * the package root of the source file. If more than one package root is
138      * to be compiled together, all the sources should share a sourcepath
139      * with multiple roots.
140      * </p>
141      * <p>
142      * Note that each source file for which editor code completion (and similar
143      * actions) should work should have a classpath of this type.
144      * </p>
145      * </div>
146      * @since org.netbeans.api.java/1 1.4
147      */

148     public static final String JavaDoc SOURCE = "classpath/source";
149
150     /**
151      * Boot ClassPath of the JDK. This type can be used to learn boot classpath
152      * which should be used for the file in question.
153      * <p class="nonnormative">
154      * It corresponds to the <code>-Xbootclasspath</code> and <code>-Xext</code>
155      * options to <code>java</code> (the Java launcher): i.e. all compiled
156      * classes in the JRE that will be needed to run the program.
157      * It may also be thought of as corresponding to the classes loadable
158      * by the primordial bootstrap class loader <em>plus</em> the standard
159      * extension and endorsed-library class loaders; i.e. class loaders lying
160      * below the regular application startup loader and any custom loaders.
161      * Generally there ought to be a single boot classpath for the entire
162      * application.
163      * </p>
164      * @since org.netbeans.api.java/1 1.4
165      */

166     public static final String JavaDoc BOOT = "classpath/boot";
167
168     /**
169      * Name of the "roots" property
170      */

171     public static final String JavaDoc PROP_ROOTS = "roots";
172
173     /**
174      * Name of the "entries" property
175      */

176     public static final String JavaDoc PROP_ENTRIES = "entries";
177     
178     /**
179      * Property to be fired when include/exclude set changes.
180      * @see FilteringPathResourceImplementation
181      * @since org.netbeans.api.java/1 1.13
182      */

183     public static final String JavaDoc PROP_INCLUDES = "includes";
184     
185     private static final ErrorManager ERR = ErrorManager.getDefault().getInstance(ClassPath.class.getName());
186     
187     private static final Lookup.Result<? extends ClassPathProvider> implementations =
188         Lookup.getDefault().lookupResult(ClassPathProvider.class);
189
190     private ClassPathImplementation impl;
191     private FileObject[] rootsCache;
192     /**
193      * Associates entry roots with the matching filter, if there is one.
194      * XXX not quite right since we could have the same root added twice with two different
195      * filters. But why would you do that?
196      */

197     private Map JavaDoc<FileObject,FilteringPathResourceImplementation> root2Filter = new WeakHashMap JavaDoc<FileObject,FilteringPathResourceImplementation>();
198     private PropertyChangeListener JavaDoc pListener;
199     private RootsListener rootsListener;
200     private List JavaDoc<ClassPath.Entry> entriesCache;
201     private long invalidEntries; //Lamport ordering of events
202
private long invalidRoots; //Lamport ordering of events
203

204     /**
205      * Retrieves valid roots of ClassPath, in the proper order.
206      * If there's an entry in the ClassPath, which cannot be accessed,
207      * its root is not returned by this method. FileObjects returned
208      * are all folders.
209      * Note that this method ignores {@link FilteringPathResourceImplementation includes and excludes}.
210      * @return array of roots (folders) of the classpath. Never returns
211      * null.
212      */

213     public FileObject[] getRoots() {
214         long current;
215         synchronized (this) {
216             if (rootsCache != null) {
217                 return this.rootsCache;
218             }
219             current = this.invalidRoots;
220         }
221         List JavaDoc<ClassPath.Entry> entries = this.entries();
222         FileObject[] ret;
223         synchronized (this) {
224             if (this.invalidRoots == current) {
225                 if (rootsCache == null || rootsListener == null) {
226                     attachRootsListener();
227                     this.rootsCache = createRoots (entries);
228                 }
229                 ret = this.rootsCache;
230             }
231             else {
232                 ret = createRoots (entries);
233             }
234         }
235         assert ret != null;
236         return ret;
237     }
238     
239     private FileObject[] createRoots (final List JavaDoc<ClassPath.Entry> entries) {
240         List JavaDoc<FileObject> l = new ArrayList JavaDoc<FileObject> ();
241         for (Entry entry : entries) {
242             RootsListener rootsListener = this.getRootsListener();
243             if (rootsListener != null) {
244                 rootsListener.addRoot (entry.getURL());
245             }
246             FileObject fo = entry.getRoot();
247             if (fo != null) {
248                 l.add(fo);
249                 root2Filter.put(fo, entry.filter);
250             }
251         }
252         return l.toArray (new FileObject[l.size()]);
253     }
254
255     /**
256      * Returns list of classpath entries from the ClassPath definition.
257      * The implementation must ensure that modifications done to the List are
258      * banned or at least not reflected in other Lists returned by this ClassPath
259      * instance. Clients must assume that the returned value is immutable.
260      * @return list of definition entries (Entry instances)
261      */

262     public List JavaDoc<ClassPath.Entry> entries() {
263         long current;
264         synchronized (this) {
265             if (this.entriesCache != null) {
266                 return this.entriesCache;
267             }
268             current = this.invalidEntries;
269         }
270         List JavaDoc<? extends PathResourceImplementation> resources = impl.getResources();
271         List JavaDoc<ClassPath.Entry> result;
272         synchronized (this) {
273             if (this.invalidEntries == current) {
274                 if (this.entriesCache == null) {
275                     this.entriesCache = createEntries (resources);
276                 }
277                 result = this.entriesCache;
278             }
279             else {
280                 result = createEntries (resources);
281             }
282         }
283         assert result != null;
284         return result;
285     }
286     
287     private List JavaDoc<ClassPath.Entry> createEntries (final List JavaDoc<? extends PathResourceImplementation> resources) {
288         //The ClassPathImplementation.getResources () should never return
289
// null but it was not explicitly stated in the javadoc
290
if (resources == null) {
291             return Collections.<ClassPath.Entry>emptyList();
292         }
293         else {
294             List JavaDoc<ClassPath.Entry> cache = new ArrayList JavaDoc<ClassPath.Entry> ();
295             for (PathResourceImplementation pr : resources) {
296                 for (URL JavaDoc root : pr.getRoots()) {
297                     cache.add(new Entry(root,
298                             pr instanceof FilteringPathResourceImplementation ? (FilteringPathResourceImplementation) pr : null));
299                 }
300             }
301             return Collections.unmodifiableList(cache);
302         }
303     }
304
305     private ClassPath (ClassPathImplementation impl) {
306         if (impl == null)
307             throw new IllegalArgumentException JavaDoc ();
308         this.impl = impl;
309         this.pListener = new SPIListener ();
310         this.impl.addPropertyChangeListener (this.pListener);
311         listenToPRIs();
312     }
313
314     private void listenToPRIs() {
315         for (PathResourceImplementation pri : impl.getResources()) {
316             pri.removePropertyChangeListener(pListener);
317             pri.addPropertyChangeListener(pListener);
318         }
319     }
320
321     /**
322      * Returns a FileObject for the specified resource. May return null,
323      * if the resource does not exist, or is not reachable through
324      * this ClassPath.<BR>
325      * If the <i>resourceName</i> identifies a package, this method will
326      * return the <code>FileObject</code> for the first <I>package fragment</I>
327      * in the <code>ClassPath</code>.
328      * {@link FilteringPathResourceImplementation} may cause an actual file
329      * beneath a registered root to not be returned.
330      * Note: do not pass names starting with slash to this method.
331      * @param resourceName name of the resource as it would be passed
332      * to {@link ClassLoader#getResource}
333      * @return FileObject for the resource, or null if the resource cannot
334      * be found in this ClassPath.
335      */

336     public final FileObject findResource(String JavaDoc resourceName) {
337         return findResourceImpl(getRoots(), new int[] {0}, parseResourceName(resourceName));
338     }
339
340     /**
341      * Gives out an ordered collection containing all FileObjects, which correspond
342      * to a given ResourceName; only the first one is seen by the ClassLoader
343      * at runtime or can be linked against. The resource name uses slashes ('/')
344      * as folder separator and must not start with slash.
345      * {@link FilteringPathResourceImplementation} may cause an actual file
346      * beneath a registered root to not be returned.
347      * @param resourceName resource name
348      * @return list of resources identified by the given name.
349      */

350     public final List JavaDoc<FileObject> findAllResources(String JavaDoc resourceName) {
351     FileObject[] roots = getRoots();
352         List JavaDoc<FileObject> l = new ArrayList JavaDoc<FileObject>(roots.length);
353         int[] idx = new int[] { 0 };
354         String JavaDoc[] namec = parseResourceName(resourceName);
355         while (idx[0] < roots.length) {
356             FileObject f = findResourceImpl(roots, idx, namec);
357             if (f != null)
358                 l.add(f);
359         }
360         return l;
361     }
362
363     /**
364      * Creates a suitable resource name for the given FileObject within the
365      * classpath. The method will return <code>null</code> if the fileobject
366      * is not underneath any of classpath roots.<BR>
367      * The returned name uses uses slashes ('/') as folder separators and
368      * dot ('.') to separate file name and its extension.
369      * Note that if the file object is in the classpath subtree, but is not reachable
370      * (it is hidden by some other resource, or {@link FilteringPathResourceImplementation excluded}), the resource name is still returned.
371      * @return Java-style resource name for the given file object (the empty string for the package root itself), or null if not
372      * within the classpath
373      * @param f FileObject whose resource name is requested
374      */

375     public final String JavaDoc getResourceName(FileObject f) {
376     return getResourceName(f, '/', true);
377     }
378
379     /**
380      * Computes a resource name for the FileObject, which uses `pathSep' character
381      * as a directory separator. The resource name can be returned without the file
382      * extension, if desired. Note that parent folder names are always returned with
383      * extension, if they have some.
384      * @param f FileObject whose resource name is requested.
385      * @param dirSep directory separator character
386      * @param includeExt whether the FileObject's extension should be included in the result
387      * @return resource name for the given FileObject (the empty string for the package root itself) or null
388      */

389     public final String JavaDoc getResourceName(FileObject f, char dirSep, boolean includeExt) {
390         FileObject owner = findOwnerRoot(f);
391         if (owner == null)
392             return null;
393         String JavaDoc partName = FileUtil.getRelativePath(owner, f);
394         assert partName != null;
395         if (!includeExt) {
396             int index = partName.lastIndexOf('.');
397             if (index >= 0 && index > partName.lastIndexOf('/')) {
398                 partName = partName.substring (0, index);
399             }
400         }
401         if (dirSep!='/') {
402             partName = partName.replace('/',dirSep);
403         }
404         return partName;
405     }
406
407     /**
408      * Finds a root in this ClassPath, that owns the given file. File resources, that
409      * are not reachable (they are hidden by other resources, or {@link FilteringPathResourceImplementation} excluded) are still considered
410      * to be part of the classpath and "owned" by one of its roots.
411      * <br>
412      * <b>Note:</b> This implementation assumes that the FileSystem hosting a classpath root
413      * contains the entire classpath subtree rooted at that root folder.
414      * @return classpath root, which hosts the specified resouce. It can return null,
415      * if the resource is not within the ClassPath contents.
416      * @param resource resource to find root for.
417      */

418     public final FileObject findOwnerRoot(FileObject resource) {
419     FileObject[] roots = getRoots();
420         Set JavaDoc<FileObject> rootsSet = new HashSet JavaDoc<FileObject>(Arrays.asList(roots));
421         for (FileObject f = resource; f != null; f = f.getParent()) {
422             if (rootsSet.contains(f)) {
423                 return f;
424             }
425         }
426         return null;
427     }
428
429     /**
430      * Checks whether a FileObject lies on this classpath.
431      * {@link FilteringPathResourceImplementation} is considered.
432      * @return true, if the parameter is inside one of the classpath subtrees,
433      * false otherwise.
434      * @param f the FileObject to check
435      */

436     public final boolean contains(FileObject f) {
437         FileObject root = findOwnerRoot(f);
438         if (root == null) {
439             return false;
440         }
441         FilteringPathResourceImplementation filter = root2Filter.get(root);
442         if (filter == null) {
443             return true;
444         }
445         String JavaDoc path = FileUtil.getRelativePath(root, f);
446         assert path != null : "could not find " + f + " in " + root;
447         if (f.isFolder()) {
448             path += "/"; // NOI18N
449
}
450         try {
451             return filter.includes(root.getURL(), path);
452         } catch (FileStateInvalidException x) {
453             throw new AssertionError JavaDoc(x);
454         }
455     }
456
457     /**
458      * Determines if the resource is <i>visible</i> in the classpath,
459      * that is if the file will be reached when a process attempts to
460      * load a resource of that name. It will return false when the resource
461      * is not contained in the classpath, or the resource is {@link FilteringPathResourceImplementation excluded}.
462      * @param resource the resource whose visibility should be tested
463      * @return true, if the resource is contained in the classpath and visible;
464      * false otherwise.
465      */

466     public final boolean isResourceVisible(FileObject resource) {
467         String JavaDoc resourceName = getResourceName(resource);
468         if (resourceName == null)
469             return false;
470         return findResource(resourceName) == resource;
471     }
472
473     /**
474      * Adds a property change listener to the bean.
475      */

476     public final synchronized void addPropertyChangeListener(PropertyChangeListener JavaDoc l) {
477         attachRootsListener ();
478         propSupport.addPropertyChangeListener(l);
479     }
480
481     /**
482      * Removes the listener registered by <code>addPropertyChangeListener</code>/
483      */

484     public final void removePropertyChangeListener(PropertyChangeListener JavaDoc l) {
485         propSupport.removePropertyChangeListener(l);
486     }
487
488     /**
489      * Find the classpath of a given type, if any, defined for a given file.
490      * <p>This method may return null, if:</p>
491      * <ul>
492      * <li>the path type (<code>id</code> parameter) is not recognized
493      * <li>the path type is not defined for the given file object
494      * </ul>
495      * <p>
496      * Generally you may pass either an individual Java file, or the root of
497      * a Java package tree, interchangeably, since in most cases all files
498      * in a given tree will share a single classpath.
499      * </p>
500      * <p class="nonnormative">
501      * Typically classpaths for files are defined by the owning project, but
502      * there may be other ways classpaths are defined. See {@link ClassPathProvider}
503      * for more details.
504      * </p>
505      * @param f the file, whose classpath settings should be returned (may <em>not</em> be null as of org.netbeans.api.java/1 1.4)
506      * @param id the type of the classpath (e.g. {@link #COMPILE})
507      * @return classpath of the desired type for the given file object, or <code>null</code>, if
508      * there is no classpath available
509      * @see ClassPathProvider
510      */

511     public static ClassPath getClassPath(FileObject f, String JavaDoc id) {
512         if (f == null) {
513             // What else can we do?? Backwards compatibility only.
514
Thread.dumpStack();
515             return null;
516         }
517         boolean log = ERR.isLoggable(ErrorManager.INFORMATIONAL);
518         if (log) ERR.log("CP.getClassPath: " + f + " of type " + id);
519         for (ClassPathProvider impl : implementations.allInstances()) {
520             ClassPath cp = impl.findClassPath(f, id);
521             if (cp != null) {
522                 if (log) ERR.log(" got result " + cp + " from " + impl);
523                 return cp;
524             }
525         }
526         return null;
527     }
528
529     /**
530      * Fires a property change event on the specified property, notifying the
531      * old and new values.
532      * @param what name of the property
533      * @param oldV old value
534      * @param newV new value
535      */

536     final void firePropertyChange(String JavaDoc what, Object JavaDoc oldV, Object JavaDoc newV) {
537     propSupport.firePropertyChange(what, oldV, newV);
538     }
539
540     public String JavaDoc toString() {
541         return "ClassPath" + entries(); // NOI18N
542
}
543
544     /**
545      * Represents an individual entry in the ClassPath. An entry is a description
546      * of a folder, which is one of the ClassPath roots. Since the Entry does not
547      * control the folder's lifetime, the folder may be removed and the entry
548      * becomes invalid. It's also expected that ClassPath implementations may
549      * use other ClassPath entries as default or base for themselves, so Entries
550      * may be propagated between ClassPaths.
551      */

552     public final class Entry {
553
554         private URL JavaDoc url;
555         private FileObject root;
556         private IOException JavaDoc lastError;
557         private FilteringPathResourceImplementation filter;
558
559         /**
560          * Returns the ClassPath instance, which defines/introduces the Entry.
561          * Note that the return value may differ from the ClassPath instance,
562          * that produced this Entry from its <code>entries()</code> method.
563          * @return the ClassPath that defines the entry.
564          */

565         public ClassPath getDefiningClassPath() {
566             return ClassPath.this;
567         }
568
569         /**
570          * The method returns the root folder represented by the Entry.
571          * If the folder does not exist, or the folder is not readable,
572          * the method may return null.
573          * @return classpath entry root folder
574          */

575         public synchronized FileObject getRoot() {
576             if (root == null || !root.isValid()) {
577                 root = URLMapper.findFileObject(this.url);
578                 if (root == null) {
579                     this.lastError = new IOException JavaDoc(MessageFormat.format("The package root {0} does not exist or can not be read.",
580                         new Object JavaDoc[] {this.url}));
581                     return null;
582                 }
583                 else if (root.isData()) {
584                     throw new IllegalArgumentException JavaDoc ("Invalid ClassPath root: "+this.url+". The root must be a folder.");
585                 }
586             }
587             return root;
588         }
589
590         /**
591          * @return true, iff the Entry refers to an existing and readable
592          * folder.
593          */

594         public boolean isValid() {
595             FileObject root = getRoot();
596             return root != null && root.isValid();
597         }
598
599         /**
600          * Retrieves the error condition of the Entry. The method will return
601          * null, if the <code>getRoot()</code> would return a FileObject.
602          * @return error condition for this Entry or null if the Entry is OK.
603          */

604         public IOException JavaDoc getError() {
605             IOException JavaDoc error = this.lastError;
606             this.lastError = null;
607             return error;
608         }
609
610         /**
611          * Returns URL of the class path root.
612          * This method is generally safer than {@link #getRoot} as
613          * it can be called even if the root does not currently exist.
614          * @return URL
615          * @since org.netbeans.api.java/1 1.4
616          */

617         public URL JavaDoc getURL () {
618             return this.url;
619         }
620
621         /**
622          * Check whether a file is included in this entry.
623          * @param resource a path relative to @{link #getURL} (must be terminted with <samp>/</samp> if a non-root folder)
624          * @return true if it is {@link FilteringPathResourceImplementation#includes included}
625          * @since org.netbeans.api.java/1 1.13
626          */

627         public boolean includes(String JavaDoc resource) {
628             return filter == null || filter.includes(url, resource);
629         }
630
631         /**
632          * Check whether a file is included in this entry.
633          * @param file a URL beneath @{link #getURL}
634          * @return true if it is {@link FilteringPathResourceImplementation#includes included}
635          * @throws IllegalArgumentException in case the argument is not beneath {@link #getURL}
636          * @since org.netbeans.api.java/1 1.13
637          */

638         public boolean includes(URL JavaDoc file) {
639             if (!file.toExternalForm().startsWith(url.toExternalForm())) {
640                 throw new IllegalArgumentException JavaDoc(file + " not in " + url);
641             }
642             URI JavaDoc relative;
643             try {
644                 relative = url.toURI().relativize(file.toURI());
645             } catch (URISyntaxException JavaDoc x) {
646                 throw new AssertionError JavaDoc(x);
647             }
648             assert !relative.isAbsolute() : "could not locate " + file + " in " + url;
649             return filter == null || filter.includes(url, relative.toString());
650         }
651
652         /**
653          * Check whether a file is included in this entry.
654          * @param file a file inside @{link #getRoot}
655          * @return true if it is {@link FilteringPathResourceImplementation#includes included}
656          * @throws IllegalArgumentException in case the argument is not beneath {@link #getRoot}, or {@link #getRoot} is null
657          * @since org.netbeans.api.java/1 1.13
658          */

659         public boolean includes(FileObject file) {
660             FileObject root = getRoot();
661             if (root == null) {
662                 throw new IllegalArgumentException JavaDoc("no root in " + url);
663             }
664             String JavaDoc path = FileUtil.getRelativePath(root, file);
665             if (path == null) {
666                 throw new IllegalArgumentException JavaDoc(file + " not in " + root);
667             }
668             if (file.isFolder()) {
669                 path += "/"; // NOI18N
670
}
671             return filter == null || filter.includes(url, path);
672         }
673
674         Entry(URL JavaDoc url, FilteringPathResourceImplementation filter) {
675             if (url == null)
676                 throw new IllegalArgumentException JavaDoc ();
677             this.url = url;
678             this.filter = filter;
679         }
680
681         public String JavaDoc toString() {
682             return "Entry[" + url + "]"; // NOI18N
683
}
684     }
685
686     //-------------------- Implementation details ------------------------//
687

688     private final PropertyChangeSupport JavaDoc propSupport = new PropertyChangeSupport JavaDoc(this);
689     
690     
691     /**
692      * Attaches the listener to the ClassPath.entries.
693      * Not synchronized, HAS TO be called from the synchronized block!
694      */

695     private void attachRootsListener () {
696         if (this.rootsListener == null) {
697             assert this.rootsCache == null;
698             this.rootsListener = new RootsListener (this);
699         }
700     }
701
702     /**
703      * Returns an array of pairs of strings, first string in the pair is the
704      * name, the next one is either the extension or null.
705      */

706     private static String JavaDoc[] parseResourceName(String JavaDoc name) {
707         Collection JavaDoc<String JavaDoc> parsed = new ArrayList JavaDoc<String JavaDoc>(name.length() / 4);
708         char[] chars = name.toCharArray();
709         char ch;
710         int pos = 0;
711         int dotPos = -1;
712         int startPos = 0;
713
714         while (pos < chars.length) {
715             ch = chars[pos];
716             switch (ch) {
717                 case '.':
718                     dotPos = pos;
719                     break;
720                 case '/':
721                     // end of name component
722
if (dotPos != -1) {
723                         parsed.add(name.substring(startPos, dotPos));
724                         parsed.add(name.substring(dotPos + 1, pos));
725                     } else {
726                         parsed.add(name.substring(startPos, pos));
727                         parsed.add(null);
728                     }
729                     // reset variables:
730
startPos = pos + 1;
731                     dotPos = -1;
732                     break;
733             }
734             pos++;
735         }
736         // if the resource name ends with '/', just ignore the empty component
737
if (pos > startPos) {
738             if (dotPos != -1) {
739                 parsed.add(name.substring(startPos, dotPos));
740                 parsed.add(name.substring(dotPos + 1, pos));
741             } else {
742                 parsed.add(name.substring(startPos, pos));
743                 parsed.add(null);
744             }
745         }
746         if ((parsed.size() % 2) != 0) {
747             System.err.println("parsed size is not even!!");
748             System.err.println("input = " + name);
749         }
750         return parsed.toArray(new String JavaDoc[parsed.size()]);
751     }
752
753     /**
754      * Finds a path underneath the `parent'. Name parts is an array of string pairs,
755      * the first String in a pair is the basename, the second is the extension or null
756      * for no extension.
757      */

758     private static FileObject findPath(FileObject parent, String JavaDoc[] nameParts) {
759         FileObject child;
760
761         for (int i = 0; i < nameParts.length && parent != null; i += 2, parent = child) {
762             child = parent.getFileObject(nameParts[i], nameParts[i + 1]);
763         }
764         return parent;
765     }
766
767     /**
768      * Searches for a resource in one or more roots, gives back the index of
769      * the first untouched root.
770      */

771     private FileObject findResourceImpl(FileObject[] roots,
772         int[] rootIndex, String JavaDoc[] nameComponents) {
773         int ridx;
774         FileObject f = null;
775         for (ridx = rootIndex[0]; ridx < roots.length && f == null; ridx++) {
776             f = findPath(roots[ridx], nameComponents);
777             FilteringPathResourceImplementation filter = root2Filter.get(roots[ridx]);
778             if (filter != null) {
779                 try {
780                     if (f != null) {
781                         String JavaDoc path = FileUtil.getRelativePath(roots[ridx], f);
782                         assert path != null;
783                         if (f.isFolder()) {
784                             path += "/"; // NOI18N
785
}
786                         if (!filter.includes(roots[ridx].getURL(), path)) {
787                             f = null;
788                         }
789                     }
790                 } catch (FileStateInvalidException x) {
791                     throw new AssertionError JavaDoc(x);
792                 }
793             }
794         }
795         rootIndex[0] = ridx;
796         return f;
797     }
798
799     private static final Reference JavaDoc<ClassLoader JavaDoc> EMPTY_REF = new SoftReference JavaDoc<ClassLoader JavaDoc>(null);
800
801     private Reference JavaDoc<ClassLoader JavaDoc> refClassLoader = EMPTY_REF;
802
803     /* package private */synchronized void resetClassLoader(ClassLoader JavaDoc cl) {
804         if (refClassLoader.get() == cl)
805             refClassLoader = EMPTY_REF;
806     }
807
808     /**
809      * Returns a ClassLoader for loading classes from this ClassPath.
810      * <p>
811      * If <code>cache</code> is false, then
812      * the method will always return a new class loader. If that parameter is true,
813      * the method may return a loader which survived from a previous call to the same <code>ClassPath</code>.
814      *
815      * @param cache true if it is permissible to cache class loaders between calls
816      * @return class loader which uses the roots in this class path to search for classes and resources
817      * @since 1.2.1
818      */

819     public final synchronized ClassLoader JavaDoc getClassLoader(boolean cache) {
820         // XXX consider adding ClassLoader and/or InputOutput and/or PermissionCollection params
821
ClassLoader JavaDoc o = refClassLoader.get();
822         if (!cache || o == null) {
823             o = ClassLoaderSupport.create(this);
824             refClassLoader = new SoftReference JavaDoc<ClassLoader JavaDoc>(o);
825         }
826         return o;
827     }
828
829
830     private class SPIListener implements PropertyChangeListener JavaDoc {
831         private Object JavaDoc propIncludesPropagationId;
832         public void propertyChange(PropertyChangeEvent JavaDoc evt) {
833             String JavaDoc prop = evt.getPropertyName();
834             if (ClassPathImplementation.PROP_RESOURCES.equals(prop) || PathResourceImplementation.PROP_ROOTS.equals(prop)) {
835                 synchronized (ClassPath.this) {
836                     if (rootsListener != null) {
837                         rootsListener.removeAllRoots ();
838                     }
839                     entriesCache = null;
840                     rootsCache = null;
841                     invalidEntries++;
842                     invalidRoots++;
843                 }
844                 firePropertyChange (PROP_ENTRIES,null,null);
845                 firePropertyChange (PROP_ROOTS,null,null);
846             } else if (FilteringPathResourceImplementation.PROP_INCLUDES.equals(prop)) {
847                 boolean fire;
848                 synchronized (this) {
849                     Object JavaDoc id = evt.getPropagationId();
850                     fire = propIncludesPropagationId == null || !propIncludesPropagationId.equals(id);
851                     propIncludesPropagationId = id;
852                 }
853                 if (fire) {
854                     firePropertyChange(PROP_INCLUDES, null, null);
855                 }
856             }
857             if (ClassPathImplementation.PROP_RESOURCES.equals(prop)) {
858                 listenToPRIs();
859             }
860         }
861     }
862
863
864     private synchronized RootsListener getRootsListener () {
865         return this.rootsListener;
866     }
867
868
869     private static class RootsListener extends WeakReference JavaDoc<ClassPath> implements FileChangeListener, Runnable JavaDoc {
870
871         private boolean initialized;
872         private Set JavaDoc<String JavaDoc> roots;
873
874         private RootsListener (ClassPath owner) {
875             super (owner, Utilities.activeReferenceQueue());
876             roots = new HashSet JavaDoc<String JavaDoc> ();
877         }
878
879         public void addRoot (URL JavaDoc url) {
880             if (!isInitialized()) {
881                 FileSystem[] fss = getFileSystems ();
882                 if (fss != null && fss.length > 0) {
883                     for (FileSystem fs : fss) {
884                         if (fs != null) {
885                             fs.addFileChangeListener (this);
886                         }
887                     }
888                     setInitialized(true);
889                 } else {
890                     ErrorManager.getDefault().log (ErrorManager.ERROR,"Can not find file system, not able to listen on changes."); //NOI18N
891
}
892             }
893             if ("jar".equals(url.getProtocol())) { //NOI18N
894
url = FileUtil.getArchiveFile(url);
895             }
896             String JavaDoc path = url.getPath();
897             if (path.endsWith("/")) { //NOI18N
898
path = path.substring(0,path.length()-1);
899             }
900             roots.add (path);
901         }
902
903         public void removeRoot (URL JavaDoc url) {
904             if ("jar".equals(url.getProtocol())) { //NOI18N
905
url = FileUtil.getArchiveFile(url);
906             }
907             String JavaDoc path = url.getPath();
908             if (path.endsWith("/")) { //NOI18N
909
path = path.substring(0,path.length()-1);
910             }
911             roots.remove (path);
912         }
913
914         public void removeAllRoots () {
915             this.roots.clear();
916             FileSystem[] fss = getFileSystems ();
917             for (FileSystem fs : fss) {
918                 if (fs != null) {
919                     fs.removeFileChangeListener (this);
920                 }
921             }
922             initialized = false; //Already synchronized
923
}
924
925         public void fileFolderCreated(FileEvent fe) {
926             this.processEvent (fe);
927         }
928
929         public void fileDataCreated(FileEvent fe) {
930             this.processEvent (fe);
931         }
932
933         public void fileChanged(FileEvent fe) {
934             if (!isInitialized()) {
935                 return; //Cache already cleared
936
}
937             String JavaDoc path = getPath (fe.getFile());
938             if (this.roots.contains(path)) {
939                 ClassPath cp = get();
940                 if (cp != null) {
941                     synchronized (cp) {
942                         cp.rootsCache = null;
943                         cp.invalidRoots++;
944                         this.removeAllRoots(); //No need to listen
945
}
946                     cp.firePropertyChange(PROP_ROOTS,null,null);
947                }
948             }
949         }
950
951         public void fileDeleted(FileEvent fe) {
952             this.processEvent (fe);
953         }
954
955         public void fileRenamed(FileRenameEvent fe) {
956             this.processEvent (fe);
957         }
958
959         public void fileAttributeChanged(FileAttributeEvent fe) {
960         }
961
962         public void run() {
963             if (isInitialized()) {
964                 for (FileSystem fs : getFileSystems()) {
965                     if (fs != null) {
966                         fs.removeFileChangeListener (this);
967                     }
968                 }
969             }
970         }
971
972         private void processEvent (FileEvent fe) {
973             if (!isInitialized()) {
974                 return; //Not interesting, cache already cleared
975
}
976             String JavaDoc path = getPath (fe.getFile());
977             if (path == null)
978                 return;
979             ClassPath cp = get();
980             if (cp == null) {
981                 return;
982             }
983             boolean fire = false;
984             synchronized (cp) {
985                 for (String JavaDoc rootPath : roots) {
986                     if (rootPath.startsWith (path)) {
987                         cp.rootsCache = null;
988                         cp.invalidRoots++;
989                         this.removeAllRoots(); //No need to listen
990
fire = true;
991                         break;
992                     }
993                 }
994             }
995             if (fire) {
996                 cp.firePropertyChange(PROP_ROOTS,null,null);
997             }
998         }
999
1000        private static String JavaDoc getPath (FileObject fo) {
1001            if (fo == null)
1002                return null;
1003            try {
1004                URL JavaDoc url = fo.getURL();
1005                String JavaDoc path = url.getPath();
1006                if (path.endsWith("/")) { //NOI18N
1007
path=path.substring(0,path.length()-1);
1008                }
1009                return path;
1010            } catch (FileStateInvalidException e) {
1011                ErrorManager.getDefault().notify (e);
1012                return null;
1013            }
1014        }
1015        
1016        private synchronized boolean isInitialized () {
1017            return this.initialized;
1018        }
1019        
1020        private synchronized void setInitialized (boolean newValue) {
1021            this.initialized = newValue;
1022        }
1023        
1024        //copy - paste programming
1025
//http://ant.netbeans.org/source/browse/ant/src-bridge/org/apache/tools/ant/module/bridge/impl/BridgeImpl.java.diff?r1=1.15&r2=1.16
1026
//http:/java.netbeans.org/source/browse/java/javacore/src/org/netbeans/modules/javacore/Util.java
1027
//http://core.netbeans.org/source/browse/core/ui/src/org/netbeans/core/ui/MenuWarmUpTask.java
1028
//http://core.netbeans.org/source/browse/core/src/org/netbeans/core/actions/RefreshAllFilesystemsAction.java
1029
//http://java.netbeans.org/source/browse/java/api/src/org/netbeans/api/java/classpath/ClassPath.java
1030

1031        private static FileSystem[] fileSystems;
1032        
1033        private static FileSystem[] getFileSystems() {
1034            if (fileSystems != null) {
1035                return fileSystems;
1036            }
1037            File JavaDoc[] roots = File.listRoots();
1038            Set JavaDoc<FileSystem> allRoots = new LinkedHashSet JavaDoc<FileSystem>();
1039            assert roots != null && roots.length > 0 : "Could not list file roots"; // NOI18N
1040

1041            for (File JavaDoc root : roots) {
1042                FileObject random = FileUtil.toFileObject(root);
1043                if (random == null) continue;
1044                
1045                FileSystem fs;
1046                try {
1047                    fs = random.getFileSystem();
1048                    allRoots.add(fs);
1049                    
1050                    /*Because there is MasterFileSystem impl. that provides conversion to FileObject for all File.listRoots
1051                    (except floppy drives and empty CD). Then there is useless to convert all roots into FileObjects including
1052                    net drives that might cause performance regression.
1053                    */

1054                    
1055                    if (fs != null) {
1056                        break;
1057                    }
1058                } catch (FileStateInvalidException e) {
1059                    throw new AssertionError JavaDoc(e);
1060                }
1061            }
1062            FileSystem[] retVal = new FileSystem [allRoots.size()];
1063            allRoots.toArray(retVal);
1064            assert retVal.length > 0 : "Could not get any filesystem"; // NOI18N
1065

1066            return fileSystems = retVal;
1067        }
1068    }
1069
1070}
1071
Popular Tags