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

org.jvnet.maven.plugin.antrun.DependencyGraph Maven / Gradle / Ivy

package org.jvnet.maven.plugin.antrun;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;

import java.io.File;
import java.util.AbstractList;
import java.util.ArrayList;
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.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Queue;
import java.util.LinkedList;

/**
 * Graph of dependencies among Maven artifacts.
 *
 * 

* This graph, which consists of interconnected {@link Node}s and {@link Edge}s, * represents a complete dependency graph rooted at the given Maven module. * The constructor recursively parses all POMs for the dependency and builds this information. * *

* For example, if you have 4 modules A,B,C, and D that has the dependencies among them as follows: *

 * A->B,C
 * B->D
 * C->D
 * 
*

* Then if you construct a graph from 'A', you'll get a graph of four nodes (each representing * maven module A,B,C, and D) and four edges (each representing dependencies among them.) * *

* Once constructed, a graph is accessible in several ways: * *

    *
  • * Start with {@link #getRoot() the root node} and traverse through edges like * {@link Node#getForwardEdges(DependencyGraph)}. * *
  • * Use {@link #createSubGraph(GraphVisitor)} and obtain a sub-graph that matches the given * criteria. *
* * * @author Kohsuke Kawaguchi */ public final class DependencyGraph { private final MavenComponentBag bag = MavenComponentBag.get(); private final Node root; /** * All {@link Node}s keyed by "groupId:artifactId:classifier" */ private final Map nodes = new TreeMap(); /** * If true, ignore the {@link Node} that have failed to load. */ private final boolean tolerateBrokenPOMs; /** * Forward edges. * * Edges are kept on {@link DependencyGraph} so that we can * create multiple {@link DependencyGraph}s that share the same node set. */ private final Map> forwardEdges = new HashMap>(); private final Map> backwardEdges = new HashMap>(); /** * Creates a full dependency graph with the given artifact at the top. */ public DependencyGraph(Artifact root, boolean tolerateBrokenPOMs) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { this.tolerateBrokenPOMs = tolerateBrokenPOMs; Queue q = new LinkedList(); this.root = buildNode(root,q); visitBFS(q); } /** * Creates a full dependency graph with the given project at the top. */ public DependencyGraph(MavenProject root, boolean tolerateBrokenPOMs) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { this.tolerateBrokenPOMs = tolerateBrokenPOMs; Queue q = new LinkedList(); this.root = buildNode(root,q); visitBFS(q); } /** * Completes the graph in a breadth-first fashion. */ private void visitBFS(Queue q) throws ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException { while(!q.isEmpty()) q.poll().expand(this,q); } /** * Used to create a subgraph. *

* This method assumes that all nodes and edges are connected, * hence the 'private' access. Use {@link #createSubGraph(GraphVisitor)} * to construct a subset reliably. */ private DependencyGraph(Node root, Collection nodes, Collection edges, boolean tolerateBrokenPOMs) { this.tolerateBrokenPOMs = tolerateBrokenPOMs; if(nodes.isEmpty()) root = null; // graph is empty this.root = root; if(root!=null) { Set reachable = new HashSet(); reachable.add(root); // root is always reachable if(!nodes.contains(root)) throw new IllegalArgumentException("root "+root+" is not a part of nodes:"+nodes); for (Node n : nodes) this.nodes.put(n.getId(),n); for (Edge e : edges) { if(contains(e.src) && contains(e.dst)) { e.addEdge(forwardEdges,e.src); e.addEdge(backwardEdges,e.dst); reachable.add(e.dst); } } // some nodes were unreachable if(reachable.size()!=this.nodes.size()) throw new IllegalArgumentException(); } } /** * Gets the root node. * *

* This is non-null unless this graph is {@link #isEmpty() empty}. */ public Node getRoot() { return root; } /** * Returns true if the graph contains nothing at all. */ public boolean isEmpty() { return root==null; } /** * Returns all nodes in this graph. */ public Collection getAllNodes() { return nodes.values(); } /** * Checks if the graph contains the given node. */ public boolean contains(Node node) { return nodes.containsKey(node.getId()); } /** * Gets the associated {@link Node}, or null if none exists. */ public Node toNode(Artifact a) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = a.getGroupId()+':'+a.getArtifactId()+':'+a.getClassifier(); return nodes.get(id); } /** * Gets the associated {@link Node}. If none exists, it will be created. * *

* Graph building has to be done breadth-first fashion so that the version * conflit resolution happens correctly — that is, if there exists dependency like * A -> B(1.0) -> C(2.0) and A -> C(2.2), then we want to pick up C(2.2), not C(2.0). * The criteria to do this in Maven is the length of the dependency chain, and that * can be done naturally by BFS. So we pass around a {@link Queue} to keep track of the remaining * {@link Node}s to be expanded. */ private Node buildNode(Artifact a, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = a.getGroupId()+':'+a.getArtifactId()+':'+a.getClassifier(); Node n = nodes.get(id); if(n==null) { n = new Node(a,this,q); nodes.put(id, n); } return n; } private Node buildNode(MavenProject p, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = p.getGroupId()+':'+p.getArtifactId()+":null"; Node n = nodes.get(id); if(n==null) { n = new Node(p, q); nodes.put(id, n); } return n; } /** * Accepts the visitor and invoke its visitor methods to create a sub-graph. * *

* This method is convenient for obtaining a sub-graph of dependencies * by filtering out nodes/edges. For example, to obtain all the transitive * dependencies that exclude provided/test dependencies, you can do: * *

     * createSubgraph(new {@link ScopeFilter}("compile","runtime"))
     * 
* * @return * A sub-graph of this graph that consists of nodes and edges for which the visitor returns true. * Can be an empty graph, but never null. */ public DependencyGraph createSubGraph(GraphVisitor visitor) { return createSubGraph(root,visitor); } /** * Accepts the visitor. Simply an alias for {@link #createSubGraph(GraphVisitor)}. */ public void accept(GraphVisitor visitor) { createSubGraph(visitor); } /** * Creates a full subgraph rooted at the given node. */ public DependencyGraph createSubGraph(Node root) { return createSubGraph(root,new DefaultGraphVisitor()); } /** * Visits the graph started at the given node, and creates a sub-graph * from visited nodes and edges. * *

* This is the slightly generalized version of {@link #createSubGraph(GraphVisitor)} */ public DependencyGraph createSubGraph(Node node, GraphVisitor visitor) { Set visited = new HashSet(); Set nodes = new HashSet(); List edges = new ArrayList(); Stack q = new Stack(); q.push(node); while(!q.isEmpty()) { DependencyGraph.Node n = q.pop(); if(visitor.visit(n)) { nodes.add(n); for (Edge e : n.getForwardEdges(this)) { if(visitor.visit(e)) { edges.add(e); if(visited.add(e.dst)) q.push(e.dst); } } } } return new DependencyGraph(node,nodes,edges, tolerateBrokenPOMs); } /** * Creates a sub-graph from the given set of nodes (which must be subset of * nodes in the current graph) with all edges { (u,v) | u \in nodes, v \in nodes } */ public DependencyGraph createSubGraph(Node root, Collection nodes) { List edges = new ArrayList(); for (List el : forwardEdges.values()) { for (Edge e : el) { if(nodes.contains(e.src) && nodes.contains(e.dst)) edges.add(e); } } return new DependencyGraph(root,nodes,edges, tolerateBrokenPOMs); } public String toString() { StringBuilder buf = new StringBuilder(); buf.append("DependencyGraph[root=").append(root).append(",\n"); buf.append(" nodes=[\n"); for (Node node : nodes.values()) buf.append(" ").append(node).append('\n'); buf.append(" ]\n"); buf.append(" edges=[\n"); for (Map.Entry> n : forwardEdges.entrySet()) { for (Edge e : n.getValue()) { buf.append(" ").append(e).append('\n'); } } buf.append(" ]\n]"); return buf.toString(); } private interface Resolver { File resolve() throws AbstractArtifactResolutionException; } private static final Resolver NULL = new Resolver() { public File resolve() { return null; } }; /** * Node, which represents an artifact. * *

* A single {@link Node} can be used in multiple {@link DependencyGraph} objects, * so the graph traversal method all takes {@link DependencyGraph} object * to determine the context in which the operation works. */ public static final class Node { /** * Basic properties of a module. * If {@link #pom} is non-null, this information is redundant, but it needs to be * kept separately for those rare cases where pom==null. */ public final String groupId,artifactId,version,type,classifier; private final MavenProject pom; private /*final*/ File artifactFile; private Resolver artifactResolver = NULL; /** * Represents the artifact that we want to fetch. */ private final Artifact artifact; private Node(Artifact artifact, DependencyGraph g, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { groupId = artifact.getGroupId(); artifactId = artifact.getArtifactId(); version = artifact.getVersion(); type = artifact.getType(); classifier = artifact.getClassifier(); this.artifact = artifact; if("system".equals(artifact.getScope())) { // system scoped artifacts don't have POM, so the attempt to load it will fail. pom = null; } else { pom = g.bag.mavenProjectBuilder.buildFromRepository( // this create another Artifact instance whose type is 'pom' g.bag.factory.createProjectArtifact(artifact.getGroupId(),artifact.getArtifactId(), artifact.getVersion()), g.bag.project.getRemoteArtifactRepositories(), g.bag.localRepository); q.add(this); // visit dependencies from this POM later } } private void checkArtifact(final Artifact artifact, final MavenComponentBag bag) { artifactResolver = new Resolver() { public File resolve() throws AbstractArtifactResolutionException { if(bag.project.getArtifact()==artifact) { // our own module. Trying to resolve this in the usual way is most likely to fail, // so use what we have, if any. artifactFile =artifact.getFile(); return artifactFile; } if(pom!=null) bag.resolveArtifact(artifact, pom.getRemoteArtifactRepositories()); else bag.resolveArtifact(artifact); artifactFile = artifact.getFile(); if(artifactFile==null) throw new IllegalStateException("Artifact is not resolved yet: "+artifact); return artifactFile; } }; } private Node(MavenProject pom, Queue q) { this.pom = pom; groupId = pom.getGroupId(); artifactId = pom.getArtifactId(); version = pom.getVersion(); type = pom.getPackaging(); // are these the same thing? classifier = null; artifact = pom.getArtifact(); q.add(this); // visit dependencies from this POM later } private void expand(DependencyGraph g, Queue q) throws ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException { checkArtifact(artifact,g.bag); loadDependencies(g,q); } private void loadDependencies(DependencyGraph g, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { for( Dependency d : (List)pom.getDependencies() ) { // the last boolean parameter is redundant, but the version that doesn't take this // has a bug. See MNG-2524 Artifact a = g.bag.factory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(), VersionRange.createFromVersion(d.getVersion()), d.getType(), d.getClassifier(), d.getScope(), false); // beware of Maven bug! make sure artifact got the value inherited from dependency assert a.getScope().equals(d.getScope()); try { new Edge(g,this,g.buildNode(a,q),d.getScope(),d.isOptional()); } catch (ProjectBuildingException e) { handleNodeResolutionException(g,e); } catch (ArtifactResolutionException e) { handleNodeResolutionException(g,e); } catch (ArtifactNotFoundException e) { handleNodeResolutionException(g,e); } } } private void handleNodeResolutionException(DependencyGraph g, Exception e) throws ProjectBuildingException { if (g.tolerateBrokenPOMs) System.err.println("Failed to parse dependencies of " + getId() + ". trail=" + getTrail(g)); else throw new ProjectBuildingException(getId(), "Failed to parse dependencies of " + getId() + ". trail=" + getTrail(g), e); } /** * Gets the parsed POM for this artifact. * * @return null * if POM is not available for this module. * That can happen for example for system-scoped artifacts. */ public MavenProject getProject() { return pom; } /** * Gets the artifact file, like a jar. * * @return * for system-scoped artifacts, this may null. If this node represents the current module * being built, this field may or may not be null, depending on whether the artifact is * already created in the current build or not. * For all the other modules, this is never null. * @throws AbstractArtifactResolutionException * Failed to resolve artifacat. */ public File getArtifactFile() throws AbstractArtifactResolutionException { if(artifactFile==null) artifactFile = artifactResolver.resolve(); return artifactFile; } /** * Gets the forward dependency edges (modules that this module depends on.) */ public List getForwardEdges(DependencyGraph g) { return getEdges(g.forwardEdges); } /** * Gets the backward dependency edges (modules that depend on this module.) */ public List getBackwardEdges(DependencyGraph g) { return getEdges(g.backwardEdges); } private List getEdges(Map> allEdges) { List edges = allEdges.get(this); if(edges==null) return Collections.emptyList(); return edges; } /** * Gets the nodes that the given node depends on. */ public List getForwardNodes(final DependencyGraph g) { return new AbstractList() { final List forward = getForwardEdges(g); public Node get(int index) { return forward.get(index).dst; } public int size() { return forward.size(); } }; } /** * Gets the nodes that depend on the given node. */ public List getBackwardNodes(final DependencyGraph g) { return new AbstractList() { final List backward = getBackwardEdges(g); public Node get(int index) { return backward.get(index).src; } public int size() { return backward.size(); } }; } public String toString() { return groupId+':'+artifactId+':'+version; } public String getId() { return groupId+':'+artifactId+':'+classifier; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; if (!artifactId.equals(node.artifactId)) return false; if (classifier != null ? !classifier.equals(node.classifier) : node.classifier != null) return false; if (!groupId.equals(node.groupId)) return false; if (version != null ? !version.equals(node.version) : node.version != null) return false; return true; } public int hashCode() { int result; result = groupId.hashCode(); result = 31 * result + artifactId.hashCode(); result = 31 * result + (version != null ? version.hashCode() : 0); result = 31 * result + (classifier != null ? classifier.hashCode() : 0); return result; } /** * Builds the dependency trail from this node to the root node, in that order. * * This is useful as diagnostic information. */ public List getTrail(DependencyGraph graph) { List trail = new ArrayList(); Node n = this; while(n!=graph.getRoot()) { List list = n.getBackwardEdges(graph); if(list.isEmpty()) throw new AssertionError("Lost trail at "+trail+" from "+this+" with "+graph); Edge e = list.get(0); trail.add(e); n = e.src; } Collections.reverse(trail); return trail; } } public static final class Edge { /** * The module that depends on another. */ public final Node src; /** * The module that is being dependent by another. */ public final Node dst; /** * Dependency scope. Stuff like "compile", "runtime", etc. * Never null. */ public final String scope; /** * True if this dependency is optional. */ public final boolean optional; public Edge(DependencyGraph g, Node src, Node dst, String scope, boolean optional) { this.src = src; this.dst = dst; if(scope==null) scope="compile"; this.scope = scope; this.optional = optional; addEdge(g.forwardEdges,src); addEdge(g.backwardEdges,dst); } private void addEdge(Map> edgeSet, Node index) { List l = edgeSet.get(index); if(l==null) edgeSet.put(index,l=new ArrayList()); for (Edge e : l) { if(e.src.equals(this.src) && e.dst.equals(this.dst)) return; // duplicate } l.add(this); } public String toString() { StringBuilder buf = new StringBuilder(); buf.append(src).append("--(").append(scope); if(optional) buf.append("/optional"); buf.append(")-->").append(dst); return buf.toString(); } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Edge that = (Edge) o; return dst.equals(that.dst) && src.equals(that.src); } public int hashCode() { int result; result = src.hashCode(); result = 31 * result + dst.hashCode(); return result; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy