This is an automated email from the ASF dual-hosted git repository. sdedic pushed a commit to branch sdedic/feature/project-dependency-add_base2 in repository https://gitbox.apache.org/repos/asf/netbeans.git
commit 1b8c5c656bd49ae07bb2441fe1c45e48e1cf47c9 Author: Svata Dedic <[email protected]> AuthorDate: Wed Dec 13 17:08:51 2023 +0100 Scope redefinition, private API break. --- .../modules/gradle/api/GradleConfiguration.java | 11 +- .../nbproject/project.properties | 2 +- .../project/dependency/DependencyResult.java | 10 +- .../modules/project/dependency/ProjectScopes.java | 49 ++++++ .../netbeans/modules/project/dependency/Scope.java | 49 +++--- .../modules/project/dependency/Scopes.java | 88 ++++------ java/gradle.java/nbproject/project.xml | 7 + .../gradle/java/queries/DependencyText.java | 63 ++++++- .../queries/GradleDependenciesImplementation.java | 191 +++++++++++++++++---- .../java/queries/GradleDependencyResult.java | 59 ++++--- .../modules/gradle/java/queries/GradleScope.java | 81 +++++++++ .../modules/gradle/java/queries/GradleScopes.java | 66 +++++++ .../gradle/java/queries/GradleScopesBuilder.java | 129 ++++++++++++++ .../gradle/java/queries/TextDependencyScanner.java | 50 ++++-- .../unit/data/dependencies/micronaut/build.gradle | 10 +- .../data/dependencies/parse/variousSyntax.gradle | 12 +- .../GradleDependenciesImplementationTest.java | 123 ++++++++++++- .../java/queries/RegexpGradleScannerTest.java | 77 ++++++++- .../queries/MavenDependenciesImplementation.java | 134 +++++++++++---- .../maven/queries/MavenDependencyResult.java | 19 +- 20 files changed, 1011 insertions(+), 219 deletions(-) diff --git a/extide/gradle/src/org/netbeans/modules/gradle/api/GradleConfiguration.java b/extide/gradle/src/org/netbeans/modules/gradle/api/GradleConfiguration.java index 3c21520b72..6b795428c1 100644 --- a/extide/gradle/src/org/netbeans/modules/gradle/api/GradleConfiguration.java +++ b/extide/gradle/src/org/netbeans/modules/gradle/api/GradleConfiguration.java @@ -85,7 +85,11 @@ public final class GradleConfiguration implements Serializable, ModuleSearchSupp * @return direct dependencies */ public Collection<? extends GradleDependency> getConfiguredDependencies() { - return directChildren; + if (canBeResolved) { + return directChildren; + } else { + return unresolved; + } } /** @@ -100,12 +104,13 @@ public final class GradleConfiguration implements Serializable, ModuleSearchSupp * @return configuration of origin or {@code null}. */ public GradleConfiguration getDependencyOrigin(GradleDependency d) { - if (!getDependencies().contains(d)) { + if (!getDependencies().contains(d) && !getConfiguredDependencies().contains(d)) { return null; } // TODO: possibly create a dependency-to-config cache in this instance to speed up further queries Set<GradleConfiguration> done = new HashSet<>(); - Queue<GradleConfiguration> toProcess = new ArrayDeque<>(getExtendsFrom()); + Queue<GradleConfiguration> toProcess = new ArrayDeque<>(); + toProcess.add(this); GradleConfiguration conf; while ((conf = toProcess.poll()) != null) { diff --git a/ide/project.dependency/nbproject/project.properties b/ide/project.dependency/nbproject/project.properties index 209dfe92e4..264c46ce0c 100644 --- a/ide/project.dependency/nbproject/project.properties +++ b/ide/project.dependency/nbproject/project.properties @@ -18,4 +18,4 @@ is.autoload=true javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial -spec.version.base=1.6.0 +spec.version.base=1.7.0 diff --git a/ide/project.dependency/src/org/netbeans/modules/project/dependency/DependencyResult.java b/ide/project.dependency/src/org/netbeans/modules/project/dependency/DependencyResult.java index e59c573805..7181e6b627 100644 --- a/ide/project.dependency/src/org/netbeans/modules/project/dependency/DependencyResult.java +++ b/ide/project.dependency/src/org/netbeans/modules/project/dependency/DependencyResult.java @@ -38,6 +38,8 @@ import org.openide.util.Lookup; * <p> * The {@link #getLookup() lookup} can be used to search for project-specific services that * can provide further info on the artifacts or dependencies. + * + * PENDING: move to SPI, make API delegating wrapper. * @author sdedic */ public interface DependencyResult extends Lookup.Provider { @@ -119,7 +121,7 @@ public interface DependencyResult extends Lookup.Provider { /** * A special part that locates a location appropriate for the surrounding * container. For example {@code dependencies} element in Maven or {@code dependencies} - * block in a gradle script. Use project root as the dependency + * block in a gradle script. Use project root or {@code null} as the dependency */ public static final String PART_CONTAINER = "container"; // NOI18N @@ -131,4 +133,10 @@ public interface DependencyResult extends Lookup.Provider { * source location can not be determined. */ public @CheckForNull SourceLocation getDeclarationRange(@NonNull Dependency d, String part) throws IOException; + + /** + * Returns description of project scopes. + * @return project scopes. + */ + public ProjectScopes getScopes(); } diff --git a/ide/project.dependency/src/org/netbeans/modules/project/dependency/ProjectScopes.java b/ide/project.dependency/src/org/netbeans/modules/project/dependency/ProjectScopes.java new file mode 100644 index 0000000000..8823de4ce6 --- /dev/null +++ b/ide/project.dependency/src/org/netbeans/modules/project/dependency/ProjectScopes.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.project.dependency; + +import java.util.Collection; + +/** + * Describes scopes supported by the project. + * PENDING: move to SPI; make an API final delegating counterpart / wrapper. + * @author sdedic + * @since 1.7 + */ +public interface ProjectScopes { + /** + * Returns the set of supported scopes. The returned set should include + * those abstract scopes supported by the project. Note that if additional + * plugins are added to the build system, the set of scopes may change. + * + * @return set of supported scopes. + */ + public Collection<? extends Scope> scopes(); + + /** + * Returns the scopes that this one implies. Note that the the {@code implies} + * relation need not to be transitive (i.e. some scopes may be filtered from + * further inheritance). + * + * @param s the scope + * @param s direct if true, just direct implications are returned. + * @return + */ + public Collection<? extends Scope> implies(Scope s, boolean direct); +} diff --git a/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scope.java b/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scope.java index 85e9161c0d..bdefe6aaa2 100644 --- a/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scope.java +++ b/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scope.java @@ -28,42 +28,17 @@ import java.util.Objects; * Scopes are identified by its {@link #name name}; two scopes with the same name are equal. * Project implementations may provide their own scopes with standard names since they * may use different include/imply hierarchy. - * + * <p/> + * Scope instances created by the build system * @author sdedic */ -public abstract class Scope { +public class Scope { private final String name; protected Scope(String name) { this.name = name; } - /** - * Checks if this scope includes the other one. If yes, then queries that executed for this scope - * should return all results from the included scope. - * - * @param s scope to test - * @return true, if data for scope "s" are included by this scope; false otherwise (i.e. unrelated scopes) - */ - public abstract boolean includes(Scope s); - - /** - * Checks if this scope exports the other scope. A scope may {@link #includes include} other - * scope, but can choose not to propagate its contents further. - * @param s the scope to test - * @return true, if data for scope "s" are exported by this scope; false otherwise (i.e. unrelated scopes) - */ - public abstract boolean exports(Scope s); - - /** - * Determines if artifacts in this scope apply to the other one. This is the reverse of {@link includes} and - * allows injection to existing scopes. - * - * @param s the scope to test. - * @return true, if the scope 's' is implied (includes) this one. - */ - public abstract boolean implies(Scope s); - /** * @return name / identifier for the scope. Not subject to L10N. */ @@ -92,4 +67,22 @@ public abstract class Scope { final Scope other = (Scope) obj; return Objects.equals(this.name, other.name); } + + // this behaviour is used in tests, change (in subclasses) carefully. + @Override + public String toString() { + return name(); + } + + /** + * Creates a named scope. Callers should strongly prefer either abstract scopes + * declared in {@link Scopes}, or get supported scopes from the project / build system. + * Instances created by this method can only serve as handles / identifiers. + * + * @param id scope Id + * @return scope + */ + public static Scope named(String id) { + return new Scope(id); + } } diff --git a/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scopes.java b/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scopes.java index 0ca68010f3..cb8f79accd 100644 --- a/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scopes.java +++ b/ide/project.dependency/src/org/netbeans/modules/project/dependency/Scopes.java @@ -18,11 +18,6 @@ */ package org.netbeans.modules.project.dependency; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - /** * * @author sdedic @@ -31,82 +26,57 @@ public final class Scopes { /** * Build process dependencies. Annotation processors, buildtime tools, code generators */ - public static final Scope PROCESS = new DefaultScope("compileProcessing", Collections.emptySet(), Collections.emptySet()); + public static final Scope PROCESS = new Scope("compileProcessing"); /** * External dependencies, not distributed with the application, but provided by the environment (= provided dependencies in Maven) */ - public static final Scope EXTERNAL = new DefaultScope("external", Collections.emptySet(), Collections.emptySet()); + public static final Scope EXTERNAL = new Scope("external"); /** - * Compile dependencies. Resources used by build tools to build the application. Includes - * {@link #PROCESS} but does not export it further. + * Compile API dependencies. Optional, if the build system supports it. Otherwise should be equal to + * {@link #COMPILE}. Gradle makes a difference between API and implementation. */ - public static final Scope COMPILE = new DefaultScope("compilation", - Collections.singleton(PROCESS), Collections.singleton(PROCESS)); + public static final Scope API = new Scope("api"); /** - * Runtime dependencies. Includes compile dependencies. + * Compile dependencies. Resources used by build tools to build the application. */ - public static final Scope RUNTIME = new DefaultScope("runtime", Collections.singleton(COMPILE), Collections.emptySet()); + public static final Scope COMPILE = new Scope("compilation"); /** - * Test compile dependencies. + * Runtime dependencies. Includes compile dependencies, but not necessarily all of them. */ - public static final Scope TEST_COMPILE = new DefaultScope("testCompile", - new HashSet<>(Arrays.asList(PROCESS, COMPILE)), Collections.emptySet()); + public static final Scope RUNTIME = new Scope("runtime"); /** - * Test compile dependencies. + * Test compile dependencies. Optional, if the build system supports it. */ - public static final Scope TEST_RUNTIME = new DefaultScope("testRuntime", - new HashSet<>(Arrays.asList(TEST_COMPILE)), Collections.emptySet()); + public static final Scope TEST_COMPILE = new Scope("testCompile"); /** - * Test dependencies. + * Test runtime dependencies. Optional, if the build system supports it. */ - public static final Scope TEST = new DefaultScope("test", - new HashSet<>(Arrays.asList(PROCESS, COMPILE, RUNTIME)), Collections.emptySet()).imply(TEST_RUNTIME, TEST_COMPILE); + public static final Scope TEST_RUNTIME = new Scope("testRuntime"); /** - * Included resources. - public static final Scope INCLUDED = new DefaultScope("included", Collections.emptySet(), Collections.emptySet()); + * Generic test dependencies. */ + public static final Scope TEST = new Scope("test"); - static final class DefaultScope extends Scope { - private final Set<Scope> includes; - private final Set<Scope> stops; - private Set<Scope> implies; - - public DefaultScope(String name, Set<Scope> includes, Set<Scope> stops) { - super(name); - this.includes = includes; - this.stops = stops; - } - - @Override - public boolean includes(Scope s) { - return s == this || includes.contains(s); - } - - @Override - public boolean exports(Scope s) { - return s == this || (!stops.contains(s) && includes(s)); - } - - @Override - public String toString() { - return name(); - } + /** + * Dependencies directly declared by the project definition. Can be combined with other types to select just specific + * dependencies. Note that dependencies obtained using this modifier may be incomplete or version-unresolved, if they appear so + * in the build file. + * <p> + * Note that it is not possible to add dependencies with this scope (exception will be thrown), it only serves as marker. Also no dependency + * will not be marked with this scope, all dependencies retain the scope they are declared for in the project's metadata. + */ + public static final Scope DECLARED = new Scope("*declared"); - @Override - public boolean implies(Scope s) { - return implies != null && implies.contains(s); - } - - public DefaultScope imply(Scope... scopes) { - this.implies = new HashSet<>(Arrays.asList(scopes)); - return this; - } - } + /** + * Represents a plugin dependency. Plugin dependency extends applies a plugin to a build. PLUGIN dependency artifacts + * specify names and versions of those plugins. + */ + public static final Scope PLUGIN = new org.netbeans.modules.project.dependency.Scope("*plugin"); } diff --git a/java/gradle.java/nbproject/project.xml b/java/gradle.java/nbproject/project.xml index e5f1c82670..80c664174f 100644 --- a/java/gradle.java/nbproject/project.xml +++ b/java/gradle.java/nbproject/project.xml @@ -376,6 +376,13 @@ <code-name-base>org.netbeans.modules.project.dependency</code-name-base> <compile-dependency/> </test-dependency> + <test-dependency> + <!-- Because otherwise data systems and their services will not materialize --> + <code-name-base>org.netbeans.modules.settings</code-name-base> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base> + </test-dependency> </test-type> </test-dependencies> <public-packages> diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/DependencyText.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/DependencyText.java index 0f645397ba..6c18d5c208 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/DependencyText.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/DependencyText.java @@ -28,7 +28,7 @@ import org.netbeans.modules.project.dependency.DependencyResult; /** * Single dependency information. */ -class DependencyText { +public class DependencyText { /** * True if the dependency is a single statement, false if declared in @@ -73,6 +73,10 @@ class DependencyText { String group; String name; String version; + + Style style; + + Container container; public DependencyText(String container, int startPos) { this.configuration = container; @@ -118,11 +122,42 @@ class DependencyText { return sb.toString(); } } + + public static enum Style { + /** + * Specified as attribtute: value list + */ + MAP_LITERAL, + + /** + * Followed by {} customization block + */ + CUSTOMIZED, + + /** + * Specified as single string, + */ + GAV_STRING, + + /** + * Item in a multi-valued configuration container + */ + CONTAINER_ITEM, + /** + * Surrounded by brackets + */ + BRACKETS, + + /** + * Surrounded by parenthesis + */ + PARENTHESIS + } /** * Dependency part information */ - static final class Part { + public static final class Part { /** * Id of the part @@ -156,9 +191,22 @@ class DependencyText { } } + public final static class Container { + final List<DependencyText> items; + final DependencyText.Part containerPart; + + public Container(List<DependencyText> items, Part containerPart) { + this.items = items; + this.containerPart = containerPart; + } + } + - final static class Mapping { + public final static class Mapping { private final Map<Dependency, DependencyText> textMapping; + /** + * Container for all dependencies. + */ private final DependencyText.Part container; public Mapping(Map<Dependency, DependencyText> textMapping, Part container) { @@ -167,7 +215,7 @@ class DependencyText { } public DependencyText.Part getText(Dependency d, String part) { - if (DependencyResult.PART_CONTAINER.equals(part)) { + if (d == null && DependencyResult.PART_CONTAINER.equals(part)) { return container; } DependencyText t = textMapping.get(d); @@ -181,6 +229,13 @@ class DependencyText { p.value = t.contents; return p; } + if (DependencyResult.PART_CONTAINER.equals(part)) { + if (t.container != null) { + return t.container.containerPart; + } else { + return null; + } + } for (DependencyText.Part p : t.partList) { if (part.equals(p.partId)) { return p; diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementation.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementation.java index bec452de03..49f09e8521 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementation.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementation.java @@ -19,15 +19,18 @@ package org.netbeans.modules.gradle.java.queries; import java.io.File; +import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -52,6 +55,7 @@ import org.netbeans.spi.project.ProjectServiceProvider; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.NbBundle; +import org.openide.util.Pair; /** * @@ -65,31 +69,44 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl private final Project project; private final NbGradleProject nbgp; - private static final Set<Scope> SCOPES = new HashSet<>(); - public GradleDependenciesImplementation(Project project) { this.project = project; nbgp = NbGradleProject.get(project); } - static { - SCOPES.add(Scopes.PROCESS); - SCOPES.add(Scopes.COMPILE); - SCOPES.add(Scopes.RUNTIME); - SCOPES.add(Scopes.EXTERNAL); - SCOPES.add(Scopes.TEST); - SCOPES.add(Scopes.TEST_COMPILE); - SCOPES.add(Scopes.TEST_RUNTIME); + private GradleScopes scopes; + + String toGradleConfigName(Scope s) { + GradleScope gs = toGradleScope(s); + if (gs != null) { + return gs.getConfigurationName(); + } else { + return null; + } + } + + GradleScopes gradleScopes() { + if (scopes == null) { + scopes = new GradleScopesBuilder(project).build(); + } + return scopes; } - static final Map<Scope, String> SCOPE_TO_CONFIURATION = new HashMap<>(); + Set<GradleScope> allScopes() { + return new HashSet<>(gradleScopes().scopes()); + } - static { - SCOPE_TO_CONFIURATION.put(Scopes.PROCESS, "annotationProcessor"); - SCOPE_TO_CONFIURATION.put(Scopes.COMPILE, "compileClasspath"); - SCOPE_TO_CONFIURATION.put(Scopes.RUNTIME, "runtimeClasspath"); - SCOPE_TO_CONFIURATION.put(Scopes.TEST_COMPILE, "testCompileClasspath"); - SCOPE_TO_CONFIURATION.put(Scopes.TEST_RUNTIME, "testRuntimeClasspath"); + GradleScope toGradleScope(Scope s) { + GradleScope gs = gradleScopes().toGradleScope(s); + if (gs != null) { + return gs; + } + String n = toGradleConfigName(s); + if (n != null) { + return gradleScopes().toGradleScope(n); + } else { + return null; + } } @NbBundle.Messages({ @@ -128,6 +145,8 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl class Collector { final ProjectDependencies.DependencyQuery query; final GradleBaseProject base; + + boolean acceptUnresolved; GradleConfiguration cfg; Scope scope; List<Dependency> problems = new ArrayList<>(); @@ -149,35 +168,116 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl base = GradleBaseProject.get(project); } + /** + * Creates a dependency result for project's declared dependencies. Since plugins may inject dependencies not present in the + * build file, the dependencies from the model are filtered for those present in the build file. + * + * @param allScopes all scopes that should be included. + * @return dependency result instance + */ + DependencyResult declaredDependencies(Set<Scope> allScopes) { + acceptUnresolved = true; + Set<String> cfgNames = new HashSet<>(); + + List<Dependency> declared = new ArrayList<>(); + + Set<? extends Scope> a; + if (allScopes.isEmpty()) { + a = allScopes(); + } else { + a = allScopes; + } + + Set<GradleScope> all = allScopes(); + + Queue<GradleScope> toProcess = new ArrayDeque<>(); + for (Scope scope : a) { + GradleScope gs = gradleScopes().toGradleScope(scope); + if (gs != null) { + toProcess.add(gs); + } + } + + GradleScope gs; + while ((gs = toProcess.poll()) != null) { + cfgNames.add(gs.getConfigurationName()); + for (GradleScope t : all) { + if (t.includes(gs)) { + toProcess.add(t); + } + } + } + + for (String cn : cfgNames) { + GradleConfiguration cfg = base.getConfigurations().get(cn); + if (cfg == null) { + continue; + } + this.cfg = cfg; + this.scope = gradleScopes().toGradleScope(cn); + for (GradleDependency dep : cfg.getConfiguredDependencies()) { + GradleConfiguration origin = cfg.getDependencyOrigin(dep); + if (origin != null && origin != cfg) { + // inherited + continue; + } + declared.add(createDependency(dep, Collections.emptyList())); + } + } + File f = nbgp.getGradleFiles().getProjectDir(); + FileObject pf = f == null ? null : FileUtil.toFileObject(f); + if (pf == null) { + throw new ProjectOperationException(project, ProjectOperationException.State.ERROR, Bundle.ERR_NoProjectDirectory(f)); + } + ArtifactSpec part = createProjectArtifact(null, base.getPath(), null); + ProjectSpec pspec = ProjectSpec.create(base.getPath(), pf); + + Dependency tempRoot = Dependency.create(pspec, part, null, declared, project); + + NbGradleProject ngp = NbGradleProject.get(project); + GradleBaseProject gbp = GradleBaseProject.get(project); + DependencyText.Mapping map; + try { + map = GradleDependencyResult.computeTextMappings(ngp, gbp, tempRoot.getChildren(), true); + } catch (IOException ex) { + throw new ProjectOperationException(project, ProjectOperationException.State.ERROR, "Unable to match dependencies to build script", ex); + } + for (Iterator<Dependency> it = declared.iterator(); it.hasNext(); ) { + if (map.getText(it.next(), null) == null) { + it.remove(); + } + } + return new GradleDependencyResult(project, scopes, tempRoot); + } + DependencyResult processDependencies(NbGradleProject nbgp) { GradleBaseProject base = GradleBaseProject.get(project); - Collection<Scope> scopes = query.getScopes(); + Collection<Scope> userScopes = query.getScopes(); - ArrayDeque<Scope> processScopes = new ArrayDeque<>(scopes); - Set<Scope> allScopes = new HashSet<>(scopes); - allScopes.retainAll(SCOPE_TO_CONFIURATION.keySet()); - processScopes.removeAll(allScopes); + ArrayDeque<Scope> processScopes = new ArrayDeque<>(userScopes); + Set<Scope> allScopes = new HashSet<>(); + if (processScopes.remove(Scopes.DECLARED)) { + return declaredDependencies(allScopes); + } + // process unknown scopes while (!processScopes.isEmpty()) { - Scope s = processScopes.poll(); - Set<Scope> newScopes = new HashSet<>(); - newScopes.add(s); - for (Scope t : SCOPES) { - if (s.includes(t)) { - newScopes.add(t); - } else if (t.implies(s)) { - newScopes.add(t); - } + Scope user = processScopes.poll(); + GradleScope s = gradleScopes().toGradleScope(user); + if (!s.getConfigurationName().equals(s.name())) { + Set<Scope> newScopes = new HashSet<>(); + newScopes.addAll(s.getIncluded()); + newScopes.removeAll(allScopes); + processScopes.addAll(newScopes); + } else { + allScopes.add(s); } - newScopes.removeAll(allScopes); - allScopes.addAll(newScopes); - processScopes.addAll(newScopes); } List<Dependency> rootDeps = new ArrayList<>(); for (Scope s : allScopes) { - String cfgName = SCOPE_TO_CONFIURATION.get(s); + String cfgName = toGradleConfigName(s); if (cfgName == null) { continue; } @@ -198,6 +298,7 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl // safeguard: we cannot determine the origin, so let's assume this configuration defines the dependency this.cfg = cfg; } + this.scope = gradleScopes().toGradleScope(this.cfg.getName()); List<Dependency> ch = processLevel(cfg, dep, new LinkedHashSet<>()); Dependency n = createDependency(dep, ch); rootDeps.add(n); @@ -214,7 +315,7 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl Dependency root = Dependency.create(pspec, part, null, rootDeps, project); - return new GradleDependencyResult(project, root); + return new GradleDependencyResult(project, scopes, root); } ArtifactSpec createProjectArtifact(GradleDependencyResult.Info info, String projectId, List<Dependency> children) { @@ -245,7 +346,23 @@ public class GradleDependenciesImplementation implements ProjectDependenciesImpl Dependency createDependency(GradleDependency dep, List<Dependency> children) { GradleDependencyResult.Info info = new GradleDependencyResult.Info(cfg, dep); if (dep instanceof GradleDependency.UnresolvedDependency) { - return null; + if (acceptUnresolved) { + GradleDependency.UnresolvedDependency gud = (GradleDependency.UnresolvedDependency)dep; + String[] gav = gud.getId().split(":"); + if (gav.length < 2) { + // not group:artifact, cannot represent as an artifact + return null; + } + ArtifactSpec spec = + ArtifactSpec.createVersionSpec(gav[0], gav[1], + null, + gav.length >= 4 ? gav[3] : null, + + gav.length >= 3 ? gav[2] : null, false, null, dep); + return Dependency.create(spec, scope, children, info); + } else { + return null; + } } if (dep instanceof GradleDependency.ProjectDependency) { diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependencyResult.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependencyResult.java index 25cffdd7de..81850909e3 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependencyResult.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleDependencyResult.java @@ -42,6 +42,7 @@ import org.netbeans.modules.gradle.api.NbGradleProject; import org.netbeans.modules.project.dependency.ArtifactSpec; import org.netbeans.modules.project.dependency.Dependency; import org.netbeans.modules.project.dependency.DependencyResult; +import org.netbeans.modules.project.dependency.ProjectScopes; import org.netbeans.modules.project.dependency.SourceLocation; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; @@ -59,6 +60,7 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC private final Project gradleProject; private final Dependency root; private boolean valid = true; + private final GradleScopes scopes; // @GuardedBy(this) PropertyChangeListener wL; @@ -73,8 +75,9 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC private final FileObject projectFile; - public GradleDependencyResult(Project gradleProject, Dependency root) { + public GradleDependencyResult(Project gradleProject, GradleScopes scopes, Dependency root) { this.gradleProject = gradleProject; + this.scopes = scopes; this.root = root; this.gp = NbGradleProject.get(gradleProject); File bs = gp.getGradleFiles().getBuildScript(); @@ -96,6 +99,11 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC return valid; } + @Override + public ProjectScopes getScopes() { + return scopes; + } + @Override public Collection<ArtifactSpec> getProblemArtifacts() { return Collections.unmodifiableCollection(problems); @@ -149,24 +157,12 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC // @GuardedBy(this) private DependencyText.Mapping sourceMapping; - - @Override - public SourceLocation getDeclarationRange(Dependency d, String part) throws IOException { - DependencyText.Mapping mapping; - DependencyText.Part p = null; - - synchronized (this) { - mapping = sourceMapping; - if (mapping != null) { - return getDeclarationRange0(mapping, d, part); - } - } - - GradleBaseProject gbp = GradleBaseProject.get(gradleProject); + + static DependencyText.Mapping computeTextMappings(NbGradleProject gp, GradleBaseProject gbp, List<Dependency> children, boolean matchScopes) throws IOException { Collection<String> cfgNames = gbp.getConfigurations().keySet(); - TextDependencyScanner ts = new TextDependencyScanner().withConfigurations(cfgNames); + TextDependencyScanner ts = new TextDependencyScanner(matchScopes).withConfigurations(cfgNames); - root.getChildren().forEach(rd -> { + children.forEach(rd -> { Info info = (Info)rd.getProjectData(); GradleConfiguration org = info.config.getDependencyOrigin(info.gradleDependency); if (org == null) { @@ -174,8 +170,9 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC } ts.addDependencyOrigin(info.gradleDependency, org.getName()); }); - + String contents = null; + FileObject projectFile = FileUtil.toFileObject(gp.getGradleFiles().getBuildScript()); if (projectFile != null) { EditorCookie cake = null; cake = projectFile.getLookup().lookup(EditorCookie.class); @@ -198,8 +195,23 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC } ts.parseDependencyList(contents); - mapping = ts.mapDependencies(root.getChildren()); + return ts.mapDependencies(children); + } + + @Override + public SourceLocation getDeclarationRange(Dependency d, String part) throws IOException { + DependencyText.Mapping mapping; + DependencyText.Part p = null; + synchronized (this) { + mapping = sourceMapping; + if (mapping != null) { + return getDeclarationRange0(mapping, d, part); + } + } + + mapping = computeTextMappings(gp, GradleBaseProject.get(gradleProject), root.getChildren(), false); + synchronized (this) { if (sourceMapping == null) { sourceMapping = mapping; @@ -210,10 +222,13 @@ public final class GradleDependencyResult implements DependencyResult, PropertyC private SourceLocation getDeclarationRange0(DependencyText.Mapping mapping, Dependency d, String part) { Dependency direct = d; - while (direct.getParent() != null && direct.getParent() != root) { - direct = direct.getParent(); + // there's the special case with CONTAINER part for all the dependencies. + if (d != null) { + while (direct.getParent() != null && direct.getParent() != root) { + direct = direct.getParent(); + } } - DependencyText.Part found = mapping.getText(direct, part); + DependencyText.Part found = mapping.getText(direct == root ? null : direct, part); if (found == null) { return null; } diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScope.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScope.java new file mode 100644 index 0000000000..191d720204 --- /dev/null +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScope.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.gradle.java.queries; + +import java.util.Collection; +import java.util.HashSet; +import org.netbeans.modules.project.dependency.Scope; + +/** + * Custom gradle scopes that copy the structure of Gradle's standard configurations. + * Each scope must point to a configuration where the artifacts for the scope will be added. + * + * @author sdedic + */ +public final class GradleScope extends Scope { + private final String configurationName; + private final String targetConfiguration; + private Collection<Scope> includes; + private Collection<Scope> implies; + + GradleScope(String name, Collection<Scope> includes, Collection<Scope> implies) { + this(name, name, name, includes, implies); + } + + GradleScope(String name, String cfgName, String modifyCfgName, Collection<Scope> includes, Collection<Scope> implies) { + super(name); // NOI18N + this.configurationName = cfgName; + this.targetConfiguration = modifyCfgName; + this.includes = includes == null ? new HashSet<>() : includes; + this.implies = implies == null ? new HashSet<>() : implies; + } + + public boolean includes(Scope s) { + return includes.contains(s.name()); + } + + public Collection<? extends Scope> getIncluded() { + return includes; + } + + public Collection<? extends Scope> getInheritedInto() { + return implies; + } + + public boolean implies(Scope s) { + return implies.contains(s.name()); + } + + public String getConfigurationName() { + return configurationName; + } + + public String getTargetConfigurationName() { + return targetConfiguration; + } + + public String toString() { + return name(); + } + + /** + * Name of the "implementation" scope + */ + static final String IMPLEMENTATION = "implementation"; +} diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopes.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopes.java new file mode 100644 index 0000000000..203e5f9515 --- /dev/null +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopes.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.gradle.java.queries; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import org.netbeans.api.project.Project; +import org.netbeans.modules.project.dependency.ProjectScopes; +import org.netbeans.modules.project.dependency.Scope; + +/** + * Implementation of ProjectScopes that bridges Gradle configutations. + * + * @author sdedic + */ +public final class GradleScopes implements ProjectScopes{ + private final Project project; + private final Map<String, GradleScope> scopes; + + public GradleScopes(Project project, Map<String, GradleScope> scopes) { + this.project = project; + this.scopes = Collections.unmodifiableMap(scopes); + } + + @Override + public Collection<GradleScope> scopes() { + return scopes.values(); + } + + public GradleScope toGradleScope(String n) { + return scopes.get(n); + } + + public GradleScope toGradleScope(Scope s) { + return scopes.getOrDefault(s.name(), scopes.get(GradleScope.IMPLEMENTATION)); + } + + @Override + public Collection<? extends Scope> implies(Scope s, boolean direct) { + GradleScope scope = scopes.get(s.name()); + if (scope == null) { + return Collections.emptyList(); + } + if (direct) { + return scope.getInheritedInto(); + } + return Collections.emptySet(); + } +} diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopesBuilder.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopesBuilder.java new file mode 100644 index 0000000000..8cc6ae70e9 --- /dev/null +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleScopesBuilder.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.gradle.java.queries; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.project.Project; +import org.netbeans.modules.gradle.api.GradleBaseProject; +import org.netbeans.modules.gradle.api.GradleConfiguration; +import org.netbeans.modules.project.dependency.Scope; +import org.netbeans.modules.project.dependency.Scopes; + +/** + * Builds scopes for the specific project, using its configurations. + * @author sdedic + */ +public final class GradleScopesBuilder { + private final static Map<String, String> REMAP_ABSTRACT_SCOPES = new HashMap<>(); + private final Project project; + private final GradleBaseProject gbp; + + public GradleScopesBuilder(Project project) { + this.project = project; + this.gbp = GradleBaseProject.get(project); + } + + private Map<String, Collection<String>> extendsFrom = new HashMap<>(); + private Map<String, Collection<String>> inheritedInto = new HashMap<>(); + + static { + REMAP_ABSTRACT_SCOPES.put(Scopes.COMPILE.name(), "compileClasspath"); + REMAP_ABSTRACT_SCOPES.put(Scopes.RUNTIME.name(), "runtimeClasspath"); + REMAP_ABSTRACT_SCOPES.put(Scopes.TEST_COMPILE.name(), "testCompileClasspath"); + REMAP_ABSTRACT_SCOPES.put(Scopes.TEST_RUNTIME.name(), "testRuntimeClasspath"); + REMAP_ABSTRACT_SCOPES.put(Scopes.TEST.name(), Scopes.TEST_RUNTIME.name() + "," + Scopes.TEST_COMPILE.name() + "," + "testRuntimeClasspath,testCompileClasspath"); + } + + private void addToMap(Map<String, Collection<String>> map, String k, String c) { + map.computeIfAbsent(k, n -> new LinkedHashSet<>(2)).add(c); + } + + private void addDependency(String inheritedTo, String inheritedFrom) { + addToMap(extendsFrom, inheritedTo, inheritedFrom); + addToMap(inheritedInto, inheritedFrom, inheritedTo); + } + + Map<String, GradleScope> scopes = new HashMap<>(); + + private GradleScope createMetaScope(String metaName, String config, String targetConfig) { + GS gs = new GS(); + scopeData.put(metaName, gs); + addDependency(metaName, config); + GradleScope s = new GradleScope(metaName, config, targetConfig, gs.extendsFrom, gs.inheritedInto); + scopes.put(metaName, s); + return s; + } + + static class GS { + Set<Scope> extendsFrom = new HashSet<>(); + Set<Scope> inheritedInto = new HashSet<>(); + } + + private Map<String, GS> scopeData = new HashMap<>(); + + public GradleScopes build() { + for (String cfg : gbp.getConfigurations().keySet()) { + GradleConfiguration c = gbp.getConfigurations().get(cfg); + c.getExtendsFrom().forEach(p -> { + addDependency(cfg, p.getName()); + }); + } + + addDependency(Scopes.EXTERNAL.name(), "compileOnly"); + addDependency(Scopes.PROCESS.name(), "annotationProcessor"); + addDependency(Scopes.COMPILE.name(), "compileClasspath"); + addDependency(Scopes.RUNTIME.name(), "runtimeClasspath"); + addDependency(Scopes.TEST_COMPILE.name(), "testCompileClasspath"); + addDependency(Scopes.TEST_RUNTIME.name(), "testRuntimeClasspath"); + addDependency(Scopes.TEST.name(), Scopes.TEST_RUNTIME.name() + "," + Scopes.TEST_COMPILE.name()); + + for (String cfg : gbp.getConfigurations().keySet()) { + GS gs = new GS(); + scopeData.put(cfg, gs); + scopes.put(cfg, new GradleScope(cfg, gs.extendsFrom, gs.inheritedInto)); + } + + createMetaScope(Scopes.EXTERNAL.name(), "compileOnly", "compileOnly"); + createMetaScope(Scopes.PROCESS.name(), "annotationProcessor", "annotationProcessor"); + createMetaScope(Scopes.COMPILE.name(), "compileClasspath", "implementation"); + createMetaScope(Scopes.RUNTIME.name(), "runtimeClasspath", "runtimeOnly"); + createMetaScope(Scopes.TEST_COMPILE.name(), "testCompileClasspath", "testCompileClasspath"); + createMetaScope(Scopes.TEST_RUNTIME.name(), "testRuntimeClasspath", "testRuntimeClasspath"); + createMetaScope(Scopes.TEST.name(), "testCompileClasspath", "testImplementation"); + + for (GradleScope gs : scopes.values()) { + GS data = scopeData.get(gs.name()); + + extendsFrom.getOrDefault(gs.name(), Collections.emptyList()). + stream(). + map(scopes::get).forEach(data.extendsFrom::add); + inheritedInto.getOrDefault(gs.name(), Collections.emptyList()). + stream(). + map(scopes::get).forEach(data.inheritedInto::add); + } + + return new GradleScopes(project, scopes); + } +} diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/TextDependencyScanner.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/TextDependencyScanner.java index d7feffb11e..cab60320a2 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/TextDependencyScanner.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/TextDependencyScanner.java @@ -41,6 +41,7 @@ public class TextDependencyScanner { private final Map<GradleDependency, String> origins = new HashMap<>(); private final Map<GradleDependency, Map<String, SourceLocation>> locations = new HashMap<>(); + private final boolean matchScopes; private List<DependencyText> dependencies = new ArrayList<>(); @@ -93,7 +94,10 @@ public class TextDependencyScanner { * read. */ int newLinePos; - + + public TextDependencyScanner(boolean matchScopes) { + this.matchScopes = matchScopes; + } /** * End of the last item in the group @@ -522,6 +526,7 @@ public class TextDependencyScanner { while ((c = nextToken()) != '}') { if ((!onlyAfterNewline || wasEndOfLine()) && (tokenType == Token.IDENTIFIER)) { if (configurationNames.contains(tokenText)) { + String saveToken = tokenText; groupStartPos = tokenStart; groupItemCount = 0; scanDepdendencyContainer(tokenText); @@ -532,6 +537,15 @@ public class TextDependencyScanner { if (groupItemsEnd != -1) { last.endPos = groupItemsEnd; } + } else { + DependencyText.Part containerPart = new DependencyText.Part(); + containerPart.partId = saveToken; + containerPart.startPos = groupStartPos; + containerPart.endPos = groupItemsEnd; + containerPart.quoted = 0; + List<DependencyText> items = new ArrayList<>(dependencies.subList(dependencies.size() - groupItemCount, dependencies.size())); + DependencyText.Container nc = new DependencyText.Container(items, containerPart); + items.forEach(i -> i.container = nc); } onlyAfterNewline = false; continue; @@ -559,6 +573,7 @@ public class TextDependencyScanner { try { findDependencyBlock(); buildDependencies(); + dependencyBlockEnd = pos; computeGAV(); } catch (EndInputException ex) { // no op, just terminate processing @@ -583,7 +598,7 @@ public class TextDependencyScanner { } } - private int dependencyBlockStart; + private int dependencyBlockStart = -1; private int dependencyBlockEnd; private void findDependencyBlock() { @@ -596,7 +611,6 @@ public class TextDependencyScanner { c = skipWhitespace(); if (c == '{') { nextChar(); - dependencyBlockEnd = pos; return; } } @@ -638,6 +652,16 @@ public class TextDependencyScanner { } } + private boolean scopeMatches(Dependency d, DependencyText t) { + if (!matchScopes) { + return true; + } + if (d.getScope() == null || t.configuration == null) { + return true; + } + return d.getScope().name().equals(t.configuration); + } + private DependencyText findDependency(Dependency d) { String projectName = null; String gav = null; @@ -661,13 +685,13 @@ public class TextDependencyScanner { if (DependencyText.KEYWORD_PROJECT.equals(t.keyword) && t.contents.equals(projectName)) { return t; - } else if (t.keyword == null && t.getContentsOrGav().equals(gav)) { + } else if (t.keyword == null && t.getContentsOrGav().equals(gav) && scopeMatches(d, t)) { return t; } } for (DependencyText t : dependencies) { - if (t.keyword == null && t.contents != null && t.contents.equals(groupAndName)) { + if (t.keyword == null && t.contents != null && t.contents.equals(groupAndName) && scopeMatches(d, t)) { return t; } } @@ -685,11 +709,17 @@ public class TextDependencyScanner { } } - DependencyText.Part containerPart = new DependencyText.Part(); - containerPart.partId = DependencyResult.PART_CONTAINER; - containerPart.startPos = dependencyBlockStart; - containerPart.endPos = dependencyBlockEnd; - containerPart.value = ""; + DependencyText.Part containerPart; + + if (dependencyBlockStart != -1) { + containerPart = new DependencyText.Part(); + containerPart.partId = DependencyResult.PART_CONTAINER; + containerPart.startPos = dependencyBlockStart; + containerPart.endPos = dependencyBlockEnd; + containerPart.value = ""; + } else { + containerPart = null; + } return new DependencyText.Mapping(result, containerPart); } diff --git a/java/gradle.java/test/unit/data/dependencies/micronaut/build.gradle b/java/gradle.java/test/unit/data/dependencies/micronaut/build.gradle index 3ce34ee2b6..ae9934a2a8 100644 --- a/java/gradle.java/test/unit/data/dependencies/micronaut/build.gradle +++ b/java/gradle.java/test/unit/data/dependencies/micronaut/build.gradle @@ -32,12 +32,16 @@ repositories { dependencies { annotationProcessor("io.micronaut:micronaut-http-validation") implementation("io.micronaut:micronaut-http-client") - implementation("io.micronaut:micronaut-jackson-databind") + implementation( + "io.micronaut:micronaut-jackson-databind" + ) implementation("jakarta.annotation:jakarta.annotation-api") runtimeOnly("ch.qos.logback:logback-classic") - implementation("io.micronaut:micronaut-validation") - implementation("org.apache.logging.log4j:log4j-core:2.17.0") + implementation( + "io.micronaut:micronaut-validation", + "org.apache.logging.log4j:log4j-core:2.17.0" + ) } diff --git a/java/gradle.java/test/unit/data/dependencies/parse/variousSyntax.gradle b/java/gradle.java/test/unit/data/dependencies/parse/variousSyntax.gradle index d3fb44b5b3..b56a21e06c 100644 --- a/java/gradle.java/test/unit/data/dependencies/parse/variousSyntax.gradle +++ b/java/gradle.java/test/unit/data/dependencies/parse/variousSyntax.gradle @@ -7,16 +7,16 @@ group = "com.example" repositories { mavenCentral() } -dependencies { +@@T@@dependencies { // with parenthesis @@A@@annotationProcessor("io.micronaut:micronaut-http-validation")@@A@@ // without parenthesis @@B@@implementation "io.micronaut:micronaut-http-client"@@B@@ // several deps in a common group - implementation( + @@P@@implementation( @@C@@'io.micronaut:micronaut-jackson-databind'@@C@@, @@D@@"jakarta.annotation:jakarta.annotation-api"@@D@@ - ) + )@@Q@@ // with a closure @@E@@runtimeOnly("ch.qos.logback:logback-classic") { transitive = true @@ -24,12 +24,12 @@ dependencies { // map in parenthesis @@F@@implementation(group : "io.micronaut", name: "micronaut-validation", version: "2.5")@@F@@ // list of maps in parenthesis - runtimeOnly( + @@R@@runtimeOnly( @@G@@[group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true]@@G@@, @@H@@[group:'org.ow2.asm', name:'asm', version:'7.1']@@H@@ - ) + )@@S@@ @@I@@implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.0'@@I@@ -} +}@@U@@ application { mainClass.set("com.example.Application") } diff --git a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementationTest.java b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementationTest.java index 82d5f3d184..db4ec94d38 100644 --- a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementationTest.java +++ b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/GradleDependenciesImplementationTest.java @@ -19,10 +19,13 @@ package org.netbeans.modules.gradle.java.queries; import java.io.File; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static junit.framework.TestCase.assertNotNull; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ui.OpenProjects; @@ -37,6 +40,7 @@ import org.netbeans.modules.project.dependency.DependencyResult; import org.netbeans.modules.project.dependency.ProjectDependencies; import org.netbeans.modules.project.dependency.Scopes; import org.netbeans.modules.project.dependency.SourceLocation; +import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.modules.DummyInstalledFileLocator; @@ -133,6 +137,84 @@ public class GradleDependenciesImplementationTest extends NbTestCase { assertSame("Project is passed as project data - internal", p, r.getRoot().getProjectData()); } + public void testDependencyNoDependencies() throws Exception { + Project p = makeProject("dependencies/simple2"); + DependencyResult r = ProjectDependencies.findDependencies(p, + ProjectDependencies.newQuery(Scopes.RUNTIME) + ); + assertNotNull("Dependency service is supported", r); + SourceLocation loc = r.getDeclarationRange(null, DependencyResult.PART_CONTAINER); + assertNull(loc); + + loc = r.getDeclarationRange(r.getRoot(), DependencyResult.PART_CONTAINER); + assertNull(loc); + } + + public void testDependencyBlockRange() throws Exception { + Project p = makeProject("dependencies/micronaut"); + DependencyResult r = ProjectDependencies.findDependencies(p, + ProjectDependencies.newQuery(Scopes.RUNTIME) + ); + assertNotNull("Dependency service is supported", r); + + SourceLocation loc = r.getDeclarationRange(null, DependencyResult.PART_CONTAINER); + assertNotNull(loc); + + loc = r.getDeclarationRange(r.getRoot(), DependencyResult.PART_CONTAINER); + assertNotNull(loc); + + FileObject buildGradle = projectDir.getFileObject("build.gradle"); + EditorCookie cake = buildGradle.getLookup().lookup(EditorCookie.class); + LineDocument doc = LineDocumentUtils.asRequired(cake.openDocument(), LineDocument.class); + + int start = loc.getStartOffset(); + int depStart = LineDocumentUtils.getLineFirstNonWhitespace(doc, start); + int depEnd = LineDocumentUtils.getLineEnd(doc, start); + + String s = doc.getText(depStart, depEnd - depStart); + assertEquals("dependencies {", s); + + int end = loc.getEndOffset(); + s = doc.getText(end - 1, 1); + assertEquals("}", s); + } + + public void testLocationOfBlockDependencyList() throws Exception { + Project p = makeProject("dependencies/micronaut"); + DependencyResult r = ProjectDependencies.findDependencies(p, + ProjectDependencies.newQuery(Scopes.RUNTIME) + ); + assertNotNull("Dependency service is supported", r); + + Dependency pin1 = r.getRoot().getChildren().stream().filter(d -> d.toString().contains("io.micronaut:micronaut-validation")).findAny().get(); + Dependency pin2 = r.getRoot().getChildren().stream().filter(d -> d.toString().contains("ch.qos.logback:logback-classic")).findAny().get(); + Dependency pin3 = r.getRoot().getChildren().stream().filter(d -> d.toString().contains("io.micronaut:micronaut-jackson-databind")).findAny().get(); + + SourceLocation loc = r.getDeclarationRange(pin2, DependencyResult.PART_CONTAINER); + assertNull(loc); + + loc = r.getDeclarationRange(pin3, DependencyResult.PART_CONTAINER); + assertNull(loc); + + loc = r.getDeclarationRange(pin1, DependencyResult.PART_CONTAINER); + assertNotNull(loc); + + FileObject buildGradle = projectDir.getFileObject("build.gradle"); + EditorCookie cake = buildGradle.getLookup().lookup(EditorCookie.class); + LineDocument doc = LineDocumentUtils.asRequired(cake.openDocument(), LineDocument.class); + + int start = loc.getStartOffset(); + int depStart = LineDocumentUtils.getLineFirstNonWhitespace(doc, start); + int depEnd = LineDocumentUtils.getLineEnd(doc, start); + + String s = doc.getText(depStart, depEnd - depStart); + assertEquals("implementation(", s); + + int end = loc.getEndOffset(); + s = doc.getText(end - 1, 1); + assertEquals(")", s); + } + public void testMicronautProject() throws Exception { Project p = makeProject("dependencies/micronaut"); DependencyResult r = ProjectDependencies.findDependencies(p, @@ -174,5 +256,44 @@ public class GradleDependenciesImplementationTest extends NbTestCase { } assertNotNull("Implied dependency should have a root dep", rd); assertSame(rd, srcLoc.getImpliedBy()); - } + } + + private void assertContainsDependency(List<Dependency> deps, String groupAndArtifact) { + for (Dependency d : deps) { + ArtifactSpec a = d.getArtifact(); + if (a != null) { + String ga = a.getGroupId() + ":" + a.getArtifactId(); + if (groupAndArtifact.equals(ga)) { + return; + } + } + } + fail("Artifact not found: " + groupAndArtifact); + } + + private static final List<String> ALL_DEPS = Arrays.asList( + "io.micronaut:micronaut-http-validation", + "io.micronaut:micronaut-http-client", + "io.micronaut:micronaut-jackson-databind", + "jakarta.annotation:jakarta.annotation-api", + "ch.qos.logback:logback-classic", + "io.micronaut:micronaut-validation", + "org.apache.logging.log4j:log4j-core" + ); + + public void testMicronautProjectDeclaredDependencies() throws Exception { + Project p = makeProject("dependencies/micronaut"); + + DependencyResult r = ProjectDependencies.findDependencies(p, + ProjectDependencies.newQuery(Scopes.DECLARED) + ); + assertNotNull("Dependency service is supported", r); + + List<Dependency> deps = r.getRoot().getChildren(); + for (String d : ALL_DEPS) { + assertContainsDependency(deps, d); + } + + assertTrue("Contains versioned log4j dependency", deps.stream().filter(d -> d.getArtifact().toString().contains(".log4j:log4j-core:2.17.0")).findAny().isPresent()); + } } diff --git a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/RegexpGradleScannerTest.java b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/RegexpGradleScannerTest.java index ace9d9d80b..d30297ab66 100644 --- a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/RegexpGradleScannerTest.java +++ b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/queries/RegexpGradleScannerTest.java @@ -31,6 +31,7 @@ import org.netbeans.junit.NbTestCase; import org.netbeans.modules.project.dependency.ArtifactSpec; import org.netbeans.modules.project.dependency.Dependency; import org.netbeans.modules.project.dependency.ProjectSpec; +import org.netbeans.modules.project.dependency.Scope; import org.netbeans.modules.project.dependency.Scopes; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; @@ -56,7 +57,7 @@ public class RegexpGradleScannerTest extends NbTestCase { */ public void testComplexPrologue() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/complexPrologue.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); scanner.withConfigurations(Arrays.asList( "runtimeOnly", "implementation" @@ -68,7 +69,7 @@ public class RegexpGradleScannerTest extends NbTestCase { public void testSkipExecutableCodeInDependencies() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/executableCodeInDependencies.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); scanner.withConfigurations(Arrays.asList( "runtimeOnly", "implementation" @@ -81,7 +82,7 @@ public class RegexpGradleScannerTest extends NbTestCase { public void testScanSimpleScript() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/simple.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); scanner.withConfigurations(Arrays.asList( "runtimeOnly", "implementation" @@ -96,7 +97,7 @@ public class RegexpGradleScannerTest extends NbTestCase { public void testMicronautStarter() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/starter.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); scanner.withConfigurations(Arrays.asList( "runtimeOnly", "implementation", "annotationProcessor" @@ -124,7 +125,7 @@ public class RegexpGradleScannerTest extends NbTestCase { */ public void testVariousSyntaxes() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/variousSyntax.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); filteredText = filterAndStorePositions(f.asText()); scanner.withConfigurations(Arrays.asList( @@ -137,9 +138,44 @@ public class RegexpGradleScannerTest extends NbTestCase { checkDependencyMap(scanner, deps); } + + + /** + * Checks that the container is properly reported. For single dependencies, the container is null. + * For dependency blocks like compileOnly {...}, the container is the 'compileOnly" block. + * @throws Exception + */ + public void testDependencyContainers() throws Exception { + FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/variousSyntax.gradle"); + TextDependencyScanner scanner = new TextDependencyScanner(true); + + filteredText = filterAndStorePositions(f.asText()); + scanner.withConfigurations(Arrays.asList( + "runtimeOnly", "implementation", "annotationProcessor" + )); + List<DependencyText> deps = scanner.parseDependencyList(filteredText); + DependencyText text = deps.stream().filter(d -> "io.micronaut:micronaut-http-validation".equals(d.contents)).findAny().get(); + assertNull(text.container); // no specific container + + text = deps.stream().filter(d -> "io.micronaut:micronaut-jackson-databind".equals(d.contents)).findAny().get(); + assertNotNull("Container is found for string lists", text.container); + assertEquals("implementation", text.container.containerPart.partId); + assertEquals((int)startPosition.get("P"), text.container.containerPart.startPos); + assertEquals((int)startPosition.get("Q"), text.container.containerPart.endPos); + + text = deps.stream().filter(d -> "ch.qos.logback:logback-classic".equals(d.contents)).findAny().get(); + assertNull("Parser is not fooled by braced customization", text.container); // no specific container + + text = deps.stream().filter(d -> "org.ow2.asm".equals(d.group)).findAny().get(); + assertNotNull("Container is found for map lists", text.container); + assertEquals("runtimeOnly", text.container.containerPart.partId); + assertEquals((int)startPosition.get("R"), text.container.containerPart.startPos); + assertEquals((int)startPosition.get("S"), text.container.containerPart.endPos); + } + public void testMapLikeDeclaration() throws Exception { FileObject f = FileUtil.toFileObject(getDataDir()).getFileObject("dependencies/parse/variousSyntax.gradle"); - TextDependencyScanner scanner = new TextDependencyScanner(); + TextDependencyScanner scanner = new TextDependencyScanner(true); filteredText = filterAndStorePositions(f.asText()); scanner.withConfigurations(Arrays.asList( @@ -154,17 +190,42 @@ public class RegexpGradleScannerTest extends NbTestCase { private Map<String, Integer> startPosition = new HashMap<>(); private Map<String, Integer> endPosition = new HashMap<>(); + /** + * Stub that only serves as an identifier, cannot answer imply/inherit questions. + */ + private static final class ScopeStub extends Scope { + public ScopeStub(String name) { + super(name); + } + + public boolean includes(Scope s) { + return false; + } + + public boolean exports(Scope s) { + return false; + } + + public boolean implies(Scope s) { + return false; + } + } + + private Scope s(String name) { + return new ScopeStub(name); + } + private void checkDependencyMap(TextDependencyScanner scanner, List<DependencyText> deps) { List<Dependency> list = new ArrayList<>(); for (DependencyText t : deps) { if (t.keyword == null && t.name != null && t.group != null && t.version != null) { ArtifactSpec as = ArtifactSpec.builder(t.group, t.name, t.version, null).build(); - Dependency d = Dependency.create(as, Scopes.RUNTIME, Collections.emptyList(), null); + Dependency d = Dependency.create(as, s(t.configuration), Collections.emptyList(), null); list.add(d); } else if ("project".equals(t.keyword)) { ProjectSpec p = ProjectSpec.create(t.contents, null); ArtifactSpec as = ArtifactSpec.builder(t.group, t.name, t.version, null).build(); - Dependency d = Dependency.create(p, as, Scopes.RUNTIME, Collections.emptyList(), null); + Dependency d = Dependency.create(p, as, s(t.configuration), Collections.emptyList(), null); list.add(d); } } diff --git a/java/maven/src/org/netbeans/modules/maven/queries/MavenDependenciesImplementation.java b/java/maven/src/org/netbeans/modules/maven/queries/MavenDependenciesImplementation.java index b3a56b0bb2..2c561c12b7 100644 --- a/java/maven/src/org/netbeans/modules/maven/queries/MavenDependenciesImplementation.java +++ b/java/maven/src/org/netbeans/modules/maven/queries/MavenDependenciesImplementation.java @@ -18,31 +18,26 @@ */ package org.netbeans.modules.maven.queries; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayDeque; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Queue; import java.util.Set; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.MavenExecutionException; import org.apache.maven.artifact.Artifact; -import org.apache.maven.model.Model; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.PlexusContainerException; import org.netbeans.api.project.Project; +import org.netbeans.modules.maven.NbMavenProjectImpl; import org.netbeans.modules.maven.api.NbMavenProject; import org.netbeans.modules.maven.embedder.DependencyTreeFactory; import org.netbeans.modules.maven.embedder.EmbedderFactory; @@ -93,19 +88,51 @@ public class MavenDependenciesImplementation implements ProjectDependenciesImple } } - static final Map<Scope, String> mavenScopes; + /** + * Mapping from the abstract scopes to Maven + */ + static final Map<Scope, String> scope2Maven = new HashMap<>(); + + /** + * Mapping from maven to the abstract scopes + */ + static final Map<String, Scope> maven2Scope = new HashMap<>(); + + static final Map<Scope, Collection<Scope>> directScopes = new HashMap<>(); + static final Map<Scope, Collection<Scope>> impliedScopes = new HashMap<>(); + static final Map<Scope, Collection<Scope>> reverseImplied = new HashMap<>(); static { - mavenScopes = new HashMap<>(); - mavenScopes.put(Scopes.PROCESS, "compile"); - mavenScopes.put(Scopes.COMPILE, "compile"); - mavenScopes.put(Scopes.RUNTIME, "runtime"); - mavenScopes.put(Scopes.TEST, "test"); - mavenScopes.put(Scopes.EXTERNAL, "provided"); + scope2Maven.put(Scopes.PROCESS, "compile"); + scope2Maven.put(Scopes.COMPILE, "compile"); + scope2Maven.put(Scopes.RUNTIME, "runtime"); + scope2Maven.put(Scopes.TEST, "test"); + scope2Maven.put(Scopes.EXTERNAL, "provided"); + + maven2Scope.put("compile", Scopes.COMPILE); + maven2Scope.put("runtime", Scopes.RUNTIME); + maven2Scope.put("test", Scopes.TEST); + maven2Scope.put("provided", Scopes.EXTERNAL); + + directScopes.put(Scopes.API, Arrays.asList(Scopes.COMPILE)); + directScopes.put(Scopes.PROCESS, Arrays.asList(Scopes.COMPILE)); + directScopes.put(Scopes.EXTERNAL, Arrays.asList(Scopes.COMPILE)); + directScopes.put(Scopes.COMPILE, Arrays.asList(Scopes.RUNTIME, Scopes.TEST)); + directScopes.put(Scopes.RUNTIME, Arrays.asList(Scopes.TEST)); + + impliedScopes.put(Scopes.API, Arrays.asList(Scopes.COMPILE, Scopes.RUNTIME, Scopes.TEST)); + impliedScopes.put(Scopes.PROCESS, Arrays.asList(Scopes.COMPILE, Scopes.RUNTIME, Scopes.TEST)); + impliedScopes.put(Scopes.EXTERNAL, Arrays.asList(Scopes.COMPILE, Scopes.RUNTIME, Scopes.TEST)); + impliedScopes.put(Scopes.COMPILE, Arrays.asList(Scopes.RUNTIME, Scopes.TEST)); + impliedScopes.put(Scopes.RUNTIME, Arrays.asList(Scopes.TEST)); + + reverseImplied.put(Scopes.TEST, Arrays.asList(Scopes.RUNTIME, Scopes.COMPILE, Scopes.API,Scopes.EXTERNAL, Scopes.PROCESS)); + reverseImplied.put(Scopes.RUNTIME, Arrays.asList(Scopes.COMPILE, Scopes.API, Scopes.EXTERNAL, Scopes.PROCESS)); + reverseImplied.put(Scopes.COMPILE, Arrays.asList(Scopes.API, Scopes.EXTERNAL, Scopes.PROCESS)); } static String mavenScope(Scope s) { - return mavenScopes.getOrDefault(s, "runtime"); + return scope2Maven.getOrDefault(s, "compile"); } private ArtifactSpec mavenToArtifactSpec(Artifact a) { @@ -119,6 +146,55 @@ public class MavenDependenciesImplementation implements ProjectDependenciesImple } } + /** + * Returns dependencies declared right in the POM file. Respects the user's query filter for artifacts. + * @param query + * @param embedder + * @return + */ + private DependencyResult findDeclaredDependencies(ProjectDependencies.DependencyQuery query, MavenEmbedder embedder) { + NbMavenProjectImpl impl = (NbMavenProjectImpl)project.getLookup().lookup(NbMavenProjectImpl.class); + MavenProject proj = impl.getFreshOriginalMavenProject(); + List<Dependency> children = new ArrayList<>(); + for (org.apache.maven.model.Dependency d : proj.getDependencies()) { + String aId = d.getArtifactId(); + String gID = d.getGroupId(); + String scope = d.getScope(); + String classsifier = d.getClassifier(); + String type = d.getType(); + String version = d.getVersion(); + + ArtifactSpec a; + + if (version != null && version.endsWith("-SNAPSHOT")) { + a = ArtifactSpec.createSnapshotSpec(gID, aId, type, classsifier, version, d.isOptional(), + d.getSystemPath() == null ? null : FileUtil.toFileObject(new File(d.getSystemPath(), aId)), d); + } else { + a = ArtifactSpec.createVersionSpec(gID, aId, type, classsifier, version, d.isOptional(), + d.getSystemPath() == null ? null : FileUtil.toFileObject(new File(d.getSystemPath(), aId)), d); + } + Scope s = scope == null ? Scopes.COMPILE : maven2Scope.get(scope); + if (s == null) { + s = Scopes.COMPILE; + } + Dependency dep = Dependency.create(a, s, Collections.emptyList(), d); + children.add(dep); + } + + ArtifactSpec prjSpec = mavenToArtifactSpec(proj.getArtifact()); + Dependency root = Dependency.create(prjSpec, Scopes.DECLARED, children, proj); + + return new MavenDependencyResult(proj, root, query.getScopes(), Collections.emptyList(), project, impl.getProjectWatcher()); + } + + static Collection<Scope> implies(Scope s) { + return impliedScopes.getOrDefault(s, Collections.emptyList()); + } + + static Collection<Scope> impliedBy(Scope s) { + return reverseImplied.getOrDefault(s, Collections.emptyList()); + } + @NbBundle.Messages({ "ERR_DependencyOnBrokenProject=Unable to collect dependencies from a broken project", "ERR_DependencyNotPrimed=Unable to collect dependencies from a broken project", @@ -162,8 +238,13 @@ public class MavenDependenciesImplementation implements ProjectDependenciesImple } } + if (query.getScopes().contains(Scopes.DECLARED)) { + return findDeclaredDependencies(query, embedder); + } + Collection<String> mavenScopes = scopes.stream(). map(MavenDependenciesImplementation::mavenScope). + filter(Objects::nonNull). collect(Collectors.toList()); org.apache.maven.shared.dependency.tree.DependencyNode n; @@ -179,24 +260,7 @@ public class MavenDependenciesImplementation implements ProjectDependenciesImple throw e; } } - Set<Scope> allScopes = new HashSet<>(); - - Queue<Scope> processScopes = new ArrayDeque<>(scopes); - while (!processScopes.isEmpty()) { - Scope s = processScopes.poll(); - Set<Scope> newScopes = new HashSet<>(); - newScopes.add(s); - for (Scope t : SCOPES) { - if (s.includes(t)) { - newScopes.add(t); - } else if (t.implies(s)) { - newScopes.add(t); - } - } - newScopes.removeAll(allScopes); - allScopes.addAll(newScopes); - processScopes.addAll(newScopes); - } + Set<Scope> allScopes = Stream.concat(scopes.stream(), scopes.stream().flatMap(x -> impliedBy(x).stream())).collect(Collectors.toSet()); Set<ArtifactSpec> broken = new HashSet<>(); Dependency.Filter compositeFiter = new Dependency.Filter() { @Override diff --git a/java/maven/src/org/netbeans/modules/maven/queries/MavenDependencyResult.java b/java/maven/src/org/netbeans/modules/maven/queries/MavenDependencyResult.java index 685ddd8800..c9509cffda 100644 --- a/java/maven/src/org/netbeans/modules/maven/queries/MavenDependencyResult.java +++ b/java/maven/src/org/netbeans/modules/maven/queries/MavenDependencyResult.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; @@ -55,6 +56,7 @@ import org.netbeans.modules.maven.model.pom.POMModelFactory; import org.netbeans.modules.project.dependency.ArtifactSpec; import org.netbeans.modules.project.dependency.Dependency; import org.netbeans.modules.project.dependency.DependencyResult; +import org.netbeans.modules.project.dependency.ProjectScopes; import org.netbeans.modules.project.dependency.Scope; import org.netbeans.modules.project.dependency.SourceLocation; import org.netbeans.modules.xml.xam.ModelSource; @@ -70,7 +72,7 @@ import org.openide.util.WeakListeners; * * @author sdedic */ -class MavenDependencyResult implements org.netbeans.modules.project.dependency.DependencyResult, PropertyChangeListener { +class MavenDependencyResult implements org.netbeans.modules.project.dependency.DependencyResult, ProjectScopes, PropertyChangeListener { final Project ideProject; final NbMavenProject mavenProject; final Dependency rootNode; @@ -94,6 +96,21 @@ class MavenDependencyResult implements org.netbeans.modules.project.dependency.D this.problems = problems; } + @Override + public ProjectScopes getScopes() { + return this; + } + + @Override + public Collection<? extends Scope> scopes() { + return MavenDependenciesImplementation.scope2Maven.keySet(); + } + + @Override + public Collection<? extends Scope> implies(Scope s, boolean direct) { + return (direct ? MavenDependenciesImplementation.directScopes : MavenDependenciesImplementation.impliedScopes).getOrDefault(s, Collections.emptySet()); + } + @Override public Collection<FileObject> getDependencyFiles() { File file = mavenProject.getMavenProject().getFile(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected] For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
