Source Code Cross Referenced for SourcesHelper.java in  » IDE-Netbeans » project.ant » org » netbeans » spi » project » support » ant » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE Netbeans » project.ant » org.netbeans.spi.project.support.ant 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003:         *
004:         * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005:         *
006:         * The contents of this file are subject to the terms of either the GNU
007:         * General Public License Version 2 only ("GPL") or the Common
008:         * Development and Distribution License("CDDL") (collectively, the
009:         * "License"). You may not use this file except in compliance with the
010:         * License. You can obtain a copy of the License at
011:         * http://www.netbeans.org/cddl-gplv2.html
012:         * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013:         * specific language governing permissions and limitations under the
014:         * License.  When distributing the software, include this License Header
015:         * Notice in each file and include the License file at
016:         * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
017:         * particular file as subject to the "Classpath" exception as provided
018:         * by Sun in the GPL Version 2 section of the License file that
019:         * accompanied this code. If applicable, add the following below the
020:         * License Header, with the fields enclosed by brackets [] replaced by
021:         * your own identifying information:
022:         * "Portions Copyrighted [year] [name of copyright owner]"
023:         *
024:         * Contributor(s):
025:         *
026:         * The Original Software is NetBeans. The Initial Developer of the Original
027:         * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028:         * Microsystems, Inc. All Rights Reserved.
029:         *
030:         * If you wish your version of this file to be governed by only the CDDL
031:         * or only the GPL Version 2, indicate your decision by adding
032:         * "[Contributor] elects to include this software in this distribution
033:         * under the [CDDL or GPL Version 2] license." If you do not indicate a
034:         * single choice of license, a recipient has the option to distribute
035:         * your version of this file under either the CDDL, the GPL Version 2 or
036:         * to extend the choice of license to its licensees as provided above.
037:         * However, if you add GPL Version 2 code and therefore, elected the GPL
038:         * Version 2 license, then the option applies only if the new code is
039:         * made subject to such option by the copyright holder.
040:         */
041:
042:        package org.netbeans.spi.project.support.ant;
043:
044:        import java.beans.PropertyChangeEvent;
045:        import java.beans.PropertyChangeListener;
046:        import java.beans.PropertyChangeSupport;
047:        import java.io.File;
048:        import java.io.IOException;
049:        import java.net.URL;
050:        import java.util.ArrayList;
051:        import java.util.Collection;
052:        import java.util.Collections;
053:        import java.util.HashMap;
054:        import java.util.HashSet;
055:        import java.util.Iterator;
056:        import java.util.LinkedHashMap;
057:        import java.util.List;
058:        import java.util.Map;
059:        import java.util.Set;
060:        import javax.swing.Icon;
061:        import javax.swing.event.ChangeListener;
062:        import org.netbeans.api.project.FileOwnerQuery;
063:        import org.netbeans.api.project.Project;
064:        import org.netbeans.api.project.ProjectManager;
065:        import org.netbeans.api.project.ProjectUtils;
066:        import org.netbeans.api.project.SourceGroup;
067:        import org.netbeans.api.project.Sources;
068:        import org.netbeans.api.queries.SharabilityQuery;
069:        import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton;
070:        import org.netbeans.modules.project.ant.FileChangeSupport;
071:        import org.netbeans.modules.project.ant.FileChangeSupportEvent;
072:        import org.netbeans.modules.project.ant.FileChangeSupportListener;
073:        import org.openide.filesystems.FileObject;
074:        import org.openide.filesystems.FileStateInvalidException;
075:        import org.openide.filesystems.FileUtil;
076:        import org.openide.util.ChangeSupport;
077:        import org.openide.util.WeakListeners;
078:
079:        // XXX should perhaps be legal to call add* methods at any time (should update things)
080:        // and perhaps also have remove* methods
081:        // and have code names for each source dir?
082:
083:        // XXX should probably all be wrapped in ProjectManager.mutex
084:
085:        /**
086:         * Helper class to work with source roots and typed folders of a project.
087:         * @author Jesse Glick
088:         */
089:        public final class SourcesHelper {
090:
091:            private class Root {
092:                protected final String location;
093:
094:                public Root(String location) {
095:                    this .location = location;
096:                }
097:
098:                public final File getActualLocation() {
099:                    String val = evaluator.evaluate(location);
100:                    if (val == null) {
101:                        return null;
102:                    }
103:                    return project.resolveFile(val);
104:                }
105:
106:                public Collection<FileObject> getIncludeRoots() {
107:                    File loc = getActualLocation();
108:                    if (loc != null) {
109:                        FileObject fo = FileUtil.toFileObject(loc);
110:                        if (fo != null) {
111:                            return Collections.singleton(fo);
112:                        }
113:                    }
114:                    return Collections.emptySet();
115:                }
116:            }
117:
118:            private class SourceRoot extends Root {
119:
120:                private final String displayName;
121:                private final Icon icon;
122:                private final Icon openedIcon;
123:                private final String includes;
124:                private final String excludes;
125:                private PathMatcher matcher;
126:
127:                public SourceRoot(String location, String includes,
128:                        String excludes, String displayName, Icon icon,
129:                        Icon openedIcon) {
130:                    super (location);
131:                    this .displayName = displayName;
132:                    this .icon = icon;
133:                    this .openedIcon = openedIcon;
134:                    this .includes = includes;
135:                    this .excludes = excludes;
136:                }
137:
138:                public final SourceGroup toGroup(FileObject loc) {
139:                    assert loc != null;
140:                    return new Group(loc);
141:                }
142:
143:                @Override
144:                public String toString() {
145:                    return "SourceRoot[" + location + "]"; // NOI18N
146:                }
147:
148:                // Copied w/ mods from GenericSources.
149:                private final class Group implements  SourceGroup,
150:                        PropertyChangeListener {
151:
152:                    private final FileObject loc;
153:                    private final PropertyChangeSupport pcs = new PropertyChangeSupport(
154:                            this );
155:
156:                    Group(FileObject loc) {
157:                        this .loc = loc;
158:                        evaluator.addPropertyChangeListener(WeakListeners
159:                                .propertyChange(this , evaluator));
160:                    }
161:
162:                    public FileObject getRootFolder() {
163:                        return loc;
164:                    }
165:
166:                    public String getName() {
167:                        return location.length() > 0 ? location : "generic"; // NOI18N
168:                    }
169:
170:                    public String getDisplayName() {
171:                        return displayName;
172:                    }
173:
174:                    public Icon getIcon(boolean opened) {
175:                        return opened ? icon : openedIcon;
176:                    }
177:
178:                    public boolean contains(FileObject file)
179:                            throws IllegalArgumentException {
180:                        if (file == loc) {
181:                            return true;
182:                        }
183:                        String path = FileUtil.getRelativePath(loc, file);
184:                        if (path == null) {
185:                            throw new IllegalArgumentException(file
186:                                    + " is not inside " + loc);
187:                        }
188:                        if (file.isFolder()) {
189:                            path += "/"; // NOI18N
190:                        }
191:                        computeIncludeExcludePatterns();
192:                        if (!matcher.matches(path, true)) {
193:                            return false;
194:                        }
195:                        Project p = getProject();
196:                        if (file.isFolder() && file != p.getProjectDirectory()
197:                                && ProjectManager.getDefault().isProject(file)) {
198:                            // #67450: avoid actually loading the nested project.
199:                            return false;
200:                        }
201:                        if (!(SourceRoot.this  instanceof  TypedSourceRoot)) {
202:                            // XXX disabled for typed source roots; difficult to make fast (#97215)
203:                            Project owner = FileOwnerQuery.getOwner(file);
204:                            if (owner != null && owner != p) {
205:                                return false;
206:                            }
207:                            File f = FileUtil.toFile(file);
208:                            if (f != null
209:                                    && SharabilityQuery.getSharability(f) == SharabilityQuery.NOT_SHARABLE) {
210:                                return false;
211:                            } // else MIXED, UNKNOWN, or SHARABLE; or not a disk file
212:                        }
213:                        return true;
214:                    }
215:
216:                    public void addPropertyChangeListener(
217:                            PropertyChangeListener l) {
218:                        pcs.addPropertyChangeListener(l);
219:                    }
220:
221:                    public void removePropertyChangeListener(
222:                            PropertyChangeListener l) {
223:                        pcs.removePropertyChangeListener(l);
224:                    }
225:
226:                    @Override
227:                    public String toString() {
228:                        return "SourcesHelper.Group[name=" + getName()
229:                                + ",rootFolder=" + getRootFolder() + "]"; // NOI18N
230:                    }
231:
232:                    public void propertyChange(PropertyChangeEvent ev) {
233:                        assert ev.getSource() == evaluator : ev;
234:                        String prop = ev.getPropertyName();
235:                        if (prop == null
236:                                || (includes != null && includes.contains("${"
237:                                        + prop + "}")) || // NOI18N
238:                                (excludes != null && excludes.contains("${"
239:                                        + prop + "}"))) { // NOI18N
240:                            matcher = null;
241:                            pcs.firePropertyChange(PROP_CONTAINERSHIP, null,
242:                                    null);
243:                        }
244:                        // XXX should perhaps react to ProjectInformation changes? but nothing to fire currently
245:                    }
246:
247:                }
248:
249:                private String evalForMatcher(String raw) {
250:                    if (raw == null) {
251:                        return null;
252:                    }
253:                    String patterns = evaluator.evaluate(raw);
254:                    if (patterns == null) {
255:                        return null;
256:                    }
257:                    if (patterns.matches("\\$\\{[^}]+\\}")) { // NOI18N
258:                        // Unevaluated single property, treat like null.
259:                        return null;
260:                    }
261:                    return patterns;
262:                }
263:
264:                private void computeIncludeExcludePatterns() {
265:                    if (matcher != null) {
266:                        return;
267:                    }
268:                    String includesPattern = evalForMatcher(includes);
269:                    String excludesPattern = evalForMatcher(excludes);
270:                    matcher = new PathMatcher(includesPattern, excludesPattern,
271:                            getActualLocation());
272:                }
273:
274:                @Override
275:                public Collection<FileObject> getIncludeRoots() {
276:                    Collection<FileObject> supe = super .getIncludeRoots();
277:                    computeIncludeExcludePatterns();
278:                    if (supe.size() == 1) {
279:                        Set<FileObject> roots = new HashSet<FileObject>();
280:                        for (File r : matcher.findIncludedRoots()) {
281:                            FileObject subroot = FileUtil.toFileObject(r);
282:                            if (subroot != null) {
283:                                roots.add(subroot);
284:                            }
285:                        }
286:                        return roots;
287:                    } else {
288:                        assert supe.isEmpty();
289:                        return supe;
290:                    }
291:                }
292:
293:            }
294:
295:            private final class TypedSourceRoot extends SourceRoot {
296:                private final String type;
297:
298:                public TypedSourceRoot(String type, String location,
299:                        String includes, String excludes, String displayName,
300:                        Icon icon, Icon openedIcon) {
301:                    super (location, includes, excludes, displayName, icon,
302:                            openedIcon);
303:                    this .type = type;
304:                }
305:
306:                public final String getType() {
307:                    return type;
308:                }
309:            }
310:
311:            private final AntProjectHelper project;
312:            private final PropertyEvaluator evaluator;
313:            private final List<SourceRoot> principalSourceRoots = new ArrayList<SourceRoot>();
314:            private final List<Root> nonSourceRoots = new ArrayList<Root>();
315:            private final List<Root> ownedFiles = new ArrayList<Root>();
316:            private final List<TypedSourceRoot> typedSourceRoots = new ArrayList<TypedSourceRoot>();
317:            private int registeredRootAlgorithm;
318:            /**
319:             * If not null, external roots that we registered the last time.
320:             * Used when a property change is encountered, to see if the set of external
321:             * roots might have changed. Hold the actual files (not e.g. URLs); see
322:             * {@link #registerExternalRoots} for the reason why.
323:             */
324:            private Set<FileObject> lastRegisteredRoots;
325:            private PropertyChangeListener propChangeL;
326:
327:            /**
328:             * Create the helper object, initially configured to recognize only sources
329:             * contained inside the project directory.
330:             * @param project an Ant project helper
331:             * @param evaluator a way to evaluate Ant properties used to define source locations
332:             */
333:            public SourcesHelper(AntProjectHelper project,
334:                    PropertyEvaluator evaluator) {
335:                this .project = project;
336:                this .evaluator = evaluator;
337:            }
338:
339:            /**
340:             * Add a possible principal source root, or top-level folder which may
341:             * contain sources that should be considered part of the project.
342:             * <p>
343:             * If the actual value of the location is inside the project directory,
344:             * this is simply ignored; so it safe to configure principal source roots
345:             * for any source directory which might be set to use an external path, even
346:             * if the common location is internal.
347:             * </p>
348:             * @param location a project-relative or absolute path giving the location
349:             *                 of a source tree; may contain Ant property substitutions
350:             * @param displayName a display name (for {@link SourceGroup#getDisplayName})
351:             * @param icon a regular icon for the source root, or null
352:             * @param openedIcon an opened variant icon for the source root, or null
353:             * @throws IllegalStateException if this method is called after either
354:             *                               {@link #createSources} or {@link #registerExternalRoots}
355:             *                               was called
356:             * @see #registerExternalRoots
357:             * @see Sources#TYPE_GENERIC
358:             */
359:            public void addPrincipalSourceRoot(String location,
360:                    String displayName, Icon icon, Icon openedIcon)
361:                    throws IllegalStateException {
362:                addPrincipalSourceRoot(location, null, null, displayName, icon,
363:                        openedIcon);
364:            }
365:
366:            /**
367:             * Add a possible principal source root, or top-level folder which may
368:             * contain sources that should be considered part of the project, with
369:             * optional include and exclude lists.
370:             * <p>
371:             * If an include or exclude string is given as null, then it is skipped. A non-null value is
372:             * evaluated and then treated as a comma- or space-separated pattern list,
373:             * as detailed in the Javadoc for {@link PathMatcher}.
374:             * (As a special convenience, a value consisting solely of an Ant property reference
375:             * which cannot be evaluated, e.g. <samp>${undefined}</samp>, is treated like null.)
376:             * {@link SourceGroup#contains} will then reflect the includes and excludes for files, but note that the
377:             * semantics of that method requires that a folder be "contained" in case any folder or file
378:             * beneath it is contained, and in particular the root folder is always contained.
379:             * </p>
380:             * @param location a project-relative or absolute path giving the location
381:             *                 of a source tree; may contain Ant property substitutions
382:             * @param includes Ant-style includes; may contain Ant property substitutions;
383:             *                 if not null, only files and folders
384:             *                 matching the pattern (or patterns), and not specified in the excludes list,
385:             *                 will be {@link SourceGroup#contains included}
386:             * @param excludes Ant-style excludes; may contain Ant property substitutions;
387:             *                 if not null, files and folders
388:             *                 matching the pattern (or patterns) will not be {@link SourceGroup#contains included},
389:             *                 even if specified in the includes list
390:             * @param displayName a display name (for {@link SourceGroup#getDisplayName})
391:             * @param icon a regular icon for the source root, or null
392:             * @param openedIcon an opened variant icon for the source root, or null
393:             * @throws IllegalStateException if this method is called after either
394:             *                               {@link #createSources} or {@link #registerExternalRoots}
395:             *                               was called
396:             * @see #registerExternalRoots
397:             * @see Sources#TYPE_GENERIC
398:             * @since org.netbeans.modules.project.ant/1 1.15
399:             */
400:            public void addPrincipalSourceRoot(String location,
401:                    String includes, String excludes, String displayName,
402:                    Icon icon, Icon openedIcon) throws IllegalStateException {
403:                if (lastRegisteredRoots != null) {
404:                    throw new IllegalStateException(
405:                            "registerExternalRoots was already called"); // NOI18N
406:                }
407:                principalSourceRoots.add(new SourceRoot(location, includes,
408:                        excludes, displayName, icon, openedIcon));
409:            }
410:
411:            /**
412:             * Similar to {@link #addPrincipalSourceRoot} but affects only
413:             * {@link #registerExternalRoots} and not {@link #createSources}.
414:             * <p class="nonnormative">
415:             * Useful for project type providers which have external paths holding build
416:             * products. These should not appear in {@link Sources}, yet it may be useful
417:             * for {@link FileOwnerQuery} to know the owning project (for example, in order
418:             * for a project-specific <a href="@org-netbeans-api-java@/org/netbeans/spi/java/queries/SourceForBinaryQueryImplementation.html"><code>SourceForBinaryQueryImplementation</code></a> to work).
419:             * </p>
420:             * @param location a project-relative or absolute path giving the location
421:             *                 of a non-source tree; may contain Ant property substitutions
422:             * @throws IllegalStateException if this method is called after
423:             *                               {@link #registerExternalRoots} was called
424:             */
425:            public void addNonSourceRoot(String location)
426:                    throws IllegalStateException {
427:                if (lastRegisteredRoots != null) {
428:                    throw new IllegalStateException(
429:                            "registerExternalRoots was already called"); // NOI18N
430:                }
431:                nonSourceRoots.add(new Root(location));
432:            }
433:
434:            /**
435:             * Add any file that is supposed to be owned by a given project 
436:             * via FileOwnerQuery, affects only {@link #registerExternalRoots} 
437:             * and not {@link #createSources}.
438:             * <p class="nonnormative">
439:             * Useful for project type providers which have external paths holding build
440:             * products. These should not appear in {@link Sources}, yet it may be useful
441:             * for {@link FileOwnerQuery} to know the owning project (for example, in order
442:             * for a project-specific <a href="@org-netbeans-api-java@/org/netbeans/spi/java/queries/SourceForBinaryQueryImplementation.html"><code>SourceForBinaryQueryImplementation</code></a> to work).
443:             * </p>
444:             * @param location a project-relative or absolute path giving the location
445:             *                 of a file; may contain Ant property substitutions
446:             * @throws IllegalStateException if this method is called after
447:             *                               {@link #registerExternalRoots} was called
448:             * @since org.netbeans.modules.project.ant/1 1.17
449:             */
450:            public void addOwnedFile(String location)
451:                    throws IllegalStateException {
452:                if (lastRegisteredRoots != null) {
453:                    throw new IllegalStateException(
454:                            "registerExternalRoots was already called"); // NOI18N
455:                }
456:                ownedFiles.add(new Root(location));
457:            }
458:
459:            /**
460:             * Add a typed source root which will be considered only in certain contexts.
461:             * @param location a project-relative or absolute path giving the location
462:             *                 of a source tree; may contain Ant property substitutions
463:             * @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>
464:             * @param displayName a display name (for {@link SourceGroup#getDisplayName})
465:             * @param icon a regular icon for the source root, or null
466:             * @param openedIcon an opened variant icon for the source root, or null
467:             * @throws IllegalStateException if this method is called after either
468:             *                               {@link #createSources} or {@link #registerExternalRoots}
469:             *                               was called
470:             */
471:            public void addTypedSourceRoot(String location, String type,
472:                    String displayName, Icon icon, Icon openedIcon)
473:                    throws IllegalStateException {
474:                addTypedSourceRoot(location, null, null, type, displayName,
475:                        icon, openedIcon);
476:            }
477:
478:            /**
479:             * Add a typed source root with optional include and exclude lists.
480:             * See {@link #addPrincipalSourceRoot(String,String,String,String,Icon,Icon)}
481:             * for details on semantics of includes and excludes.
482:             * @param location a project-relative or absolute path giving the location
483:             *                 of a source tree; may contain Ant property substitutions
484:             * @param includes an optional list of Ant-style includes
485:             * @param excludes an optional list of Ant-style excludes
486:             * @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>
487:             * @param displayName a display name (for {@link SourceGroup#getDisplayName})
488:             * @param icon a regular icon for the source root, or null
489:             * @param openedIcon an opened variant icon for the source root, or null
490:             * @throws IllegalStateException if this method is called after either
491:             *                               {@link #createSources} or {@link #registerExternalRoots}
492:             *                               was called
493:             * @since org.netbeans.modules.project.ant/1 1.15
494:             */
495:            public void addTypedSourceRoot(String location, String includes,
496:                    String excludes, String type, String displayName,
497:                    Icon icon, Icon openedIcon) throws IllegalStateException {
498:                if (lastRegisteredRoots != null) {
499:                    throw new IllegalStateException(
500:                            "registerExternalRoots was already called"); // NOI18N
501:                }
502:                typedSourceRoots.add(new TypedSourceRoot(type, location,
503:                        includes, excludes, displayName, icon, openedIcon));
504:            }
505:
506:            private Project getProject() {
507:                return AntBasedProjectFactorySingleton.getProjectFor(project);
508:            }
509:
510:            /**
511:             * Register all external source or non-source roots using {@link FileOwnerQuery#markExternalOwner}.
512:             * <p>
513:             * Only roots added by {@link #addPrincipalSourceRoot} and {@link #addNonSourceRoot}
514:             * are considered. They are registered if (and only if) they in fact fall
515:             * outside of the project directory, and of course only if the folders really
516:             * exist on disk. Currently it is not defined when this file existence check
517:             * is done (e.g. when this method is first called, or periodically) or whether
518:             * folders which are created subsequently will be registered, so project type
519:             * providers are encouraged to create all desired external roots before calling
520:             * this method.
521:             * </p>
522:             * <p>
523:             * If the actual value of the location changes (due to changes being
524:             * fired from the property evaluator), roots which were previously internal
525:             * and are now external will be registered, and roots which were previously
526:             * external and are now internal will be unregistered. The (un-)registration
527:             * will be done using the same algorithm as was used initially.
528:             * </p>
529:             * <p>
530:             * If an explicit include list is configured for a principal source root, only those
531:             * subfolders which are included (or folders directly containing included files)
532:             * will be registered. Note that the source root, or an included subfolder, will
533:             * be registered even if it contains excluded files or folders beneath it.
534:             * </p>
535:             * <p>
536:             * Calling this method causes the helper object to hold strong references to the
537:             * current external roots, which helps a project satisfy the requirements of
538:             * {@link FileOwnerQuery#EXTERNAL_ALGORITHM_TRANSIENT}.
539:             * </p>
540:             * <p>
541:             * You may <em>not</em> call this method inside the project's constructor, as
542:             * it requires the actual project to exist and be registered in {@link ProjectManager}.
543:             * Typically you would use {@link org.openide.util.Mutex#postWriteRequest} to run it
544:             * later, if you were creating the helper in your constructor, since the project construction
545:             * normally occurs in read access.
546:             * </p>
547:             * @param algorithm an external root registration algorithm as per
548:             *                  {@link FileOwnerQuery#markExternalOwner}
549:             * @throws IllegalArgumentException if the algorithm is unrecognized
550:             * @throws IllegalStateException if this method is called more than once on a
551:             *                               given <code>SourcesHelper</code> object
552:             */
553:            public void registerExternalRoots(int algorithm)
554:                    throws IllegalArgumentException, IllegalStateException {
555:                if (lastRegisteredRoots != null) {
556:                    throw new IllegalStateException(
557:                            "registerExternalRoots was already called before"); // NOI18N
558:                }
559:                registeredRootAlgorithm = algorithm;
560:                remarkExternalRoots();
561:            }
562:
563:            private void remarkExternalRoots() throws IllegalArgumentException {
564:                List<Root> allRoots = new ArrayList<Root>(principalSourceRoots);
565:                allRoots.addAll(nonSourceRoots);
566:                allRoots.addAll(ownedFiles);
567:                Project p = getProject();
568:                FileObject pdir = project.getProjectDirectory();
569:                // First time: register roots and add to lastRegisteredRoots.
570:                // Subsequent times: add to newRootsToRegister and maybe add them later.
571:                if (lastRegisteredRoots == null) {
572:                    // First time.
573:                    lastRegisteredRoots = Collections.emptySet();
574:                    propChangeL = new PropChangeL(); // hold a strong ref
575:                    evaluator.addPropertyChangeListener(WeakListeners
576:                            .propertyChange(propChangeL, evaluator));
577:                }
578:                Set<FileObject> newRegisteredRoots = new HashSet<FileObject>();
579:                // XXX might be a bit more efficient to cache for each root the actualLocation value
580:                // that was last computed, and just check if that has changed... otherwise we wind
581:                // up calling APH.resolveFileObject repeatedly (for each property change)
582:                for (Root r : allRoots) {
583:                    for (FileObject loc : r.getIncludeRoots()) {
584:                        if (FileUtil.getRelativePath(pdir, loc) != null) {
585:                            // Inside projdir already. Skip it.
586:                            continue;
587:                        }
588:                        if (loc.isFolder()) {
589:                            try {
590:                                Project other = ProjectManager.getDefault()
591:                                        .findProject(loc);
592:                                if (other != null) {
593:                                    // This is a foreign project; we cannot own it. Skip it.
594:                                    continue;
595:                                }
596:                            } catch (IOException e) {
597:                                // Assume it is a foreign project and skip it.
598:                                continue;
599:                            }
600:                        }
601:                        // It's OK to go.
602:                        newRegisteredRoots.add(loc);
603:                    }
604:                }
605:                // Just check for changes since the last time.
606:                Set<FileObject> toUnregister = new HashSet<FileObject>(
607:                        lastRegisteredRoots);
608:                toUnregister.removeAll(newRegisteredRoots);
609:                for (FileObject loc : toUnregister) {
610:                    FileOwnerQuery.markExternalOwner(loc, null,
611:                            registeredRootAlgorithm);
612:                }
613:                Set<FileObject> toRegister = new HashSet<FileObject>(
614:                        newRegisteredRoots);
615:                toRegister.removeAll(lastRegisteredRoots);
616:                for (FileObject loc : toRegister) {
617:                    FileOwnerQuery.markExternalOwner(loc, p,
618:                            registeredRootAlgorithm);
619:                }
620:                lastRegisteredRoots = newRegisteredRoots;
621:            }
622:
623:            /**
624:             * Create a source list object.
625:             * <p>
626:             * All principal source roots are listed as {@link Sources#TYPE_GENERIC} unless they
627:             * are inside the project directory. The project directory itself is also listed
628:             * (with a display name according to {@link ProjectUtils#getInformation}), unless
629:             * it is contained by an explicit principal source root (i.e. ancestor directory).
630:             * Principal source roots should never overlap; if two configured
631:             * principal source roots are determined to have the same root folder, the first
632:             * configured root takes precedence (which only matters in regard to the display
633:             * name); if one root folder is contained within another, the broader
634:             * root folder subsumes the narrower one so only the broader root is listed.
635:             * </p>
636:             * <p>
637:             * Other source groups are listed according to the named typed source roots.
638:             * There is no check performed that these do not overlap (though a project type
639:             * provider should for UI reasons avoid this situation).
640:             * </p>
641:             * <p>
642:             * Any source roots which do not exist on disk are ignored, as if they had
643:             * not been configured at all. Currently it is not defined when this existence
644:             * check is performed (e.g. when this method is called, when the source root
645:             * is first accessed, periodically, etc.), so project type providers are
646:             * generally encouraged to make sure all desired source folders exist
647:             * before calling this method, if creating a new project.
648:             * </p>
649:             * <p>
650:             * Source groups are created according to the semantics described in
651:             * {@link org.netbeans.spi.project.support.GenericSources#group}. They are listed in the order they
652:             * were configured (for those roots that are actually used as groups).
653:             * </p>
654:             * <p>
655:             * You may call this method inside the project's constructor, but
656:             * {@link Sources#getSourceGroups} may <em>not</em> be called within the
657:             * constructor, as it requires the actual project object to exist and be
658:             * registered in {@link ProjectManager}.
659:             * </p>
660:             * @return a source list object suitable for {@link Project#getLookup}
661:             */
662:            public Sources createSources() {
663:                return new SourcesImpl();
664:            }
665:
666:            private final class SourcesImpl implements  Sources,
667:                    PropertyChangeListener, FileChangeSupportListener {
668:
669:                private final ChangeSupport cs = new ChangeSupport(this );
670:                private boolean haveAttachedListeners;
671:                private final Set<File> rootsListenedTo = new HashSet<File>();
672:                /**
673:                 * The root URLs which were computed last, keyed by group type.
674:                 */
675:                private final Map<String, List<URL>> lastComputedRoots = new HashMap<String, List<URL>>();
676:
677:                public SourcesImpl() {
678:                    evaluator.addPropertyChangeListener(WeakListeners
679:                            .propertyChange(this , evaluator));
680:                }
681:
682:                public SourceGroup[] getSourceGroups(String type) {
683:                    List<SourceGroup> groups = new ArrayList<SourceGroup>();
684:                    if (type.equals(Sources.TYPE_GENERIC)) {
685:                        List<SourceRoot> roots = new ArrayList<SourceRoot>(
686:                                principalSourceRoots);
687:                        // Always include the project directory itself as a default:
688:                        roots.add(new SourceRoot("", null, null, ProjectUtils
689:                                .getInformation(getProject()).getDisplayName(),
690:                                null, null)); // NOI18N
691:                        Map<FileObject, SourceRoot> rootsByDir = new LinkedHashMap<FileObject, SourceRoot>();
692:                        // First collect all non-redundant existing roots.
693:                        for (SourceRoot r : roots) {
694:                            File locF = r.getActualLocation();
695:                            if (locF == null) {
696:                                continue;
697:                            }
698:                            listen(locF);
699:                            FileObject loc = FileUtil.toFileObject(locF);
700:                            if (loc == null) {
701:                                continue;
702:                            }
703:                            if (rootsByDir.containsKey(loc)) {
704:                                continue;
705:                            }
706:                            rootsByDir.put(loc, r);
707:                        }
708:                        // Remove subroots.
709:                        Iterator<FileObject> it = rootsByDir.keySet()
710:                                .iterator();
711:                        while (it.hasNext()) {
712:                            FileObject loc = it.next();
713:                            FileObject parent = loc.getParent();
714:                            while (parent != null) {
715:                                if (rootsByDir.containsKey(parent)) {
716:                                    // This is a subroot of something, so skip it.
717:                                    it.remove();
718:                                    break;
719:                                }
720:                                parent = parent.getParent();
721:                            }
722:                        }
723:                        // Everything else is kosher.
724:                        for (Map.Entry<FileObject, SourceRoot> entry : rootsByDir
725:                                .entrySet()) {
726:                            groups
727:                                    .add(entry.getValue().toGroup(
728:                                            entry.getKey()));
729:                        }
730:                    } else {
731:                        Set<FileObject> dirs = new HashSet<FileObject>();
732:                        for (TypedSourceRoot r : typedSourceRoots) {
733:                            if (!r.getType().equals(type)) {
734:                                continue;
735:                            }
736:                            File locF = r.getActualLocation();
737:                            if (locF == null) {
738:                                continue;
739:                            }
740:                            listen(locF);
741:                            FileObject loc = FileUtil.toFileObject(locF);
742:                            if (loc == null) {
743:                                continue;
744:                            }
745:                            if (!dirs.add(loc)) {
746:                                // Already had one.
747:                                continue;
748:                            }
749:                            groups.add(r.toGroup(loc));
750:                        }
751:                    }
752:                    // Remember what we computed here so we know whether to fire changes later.
753:                    List<URL> rootURLs = new ArrayList<URL>(groups.size());
754:                    for (SourceGroup g : groups) {
755:                        try {
756:                            rootURLs.add(g.getRootFolder().getURL());
757:                        } catch (FileStateInvalidException e) {
758:                            assert false : e; // should be a valid file object!
759:                        }
760:                    }
761:                    lastComputedRoots.put(type, rootURLs);
762:                    return groups.toArray(new SourceGroup[groups.size()]);
763:                }
764:
765:                private synchronized void listen(File rootLocation) {
766:                    // #40845. Need to fire changes if a source root is added or removed.
767:                    if (rootsListenedTo.add(rootLocation)
768:                            && /* be lazy */haveAttachedListeners) {
769:                        FileChangeSupport.DEFAULT.addListener(this ,
770:                                rootLocation);
771:                    }
772:                }
773:
774:                public synchronized void addChangeListener(
775:                        ChangeListener listener) {
776:                    if (!haveAttachedListeners) {
777:                        haveAttachedListeners = true;
778:                        for (File rootLocation : rootsListenedTo) {
779:                            FileChangeSupport.DEFAULT.addListener(this ,
780:                                    rootLocation);
781:                        }
782:                    }
783:                    cs.addChangeListener(listener);
784:                }
785:
786:                public void removeChangeListener(ChangeListener listener) {
787:                    cs.removeChangeListener(listener);
788:                }
789:
790:                private void maybeFireChange() {
791:                    // #47451: check whether anything really changed.
792:                    boolean change = false;
793:                    // Cannot iterate over entrySet, as the map will be modified by getSourceGroups.
794:                    for (String type : new HashSet<String>(lastComputedRoots
795:                            .keySet())) {
796:                        List<URL> previous = new ArrayList<URL>(
797:                                lastComputedRoots.get(type));
798:                        getSourceGroups(type);
799:                        List<URL> nue = lastComputedRoots.get(type);
800:                        if (!nue.equals(previous)) {
801:                            change = true;
802:                            break;
803:                        }
804:                    }
805:                    if (change) {
806:                        cs.fireChange();
807:                    }
808:                }
809:
810:                public void fileCreated(FileChangeSupportEvent event) {
811:                    // Root might have been created on disk.
812:                    maybeFireChange();
813:                }
814:
815:                public void fileDeleted(FileChangeSupportEvent event) {
816:                    // Root might have been deleted.
817:                    maybeFireChange();
818:                }
819:
820:                public void fileModified(FileChangeSupportEvent event) {
821:                    // ignore; generally should not happen (listening to dirs)
822:                }
823:
824:                public void propertyChange(
825:                        PropertyChangeEvent propertyChangeEvent) {
826:                    // Properties may have changed so as cause external roots to move etc.
827:                    maybeFireChange();
828:                }
829:
830:            }
831:
832:            private final class PropChangeL implements  PropertyChangeListener {
833:
834:                public PropChangeL() {
835:                }
836:
837:                public void propertyChange(PropertyChangeEvent evt) {
838:                    // Some properties changed; external roots might have changed, so check them.
839:                    for (SourceRoot r : principalSourceRoots) {
840:                        r.matcher = null;
841:                    }
842:                    remarkExternalRoots();
843:                }
844:
845:            }
846:
847:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.