# HG changeset patch # Parent 4761a1f511a39758b48feb8e99c4ca8c7fa25dcc diff --git a/java.api.common/src/org/netbeans/modules/java/api/common/queries/SourcesImpl.java b/java.api.common/src/org/netbeans/modules/java/api/common/queries/SourcesImpl.java --- a/java.api.common/src/org/netbeans/modules/java/api/common/queries/SourcesImpl.java +++ b/java.api.common/src/org/netbeans/modules/java/api/common/queries/SourcesImpl.java @@ -73,6 +73,7 @@ import org.netbeans.modules.java.api.common.impl.RootsAccessor; import org.netbeans.modules.java.api.common.project.ProjectProperties; import org.netbeans.spi.project.SourceGroupModifierImplementation; +import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation; import org.netbeans.spi.project.support.GenericSources; import org.netbeans.spi.project.support.ant.SourcesHelper; import org.netbeans.spi.project.support.ant.AntProjectHelper; @@ -87,7 +88,7 @@ /** * Implementation of {@link Sources} interface. */ -final class SourcesImpl implements Sources, SourceGroupModifierImplementation, PropertyChangeListener, ChangeListener { +final class SourcesImpl implements Sources, SourceGroupModifierImplementation, SourceGroupRelativeModifierImplementation, PropertyChangeListener, ChangeListener { @StaticResource private static final String MODULE_ICON = "org/netbeans/modules/java/api/common/project/ui/resources/module.png"; //NOI18N @@ -183,6 +184,15 @@ } @Override + public SourceGroupModifierImplementation relativeTo(SourceGroup existingGroup, String... projectPart) { + if (sgmi instanceof SourceGroupRelativeModifierImplementation) { + return ((SourceGroupRelativeModifierImplementation)sgmi).relativeTo(existingGroup, projectPart); + } else { + return this; + } + } + + @Override public SourceGroup createSourceGroup(String type, String hint) { return sgmi.createSourceGroup(type, hint); } @@ -250,20 +260,23 @@ final String[] rootPathPropNames = RootsAccessor.getInstance().getRootPathProperties(roots); final String[] propNames = roots.getRootProperties(); final String[] displayNames = roots.getRootDisplayNames(); - + for (int i = 0; i < propNames.length; i++) { final String prop = propNames[i]; final String pathProp = rootPathPropNames[i]; final List locations; final List names; + final List parts; if (pathProp == null || JavaProjectConstants.SOURCES_TYPE_MODULES.equals(type)) { locations = Collections.singletonList("${" + prop + "}"); // NOI18N names = Collections.singletonList(displayNames[i]); + parts = Collections.singletonList(null); } else { locations = new ArrayList<>(); names = new ArrayList<>(); + parts = new ArrayList<>(); final String pathToModules = evaluator.getProperty(prop); final File file = helper.resolveFile(pathToModules); if (file.isDirectory()) { @@ -279,7 +292,16 @@ pathToModules, f.getName(), variant); + String[] v = variant.split("/"); + String[] p = new String[v.length + 2]; + // the most important is the module name + p[0] = f.getName(); + // 2nd project part is the [resolved] path to modules; usually src.dir and test.dir point to the same location, + // but if they do not, the 'same location' gets a preference. + p[1] = pathToModules; + System.arraycopy(v, 0, p, 2, v.length); locations.add(resolvedSrcPath); //Todo: Should be unevaluated + parts.add(p); String dispName = displayNames[i]; if (dispName == null || dispName.isEmpty()) { names.add(Bundle.FMT_ModularSourceRootNoName(f.getName(), pathToModules)); @@ -292,7 +314,8 @@ } } assert locations.size() == names.size(); - + assert locations.size() == parts.size(); + Iterator partIt = parts.iterator(); for (Iterator locationIt = locations.iterator(), nameIt = names.iterator(); locationIt.hasNext() && nameIt.hasNext();) { final SourcesHelper.SourceRootConfig cfg = sourcesHelper.sourceRoot(locationIt.next()); cfg.displayName(nameIt.next()); @@ -305,6 +328,7 @@ if (hint != null) { cfg.hint(hint); } + cfg.inParts(partIt.next()); cfg.add(); // principal root if (type != null) { cfg.type(type).add(); // typed root diff --git a/project.ant/apichanges.xml b/project.ant/apichanges.xml --- a/project.ant/apichanges.xml +++ b/project.ant/apichanges.xml @@ -107,6 +107,20 @@ + + + Support for source root grouping + + + + + +

+ A SourceRootConfig can be identifier by a structured "identifier" so when a new SourceGroup should be created as an + associcate to existing sources, it is created in appropriate "sibling" location. See SourceGroupModifier for more details. +

+
+
Desktop dependent Ant Based Project Support UI extracted diff --git a/project.ant/manifest.mf b/project.ant/manifest.mf --- a/project.ant/manifest.mf +++ b/project.ant/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.project.ant/1 -OpenIDE-Module-Specification-Version: 1.67 +OpenIDE-Module-Specification-Version: 1.68 OpenIDE-Module-Layer: org/netbeans/modules/project/ant/resources/mf-layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties diff --git a/project.ant/nbproject/project.xml b/project.ant/nbproject/project.xml --- a/project.ant/nbproject/project.xml +++ b/project.ant/nbproject/project.xml @@ -81,7 +81,7 @@ 1 - 1.40 + 1.68 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 @@ -60,6 +60,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; @@ -74,11 +75,7 @@ import org.netbeans.api.queries.SharabilityQuery; import org.netbeans.modules.project.ant.AntBasedProjectFactorySingleton; import org.netbeans.spi.project.SourceGroupModifierImplementation; -import org.netbeans.spi.project.support.ant.AntProjectHelper; -import org.netbeans.spi.project.support.ant.PathMatcher; -import org.netbeans.spi.project.support.ant.PathMatcher; -import org.netbeans.spi.project.support.ant.PropertyEvaluator; -import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation; import org.netbeans.spi.project.ui.support.ProjectConvertors; import org.openide.filesystems.FileAttributeEvent; import org.openide.filesystems.FileChangeListener; @@ -142,8 +139,9 @@ private final String excludes; private final String hint; private boolean removed; // just for sanity checking - - public SourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon) { + private final String[] projectParts; + + public SourceRoot(String location, String includes, String excludes, String hint, String displayName, Icon icon, Icon openedIcon, String[] parts) { super(location); this.displayName = displayName; this.icon = icon; @@ -151,6 +149,7 @@ this.includes = includes; this.excludes = excludes; this.hint = hint; + this.projectParts = parts; removed = false; } @@ -322,8 +321,8 @@ private final class TypedSourceRoot extends SourceRoot { private final String type; - 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); + public TypedSourceRoot(String type, String hint, String location, String includes, String excludes, String displayName, Icon icon, Icon openedIcon, String[] parts) { + super(location, includes, excludes, hint, displayName, icon, openedIcon, parts); this.type = type; } public final String getType() { @@ -404,6 +403,7 @@ private String excludes; private String type; private String hint; + private String[] parts; private SourceRootConfig(String location) { this.location = location; @@ -505,6 +505,19 @@ openedIcon = value; return this; } + + /** + * Declares that the source root resides in some (hierarchical) project part. + * The project can be partitioned on multiple levels, each source root may represent some + * part of the project. Partitioning can be used to identify "sibling" roots + * @param parts abstract location of this root + * @return {@code this} + * @since 1.68 + */ + public SourceRootConfig inParts(String... parts) { + this.parts = parts; + return this; + } /** * Adds configured source root to SourcesHelper. @@ -519,9 +532,9 @@ throw new IllegalStateException("registerExternalRoots was already called"); // NOI18N } if (type != null) { - typedSourceRoots.add(new TypedSourceRoot(type, hint, location, includes, excludes, displayName, icon, openedIcon)); + typedSourceRoots.add(new TypedSourceRoot(type, hint, location, includes, excludes, displayName, icon, openedIcon, parts)); } else { - principalSourceRoots.add(new SourceRoot(location, includes, excludes, hint, displayName, icon, openedIcon)); + principalSourceRoots.add(new SourceRoot(location, includes, excludes, hint, displayName, icon, openedIcon, parts)); } return this; } @@ -951,7 +964,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, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null)); + roots.add(new SourceRoot("", null, null, null, ProjectUtils.getInformation(getProject()).getDisplayName(), null, null, null)); Map rootsByDir = new LinkedHashMap(); // First collect all non-redundant existing roots. for (SourceRoot r : roots) { @@ -1120,11 +1133,56 @@ public SourceGroupModifierImplementation createSourceGroupModifierImplementation() { return new SourceGroupModifierImpl(); } + + private static final class Key implements Function { + private SourceRoot root; + private String[] parts; + + public Key(SourceRoot root, String[] parts) { + this.root = root; + this.parts = parts; + } + + public Integer apply(SourceRoot r) { + int result = 0; + int start = 0; + if (this.root != null) { + if (root.projectParts != null && r.projectParts != null) { + for (int i = 0; i < root.projectParts.length && i < r.projectParts.length; i++) { + if (root.projectParts[i].equals(r.projectParts[i])) { + start++; + } else { + break; + } + } + } + } + if (parts != null && r.projectParts != null) { + for (int i = start; i < parts.length && i < r.projectParts.length; i++) { + if (parts[i].equals(r.projectParts[i])) { + result++; + } else { + break; + } + } + } + return result + start; + } + } + + private class SourceGroupModifierImpl implements SourceGroupModifierImplementation, SourceGroupRelativeModifierImplementation { + private final Function similarity; - private class SourceGroupModifierImpl implements SourceGroupModifierImplementation { - + public SourceGroupModifierImpl() { + this(null); + } + + public SourceGroupModifierImpl(Function similarity) { + this.similarity = similarity; + } + public SourceGroup createSourceGroup(String type, String hint) { - SourceRoot root = findRoot(type, hint); + SourceRoot root = findRoot(type, hint, similarity); if (root == null) return null; if (root.isRemoved()) @@ -1151,25 +1209,63 @@ } public boolean canCreateSourceGroup(String type, String hint) { - return findRoot(type, hint) != null; + return findRoot(type, hint, similarity) != null; } - private SourceRoot findRoot(String type, String hint) { + + private SourceRoot findRoot(String type, String hint, Function similarity) { + int maxSimilarity = -1; + SourceRoot candidate = null; + if (Sources.TYPE_GENERIC.equals(type)) { for (SourceRoot root : principalSourceRoots) { if (root.getHint() != null && root.getHint().equals(hint) - && ! root.isRemoved()) - return root; + && ! root.isRemoved()) { + if (similarity == null) { + return root; + } else { + int sim = similarity.apply(root); + if (sim > maxSimilarity) { + candidate = root; + maxSimilarity = sim; + } + } + } } } else { for (TypedSourceRoot root : typedSourceRoots) { if (root.getHint() != null && root.getType().equals(type) - && root.getHint().equals(hint)) - return root; + && root.getHint().equals(hint)) { + if (similarity == null) { + return root; + } else { + int sim = similarity.apply(root); + if (sim > maxSimilarity) { + candidate = root; + maxSimilarity = sim; + } + } + } } } - return null; + return candidate; + } + + public SourceGroupModifierImplementation relativeTo(SourceGroup existingGroup, String... projectPart) { + SourceRoot origin = null; + if (existingGroup != null) { + FileObject fo = existingGroup.getRootFolder(); + File f = FileUtil.toFile(fo); + for (SourceRoot r : principalSourceRoots) { + File loc = r.getActualLocation(); + if (loc != null && loc.equals(f)) { + origin = r; + break; + } + } + } + return new SourceGroupModifierImpl(new Key(origin, projectPart)); } } diff --git a/projectapi/apichanges.xml b/projectapi/apichanges.xml --- a/projectapi/apichanges.xml +++ b/projectapi/apichanges.xml @@ -107,6 +107,22 @@ + + + Added a SourceGroupRelativeModifierImplementation to improve source root creation + + + + + +

+ In presence of multiple source roots, e.g. several source folders, or test folders, some of them may be more + related to the +

+
+ + +
Added a LookupMerger for SharabilityQueryImplementation2 diff --git a/projectapi/manifest.mf b/projectapi/manifest.mf --- a/projectapi/manifest.mf +++ b/projectapi/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.projectapi/1 -OpenIDE-Module-Specification-Version: 1.67 +OpenIDE-Module-Specification-Version: 1.68 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml OpenIDE-Module-Recommends: cnb.org.netbeans.modules.projectapi.nb diff --git a/projectapi/nbproject/project.properties b/projectapi/nbproject/project.properties --- a/projectapi/nbproject/project.properties +++ b/projectapi/nbproject/project.properties @@ -43,7 +43,7 @@ is.autoload=true javac.compilerargs=-Xlint -Xlint:-serial -javac.source=1.7 +javac.source=1.8 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml diff --git a/projectapi/src/org/netbeans/api/project/SourceGroupModifier.java b/projectapi/src/org/netbeans/api/project/SourceGroupModifier.java --- a/projectapi/src/org/netbeans/api/project/SourceGroupModifier.java +++ b/projectapi/src/org/netbeans/api/project/SourceGroupModifier.java @@ -43,6 +43,7 @@ package org.netbeans.api.project; import org.netbeans.spi.project.SourceGroupModifierImplementation; +import org.netbeans.spi.project.SourceGroupRelativeModifierImplementation; /** * SourceGroupModifier provides ways of create specific folders ({@link org.netbeans.api.project.SourceGroup} root folders) @@ -76,6 +77,46 @@ } return impl.createSourceGroup(type, hint); } + + /** + * Creates a source group associated to an existing one. In a project with multiple locations for sources or tests some of those locations + * can be more appropriate (or completely unrelated) to already existing specific sources. This variant of {@link #createSourceGroup(org.netbeans.api.project.Project, java.lang.String, java.lang.String)} + * allows to select appropriate locations, if the newly created {@code SourceGroup} should work in association with some existing one. + *

+ * The source group will be created on location most similar to the provided {@code original} group. If {@code projectParts} are specified, the most matching + * location will be selected. + *

+ * This feature is prototypically used in J2SE modular projects, where multiple locations exists for tests and sources, yet they are related by their owning module. Other + * project types may also partition project sources into logical groups, similar to modules. + *

+ * Some (java) examples: + *

    + *
  • to create a source folder in project module, use relativeTo(modulesGroup, "moduleName").createSourceGroup(..) + *
  • to create a specific module root in project module, use relativeTo(modulesGroup, "moduleName", "path-to-modules").createSourceGroup(...) + *
  • to create a test folder for a specific source location, use relativeTo(sourceLocation).createSourceGroup(...) + *
  • or, if there are more test locations to choose, you can use relativeTo(sourceLocation, "test2").createSourceGroup(...). + *
+ * @param project the project + * @param original the original SourceGroup, which the new one should be related to. + * @param type type of sources + * @param hint additional type hint + * @param projectParts optional; abstract location within the project. + * @return the creaed SourceGroup or {@code null} + * @since 1.68 + */ + public static final SourceGroup createAssociatedSourceGroup(Project project, SourceGroup original, String type, String hint, String... projectParts) { + SourceGroupRelativeModifierImplementation relMod = project.getLookup().lookup(SourceGroupRelativeModifierImplementation.class); + if (relMod == null) { + return createSourceGroup(project, type, hint); + } else { + SourceGroupModifierImplementation impl = relMod.relativeTo(original, projectParts); + if (impl == null) { + return createSourceGroup(project, type, hint); + } else { + return impl.createSourceGroup(type, hint); + } + } + } /** * Creates a {@link org.netbeans.api.project.SourceGroupModifier.Future} object diff --git a/projectapi/src/org/netbeans/spi/project/SourceGroupRelativeModifierImplementation.java b/projectapi/src/org/netbeans/spi/project/SourceGroupRelativeModifierImplementation.java new file mode 100644 --- /dev/null +++ b/projectapi/src/org/netbeans/spi/project/SourceGroupRelativeModifierImplementation.java @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + */ +package org.netbeans.spi.project; + +import org.netbeans.api.project.SourceGroup; + +/** + * Intermediate level for more structured projects, where the simple + * type-based information are not sufficient to create an appropriate folder + * structure. + *

+ * Prototypically used in J2SE Modular projects, where tests or sources belong + * to different modules, and it is critical to create the folder in the "correct" + * one. + *

+ * The project can be partitioned to several (hiearchical) parts. SourceGroups for + * certain types/hints can be created in some of those parts (see {@link SourceGroupModifierImplementation#canCreateSourceGroup}. + * For example, java modular projects contains modules, a module may contain several places where sources are expected - these + * form the part hierarchy. When the original SourceGroup is specific enough, the hierarchy argument may be + * missing or can be even ignored by the modifier implementation - provided that the newly created folders have the correct + * relationship to the original source group. + *

+ * Similar structure may be used in other types of projects. {@code projectParts} are abstract uninterpreted identifiers, so + * the implementation / project may choose any semantics suitable for the project type. + * @author sdedic + * @since 1.68 + */ +public interface SourceGroupRelativeModifierImplementation { + /** + * Returns Modifier, which is bound to a specific location or conceptual part of the project. + * @param existingGroup existing location or concept within the project + * @param projectPart identifies part of the project. The meaning depends on the "existingGroup" + * @return modifier able to create folders, or {@code null}, if the specified project part does not exist + */ + public SourceGroupModifierImplementation relativeTo(SourceGroup existingGroup, String... projectPart); +}