KickJava   Java API By Example, From Geeks To Geeks.

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


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.util.ArrayList JavaDoc;
25 import java.util.Arrays JavaDoc;
26 import java.util.Collections JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.HashSet JavaDoc;
29 import java.util.LinkedHashSet JavaDoc;
30 import java.util.LinkedList JavaDoc;
31 import java.util.List JavaDoc;
32 import java.util.Map JavaDoc;
33 import java.util.Set JavaDoc;
34 import javax.swing.event.ChangeEvent JavaDoc;
35 import javax.swing.event.ChangeListener JavaDoc;
36 import org.netbeans.api.java.queries.SourceForBinaryQuery;
37 import org.openide.filesystems.FileObject;
38
39 /**
40  * Maintains a global registry of "interesting" classpaths of various kinds.
41  * You may add and remove different kinds of {@link ClassPath}s to the registry
42  * and listen to changes in them.
43  * <p>
44  * It is permitted to register the same classpath more than once; unregistration
45  * keeps track of the number of registrations so that the operation is symmetric.
46  * However {@link #getPaths} only ever returns one copy of the classpath, and
47  * listeners are only notified the first time a given classpath is added to the
48  * registry, or the last time it is removed.
49  * (Classpath identity is object identity, so there could be multiple paths
50  * returned that at the time share the same list of roots. There may also be
51  * several paths which contain some shared roots.)
52  * </p>
53  * <p>
54  * The registry is not persisted between JVM sessions.
55  * </p>
56  * <div class="nonnormative">
57  * <p>
58  * Intended usage patterns:
59  * </p>
60  * <ol>
61  * <li><p>When a project is opened using
62  * {@link org.netbeans.spi.project.ui.ProjectOpenedHook} it should add any paths
63  * it defines, i.e. paths it might return from a
64  * {@link org.netbeans.spi.java.classpath.ClassPathProvider}.
65  * When closed it should remove them.</p></li>
66  * <li><p>The <b>Fast&nbsp;Open</b> feature of the editor and other features which
67  * require a global list of relevant sources should use {@link #getSourceRoots} or
68  * the equivalent.</p></li>
69  * <li><p>The <b>Javadoc&nbsp;Index&nbsp;Search</b> feature and <b>View&nbsp;&#8594;
70  * Documentation&nbsp;Indices</b> submenu should operate on open Javadoc paths,
71  * meaning that Javadoc corresponding to registered compile and boot classpaths
72  * (according to {@link org.netbeans.api.java.queries.JavadocForBinaryQuery}).</p></li>
73  * <li><p>Stack trace hyperlinking can use the global list of source paths
74  * to find sources, in case no more specific information about their origin is
75  * available. The same would be true of debugging: if the debugger cannot find
76  * Java-like sources using more precise means ({@link SourceForBinaryQuery}), it
77  * can use {@link #findResource} as a fallback.</p></li>
78  * </ol>
79  * </div>
80  * @author Jesse Glick
81  * @since org.netbeans.api.java/1 1.4
82  */

83 public final class GlobalPathRegistry {
84     
85     private static GlobalPathRegistry DEFAULT = new GlobalPathRegistry();
86     
87     /**
88      * Get the singleton instance of the registry.
89      * @return the default instance
90      */

91     public static GlobalPathRegistry getDefault() {
92         return DEFAULT;
93     }
94     
95     private int resetCount;
96     private final Map JavaDoc<String JavaDoc,List JavaDoc<ClassPath>> paths = new HashMap JavaDoc<String JavaDoc,List JavaDoc<ClassPath>>();
97     private final List JavaDoc<GlobalPathRegistryListener> listeners = new ArrayList JavaDoc<GlobalPathRegistryListener>();
98     private Set JavaDoc<FileObject> sourceRoots = null;
99     private Set JavaDoc<SourceForBinaryQuery.Result> results = new HashSet JavaDoc<SourceForBinaryQuery.Result>();
100     
101     
102     private final ChangeListener JavaDoc resultListener = new SFBQListener ();
103     
104     private PropertyChangeListener JavaDoc classpathListener = new PropertyChangeListener JavaDoc() {
105         public void propertyChange(PropertyChangeEvent JavaDoc evt) {
106             synchronized (GlobalPathRegistry.this) {
107                 //Reset cache
108
GlobalPathRegistry.this.resetSourceRootsCache ();
109             }
110         }
111     };
112     
113     private GlobalPathRegistry() {}
114     
115     /** for use from unit test */
116     void clear() {
117         paths.clear();
118         listeners.clear();
119     }
120     
121     /**
122      * Find all paths of a certain type.
123      * @param id a classpath type, e.g. {@link ClassPath#SOURCE}
124      * @return an immutable set of all registered {@link ClassPath}s of that type (may be empty but not null)
125      */

126     public synchronized Set JavaDoc<ClassPath> getPaths(String JavaDoc id) {
127         if (id == null) {
128             throw new NullPointerException JavaDoc();
129         }
130         List JavaDoc<ClassPath> l = paths.get(id);
131         if (l != null && !l.isEmpty()) {
132             return Collections.unmodifiableSet(new HashSet JavaDoc<ClassPath>(l));
133         } else {
134             return Collections.<ClassPath>emptySet();
135         }
136     }
137     
138     /**
139      * Register some classpaths of a certain type.
140      * @param id a classpath type, e.g. {@link ClassPath#SOURCE}
141      * @param paths a list of classpaths to add to the registry
142      */

143     public void register(String JavaDoc id, ClassPath[] paths) {
144         if (id == null || paths == null) {
145             throw new NullPointerException JavaDoc();
146         }
147         GlobalPathRegistryEvent evt = null;
148         GlobalPathRegistryListener[] _listeners = null;
149         synchronized (this) {
150             List JavaDoc<ClassPath> l = this.paths.get(id);
151             if (l == null) {
152                 l = new ArrayList JavaDoc<ClassPath>();
153                 this.paths.put(id, l);
154             }
155             Set JavaDoc<ClassPath> added = listeners.isEmpty() ? null : new HashSet JavaDoc<ClassPath>();
156             for (ClassPath path : paths) {
157                 if (path == null) {
158                     throw new NullPointerException JavaDoc("Null path encountered in " + Arrays.asList(paths) + " of type " + id); // NOI18N
159
}
160                 if (added != null && !added.contains(path) && !l.contains(path)) {
161                     added.add(path);
162                 }
163                 if (!l.contains(path)) {
164                     path.addPropertyChangeListener(classpathListener);
165                 }
166                 l.add(path);
167             }
168             if (added != null && !added.isEmpty()) {
169                 _listeners = listeners.toArray(new GlobalPathRegistryListener[listeners.size()]);
170                 evt = new GlobalPathRegistryEvent(this, id, Collections.unmodifiableSet(added));
171             }
172             // Invalidate cache for getSourceRoots and findResource:
173
resetSourceRootsCache ();
174         }
175         if (_listeners != null) {
176             assert evt != null;
177             for (GlobalPathRegistryListener listener : _listeners) {
178                 listener.pathsAdded(evt);
179             }
180         }
181     }
182     
183     /**
184      * Unregister some classpaths of a certain type.
185      * @param id a classpath type, e.g. {@link ClassPath#SOURCE}
186      * @param paths a list of classpaths to remove from the registry
187      * @throws IllegalArgumentException if they had not been registered before
188      */

189     public void unregister(String JavaDoc id, ClassPath[] paths) throws IllegalArgumentException JavaDoc {
190         if (id == null || paths == null) {
191             throw new NullPointerException JavaDoc();
192         }
193         GlobalPathRegistryEvent evt = null;
194         GlobalPathRegistryListener[] _listeners = null;
195         synchronized (this) {
196             List JavaDoc<ClassPath> l = this.paths.get(id);
197             if (l == null) {
198                 l = new ArrayList JavaDoc<ClassPath>();
199             }
200             List JavaDoc<ClassPath> l2 = new ArrayList JavaDoc<ClassPath>(l); // in case IAE thrown below
201
Set JavaDoc<ClassPath> removed = listeners.isEmpty() ? null : new HashSet JavaDoc<ClassPath>();
202             for (ClassPath path : paths) {
203                 if (path == null) {
204                     throw new NullPointerException JavaDoc();
205                 }
206                 if (!l2.remove(path)) {
207                     throw new IllegalArgumentException JavaDoc("Attempt to remove nonexistent path " + path); // NOI18N
208
}
209                 if (removed != null && !removed.contains(path) && !l2.contains(path)) {
210                     removed.add(path);
211                 }
212                 if (!l2.contains(path)) {
213                     path.removePropertyChangeListener(classpathListener);
214                 }
215             }
216             this.paths.put(id, l2);
217             if (removed != null && !removed.isEmpty()) {
218                 _listeners = listeners.toArray(new GlobalPathRegistryListener[listeners.size()]);
219                 evt = new GlobalPathRegistryEvent(this, id, Collections.unmodifiableSet(removed));
220             }
221             resetSourceRootsCache ();
222         }
223         if (_listeners != null) {
224             assert evt != null;
225             for (GlobalPathRegistryListener listener : _listeners) {
226                 listener.pathsRemoved(evt);
227             }
228         }
229     }
230     
231     /**
232      * Add a listener to the registry.
233      * @param l a listener to add
234      */

235     public synchronized void addGlobalPathRegistryListener(GlobalPathRegistryListener l) {
236         if (l == null) {
237             throw new NullPointerException JavaDoc();
238         }
239         listeners.add(l);
240     }
241     
242     /**
243      * Remove a listener to the registry.
244      * @param l a listener to remove
245      */

246     public synchronized void removeGlobalPathRegistryListener(GlobalPathRegistryListener l) {
247         if (l == null) {
248             throw new NullPointerException JavaDoc();
249         }
250         listeners.remove(l);
251     }
252     
253     /**
254      * Convenience method to find all relevant source roots.
255      * This consists of:
256      * <ol>
257      * <li>Roots of all registered {@link ClassPath#SOURCE} paths.
258      * <li>Sources (according to {@link SourceForBinaryQuery}) of all registered
259      * {@link ClassPath#COMPILE} paths.
260      * <li>Sources of all registered {@link ClassPath#BOOT} paths.
261      * </ol>
262      * Order is not significant.
263      * <p>
264      * Currently there is no reliable way to listen for changes in the
265      * value of this method: while you can listen to changes in the paths
266      * mentioned, it is possible for {@link SourceForBinaryQuery} results to
267      * change. In the future a change listener might be added for the value
268      * of the source roots.
269      * </p>
270      * <p>
271      * Note that this method takes no account of package includes/excludes.
272      * </p>
273      * @return an immutable set of <code>FileObject</code> source roots
274      */

275     public Set JavaDoc<FileObject> getSourceRoots() {
276         int currentResetCount;
277         Set JavaDoc<ClassPath> sourcePaths, compileAndBootPaths;
278         synchronized (this) {
279             if (this.sourceRoots != null) {
280                 return this.sourceRoots;
281             }
282             currentResetCount = this.resetCount;
283             sourcePaths = getPaths(ClassPath.SOURCE);
284             compileAndBootPaths = new LinkedHashSet JavaDoc<ClassPath>(getPaths(ClassPath.COMPILE));
285             compileAndBootPaths.addAll(getPaths(ClassPath.BOOT));
286         }
287         
288         Set JavaDoc<FileObject> newSourceRoots = new LinkedHashSet JavaDoc<FileObject>();
289         for (ClassPath sp : sourcePaths) {
290             newSourceRoots.addAll(Arrays.asList(sp.getRoots()));
291         }
292         
293         final List JavaDoc<SourceForBinaryQuery.Result> newResults = new LinkedList JavaDoc<SourceForBinaryQuery.Result> ();
294         final ChangeListener JavaDoc tmpResultListener = new SFBQListener ();
295         for (ClassPath cp : compileAndBootPaths) {
296             for (ClassPath.Entry entry : cp.entries()) {
297                 SourceForBinaryQuery.Result result = SourceForBinaryQuery.findSourceRoots(entry.getURL());
298                 result.addChangeListener(tmpResultListener);
299                 newResults.add (result);
300                 FileObject[] someRoots = result.getRoots();
301                 newSourceRoots.addAll(Arrays.asList(someRoots));
302             }
303         }
304         
305         newSourceRoots = Collections.unmodifiableSet(newSourceRoots);
306         synchronized (this) {
307             if (this.resetCount == currentResetCount) {
308                 this.sourceRoots = newSourceRoots;
309                 removeTmpSFBQListeners (newResults, tmpResultListener, true);
310                 this.results.addAll (newResults);
311             }
312             else {
313                 removeTmpSFBQListeners (newResults, tmpResultListener, false);
314             }
315             return newSourceRoots;
316         }
317     }
318     
319     
320     private void removeTmpSFBQListeners (List JavaDoc<? extends SourceForBinaryQuery.Result> results, ChangeListener JavaDoc listener, boolean addListener) {
321         for (SourceForBinaryQuery.Result res : results) {
322             if (addListener) {
323                 res.addChangeListener (this.resultListener);
324             }
325             res.removeChangeListener(listener);
326         }
327     }
328     
329     /**
330      * Convenience method to find a particular source file by resource path.
331      * This simply uses {@link #getSourceRoots} to find possible roots and
332      * looks up the resource among them.
333      * In case more than one source root contains the resource, one is chosen
334      * arbitrarily.
335      * As with {@link ClassPath#findResource}, include/exclude lists can affect the result.
336      * @param resource a resource path, e.g. <samp>somepkg/Foo.java</samp>
337      * @return some file found with that path, or null
338      */

339     public FileObject findResource(String JavaDoc resource) {
340         // XXX try to use java.io.File's wherever possible for the search; FileObject is too slow
341
for (ClassPath cp : getPaths(ClassPath.SOURCE)) {
342             FileObject f = cp.findResource(resource);
343             if (f != null) {
344                 return f;
345             }
346         }
347         return null;
348     }
349     
350     
351     private synchronized void resetSourceRootsCache () {
352         this.sourceRoots = null;
353         for (SourceForBinaryQuery.Result result : results) {
354             result.removeChangeListener(this.resultListener);
355         }
356         this.resetCount++;
357     }
358     
359     private class SFBQListener implements ChangeListener JavaDoc {
360         
361         public void stateChanged (ChangeEvent JavaDoc event) {
362             synchronized (GlobalPathRegistry.this) {
363                 //Reset cache
364
GlobalPathRegistry.this.resetSourceRootsCache ();
365             }
366         }
367     };
368
369 }
370
Popular Tags