KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > spi > project > support > ant > SourcesHelper


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.spi.project.support.ant;
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.net.URL JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Collection JavaDoc;
30 import java.util.Collections JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.HashSet JavaDoc;
33 import java.util.Iterator JavaDoc;
34 import java.util.LinkedHashMap JavaDoc;
35 import java.util.List JavaDoc;
36 import java.util.Map JavaDoc;
37 import java.util.Set JavaDoc;
38 import javax.swing.Icon JavaDoc;
39 import javax.swing.event.ChangeEvent JavaDoc;
40 import javax.swing.event.ChangeListener JavaDoc;
41 import org.netbeans.api.project.FileOwnerQuery;
42 import org.netbeans.api.project.Project;
43 import org.netbeans.api.project.ProjectManager;
44 import org.netbeans.api.project.ProjectUtils;
45 import org.netbeans.api.project.SourceGroup;
46 import org.netbeans.api.project.Sources;
47 import org.netbeans.api.queries.SharabilityQuery;
48 import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton;
49 import org.netbeans.modules.project.ant.FileChangeSupport;
50 import org.netbeans.modules.project.ant.FileChangeSupportEvent;
51 import org.netbeans.modules.project.ant.FileChangeSupportListener;
52 import org.openide.filesystems.FileObject;
53 import org.openide.filesystems.FileStateInvalidException;
54 import org.openide.filesystems.FileUtil;
55 import org.openide.util.WeakListeners;
56
57 // XXX should perhaps be legal to call add* methods at any time (should update things)
58
// and perhaps also have remove* methods
59
// and have code names for each source dir?
60

61 // XXX should probably all be wrapped in ProjectManager.mutex
62

63 /**
64  * Helper class to work with source roots and typed folders of a project.
65  * @author Jesse Glick
66  */

67 public final class SourcesHelper {
68     
69     private class Root {
70         protected final String JavaDoc location;
71         public Root(String JavaDoc location) {
72             this.location = location;
73         }
74         public final File JavaDoc getActualLocation() {
75             String JavaDoc val = evaluator.evaluate(location);
76             if (val == null) {
77                 return null;
78             }
79             return project.resolveFile(val);
80         }
81         public Collection JavaDoc<FileObject> getIncludeRoots() {
82             File JavaDoc loc = getActualLocation();
83             if (loc != null) {
84                 FileObject fo = FileUtil.toFileObject(loc);
85                 if (fo != null) {
86                     return Collections.singleton(fo);
87                 }
88             }
89             return Collections.emptySet();
90         }
91     }
92     
93     private class SourceRoot extends Root {
94
95         private final String JavaDoc displayName;
96         private final Icon JavaDoc icon;
97         private final Icon JavaDoc openedIcon;
98         private final String JavaDoc includes;
99         private final String JavaDoc excludes;
100         private PathMatcher matcher;
101
102         public SourceRoot(String JavaDoc location, String JavaDoc includes, String JavaDoc excludes, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) {
103             super(location);
104             this.displayName = displayName;
105             this.icon = icon;
106             this.openedIcon = openedIcon;
107             this.includes = includes;
108             this.excludes = excludes;
109         }
110
111         public final SourceGroup toGroup(FileObject loc) {
112             assert loc != null;
113             return new Group(loc);
114         }
115
116         @Override JavaDoc
117         public String JavaDoc toString() {
118             return "SourceRoot[" + location + "]"; // NOI18N
119
}
120
121         // Copied w/ mods from GenericSources.
122
private final class Group implements SourceGroup, PropertyChangeListener JavaDoc {
123
124             private final FileObject loc;
125             private final PropertyChangeSupport JavaDoc pcs = new PropertyChangeSupport JavaDoc(this);
126
127             Group(FileObject loc) {
128                 this.loc = loc;
129                 evaluator.addPropertyChangeListener(WeakListeners.propertyChange(this, evaluator));
130             }
131
132             public FileObject getRootFolder() {
133                 return loc;
134             }
135
136             public String JavaDoc getName() {
137                 return location.length() > 0 ? location : "generic"; // NOI18N
138
}
139
140             public String JavaDoc getDisplayName() {
141                 return displayName;
142             }
143
144             public Icon JavaDoc getIcon(boolean opened) {
145                 return opened ? icon : openedIcon;
146             }
147
148             public boolean contains(FileObject file) throws IllegalArgumentException JavaDoc {
149                 if (file == loc) {
150                     return true;
151                 }
152                 String JavaDoc path = FileUtil.getRelativePath(loc, file);
153                 if (path == null) {
154                     throw new IllegalArgumentException JavaDoc();
155                 }
156                 if (file.isFolder()) {
157                     path += "/"; // NOI18N
158
}
159                 computeIncludeExcludePatterns();
160                 if (!matcher.matches(path, true)) {
161                     return false;
162                 }
163                 Project p = getProject();
164                 if (file.isFolder() && file != p.getProjectDirectory() && ProjectManager.getDefault().isProject(file)) {
165                     // #67450: avoid actually loading the nested project.
166
return false;
167                 }
168                 Project owner = FileOwnerQuery.getOwner(file);
169                 if (owner != null && owner != p) {
170                     return false;
171                 }
172                 File JavaDoc f = FileUtil.toFile(file);
173                 if (f != null && SharabilityQuery.getSharability(f) == SharabilityQuery.NOT_SHARABLE) {
174                     return false;
175                 } // else MIXED, UNKNOWN, or SHARABLE; or not a disk file
176
return true;
177             }
178
179             public void addPropertyChangeListener(PropertyChangeListener JavaDoc l) {
180                 pcs.addPropertyChangeListener(l);
181             }
182
183             public void removePropertyChangeListener(PropertyChangeListener JavaDoc l) {
184                 pcs.removePropertyChangeListener(l);
185             }
186
187             @Override JavaDoc
188             public String JavaDoc toString() {
189                 return "SourcesHelper.Group[name=" + getName() + ",rootFolder=" + getRootFolder() + "]"; // NOI18N
190
}
191
192             public void propertyChange(PropertyChangeEvent JavaDoc ev) {
193                 assert ev.getSource() == evaluator : ev;
194                 String JavaDoc prop = ev.getPropertyName();
195                 if (prop == null ||
196                         (includes != null && includes.contains("${" + prop + "}")) || // NOI18N
197
(excludes != null && excludes.contains("${" + prop + "}"))) { // NOI18N
198
matcher = null;
199                     pcs.firePropertyChange(PROP_CONTAINERSHIP, null, null);
200                 }
201                 // XXX should perhaps react to ProjectInformation changes? but nothing to fire currently
202
}
203
204         }
205
206         private String JavaDoc evalForMatcher(String JavaDoc raw) {
207             if (raw == null) {
208                 return null;
209             }
210             String JavaDoc patterns = evaluator.evaluate(raw);
211             if (patterns == null) {
212                 return null;
213             }
214             if (patterns.matches("\\$\\{[^}]+\\}")) { // NOI18N
215
// Unevaluated single property, treat like null.
216
return null;
217             }
218             return patterns;
219         }
220
221         private void computeIncludeExcludePatterns() {
222             if (matcher != null) {
223                 return;
224             }
225             String JavaDoc includesPattern = evalForMatcher(includes);
226             String JavaDoc excludesPattern = evalForMatcher(excludes);
227             matcher = new PathMatcher(includesPattern, excludesPattern, getActualLocation());
228         }
229
230
231         @Override JavaDoc
232         public Collection JavaDoc<FileObject> getIncludeRoots() {
233             Collection JavaDoc<FileObject> supe = super.getIncludeRoots();
234             computeIncludeExcludePatterns();
235             if (supe.size() == 1) {
236                 Set JavaDoc<FileObject> roots = new HashSet JavaDoc<FileObject>();
237                 for (File JavaDoc r : matcher.findIncludedRoots()) {
238                     FileObject subroot = FileUtil.toFileObject(r);
239                     if (subroot != null) {
240                         roots.add(subroot);
241                     }
242                 }
243                 return roots;
244             } else {
245                 assert supe.isEmpty();
246                 return supe;
247             }
248         }
249
250     }
251     
252     private final class TypedSourceRoot extends SourceRoot {
253         private final String JavaDoc type;
254         public TypedSourceRoot(String JavaDoc type, String JavaDoc location, String JavaDoc includes, String JavaDoc excludes, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) {
255             super(location, includes, excludes, displayName, icon, openedIcon);
256             this.type = type;
257         }
258         public final String JavaDoc getType() {
259             return type;
260         }
261     }
262     
263     private final AntProjectHelper project;
264     private final PropertyEvaluator evaluator;
265     private final List JavaDoc<SourceRoot> principalSourceRoots = new ArrayList JavaDoc<SourceRoot>();
266     private final List JavaDoc<Root> nonSourceRoots = new ArrayList JavaDoc<Root>();
267     private final List JavaDoc<TypedSourceRoot> typedSourceRoots = new ArrayList JavaDoc<TypedSourceRoot>();
268     private int registeredRootAlgorithm;
269     /**
270      * If not null, external roots that we registered the last time.
271      * Used when a property change is encountered, to see if the set of external
272      * roots might have changed. Hold the actual files (not e.g. URLs); see
273      * {@link #registerExternalRoots} for the reason why.
274      */

275     private Set JavaDoc<FileObject> lastRegisteredRoots;
276     private PropertyChangeListener JavaDoc propChangeL;
277     
278     /**
279      * Create the helper object, initially configured to recognize only sources
280      * contained inside the project directory.
281      * @param project an Ant project helper
282      * @param evaluator a way to evaluate Ant properties used to define source locations
283      */

284     public SourcesHelper(AntProjectHelper project, PropertyEvaluator evaluator) {
285         this.project = project;
286         this.evaluator = evaluator;
287     }
288     
289     /**
290      * Add a possible principal source root, or top-level folder which may
291      * contain sources that should be considered part of the project.
292      * <p>
293      * If the actual value of the location is inside the project directory,
294      * this is simply ignored; so it safe to configure principal source roots
295      * for any source directory which might be set to use an external path, even
296      * if the common location is internal.
297      * </p>
298      * @param location a project-relative or absolute path giving the location
299      * of a source tree; may contain Ant property substitutions
300      * @param displayName a display name (for {@link SourceGroup#getDisplayName})
301      * @param icon a regular icon for the source root, or null
302      * @param openedIcon an opened variant icon for the source root, or null
303      * @throws IllegalStateException if this method is called after either
304      * {@link #createSources} or {@link #registerExternalRoots}
305      * was called
306      * @see #registerExternalRoots
307      * @see Sources#TYPE_GENERIC
308      */

309     public void addPrincipalSourceRoot(String JavaDoc location, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) throws IllegalStateException JavaDoc {
310         addPrincipalSourceRoot(location, null, null, displayName, icon, openedIcon);
311     }
312
313     /**
314      * Add a possible principal source root, or top-level folder which may
315      * contain sources that should be considered part of the project, with
316      * optional include and exclude lists.
317      * <p>
318      * If an include or exclude string is given as null, then it is skipped. A non-null value is
319      * evaluated and then treated as a comma- or space-separated pattern list,
320      * as detailed in the Javadoc for {@link PathMatcher}.
321      * (As a special convenience, a value consisting solely of an Ant property reference
322      * which cannot be evaluated, e.g. <samp>${undefined}</samp>, is treated like null.)
323      * {@link SourceGroup#contains} will then reflect the includes and excludes for files, but note that the
324      * semantics of that method requires that a folder be "contained" in case any folder or file
325      * beneath it is contained, and in particular the root folder is always contained.
326      * </p>
327      * @param location a project-relative or absolute path giving the location
328      * of a source tree; may contain Ant property substitutions
329      * @param includes Ant-style includes; may contain Ant property substitutions;
330      * if not null, only files and folders
331      * matching the pattern (or patterns), and not specified in the excludes list,
332      * will be {@link SourceGroup#contains included}
333      * @param excludes Ant-style excludes; may contain Ant property substitutions;
334      * if not null, files and folders
335      * matching the pattern (or patterns) will not be {@link SourceGroup#contains included},
336      * even if specified in the includes list
337      * @param displayName a display name (for {@link SourceGroup#getDisplayName})
338      * @param icon a regular icon for the source root, or null
339      * @param openedIcon an opened variant icon for the source root, or null
340      * @throws IllegalStateException if this method is called after either
341      * {@link #createSources} or {@link #registerExternalRoots}
342      * was called
343      * @see #registerExternalRoots
344      * @see Sources#TYPE_GENERIC
345      * @since org.netbeans.modules.project.ant/1 1.15
346      */

347     public void addPrincipalSourceRoot(String JavaDoc location, String JavaDoc includes, String JavaDoc excludes, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) throws IllegalStateException JavaDoc {
348         if (lastRegisteredRoots != null) {
349             throw new IllegalStateException JavaDoc("registerExternalRoots was already called"); // NOI18N
350
}
351         principalSourceRoots.add(new SourceRoot(location, includes, excludes, displayName, icon, openedIcon));
352     }
353     
354     /**
355      * Similar to {@link #addPrincipalSourceRoot} but affects only
356      * {@link #registerExternalRoots} and not {@link #createSources}.
357      * <p class="nonnormative">
358      * Useful for project type providers which have external paths holding build
359      * products. These should not appear in {@link Sources}, yet it may be useful
360      * for {@link FileOwnerQuery} to know the owning project (for example, in order
361      * for a project-specific {@link org.netbeans.spi.queries.SourceForBinaryQueryImplementation} to work).
362      * </p>
363      * @param location a project-relative or absolute path giving the location
364      * of a non-source tree; may contain Ant property substitutions
365      * @throws IllegalStateException if this method is called after
366      * {@link #registerExternalRoots} was called
367      */

368     public void addNonSourceRoot(String JavaDoc location) throws IllegalStateException JavaDoc {
369         if (lastRegisteredRoots != null) {
370             throw new IllegalStateException JavaDoc("registerExternalRoots was already called"); // NOI18N
371
}
372         nonSourceRoots.add(new Root(location));
373     }
374     
375     /**
376      * Add a typed source root which will be considered only in certain contexts.
377      * @param location a project-relative or absolute path giving the location
378      * of a source tree; may contain Ant property substitutions
379      * @param type a source root type such as <a HREF="@JAVA/PROJECT@/org/netbeans/api/java/project/JavaProjectConstants.html#SOURCES_TYPE_JAVA"><code>JavaProjectConstants.SOURCES_TYPE_JAVA</code></a>
380      * @param displayName a display name (for {@link SourceGroup#getDisplayName})
381      * @param icon a regular icon for the source root, or null
382      * @param openedIcon an opened variant icon for the source root, or null
383      * @throws IllegalStateException if this method is called after either
384      * {@link #createSources} or {@link #registerExternalRoots}
385      * was called
386      */

387     public void addTypedSourceRoot(String JavaDoc location, String JavaDoc type, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) throws IllegalStateException JavaDoc {
388         addTypedSourceRoot(location, null, null, type, displayName, icon, openedIcon);
389     }
390     
391     /**
392      * Add a typed source root with optional include and exclude lists.
393      * See {@link #addPrincipalSourceRoot(String,String,String,String,Icon,Icon)}
394      * for details on semantics of includes and excludes.
395      * @param location a project-relative or absolute path giving the location
396      * of a source tree; may contain Ant property substitutions
397      * @param includes an optional list of Ant-style includes
398      * @param excludes an optional list of Ant-style excludes
399      * @param type a source root type such as <a HREF="@JAVA/PROJECT@/org/netbeans/api/java/project/JavaProjectConstants.html#SOURCES_TYPE_JAVA"><code>JavaProjectConstants.SOURCES_TYPE_JAVA</code></a>
400      * @param displayName a display name (for {@link SourceGroup#getDisplayName})
401      * @param icon a regular icon for the source root, or null
402      * @param openedIcon an opened variant icon for the source root, or null
403      * @throws IllegalStateException if this method is called after either
404      * {@link #createSources} or {@link #registerExternalRoots}
405      * was called
406      * @since org.netbeans.modules.project.ant/1 1.15
407      */

408     public void addTypedSourceRoot(String JavaDoc location, String JavaDoc includes, String JavaDoc excludes, String JavaDoc type, String JavaDoc displayName, Icon JavaDoc icon, Icon JavaDoc openedIcon) throws IllegalStateException JavaDoc {
409         if (lastRegisteredRoots != null) {
410             throw new IllegalStateException JavaDoc("registerExternalRoots was already called"); // NOI18N
411
}
412         typedSourceRoots.add(new TypedSourceRoot(type, location, includes, excludes, displayName, icon, openedIcon));
413     }
414     
415     private Project getProject() {
416         return AntBasedProjectFactorySingleton.getProjectFor(project);
417     }
418     
419     /**
420      * Register all external source or non-source roots using {@link FileOwnerQuery#markExternalOwner}.
421      * <p>
422      * Only roots added by {@link #addPrincipalSourceRoot} and {@link #addNonSourceRoot}
423      * are considered. They are registered if (and only if) they in fact fall
424      * outside of the project directory, and of course only if the folders really
425      * exist on disk. Currently it is not defined when this file existence check
426      * is done (e.g. when this method is first called, or periodically) or whether
427      * folders which are created subsequently will be registered, so project type
428      * providers are encouraged to create all desired external roots before calling
429      * this method.
430      * </p>
431      * <p>
432      * If the actual value of the location changes (due to changes being
433      * fired from the property evaluator), roots which were previously internal
434      * and are now external will be registered, and roots which were previously
435      * external and are now internal will be unregistered. The (un-)registration
436      * will be done using the same algorithm as was used initially.
437      * </p>
438      * <p>
439      * If an explicit include list is configured for a principal source root, only those
440      * subfolders which are included (or folders directly containing included files)
441      * will be registered. Note that the source root, or an included subfolder, will
442      * be registered even if it contains excluded files or folders beneath it.
443      * </p>
444      * <p>
445      * Calling this method causes the helper object to hold strong references to the
446      * current external roots, which helps a project satisfy the requirements of
447      * {@link FileOwnerQuery#EXTERNAL_ALGORITHM_TRANSIENT}.
448      * </p>
449      * <p>
450      * You may <em>not</em> call this method inside the project's constructor, as
451      * it requires the actual project to exist and be registered in {@link ProjectManager}.
452      * Typically you would use {@link org.openide.util.Mutex#postWriteRequest} to run it
453      * later, if you were creating the helper in your constructor, since the project construction
454      * normally occurs in read access.
455      * </p>
456      * @param algorithm an external root registration algorithm as per
457      * {@link FileOwnerQuery#markExternalOwner}
458      * @throws IllegalArgumentException if the algorithm is unrecognized
459      * @throws IllegalStateException if this method is called more than once on a
460      * given <code>SourcesHelper</code> object
461      */

462     public void registerExternalRoots(int algorithm) throws IllegalArgumentException JavaDoc, IllegalStateException JavaDoc {
463         if (lastRegisteredRoots != null) {
464             throw new IllegalStateException JavaDoc("registerExternalRoots was already called before"); // NOI18N
465
}
466         registeredRootAlgorithm = algorithm;
467         remarkExternalRoots();
468     }
469     
470     private void remarkExternalRoots() throws IllegalArgumentException JavaDoc {
471         List JavaDoc<Root> allRoots = new ArrayList JavaDoc<Root>(principalSourceRoots);
472         allRoots.addAll(nonSourceRoots);
473         Project p = getProject();
474         FileObject pdir = project.getProjectDirectory();
475         // First time: register roots and add to lastRegisteredRoots.
476
// Subsequent times: add to newRootsToRegister and maybe add them later.
477
if (lastRegisteredRoots == null) {
478             // First time.
479
lastRegisteredRoots = Collections.emptySet();
480             propChangeL = new PropChangeL(); // hold a strong ref
481
evaluator.addPropertyChangeListener(WeakListeners.propertyChange(propChangeL, evaluator));
482         }
483         Set JavaDoc<FileObject> newRegisteredRoots = new HashSet JavaDoc<FileObject>();
484         // XXX might be a bit more efficient to cache for each root the actualLocation value
485
// that was last computed, and just check if that has changed... otherwise we wind
486
// up calling APH.resolveFileObject repeatedly (for each property change)
487
for (Root r : allRoots) {
488             for (FileObject loc : r.getIncludeRoots()) {
489                 if (!loc.isFolder()) {
490                     continue;
491                 }
492                 if (FileUtil.getRelativePath(pdir, loc) != null) {
493                     // Inside projdir already. Skip it.
494
continue;
495                 }
496                 try {
497                     Project other = ProjectManager.getDefault().findProject(loc);
498                     if (other != null) {
499                         // This is a foreign project; we cannot own it. Skip it.
500
continue;
501                     }
502                 } catch (IOException JavaDoc e) {
503                     // Assume it is a foreign project and skip it.
504
continue;
505                 }
506                 // It's OK to go.
507
newRegisteredRoots.add(loc);
508             }
509         }
510         // Just check for changes since the last time.
511
Set JavaDoc<FileObject> toUnregister = new HashSet JavaDoc<FileObject>(lastRegisteredRoots);
512         toUnregister.removeAll(newRegisteredRoots);
513         for (FileObject loc : toUnregister) {
514             FileOwnerQuery.markExternalOwner(loc, null, registeredRootAlgorithm);
515         }
516         Set JavaDoc<FileObject> toRegister = new HashSet JavaDoc<FileObject>(newRegisteredRoots);
517         toRegister.removeAll(lastRegisteredRoots);
518         for (FileObject loc : toRegister) {
519             FileOwnerQuery.markExternalOwner(loc, p, registeredRootAlgorithm);
520         }
521         lastRegisteredRoots = newRegisteredRoots;
522     }
523
524     /**
525      * Create a source list object.
526      * <p>
527      * All principal source roots are listed as {@link Sources#TYPE_GENERIC} unless they
528      * are inside the project directory. The project directory itself is also listed
529      * (with a display name according to {@link ProjectUtils#getInformation}), unless
530      * it is contained by an explicit principal source root (i.e. ancestor directory).
531      * Principal source roots should never overlap; if two configured
532      * principal source roots are determined to have the same root folder, the first
533      * configured root takes precedence (which only matters in regard to the display
534      * name); if one root folder is contained within another, the broader
535      * root folder subsumes the narrower one so only the broader root is listed.
536      * </p>
537      * <p>
538      * Other source groups are listed according to the named typed source roots.
539      * There is no check performed that these do not overlap (though a project type
540      * provider should for UI reasons avoid this situation).
541      * </p>
542      * <p>
543      * Any source roots which do not exist on disk are ignored, as if they had
544      * not been configured at all. Currently it is not defined when this existence
545      * check is performed (e.g. when this method is called, when the source root
546      * is first accessed, periodically, etc.), so project type providers are
547      * generally encouraged to make sure all desired source folders exist
548      * before calling this method, if creating a new project.
549      * </p>
550      * <p>
551      * Source groups are created according to the semantics described in
552      * {@link GenericSources#group}. They are listed in the order they
553      * were configured (for those roots that are actually used as groups).
554      * </p>
555      * <p>
556      * You may call this method inside the project's constructor, but
557      * {@link Sources#getSourceGroups} may <em>not</em> be called within the
558      * constructor, as it requires the actual project object to exist and be
559      * registered in {@link ProjectManager}.
560      * </p>
561      * @return a source list object suitable for {@link Project#getLookup}
562      */

563     public Sources createSources() {
564         return new SourcesImpl();
565     }
566     
567     private final class SourcesImpl implements Sources, PropertyChangeListener JavaDoc, FileChangeSupportListener {
568         
569         private final List JavaDoc<ChangeListener JavaDoc> listeners = new ArrayList JavaDoc<ChangeListener JavaDoc>();
570         private boolean haveAttachedListeners;
571         private final Set JavaDoc<File JavaDoc> rootsListenedTo = new HashSet JavaDoc<File JavaDoc>();
572         /**
573          * The root URLs which were computed last, keyed by group type.
574          */

575         private final Map JavaDoc<String JavaDoc,List JavaDoc<URL JavaDoc>> lastComputedRoots = new HashMap JavaDoc<String JavaDoc,List JavaDoc<URL JavaDoc>>();
576         
577         public SourcesImpl() {
578             evaluator.addPropertyChangeListener(WeakListeners.propertyChange(this, evaluator));
579         }
580         
581         public SourceGroup[] getSourceGroups(String JavaDoc type) {
582             List JavaDoc<SourceGroup> groups = new ArrayList JavaDoc<SourceGroup>();
583             if (type.equals(Sources.TYPE_GENERIC)) {
584                 List JavaDoc<SourceRoot> roots = new ArrayList JavaDoc<SourceRoot>(principalSourceRoots);
585                 // Always include the project directory itself as a default:
586
roots.add(new SourceRoot("", null, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null)); // NOI18N
587
Map JavaDoc<FileObject,SourceRoot> rootsByDir = new LinkedHashMap JavaDoc<FileObject,SourceRoot>();
588                 // First collect all non-redundant existing roots.
589
for (SourceRoot r : roots) {
590                     File JavaDoc locF = r.getActualLocation();
591                     if (locF == null) {
592                         continue;
593                     }
594                     listen(locF);
595                     FileObject loc = FileUtil.toFileObject(locF);
596                     if (loc == null) {
597                         continue;
598                     }
599                     if (rootsByDir.containsKey(loc)) {
600                         continue;
601                     }
602                     rootsByDir.put(loc, r);
603                 }
604                 // Remove subroots.
605
Iterator JavaDoc<FileObject> it = rootsByDir.keySet().iterator();
606                 while (it.hasNext()) {
607                     FileObject loc = it.next();
608                     FileObject parent = loc.getParent();
609                     while (parent != null) {
610                         if (rootsByDir.containsKey(parent)) {
611                             // This is a subroot of something, so skip it.
612
it.remove();
613                             break;
614                         }
615                         parent = parent.getParent();
616                     }
617                 }
618                 // Everything else is kosher.
619
for (Map.Entry JavaDoc<FileObject,SourceRoot> entry : rootsByDir.entrySet()) {
620                     groups.add(entry.getValue().toGroup(entry.getKey()));
621                 }
622             } else {
623                 Set JavaDoc<FileObject> dirs = new HashSet JavaDoc<FileObject>();
624                 for (TypedSourceRoot r : typedSourceRoots) {
625                     if (!r.getType().equals(type)) {
626                         continue;
627                     }
628                     File JavaDoc locF = r.getActualLocation();
629                     if (locF == null) {
630                         continue;
631                     }
632                     listen(locF);
633                     FileObject loc = FileUtil.toFileObject(locF);
634                     if (loc == null) {
635                         continue;
636                     }
637                     if (!dirs.add(loc)) {
638                         // Already had one.
639
continue;
640                     }
641                     groups.add(r.toGroup(loc));
642                 }
643             }
644             // Remember what we computed here so we know whether to fire changes later.
645
List JavaDoc<URL JavaDoc> rootURLs = new ArrayList JavaDoc<URL JavaDoc>(groups.size());
646             for (SourceGroup g : groups) {
647                 try {
648                     rootURLs.add(g.getRootFolder().getURL());
649                 } catch (FileStateInvalidException e) {
650                     assert false : e; // should be a valid file object!
651
}
652             }
653             lastComputedRoots.put(type, rootURLs);
654             return groups.toArray(new SourceGroup[groups.size()]);
655         }
656         
657         private void listen(File JavaDoc rootLocation) {
658             // #40845. Need to fire changes if a source root is added or removed.
659
if (rootsListenedTo.add(rootLocation) && /* be lazy */ haveAttachedListeners) {
660                 FileChangeSupport.DEFAULT.addListener(this, rootLocation);
661             }
662         }
663         
664         public void addChangeListener(ChangeListener JavaDoc listener) {
665             if (!haveAttachedListeners) {
666                 haveAttachedListeners = true;
667                 for (File JavaDoc rootLocation : rootsListenedTo) {
668                     FileChangeSupport.DEFAULT.addListener(this, rootLocation);
669                 }
670             }
671             synchronized (listeners) {
672                 listeners.add(listener);
673             }
674         }
675         
676         public void removeChangeListener(ChangeListener JavaDoc listener) {
677             synchronized (listeners) {
678                 listeners.remove(listener);
679             }
680         }
681         
682         private void fireChange() {
683             ChangeListener JavaDoc[] _listeners;
684             synchronized (listeners) {
685                 if (listeners.isEmpty()) {
686                     return;
687                 }
688                 _listeners = listeners.toArray(new ChangeListener JavaDoc[listeners.size()]);
689             }
690             ChangeEvent JavaDoc ev = new ChangeEvent JavaDoc(this);
691             for (ChangeListener JavaDoc l : _listeners) {
692                 l.stateChanged(ev);
693             }
694         }
695         
696         private void maybeFireChange() {
697             // #47451: check whether anything really changed.
698
boolean change = false;
699             // Cannot iterate over entrySet, as the map will be modified by getSourceGroups.
700
for (String JavaDoc type : new HashSet JavaDoc<String JavaDoc>(lastComputedRoots.keySet())) {
701                 List JavaDoc<URL JavaDoc> previous = new ArrayList JavaDoc<URL JavaDoc>(lastComputedRoots.get(type));
702                 getSourceGroups(type);
703                 List JavaDoc<URL JavaDoc> nue = lastComputedRoots.get(type);
704                 if (!nue.equals(previous)) {
705                     change = true;
706                     break;
707                 }
708             }
709             if (change) {
710                 fireChange();
711             }
712         }
713
714         public void fileCreated(FileChangeSupportEvent event) {
715             // Root might have been created on disk.
716
maybeFireChange();
717         }
718
719         public void fileDeleted(FileChangeSupportEvent event) {
720             // Root might have been deleted.
721
maybeFireChange();
722         }
723
724         public void fileModified(FileChangeSupportEvent event) {
725             // ignore; generally should not happen (listening to dirs)
726
}
727         
728         public void propertyChange(PropertyChangeEvent JavaDoc propertyChangeEvent) {
729             // Properties may have changed so as cause external roots to move etc.
730
maybeFireChange();
731         }
732
733     }
734     
735     private final class PropChangeL implements PropertyChangeListener JavaDoc {
736         
737         public PropChangeL() {}
738         
739         public void propertyChange(PropertyChangeEvent JavaDoc evt) {
740             // Some properties changed; external roots might have changed, so check them.
741
for (SourceRoot r : principalSourceRoots) {
742                 r.matcher = null;
743             }
744             remarkExternalRoots();
745         }
746         
747     }
748     
749 }
750
Popular Tags