KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > j2ee > metadata > ClassPathSourceCache


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.modules.j2ee.metadata;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.beans.PropertyChangeListener JavaDoc;
24 import java.beans.PropertyChangeSupport JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.lang.ref.WeakReference JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Collections JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Set JavaDoc;
32 import javax.swing.event.ChangeEvent JavaDoc;
33 import javax.swing.event.ChangeListener JavaDoc;
34 import org.netbeans.api.java.classpath.ClassPath;
35 import org.netbeans.api.java.queries.SourceForBinaryQuery;
36 import org.openide.ErrorManager;
37 import org.openide.filesystems.FileObject;
38 import org.openide.filesystems.FileSystem;
39 import org.openide.filesystems.Repository;
40 import org.openide.util.Utilities;
41 import org.openide.util.WeakListeners;
42
43 /**
44  * Wrapper for a ClassPath whose {@link #contains(FileObject)} method
45  * will also search any source roots for the classpath's binary roots.
46  * Implemented as a cache because the trivial approach (retrieving
47  * the source roots whenever <code>contains()</code> is called)
48  * is much slower.
49  *
50  * @author Andrei Badea
51  */

52 public class ClassPathSourceCache {
53
54     public final static String JavaDoc PROP_ROOTS = ClassPath.PROP_ROOTS;
55
56     // the invalidResults and invalidSourceRoots counters are used
57
// to detect that a change in the class path entries or the
58
// SFBQ results occured while the respective caches are being
59
// computed (in this case the caches cannot be set, because they
60
// are obsolete).
61
//
62
// This is needed in order to prevent the following scenario:
63
//
64
// 1) Start with sourceRoots == null.
65
// 2) Thread A enters getSourceRoots() and computes newSourceRoots.
66
// 3) Thread B preempts A and causes the roots of a SFBQ.Result
67
// to change. The old roots is what newSourceRoots was computed from in
68
// step 2, so thread A now holds an obsolete set of roots.
69
// 4) Thread A enters the second synchronized block in getSourceRoots()
70
// and sets the cache to the obsolete roots. The cache is never
71
// set to the new, correct set of roots.
72

73     /**
74      * The classpath the source roots are retrieved from.
75      * Held weakly in order to avoid leaks.
76      */

77     private final WeakReference JavaDoc<ClassPath> classPathRef;
78     private final Listener JavaDoc listener = new Listener JavaDoc();
79
80     private final PropertyChangeSupport JavaDoc propChangeSupport = new PropertyChangeSupport JavaDoc(this);
81
82     /**
83      * The SFBQ results cache built from the classpath entries.
84      * Not private because used in tests.
85      */

86     List JavaDoc<SourceForBinaryQuery.Result> results;
87
88     /**
89      * Incremented each time the classpath entries change.
90      */

91     private long invalidResults;
92
93     /**
94      * The source roots cache built from the SBFQ results.
95      * Not private because used in tests.
96      */

97     Set JavaDoc<FileObject> sourceRoots;
98
99     /**
100      * Incremented each time a change occurs in any SFBQ result
101      * in {@link #results}.
102      */

103     private long invalidSourceRoots;
104
105     /**
106      * If true, then the whole class has been deinitialized
107      * because the classpath was GCd.
108      */

109     boolean deinitialized;
110
111     private boolean resolveSources;
112
113     public static ClassPathSourceCache newInstance(ClassPath classPath, boolean resolveSources) {
114         ClassPathSourceCache created = new ClassPathSourceCache(classPath, resolveSources);
115         created.initialize();
116         return created;
117     }
118
119     private ClassPathSourceCache(ClassPath classPath, boolean resolveSources) {
120         assert classPath != null;
121         classPathRef = new CleanupReference<ClassPath>(classPath);
122         this.resolveSources = resolveSources;
123     }
124
125     /**
126      * Starts listening on the classpath. This should not be done in
127      * the constructor, because it would expose <code>this</code> before
128      * the constructor has finished. This is the only reason for the
129      * static factory method + private constructor instead of a plain
130      * public constructor.
131      */

132     private void initialize() {
133         ClassPath classPath = classPathRef.get();
134         if (classPath != null) {
135             classPath.addPropertyChangeListener(WeakListeners.propertyChange(listener, classPath));
136         }
137     }
138
139     public void addPropertyChangeListener(PropertyChangeListener JavaDoc listener) {
140         propChangeSupport.addPropertyChangeListener(listener);
141     }
142
143     public void removePropertyChangeListener(PropertyChangeListener JavaDoc listener) {
144         propChangeSupport.removePropertyChangeListener(listener);
145     }
146
147     /**
148      * Returns true if the roots returned by {@link #getRoots()} contain
149      * the specified <code>FileObject</code>.
150      *
151      * @param f the file to search for.
152      * @return true if <code>f</code> could be find on any source root;
153      * false otherwise.
154      */

155     public boolean contains(FileObject f) {
156         if (findOwnerRoot(getSourceRootSet(), f) != null) {
157             return true;
158         }
159         return false;
160     }
161
162     public FileObject[] getRoots() {
163         Set JavaDoc<FileObject> sourceRoots = getSourceRootSet();
164         return sourceRoots.toArray(new FileObject[sourceRoots.size()]);
165     }
166
167     private Set JavaDoc<FileObject> getSourceRootSet() {
168         long invalidSourceRootsCopy;
169
170         synchronized (this) {
171             if (sourceRoots != null) {
172                 return sourceRoots;
173             }
174
175             invalidSourceRootsCopy = invalidSourceRoots;
176         }
177
178         final Set JavaDoc<FileObject> newSourceRoots = new HashSet JavaDoc<FileObject>();
179         // add the original classpath roots
180
// (in an atomic action as a workaround for issue 85995)
181
try {
182             Repository.getDefault().getDefaultFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
183                 public void run() throws IOException JavaDoc {
184                     ClassPath classPath = classPathRef.get();
185                     if (classPath != null) {
186                         for (FileObject root : classPath.getRoots()) {
187                             newSourceRoots.add(root);
188                         }
189                     }
190                 }
191             });
192         } catch (IOException JavaDoc e) {
193             // should never occur, as ClassPath.getRoots() does not throw an IOException
194
ErrorManager.getDefault().notify(e);
195         }
196
197         if (resolveSources) {
198             // add the source roots computed from original classpath's binary roots
199
for (SourceForBinaryQuery.Result result : getResults()) {
200                 for (FileObject sourceRoot : result.getRoots()) {
201                     newSourceRoots.add(sourceRoot);
202                 }
203             }
204         }
205
206         synchronized (this) {
207             if (invalidSourceRoots == invalidSourceRootsCopy) {
208                 if (sourceRoots == null) {
209                     sourceRoots = newSourceRoots;
210                 }
211                 return sourceRoots;
212             } else {
213                 return newSourceRoots;
214             }
215         }
216     }
217
218     private List JavaDoc<SourceForBinaryQuery.Result> getResults() {
219         long invalidResultsCopy;
220
221         synchronized (this) {
222             if (results != null) {
223                 return results;
224             }
225
226             // just to be sure: sourceRoots must be also null at this moment
227
// (the source roots cannot be valid since we don't even know
228
// the source for binary query results -- which the source roots
229
// are retrieved from)
230
assert sourceRoots == null;
231
232             invalidResultsCopy = invalidResults;
233         }
234
235         List JavaDoc<SourceForBinaryQuery.Result> newResults = null;
236         ClassPath classPath = classPathRef.get();
237         if (classPath == null) {
238             newResults = Collections.emptyList();
239         } else {
240             newResults = new ArrayList JavaDoc<SourceForBinaryQuery.Result>();
241             List JavaDoc<ClassPath.Entry> entries = classPath.entries();
242             for (ClassPath.Entry entry : entries) {
243                 newResults.add(SourceForBinaryQuery.findSourceRoots(entry.getURL()));
244             }
245         }
246
247         synchronized (this) {
248             if (invalidResults == invalidResultsCopy) {
249                 if (results == null) {
250                     for (SourceForBinaryQuery.Result result : newResults) {
251                         result.addChangeListener(listener);
252                     }
253                     results = newResults;
254                 }
255                 return results;
256             } else {
257                 return newResults;
258             }
259         }
260     }
261
262     private static FileObject findOwnerRoot(Set JavaDoc<FileObject> roots, FileObject resource) {
263         for (FileObject f = resource; f != null; f = f.getParent()) {
264             if (roots.contains(f)) {
265                 return f;
266             }
267         }
268         return null;
269     }
270
271     /**
272      * Listens on the classpath and on all SFBQ results and invalidates
273      * the results and sourceRoots caches.
274      */

275     private final class Listener implements PropertyChangeListener JavaDoc, ChangeListener JavaDoc {
276
277         public void propertyChange(PropertyChangeEvent JavaDoc evt) {
278             String JavaDoc propertyName = evt.getPropertyName();
279             if (ClassPath.PROP_ENTRIES.equals(propertyName)) {
280                 if (resolveSources) {
281                     entriesChanged();
282                 }
283             } else if (ClassPath.PROP_ROOTS.equals(propertyName)) {
284                 rootsChanged();
285             }
286         }
287
288         public void stateChanged(ChangeEvent JavaDoc evt) {
289             rootsChanged();
290         }
291
292         private void entriesChanged() {
293             synchronized (ClassPathSourceCache.this) {
294                 if (!deinitialized) {
295                     if (results != null) {
296                         for (SourceForBinaryQuery.Result result : results) {
297                             result.removeChangeListener(this);
298                         }
299                     }
300                     results = null;
301                     invalidResults++;
302                     sourceRoots = null;
303                     invalidSourceRoots++;
304                     propChangeSupport.firePropertyChange(PROP_ROOTS, null, null);
305                 }
306             }
307         }
308
309         private void rootsChanged() {
310             synchronized (ClassPathSourceCache.this) {
311                 if (!deinitialized) {
312                     sourceRoots = null;
313                     invalidSourceRoots++;
314                     propChangeSupport.firePropertyChange(PROP_ROOTS, null, null);
315                 }
316             }
317         }
318     }
319
320     /**
321      * Weak reference holding the cache's classpath and cleans up when
322      * the classpath is GCd.
323      */

324     private final class CleanupReference<T> extends WeakReference JavaDoc<T> implements Runnable JavaDoc {
325
326         public CleanupReference(T referent) {
327             super(referent, Utilities.activeReferenceQueue());
328         }
329
330         public void run() {
331             synchronized (ClassPathSourceCache.this) {
332                 if (results != null) {
333                     for (SourceForBinaryQuery.Result result : results) {
334                         result.removeChangeListener(listener);
335                     }
336                 }
337                 results = Collections.emptyList();
338                 invalidResults++;
339                 sourceRoots = Collections.emptySet();
340                 invalidSourceRoots++;
341
342                 deinitialized = true;
343             }
344         }
345     }
346 }
347
Popular Tags