All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.maven.queries.MavenDependenciesImplementation Maven / Gradle / Ivy

The newest version!
/*
 * 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.maven.queries;

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.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
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.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.Constants;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.PluginPropertyUtils;
import org.netbeans.modules.maven.embedder.DependencyTreeFactory;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.netbeans.modules.project.dependency.ArtifactSpec;
import org.netbeans.modules.project.dependency.Dependency;
import org.netbeans.modules.project.dependency.DependencyChangeException;
import org.netbeans.modules.project.dependency.DependencyResult;
import org.netbeans.modules.project.dependency.ProjectDependencies;
import org.netbeans.modules.project.dependency.ProjectOperationException;
import org.netbeans.modules.project.dependency.Scope;
import org.netbeans.modules.project.dependency.Scopes;
import org.netbeans.modules.project.dependency.spi.ProjectDependenciesImplementation;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;

/**
 *
 * @author sdedic
 */
@ProjectServiceProvider(service = ProjectDependenciesImplementation.class, projectType="org-netbeans-modules-maven")
public class MavenDependenciesImplementation implements ProjectDependenciesImplementation {
    private static final String ELEMENT_PATH = "path"; // NOI18N
    private static final String ELEMENT_PROCESSOR_PATHS = "annotationProcessorPaths"; // NOI18N

    private static final Logger LOG = Logger.getLogger(MavenDependenciesImplementation.class.getName());
    
    private final Project project;
    private NbMavenProject nbMavenProject;
    
    private static final Set SCOPES = new HashSet<>();
    
    static {
        SCOPES.add(Scopes.COMPILE);
        SCOPES.add(Scopes.RUNTIME);
        SCOPES.add(Scopes.EXTERNAL);
        SCOPES.add(Scopes.TEST);
    }
    
    public MavenDependenciesImplementation(Project project) {
        this.project = project;
    }
    
    private void init() {
        synchronized (this) {
            if (nbMavenProject != null) {
                return;
            }
            nbMavenProject = project.getLookup().lookup(NbMavenProject.class);
        }
    }
    
    /**
     * Mapping from the abstract scopes to Maven
     */
    static final Map scope2Maven = new HashMap<>();

    /**
     * Mapping from maven to the abstract scopes
     */
    static final Map maven2Scope = new HashMap<>();
    
    static final Map> directScopes = new HashMap<>();
    static final Map> impliedScopes = new HashMap<>();
    static final Map> reverseImplied = new HashMap<>();
    
    static {
        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 scope2Maven.getOrDefault(s, "compile");
    }
    
    private ArtifactSpec mavenToArtifactSpec(Artifact a) {
        FileObject f = a.getFile() == null ? null : FileUtil.toFileObject(a.getFile());
        String v = a.getVersion();
        if ("".equals(v)) { // NOI18N
            v = null;
        }
        if (a.isSnapshot()) {
            return ArtifactSpec.createSnapshotSpec(a.getGroupId(), a.getArtifactId(), 
                    a.getType(), a.getClassifier(), v, a.isOptional(), f, a);
        } else {
            return ArtifactSpec.createVersionSpec(a.getGroupId(), a.getArtifactId(), 
                    a.getType(), a.getClassifier(), v, a.isOptional(), f, a);
        }
    }
    
    /**
     * 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;
        
        try {
            proj = impl.getFreshOriginalMavenProject().get();
        } catch (ExecutionException | InterruptedException | CancellationException ex) {
            throw new ProjectOperationException(project, ProjectOperationException.State.ERROR, "Unexpected exception", ex);
        }
        List 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);
        }
        PluginPropertyUtils.PluginConfigPathParams params = new PluginPropertyUtils.PluginConfigPathParams(
            Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_COMPILER, ELEMENT_PROCESSOR_PATHS, ELEMENT_PATH);
        List arts = PluginPropertyUtils.getPluginPathProperty(project, params, false, null);
        if (arts != null) {
            for (Artifact a : arts) {
                ArtifactSpec ann = mavenToArtifactSpec(a);
                Dependency annDep = Dependency.create(ann, Scopes.PROCESS, Collections.emptyList(), ann);
                children.add(annDep);
            }
        }

        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 implies(Scope s) {
        return impliedScopes.getOrDefault(s, Collections.emptyList());
    }
    
    static Collection 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",
        "ERR_DependencyMissing=Cannot resolve project dependencies",
        "ERR_DependencyGraphError=Cannot construct dependency graph",
        "ERR_DependencyGraphOffline=Not all artifacts are available locally, run priming build."
    })
    @Override
    public DependencyResult findDependencies(ProjectDependencies.DependencyQuery query) {
        init();
        Collection scopes = query.getScopes();
        Dependency.Filter filter = query.getFilter();
        
        MavenProject mp = nbMavenProject.getMavenProject();
        if (NbMavenProject.isErrorPlaceholder(mp)) {
            if (nbMavenProject.isMavenProjectLoaded()) {
                throw new ProjectOperationException(project, ProjectOperationException.State.BROKEN, Bundle.ERR_DependencyOnBrokenProject());
            } else {
                throw new ProjectOperationException(project, ProjectOperationException.State.UNINITIALIZED, Bundle.ERR_DependencyNotPrimed());
            }
        }
        
        MavenEmbedder embedder;
        
        if (query.isOffline()) {
            try {
                if (query.isFlushChaches()) {
                    embedder = EmbedderFactory.createProjectLikeEmbedder();
                } else {
                    embedder = EmbedderFactory.getProjectEmbedder();
                }
            } catch (PlexusContainerException ex) {
                throw new ProjectOperationException(project, ProjectOperationException.State.ERROR, Bundle.ERR_DependencyGraphError(), ex.getCause());
            }
        } else {
            try {
                embedder = EmbedderFactory.getOnlineEmbedder();
            } catch (IllegalStateException ex) {
                // yuck, the real exc. is wrapped
                throw new ProjectOperationException(project, ProjectOperationException.State.ERROR, Bundle.ERR_DependencyGraphError(), ex.getCause());
            }
        }
        
        if (query.getScopes().contains(Scopes.DECLARED)) {
            return findDeclaredDependencies(query, embedder);
        }
        
        Collection mavenScopes = scopes.stream().
                map(MavenDependenciesImplementation::mavenScope).
                filter(Objects::nonNull).
                collect(Collectors.toList());
        
        org.apache.maven.shared.dependency.tree.DependencyNode n;
        try {
            n = DependencyTreeFactory.createDependencyTree(mp, embedder, mavenScopes);
        } catch (MavenExecutionException ex) {
            throw new ProjectOperationException(project, ProjectOperationException.State.OK, Bundle.ERR_DependencyGraphError(), ex.getCause());
        } catch (AssertionError e) {
            if (EmbedderFactory.isOfflineException(e)) {
                // HACK: special assertion error from our embedder
                throw new ProjectOperationException(project, ProjectOperationException.State.OFFLINE, Bundle.ERR_DependencyGraphOffline());
            } else {
                throw e;
            }
        }
        Set allScopes = Stream.concat(scopes.stream(), scopes.stream().flatMap(x -> impliedBy(x).stream())).collect(Collectors.toSet());
        Dependency.Filter compositeFiter = new Dependency.Filter() {
            @Override
            public boolean accept(Scope s, ArtifactSpec a) {
                return allScopes.contains(s) && 
                    (filter == null || filter.accept(s, a));
            }
        };
        
        Converter c = new Converter(compositeFiter);

        // TODO: temporary hack: one has to explicitly ask for PROCESS, to avoid implications from RUNTIME scope at the moment
        if (scopes.contains(Scopes.PROCESS)) {
            PluginPropertyUtils.PluginConfigPathParams params = new PluginPropertyUtils.PluginConfigPathParams(
                Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_COMPILER, "annotationProcessorPaths", "path" // NOI18N
            );
            // TODO: process transitive dependencies as proper children
            List arts = PluginPropertyUtils.getPluginPathProperty(project, params, true, null);
            if (arts == null) {
                arts = Collections.emptyList();
            }
            c.annotationProcessors = arts;
        }
        
        Dependency root = c.convertDependencies(n);
        return new MavenDependencyResult(nbMavenProject.getMavenProject(), root, new ArrayList<>(scopes), c.broken, 
                project, nbMavenProject);
    }
    
    static Scope scope(Artifact a) {
        String as = a.getScope();
        if (as == null) {
            return Scopes.COMPILE;
        }
        switch (as) {
            case Artifact.SCOPE_COMPILE:
                return Scopes.COMPILE;
                
            case Artifact.SCOPE_RUNTIME:
            case Artifact.SCOPE_COMPILE_PLUS_RUNTIME:
            case Artifact.SCOPE_RUNTIME_PLUS_SYSTEM:
                return Scopes.RUNTIME;
                
            case Artifact.SCOPE_IMPORT:
            case Artifact.SCOPE_SYSTEM:
                return Scopes.EXTERNAL;
                
            case Artifact.SCOPE_TEST:
                return Scopes.TEST;
            default:
                return Scopes.COMPILE;
        }
    }
    
    private static String getFullArtifactId(Artifact a) {
        if (a.getDependencyTrail() == null) {
            return "/" + a.getId();
        } else {
            return String.join("/", a.getDependencyTrail()) + "/" + a.getId(); // NOI18N
        }
    }
    
    private class Converter {
        final Map> realNodes = new HashMap<>();
        final Dependency.Filter filter;
        final Set broken = new HashSet<>();
        List annotationProcessors = Collections.emptyList();

        public Converter(Dependency.Filter filter) {
            this.filter = filter;
        }

        private void findRealNodes(org.apache.maven.shared.dependency.tree.DependencyNode n) {
            if (n.getArtifact() == null) {
                return;
            }
            Artifact a = n.getArtifact();
            if (n.getState() != org.apache.maven.shared.dependency.tree.DependencyNode.INCLUDED) {
                return;
            }
            // register (if not present) using plain artifact ID, but also using the full path, which will be preferred for the lookup.
            realNodes.putIfAbsent(a.getId(), n.getChildren());
            realNodes.put(getFullArtifactId(a), n.getChildren());

            for (org.apache.maven.shared.dependency.tree.DependencyNode c : n.getChildren()) {
                findRealNodes(c);
            }
        }

        private Dependency convertDependencies(org.apache.maven.shared.dependency.tree.DependencyNode n) {
            findRealNodes(n);
            return convert2(true, n);
        }


        private Dependency convert2(boolean root, org.apache.maven.shared.dependency.tree.DependencyNode n) {
            List ch = new ArrayList<>();
            
            List children = n.getChildren();
            org.apache.maven.artifact.Artifact thisArtifact = n.getArtifact();
            org.apache.maven.artifact.Artifact relatedArtifact = n.getRelatedArtifact();

            switch (n.getState()) {
                case org.apache.maven.shared.dependency.tree.DependencyNode.OMITTED_FOR_CYCLE:
                case org.apache.maven.shared.dependency.tree.DependencyNode.OMITTED_FOR_DUPLICATE:
                    // TODO: unless the client specifies NOT to eliminate duplicates from the tree,
                    // we need to include the duplicate including the children, to form a correct full dependency tree. 
                    if (relatedArtifact != null) {
                        children = realNodes.get(getFullArtifactId(n.getRelatedArtifact()));
                    }
                    if (children == null) {
                        children = realNodes.getOrDefault(n.getArtifact().getId(), n.getChildren());
                    }
                    break;
                case org.apache.maven.shared.dependency.tree.DependencyNode.OMITTED_FOR_CONFLICT:
                    // there are two cases when OMITTED_FOR_CONFLICT is used: 
                    // 1. another version is actually used, which means that unless the client requests to omit 
                    //    duplicates, we need to include the ACTUAL dependency's artifact version and its children.
                    // 2. different artifact is related, meaning this dependency is forcibly excluded. In this
                    //    case, the dependency should not be reported at all unless (TODO:) client requests full 
                    //    dependency info
                    if (relatedArtifact != null) {
                        if (Objects.equals(relatedArtifact.getGroupId(), thisArtifact.getGroupId()) &&
                            Objects.equals(relatedArtifact.getArtifactId(), thisArtifact.getArtifactId())) {
                            thisArtifact = relatedArtifact;
                            // TODO: report the original artifact to the client when Relations appear in the API.
                            // use children from the artifact:
                            children = realNodes.getOrDefault(relatedArtifact.getId(), n.getChildren());
                        } else {
                            // exclude the artifact
                            return null;
                        }
                    } else {
                        // the conflict is not known. Omit, because we do not have any information and this 
                        // artifact does not appear in the tree.
                        return null;
                    }
            }

            if (root && !annotationProcessors.isEmpty()) {
                for (Artifact a : annotationProcessors) {
                    ArtifactSpec annoSpec = mavenToArtifactSpec(a);
                    ch.add(Dependency.create(annoSpec, Scopes.PROCESS, Collections.emptyList(), annoSpec));
                }
            }

            for (org.apache.maven.shared.dependency.tree.DependencyNode c : children) {
                Dependency cd = convert2(false, c);
                if (cd != null) {
                    ch.add(cd);
                }
            }
            ArtifactSpec aspec;
            aspec = mavenToArtifactSpec(thisArtifact);
            if (aspec.getLocalFile() == null) {
                broken.add(aspec);
            }
            Scope s = scope(thisArtifact);

            if (!root && !filter.accept(s, aspec)) {
                return null;
            }

            return Dependency.create(aspec, s, ch, n);
        }
    }
    
    static boolean dependencyEquals(Dependency dspec, org.apache.maven.model.Dependency mavenD) {
        ArtifactSpec spec = dspec.getArtifact();
        String mavenClass = mavenD.getClassifier();
        if ("".equals(mavenClass)) {
            mavenClass = null;
        }
        if (!(
            Objects.equals(spec.getGroupId(), mavenD.getGroupId()) &&
            Objects.equals(spec.getArtifactId(), mavenD.getArtifactId()) &&    
            Objects.equals(spec.getClassifier(), mavenClass) &&
            Objects.equals(spec.getVersionSpec(), mavenD.getVersion()))) {
            return false;
        }
        if (spec.getType() != null && !Objects.equals(spec.getType(), mavenD.getType())) {
            return false;
        }
        if (dspec.getScope() != null) {
            if (!Objects.equals(mavenScope(dspec.getScope()), mavenD.getScope())) {
                return false;
            }
        }
        return true;
    }
    
    static boolean artifactEquals(ArtifactSpec spec, Artifact mavenA) {
        String mavenClass = mavenA.getClassifier();
        if ("".equals(mavenClass)) {
            mavenClass = null;
        }
        if (!(
            Objects.equals(spec.getGroupId(), mavenA.getGroupId()) &&
            Objects.equals(spec.getArtifactId(), mavenA.getArtifactId()) &&    
            Objects.equals(spec.getClassifier(), mavenClass) &&
            Objects.equals(spec.getVersionSpec(), mavenA.getVersion()))) {
            return false;
        }
        if (spec.getType() != null && !Objects.equals(spec.getType(), mavenA.getType())) {
            return false;
        }
        return true;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy