View Javadoc
1   /*
2    * (C) Copyright 2014-2015 Nuxeo SA (http://nuxeo.com/) and contributors.
3    *
4    * All rights reserved. This program and the accompanying materials
5    * are made available under the terms of the GNU Lesser General Public License
6    * (LGPL) version 2.1 which accompanies this distribution, and is available at
7    * http://www.gnu.org/licenses/lgpl-2.1.html
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * Contributors:
15   *     Julien Carsique
16   *
17   */
18  
19  package org.nuxeo.build.maven.graph;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.IdentityHashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.tools.ant.Project;
31  import org.eclipse.aether.artifact.Artifact;
32  import org.eclipse.aether.graph.Dependency;
33  import org.eclipse.aether.graph.DependencyNode;
34  import org.eclipse.aether.graph.DependencyVisitor;
35  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
36  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
37  
38  import org.nuxeo.build.ant.AntClient;
39  
40  /**
41   * Prints the graph while traversing it. Do not print nodes provided through {@link #addIgnores(Collection)}. Inspired
42   * from {@link org.eclipse.aether.util.graph.visitor.AbstractDepthFirstNodeListGenerator}
43   *
44   * @since 2.0
45   */
46  public abstract class AbstractDependencyVisitor implements DependencyVisitor {
47  
48      private final Map<DependencyNode, Object> visitedNodes;
49  
50      protected final Map<String, DependencyNode> nodesMap;
51  
52      /**
53       * Gets the list of dependency nodes that was generated during the graph traversal.
54       *
55       * @return The list of dependency nodes, never {@code null}.
56       */
57      public Collection<DependencyNode> getNodes() {
58          return nodesMap.values();
59      }
60  
61      protected final List<DependencyNode> ignores;
62  
63      protected List<String> scopes = null;
64  
65      /**
66       * @param nodesToIgnore Nodes to ignore during the visit
67       */
68      public void addIgnores(Collection<? extends DependencyNode> nodesToIgnore) {
69          ignores.addAll(nodesToIgnore);
70      }
71  
72      /**
73       * @param scopes Included scopes (if null, all scopes are included).
74       */
75      public AbstractDependencyVisitor(List<String> scopes) {
76          nodesMap = new HashMap<>();
77          visitedNodes = new IdentityHashMap<>();
78          ignores = new ArrayList<>();
79          this.scopes = scopes;
80      }
81  
82      /**
83       * Marks the specified node as being visited and determines whether the node has been visited before.
84       *
85       * @param node The node being visited, must not be {@code null}.
86       * @return {@code true} if the node has not been visited before, {@code false} if the node was already visited.
87       */
88      protected boolean setVisited(DependencyNode node) {
89          return visitedNodes.put(node, Boolean.TRUE) == null;
90      }
91  
92      @Override
93      public boolean visitEnter(DependencyNode node) {
94          // AntClient.getInstance().log("enter: " + node, Project.MSG_VERBOSE);
95          boolean newNode = setVisited(node);
96          boolean visitChildren = newNode;
97          boolean ignoreNode = false;
98          Dependency dependency = node.getDependency();
99          if (dependency == null) {
100             AntClient.getInstance().log("Ignored node with null dependency: " + node);
101             ignoreNode = true;
102         } else if (dependency.getScope() == null || "".equals(dependency.getScope())) {
103             AntClient.getInstance().log(String.format("Found node %s with null scope!", node), Project.MSG_DEBUG);
104         } else if (scopes != null && !scopes.contains(dependency.getScope())) {
105             AntClient.getInstance().log(
106                     String.format("Ignored node %s which scope is %s", node, dependency.getScope()), Project.MSG_DEBUG);
107             ignoreNode = true;
108         }
109         if (ignores != null && ignores.contains(node)) {
110             AntClient.getInstance().log("Ignored node as requested: " + node, Project.MSG_DEBUG);
111             ignoreNode = true;
112         }
113         if (ignoreNode) {
114             visitChildren = false;
115             ignores.add(node);
116         } else {
117             DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER);
118             if (winner != null && !ArtifactIdUtils.equalsId(node.getArtifact(), winner.getArtifact())) {
119                 // This is a conflicting node, not really a new one
120                 newNode = false;
121             }
122             if (newNode) {
123                 nodesMap.put(node.getArtifact().toString(), node);
124             }
125             doVisit(node, newNode);
126         }
127         return visitChildren;
128     }
129 
130     /**
131      * Actions to perform when visiting a node. The method is not called if the node was "ignored" (ie included in
132      * {@link #ignores}.
133      *
134      * @param newNode True if visiting the node for the first time
135      */
136     protected abstract void doVisit(DependencyNode node, boolean newNode);
137 
138     /**
139      * Note that method is always called, even if {@link #doVisit(DependencyNode, boolean)} was not. Check
140      * {@link #ignores} if needed.
141      */
142     @Override
143     public boolean visitLeave(DependencyNode node) {
144         // AntClient.getInstance().log("leave: " + node, Project.MSG_VERBOSE);
145         return true;
146     }
147 
148     /**
149      * Gets the dependencies seen during the graph traversal.
150      *
151      * @param includeUnresolved Whether unresolved dependencies shall be included in the result or not.
152      * @return The list of dependencies, never {@code null}.
153      */
154     public List<Dependency> getDependencies(boolean includeUnresolved) {
155         List<Dependency> dependencies = new ArrayList<>(getNodes().size());
156         for (DependencyNode node : getNodes()) {
157             Dependency dependency = node.getDependency();
158             if (dependency != null && (includeUnresolved || dependency.getArtifact().getFile() != null)) {
159                 dependencies.add(dependency);
160             }
161         }
162         return dependencies;
163     }
164 
165     /**
166      * Gets the artifacts associated with the list of dependency nodes generated during the graph traversal.
167      *
168      * @param includeUnresolved Whether unresolved artifacts shall be included in the result or not.
169      * @return The list of artifacts, never {@code null}.
170      */
171     public List<Artifact> getArtifacts(boolean includeUnresolved) {
172         List<Artifact> artifacts = new ArrayList<>(getNodes().size());
173         for (DependencyNode node : getNodes()) {
174             Artifact artifact = node.getArtifact();
175             if (artifact != null && (includeUnresolved || artifact.getFile() != null)) {
176                 artifacts.add(artifact);
177             }
178         }
179         return artifacts;
180     }
181 
182     /**
183      * Gets the files of resolved artifacts seen during the graph traversal.
184      *
185      * @return The list of artifact files, never {@code null}.
186      */
187     public List<File> getFiles() {
188         List<File> files = new ArrayList<>(getNodes().size());
189         for (DependencyNode node : getNodes()) {
190             if (node.getDependency() != null) {
191                 File file = node.getDependency().getArtifact().getFile();
192                 if (file != null) {
193                     files.add(file);
194                 }
195             }
196         }
197         return files;
198     }
199 
200     /**
201      * Gets a class path by concatenating the artifact files of the visited dependency nodes. Nodes with unresolved
202      * artifacts are automatically skipped.
203      *
204      * @return The class path, using the platform-specific path separator, never {@code null}.
205      */
206     public String getClassPath() {
207         StringBuilder buffer = new StringBuilder(1024);
208         for (Iterator<DependencyNode> it = getNodes().iterator(); it.hasNext();) {
209             DependencyNode node = it.next();
210             Artifact artifact = node.getArtifact();
211             if (artifact != null && artifact.getFile() != null) {
212                 buffer.append(artifact.getFile().getAbsolutePath());
213                 if (it.hasNext()) {
214                     buffer.append(File.pathSeparatorChar);
215                 }
216             }
217         }
218         return buffer.toString();
219     }
220 }