org.nuxeo.build.maven.graph.Graph.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.build.maven.graph.Graph.java

Source

/*
 * (C) Copyright 2006-2011 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu, jcarsique, slacoin
 */
package org.nuxeo.build.maven.graph;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.project.MavenProject;
import org.apache.tools.ant.BuildException;
import org.nuxeo.build.maven.ArtifactDescriptor;
import org.nuxeo.build.maven.Logger;
import org.nuxeo.build.maven.MavenClient;
import org.nuxeo.build.maven.MavenClientFactory;
import org.nuxeo.build.maven.filter.Filter;
import org.nuxeo.build.maven.filter.VersionManagement;

/**
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 *
 */
public class Graph {

    protected MavenClient maven;

    public final TreeMap<String, Node> nodes = new TreeMap<String, Node>();

    public final LinkedList<Node> roots = new LinkedList<Node>();

    protected Resolver resolver = new Resolver(this);

    protected Map<String, Artifact> file2artifacts = new HashMap<String, Artifact>();

    // manage versions from dependency management -> lazy initialized when
    // required. (by calling artifact:resolveFile without a version)
    protected VersionManagement vmgr;

    protected boolean shouldLoadDependencyManagement = false;

    public Graph(MavenClient maven) {
        this.maven = maven;
        this.vmgr = new VersionManagement();
    }

    public VersionManagement getVersionManagement() {
        return vmgr;
    }

    public void setShouldLoadDependencyManagement(boolean shouldLoadDependencyManagement) {
        this.shouldLoadDependencyManagement = shouldLoadDependencyManagement;
    }

    public boolean shouldLoadDependencyManagement() {
        return shouldLoadDependencyManagement;
    }

    public MavenClient getMaven() {
        return maven;
    }

    public List<Node> getRoots() {
        return roots;
    }

    public Collection<Node> getNodes() {
        return nodes.values();
    }

    public Artifact getArtifactByFile(String fileName) {
        return file2artifacts.get(fileName);
    }

    public void collectNodes(Collection<Node> nodesToCollect) {
        for (Node node : roots) {
            node.collectNodes(nodesToCollect);
        }
    }

    public void collectNodes(Collection<Node> nodesToCollect, Filter filter) {
        for (Node node : roots) {
            node.collectNodes(nodesToCollect, filter);
        }
    }

    public Node[] getNodesArray() {
        return nodes.values().toArray(new Node[nodes.size()]);
    }

    public Node findFirst(String pattern) {
        return findFirst(pattern, false);
    }

    public Node findFirst(String pattern, boolean stopIfNotUnique) {
        SortedMap<String, Node> map = nodes.subMap(pattern + ':', pattern + ((char) (':' + 1)));
        int size = map.size();
        if (size == 0) {
            return null;
        }
        if (stopIfNotUnique && size > 1) {
            throw new BuildException("Pattern '" + pattern
                    + "' cannot be resolved to a unique node. Matching nodes are: " + map.values());
        }
        return map.get(map.firstKey());
    }

    public Collection<Node> find(String pattern) {
        SortedMap<String, Node> map = nodes.subMap(pattern + ':', pattern + ((char) (':' + 1)));
        return map.values();
    }

    /**
     * Add a root node given an artifact pom. This can be used by the embedder
     * maven mojo to initialize the graph with the current pom.
     */
    public Node addRootNode(MavenProject pom) {
        return getRootNode(pom, pom.getArtifact());
    }

    public Node addRootNode(String key) {
        ArtifactDescriptor ad = new ArtifactDescriptor(key);
        Artifact artifact = ad.getBuildArtifact();
        return getRootNode(artifact);
    }

    public Node getRootNode(Artifact artifact) {
        MavenProject pom = resolver.load(artifact);
        return getRootNode(pom, artifact);
    }

    /**
     * @since 1.10.2
     */
    public Node getRootNode(MavenProject pom, Artifact artifact) {
        Node node = nodes.get(Node.createNodeId(artifact));
        if (node == null) {
            node = new Node(Graph.this, artifact, pom);
            nodes.put(node.getId(), node);
            nodesByArtifact.put(artifact, node);
            roots.add(node);
        }
        return node;
    }

    public Resolver getResolver() {
        return resolver;
    }

    public Node lookup(String id) {
        return nodes.get(id);
    }

    public Node lookup(Artifact artifact) {
        return lookup(Node.createNodeId(artifact));
    }

    @SuppressWarnings("unchecked")
    public Node findNode(ArtifactDescriptor ad) {
        String key = ad.getNodeKeyPattern();
        Collection<Node> nodesToParse = null;
        if (key == null) {
            nodesToParse = getNodes();
        } else {
            nodesToParse = find(key);
        }
        Node returnNode = null;
        for (Node node : nodesToParse) {
            Artifact artifact = node.getArtifact();
            if (ad.artifactId != null && !ad.artifactId.equals(artifact.getArtifactId())) {
                continue;
            }
            if (ad.groupId != null && !ad.groupId.equals(artifact.getGroupId())) {
                continue;
            }
            if (ad.version != null && !ad.version.equals(artifact.getVersion())) {
                continue;
            }
            if (ad.type != null && !ad.type.equals(artifact.getType())) {
                continue;
            }
            try {
                if (returnNode != null && artifact.getSelectedVersion()
                        .compareTo(returnNode.getArtifact().getSelectedVersion()) < 0) {
                    continue;
                }
            } catch (OverConstrainedVersionException e) {
                MavenClientFactory.getLog().error("Versions comparison failed on " + artifact, e);
            }
            returnNode = node;
        }
        return returnNode;
    }

    public MavenProject loadPom(Artifact artifact) {
        if ("system".equals(artifact.getScope()))
            return null;
        try {
            return maven.getProjectBuilder().buildFromRepository(
                    // this create another Artifact instance whose type is 'pom'
                    maven.getArtifactFactory().createProjectArtifact(artifact.getGroupId(),
                            artifact.getArtifactId(), artifact.getVersion()),
                    maven.getRemoteRepositories(), maven.getLocalRepository());
        } catch (Exception e) {
            MavenClientFactory.getLog().error(e.getMessage(), e);
            return null;
        }
    }

    public final IdentityHashMap<Artifact, Node> nodesByArtifact = new IdentityHashMap<Artifact, Node>();

    protected class NodesInjector implements org.apache.maven.artifact.resolver.ResolutionListener {

        protected final HashSet<Node> filteredNodes = new HashSet<Node>();

        protected final Stack<Node> parentNodes = new Stack<Node>();

        protected final Node rootNode;

        protected final Filter filter;

        protected final int maxDepth;

        protected Node currentNode;

        protected NodesInjector(Node node, Filter filter, int maxDepth) {
            this.currentNode = node;
            this.rootNode = node;
            this.filter = filter;
            this.maxDepth = maxDepth;
            this.rootNode.state = Node.INCLUDED;
        }

        @Override
        public void testArtifact(Artifact node) {
            debug("testArtifact: artifact=" + node);
        }

        @Override
        public void startProcessChildren(Artifact artifact) {
            debug("startProcessChildren: artifact=" + artifact);

            if (!currentNode.getArtifact().equals(artifact)) {
                throw new IllegalStateException(
                        "Artifact was expected to be " + currentNode.getArtifact() + " but was " + artifact);
            }

            parentNodes.push(currentNode);
        }

        @Override
        public void endProcessChildren(Artifact artifact) {
            Node node = parentNodes.pop();

            debug("endProcessChildren: artifact=" + artifact);

            if (node == null) {
                throw new IllegalStateException("Parent dependency node was null");
            }

            if (!node.getArtifact().equals(artifact)) {
                throw new IllegalStateException("Parent dependency node artifact was expected to be "
                        + node.getArtifact() + " but was " + artifact);
            }
        }

        @Override
        public void includeArtifact(Artifact artifact) {
            debug("includeArtifact: artifact=" + artifact);

            Node node = nodesByArtifact.get(artifact);

            if (node != null) {
                debug("already included, returning : artifact=" + artifact);
                return;
            }

            if (!isCurrentNodeIncluded()) {
                debug("not included, returning : artifact=" + currentNode);
                return;
            }

            addNode(artifact);

        }

        @Override
        public void omitForNearer(Artifact omitted, Artifact kept) {
            debug("omitForNearer: omitted=" + omitted + "( " + System.identityHashCode(omitted) + ") kept=" + kept
                    + "(" + System.identityHashCode(kept) + ")");

            if (!omitted.getDependencyConflictId().equals(kept.getDependencyConflictId())) {
                throw new IllegalArgumentException("Omitted artifact dependency conflict id "
                        + omitted.getDependencyConflictId() + " differs from kept artifact dependency conflict id "
                        + kept.getDependencyConflictId());
            }

            if (!isCurrentNodeIncluded()) {
                debug("not included, returning : artifact=" + currentNode);
                return;
            }

            // Node omittedNode = nodesByArtifact.get(omitted);
            //
            // if (omittedNode != null) {
            // removeNode(omitted);
            // validateDependencyTree();
            // omittedNode.state = Node.OMITTED;
            // }

            Node keptNode = nodesByArtifact.get(kept);

            if (keptNode == null) {
                keptNode = addNode(kept);
            } else {
                addEdges(keptNode);
                currentNode = keptNode;
            }

        }

        @Override
        public void updateScope(Artifact artifact, String scope) {
            debug("updateScope: artifact=" + artifact + ", scope=" + scope);
        }

        @Deprecated
        @Override
        public void manageArtifact(Artifact artifact, Artifact replacement) {
            debug("manageArtifact: artifact=" + artifact + ", replacement=" + replacement);
        }

        @Override
        public void omitForCycle(Artifact artifact) {
            warn("omitForCycle: artifact=" + artifact);
        }

        @Override
        public void updateScopeCurrentPom(Artifact artifact, String ignoredScope) {
            debug("updateScopeCurrentPom: artifact=" + artifact + ", scopeIgnored=" + ignoredScope);
        }

        @Override
        public void selectVersionFromRange(Artifact artifact) {
            warn("selectVersionFromRange: artifact=" + artifact);
        }

        @Override
        public void restrictRange(Artifact artifact, Artifact replacement, VersionRange newRange) {
            warn("restrictRange: artifact=" + artifact + ", replacement=" + replacement + ", versionRange="
                    + newRange);
        }

        /**
         * The log to write debug messages to.
         */
        protected final Logger logger = MavenClientFactory.getLog();

        /**
         * Writes the specified message to the log at debug level with
         * indentation for the current node's depth.
         *
         * @param message the message to write to the log
         */
        protected void debug(String message) {

            if (logger.isDebugEnabled() == false) {
                return;
            }

            int depth = parentNodes.size();

            StringBuffer buffer = new StringBuffer();

            for (int i = 0; i < depth; i++) {
                buffer.append("  ");
            }

            buffer.append(message);

            logger.debug(buffer.toString());
        }

        protected void warn(String message) {

            int depth = parentNodes.size();

            StringBuffer buffer = new StringBuffer();

            for (int i = 0; i < depth; i++) {
                buffer.append("  ");
            }

            buffer.append(message);

            logger.info(buffer.toString());
        }

        protected boolean isCurrentNodeIncluded() {

            for (Iterator<Node> iterator = parentNodes.iterator(); iterator.hasNext();) {
                Node node = iterator.next();

                if (node.state != Node.INCLUDED && node.state != Node.FILTERED) {
                    return false;
                }
            }

            return true;
        }

        protected Node addNode(Artifact artifact) {
            Node node = createNode(artifact);
            Node previousNode = nodesByArtifact.put(artifact, node);
            nodes.put(node.id, node);

            if (previousNode != null) {
                throw new IllegalStateException("Duplicate node registered for artifact: " + node.getArtifact());
            }
            currentNode = node;

            debug("indexed artifact=" + artifact + ",identity=" + System.identityHashCode(artifact));

            return node;
        }

        protected void removeNode(Artifact artifact) {
            Node node = nodesByArtifact.remove(artifact);
            if (node == null) {
                warn("removing not indexed " + System.identityHashCode(artifact) + " : artifact=" + artifact);
                node = nodes.get(Node.createNodeId(artifact));
            }
            nodes.remove(node.id);
            if (filteredNodes.remove(node)) {
                debug("Reset filtering : " + node);
            }
            for (Edge e : node.edgesOut) {
                e.out.edgesIn.remove(e);
            }
            for (Edge e : node.edgesIn) {
                e.in.edgesOut.remove(e);
            }
            if (!artifact.equals(node.getArtifact())) {
                throw new IllegalStateException("Removed dependency node artifact was expected to be " + artifact
                        + " but was " + node.getArtifact());
            }

            debug("unindexed   artifact=" + artifact + ",identity= " + System.identityHashCode(artifact));
        }

        protected Node createNode(Artifact artifact) {
            MavenProject pom = resolver.load(artifact);
            Node node = new Node(Graph.this, artifact, pom);

            addEdges(node);

            return node;
        }

        protected void addEdges(Node out) {

            if (parentNodes.isEmpty()) {
                out.state = Node.INCLUDED;
                return;
            }

            Node in = parentNodes.peek();
            Edge edge = new Edge(in, out);

            switch (out.state) {
            case Node.UNKNOWN: // node injection
                if (!accept(edge)) {
                    filteredNodes.add(out);
                    out.state = Node.FILTERED;
                    return;
                }
                out.state = Node.INCLUDED;
                break;
            case Node.FILTERED:
                if (accept(edge)) {
                    filteredNodes.remove(out);
                    warn("unfiltering : artifact=" + out.artifact);
                    out.state = Node.INCLUDED;
                }
                break;
            case Node.INCLUDED: // edges injection
                break;
            default:
                throw new IllegalStateException("Cannot add edge : artifact=" + out.artifact);
            }

            out.edgesIn.add(edge);
            in.edgesOut.add(edge);

        }

        protected boolean accept(Edge edge) {

            if (edge.in.state == Node.FILTERED) {
                debug("filtering edge (inherited from parent) : artifact=" + edge.out);
                return false;
            }

            if (parentNodes.size() > maxDepth) {
                debug("filtering edge (max depth) : artifact=" + edge.out);
                return false;
            }

            if (!filter.accept(edge)) {
                debug("filtering edge (filter) : artifact=" + edge.out.artifact);
                return false;
            }

            return true;
        }

        protected void removeFiltered() {
            Iterator<Node> it = filteredNodes.iterator();
            while (it.hasNext()) {
                Node n = it.next();
                it.remove();
                removeNode(n.artifact);
            }
        }

    }

    protected class UnreferencedNodesValidator extends AbstractGraphVisitor {

        protected HashSet<Node> unreferencedNodes = new HashSet<Node>();

        @Override
        public boolean visitNode(Node node) {
            if (nodes.get(node.id) == null && !roots.contains(node)) {
                unreferencedNodes.add(node);
                // re-inject unreferenced node
                nodes.put(node.id, node);
            }
            return true;
        }

        @Override
        public boolean visitEdge(Edge edge) {
            return true;
        }
    }

    protected void validateDependencyTree() {
        UnreferencedNodesValidator validator = new UnreferencedNodesValidator();
        validator.process(this);
        if (validator.unreferencedNodes.size() > 0) {
            MavenClientFactory.getLog().warn("Fixed unreferenced nodes : " + validator.unreferencedNodes);
        }
    }

    public void resolveDependencyTree(Node node, Filter filter, int depth) {
        MavenClientFactory.getLog().info("Resolving dependencies for " + node);
        final NodesInjector injector = new NodesInjector(node, filter, depth);
        try {
            maven.resolveDependencyTree(node.artifact, new ArtifactFilter() {

                public boolean include(Artifact artifact) {
                    return false;
                }
            }, injector);
        } catch (Exception cause) {
            throw new Error("Cannot resolve dependency tree for " + node, cause);
        }
        validateDependencyTree();

        // remove filtered artifacts
        MavenClientFactory.getLog().info("Filtering dependency tree");
        injector.removeFiltered();

        validateDependencyTree();

    }

}