diff --git a/project.ant/src/org/netbeans/spi/project/support/ant/SourcesHelper.java b/project.ant/src/org/netbeans/spi/project/support/ant/SourcesHelper.java --- a/project.ant/src/org/netbeans/spi/project/support/ant/SourcesHelper.java +++ b/project.ant/src/org/netbeans/spi/project/support/ant/SourcesHelper.java @@ -57,6 +57,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.event.ChangeListener; import org.netbeans.api.project.FileOwnerQuery; @@ -64,9 +66,11 @@ import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; +import org.netbeans.api.project.SourceGroupModifier; import org.netbeans.api.project.Sources; import org.netbeans.api.queries.SharabilityQuery; import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton; +import org.netbeans.spi.project.SourceGroupModifierImplementation; import org.openide.filesystems.FileAttributeEvent; import org.openide.filesystems.FileChangeListener; import org.openide.filesystems.FileEvent; @@ -125,19 +129,31 @@ private final Icon openedIcon; private final String includes; private final String excludes; + private final String hint; + private boolean removed; // just for sanity checking - public SourceRoot(String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon) { + public SourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon) { super(location); this.displayName = displayName; this.icon = icon; this.openedIcon = openedIcon; this.includes = includes; this.excludes = excludes; + this.hint = hint; + removed = false; } public final SourceGroup toGroup(FileObject loc) { assert loc != null; return new Group(loc); + } + + public String getHint() { + return hint; + } + + public boolean isRemoved() { + return removed; } @Override @@ -291,8 +307,8 @@ private final class TypedSourceRoot extends SourceRoot { private final String type; - public TypedSourceRoot(String type, String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon) { - super(location, includes, excludes, displayName, icon, openedIcon); + public TypedSourceRoot(String type, String hint, String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon) { + super(location, includes, excludes, hint, displayName, icon, openedIcon); this.type = type; } public final String getType() { @@ -371,6 +387,9 @@ addPrincipalSourceRoot(location, null, null, displayName, icon, openedIcon); } + // no code hint for source root, group cannot be created from it via SourceGroupModifier + private static final String HINT_NONE = "none"; + /** * Add a possible principal source root, or top-level folder which may * contain sources that should be considered part of the project, with @@ -409,9 +428,88 @@ if (lastRegisteredRoots != null) { throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N } - principalSourceRoots.add(new SourceRoot(location, includes, excludes, displayName, icon, openedIcon)); + principalSourceRoots.add(new SourceRoot(location, includes, excludes, HINT_NONE, displayName, icon, openedIcon)); } - + + /** + * Add a possible principal source root, or top-level folder which may + * contain sources that should be considered part of the project. + *

+ * If the actual value of the location is inside the project directory, + * this is simply ignored; so it safe to configure principal source roots + * for any source directory which might be set to use an external path, even + * if the common location is internal. + *

+ * Location need not to exist physically, when hint is specified + * and {@link SourceGroupModifier} created by this helper is added to project + * lookup, source root can be created on demand. + *

+ * @param location a project-relative or absolute path giving the location + * of a source tree; may contain Ant property substitutions + * @param hint Hint for {@link SourceGroupModifier} allowing creation of this + * source root on demand + * @param displayName a display name (for {@link SourceGroup#getDisplayName}) + * @param icon a regular icon for the source root, or null + * @param openedIcon an opened variant icon for the source root, or null + * @throws IllegalStateException if this method is called after either + * {@link #createSources} or {@link #registerExternalRoots} + * was called + * @see #registerExternalRoots + * @see Sources#TYPE_GENERIC + * @see #createSourceGroupModifierImplementation() + */ + public void addPrincipalSourceRoot(String location, String hint, String displayName, Icon icon, Icon openedIcon) throws IllegalStateException { + addPrincipalSourceRoot(location, null, null, hint, displayName, icon, openedIcon); + } + + /** + * Add a possible principal source root, or top-level folder which may + * contain sources that should be considered part of the project, with + * optional include and exclude lists. + *

+ * If an include or exclude string is given as null, then it is skipped. A non-null value is + * evaluated and then treated as a comma- or space-separated pattern list, + * as detailed in the Javadoc for {@link PathMatcher}. + * (As a special convenience, a value consisting solely of an Ant property reference + * which cannot be evaluated, e.g. ${undefined}, is treated like null.) + * {@link SourceGroup#contains} will then reflect the includes and excludes for files, but note that the + * semantics of that method requires that a folder be "contained" in case any folder or file + * beneath it is contained, and in particular the root folder is always contained. + *

+ * Location need not to exist physically, when hint is specified + * and {@link SourceGroupModifier} created by this helper is added to project + * lookup, source root can be created on demand. + *

+ * @param location a project-relative or absolute path giving the location + * of a source tree; may contain Ant property substitutions + * @param includes Ant-style includes; may contain Ant property substitutions; + * if not null, only files and folders + * matching the pattern (or patterns), and not specified in the excludes list, + * will be {@link SourceGroup#contains included} + * @param excludes Ant-style excludes; may contain Ant property substitutions; + * if not null, files and folders + * matching the pattern (or patterns) will not be {@link SourceGroup#contains included}, + * even if specified in the includes list + * @param hint Hint for {@link SourceGroupModifier} allowing creation of this + * source root on demand + * @param displayName a display name (for {@link SourceGroup#getDisplayName}) + * @param icon a regular icon for the source root, or null + * @param openedIcon an opened variant icon for the source root, or null + * @throws IllegalStateException if this method is called after either + * {@link #createSources} or {@link #registerExternalRoots} + * was called + * @see #registerExternalRoots + * @see Sources#TYPE_GENERIC + * @see #createSourceGroupModifierImplementation() + * @since org.netbeans.modules.project.ant/1 1.15 + */ + public void addPrincipalSourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon) throws IllegalStateException { + if (lastRegisteredRoots != null) { + throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N + } + principalSourceRoots.add(new SourceRoot(location, includes, excludes, hint, displayName, icon, openedIcon)); + } + /** * Similar to {@link #addPrincipalSourceRoot} but affects only * {@link #registerExternalRoots} and not {@link #createSources}. @@ -493,7 +591,63 @@ if (lastRegisteredRoots != null) { throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N } - typedSourceRoots.add(new TypedSourceRoot(type, location, includes, excludes, displayName, icon, openedIcon)); + typedSourceRoots.add(new TypedSourceRoot(type, HINT_NONE, location, includes, excludes, displayName, icon, openedIcon)); + } + + /** + * Add a typed source root which will be considered only in certain contexts. + *

+ * Location need not to exist physically, when hint is specified + * and {@link SourceGroupModifier} created by this helper is added to project + * lookup, source root can be created on demand. + *

+ * @param location a project-relative or absolute path giving the location + * of a source tree; may contain Ant property substitutions + * @param type a source root type such as JavaProjectConstants.SOURCES_TYPE_JAVA + * @param hint Hint for {@link SourceGroupModifier} allowing creation of this + * source root on demand + * @param displayName a display name (for {@link SourceGroup#getDisplayName}) + * @param icon a regular icon for the source root, or null + * @param openedIcon an opened variant icon for the source root, or null + * @throws IllegalStateException if this method is called after either + * {@link #createSources} or {@link #registerExternalRoots} + * was called + * @see #createSourceGroupModifierImplementation() + */ + public void addTypedSourceRoot(String location, String type, String hint, String displayName, Icon icon, Icon openedIcon) throws IllegalStateException { + addTypedSourceRoot(location, null, null, type, hint, displayName, icon, openedIcon); + } + + /** + * Add a typed source root with optional include and exclude lists. + * See {@link #addPrincipalSourceRoot(String,String,String,String,String,Icon,Icon)} + * for details on semantics of includes and excludes. + *

+ * Location need not to exist physically, when hint is specified + * and {@link SourceGroupModifier} created by this helper is added to project + * lookup, source root can be created on demand. + *

+ * @param location a project-relative or absolute path giving the location + * of a source tree; may contain Ant property substitutions + * @param includes an optional list of Ant-style includes + * @param excludes an optional list of Ant-style excludes + * @param type a source root type such as JavaProjectConstants.SOURCES_TYPE_JAVA + * @param hint Hint for {@link SourceGroupModifier} allowing creation of this + * source root on demand + * @param displayName a display name (for {@link SourceGroup#getDisplayName}) + * @param icon a regular icon for the source root, or null + * @param openedIcon an opened variant icon for the source root, or null + * @throws IllegalStateException if this method is called after either + * {@link #createSources} or {@link #registerExternalRoots} + * was called + * @see #createSourceGroupModifierImplementation() + * @since org.netbeans.modules.project.ant/1 1.15 + */ + public void addTypedSourceRoot(String location, String includes, String excludes, String type, String hint, String displayName, Icon icon, Icon openedIcon) throws IllegalStateException { + if (lastRegisteredRoots != null) { + throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N + } + typedSourceRoots.add(new TypedSourceRoot(type, hint, location, includes, excludes, displayName, icon, openedIcon)); } private Project getProject() { @@ -725,7 +879,7 @@ if (type.equals(Sources.TYPE_GENERIC)) { List roots = new ArrayList(principalSourceRoots); // Always include the project directory itself as a default: - roots.add(new SourceRoot("", null, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null)); // NOI18N + roots.add(new SourceRoot("", null, null, HINT_NONE, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null)); // NOI18N Map rootsByDir = new LinkedHashMap(); // First collect all non-redundant existing roots. for (SourceRoot r : roots) { @@ -751,6 +905,7 @@ while (parent != null) { if (rootsByDir.containsKey(parent)) { // This is a subroot of something, so skip it. + rootsByDir.get(loc).removed = true; it.remove(); break; } @@ -866,7 +1021,82 @@ maybeFireChange(); } } - + + /** + * Creates new {@link SourceGroupModifierImplementation} that can be put into project lookup. + *

+ * Only source roots added with "hint-ed" variant of addPrincipalSourceRoot + * or AddTypedSourceRoot can be created with this SourceGroupModifierImplementation. + *

+ * @param sources Sources object to which creatable source roots has been added. Must be created + * by {@link #createSources()}. + * @return SourceGroupModifierImplementation implementation for given sources. + * @throws java.lang.IllegalArgumentException When passed sources were not created with {@link #createSources()}. + * @see #addPrincipalSourceRoot(java.lang.String, java.lang.String, java.lang.String, javax.swing.Icon, javax.swing.Icon) + * @see #addPrincipalSourceRoot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, javax.swing.Icon, javax.swing.Icon) + * @see #addTypedSourceRoot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, javax.swing.Icon, javax.swing.Icon) + * @see #addTypedSourceRoot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, javax.swing.Icon, javax.swing.Icon) + */ + public SourceGroupModifierImplementation createSourceGroupModifierImplementation(Sources sources) throws IllegalArgumentException { + if (!(sources instanceof SourcesImpl)) + throw new IllegalArgumentException("Sources object must be created by " + + "org.netbeans.spi.project.support.ant.SourcesHelper#createSources(), got " + + sources.getClass().getName()); + return new SourceGroupModifierImpl((SourcesImpl) sources); + } + + private class SourceGroupModifierImpl implements SourceGroupModifierImplementation { + private Logger logger = Logger.getLogger(SourcesHelper.class.getName()); + private SourcesImpl sources; + + private SourceGroupModifierImpl(SourcesImpl sources) { + this.sources = sources; + } + + public SourceGroup createSourceGroup(String type, String hint) { + SourceRoot root = findRoot(type, hint); + if (root == null) + return null; + if (root.isRemoved()) + return null; // getSourceGroups wouldn't return it, neither will we + + File loc = root.getActualLocation(); + if (! loc.exists()) + loc.mkdirs(); + FileObject foloc = FileUtil.toFileObject(loc); + if (foloc == null) { + logger.log(Level.WARNING, "Failed to create folder " + loc); + return null; + } + SourceGroup sg = root.toGroup(foloc); + assert sg != null; + sources.maybeFireChange(); + return sg; + } + + public boolean canCreateSourceGroup(String type, String hint) { + return findRoot(type, hint) != null; + } + private SourceRoot findRoot(String type, String hint) { + if (Sources.TYPE_GENERIC.equals(type)) { + for (SourceRoot root : principalSourceRoots) { + if (root.getHint().equals(hint) + && ! root.getHint().equals(HINT_NONE) + && ! root.isRemoved()) + return root; + } + } else { + for (TypedSourceRoot root : typedSourceRoots) { + if (root.getType().equals(type) + && root.getHint().equals(hint) + && ! root.getHint().equals(HINT_NONE)) + return root; + } + } + return null; + } + } + private final class PropChangeL implements PropertyChangeListener { public PropChangeL() {}