View Javadoc
1   /*
2    * (C) Copyright 2014 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.IOException;
22  import java.io.OutputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.tools.ant.BuildException;
31  import org.apache.tools.ant.Project;
32  import org.eclipse.aether.artifact.Artifact;
33  import org.eclipse.aether.graph.DefaultDependencyNode;
34  import org.eclipse.aether.graph.Dependency;
35  import org.eclipse.aether.graph.DependencyNode;
36  import org.eclipse.aether.resolution.ArtifactDescriptorException;
37  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
38  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
39  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
40  import org.eclipse.aether.util.artifact.JavaScopes;
41  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
42  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
43  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
44  import org.nuxeo.build.ant.AntClient;
45  import org.nuxeo.build.ant.artifact.PrintGraphTask;
46  import org.nuxeo.build.maven.AntBuildMojo;
47  
48  /**
49   * Prints all dependencies as a tree. Duplicates and conflicting
50   * versions mentioned but not removed. However, nodes are only expanded once.
51   *
52   * @since 2.0
53   */
54  public class TreePrinterDependencyVisitor extends AbstractDependencyVisitor {
55  
56      /**
57       * Tabulation used in {@link PrintGraphTask#MODE_TREE} mode.
58       */
59      public static final String TAB_STR = " |-- ";
60  
61      protected String tabs = "";
62  
63      protected int format;
64  
65      protected OutputStream output;
66  
67      protected Map<String, DependencyNode> dependencyMap = new HashMap<>();
68  
69      protected Map<String, List<Dependency>> directDepsByArtifact = new HashMap<>();
70  
71      /**
72       * @param output
73       * @param format 0 = standard GAV ; 1 = File + GAV
74       * @param scopes
75       * @param roots
76       */
77      public TreePrinterDependencyVisitor(OutputStream output, int format,
78              List<String> scopes, List<Node> roots) {
79          super(scopes);
80          this.output = output;
81          this.format = format;
82          PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
83          for (Node node : roots) {
84              node.accept(nlg);
85          }
86          for (DependencyNode node : nlg.getNodes()) {
87              dependencyMap.put(node.getArtifact().toString(), node);
88          }
89      }
90  
91      @Override
92      protected void doVisit(DependencyNode node, boolean newNode) {
93          try {
94              print(node);
95              if (newNode) {
96                  addMissingChildren(node);
97              }
98              incTabs();
99          } catch (IOException e) {
100             throw new BuildException(e);
101         }
102     }
103 
104     /**
105      * Add child nodes which were removed by the ConflictResolver (it lets no
106      * duplicate)
107      */
108     protected void addMissingChildren(DependencyNode node) {
109         AntBuildMojo mojo = AntBuildMojo.getInstance();
110         incTabs();
111         try {
112             List<Dependency> directDeps = directDepsByArtifact.get(node.getArtifact().toString());
113             if (directDeps == null) {
114                 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(
115                         node.getArtifact(), mojo.getRemoteRepositories(), null);
116                 ArtifactDescriptorResult descriptorResult = mojo.getSystem().readArtifactDescriptor(
117                         mojo.getSession(), descriptorRequest);
118                 directDeps = descriptorResult.getDependencies();
119             }
120             for (Dependency dependency : directDeps) {
121                 boolean removed = true;
122                 if (JavaScopes.TEST.equals(dependency.getScope())
123                         && !JavaScopes.TEST.equals(node.getDependency().getScope())) {
124                     continue;
125                 }
126                 for (DependencyNode childNode : node.getChildren()) {
127                     if (childNode.getArtifact().toString().equals(
128                             dependency.getArtifact().toString())) {
129                         removed = false;
130                     }
131                 }
132                 if (removed) {
133                     DependencyNode childNode = dependencyMap.get(dependency.getArtifact().toString());
134                     if (childNode == null) {
135                         AntClient.getInstance().log(
136                                 "Ignored dependency " + dependency
137                                         + " not in graph", Project.MSG_DEBUG);
138                         // childNode = new DefaultDependencyNode(dependency);
139                         continue;
140                     } else {
141                         int managedBits = 0;
142                         childNode = new DefaultDependencyNode(childNode);
143                         childNode.setScope(childNode.getDependency().getScope());
144                         String managedScope = dependency.getScope();
145                         String scope = childNode.getDependency().getScope();
146                         if (!managedScope.equals(scope)) {
147                             managedBits |= DependencyNode.MANAGED_SCOPE;
148                             childNode.setData(
149                                     DependencyManagerUtils.NODE_DATA_PREMANAGED_SCOPE,
150                                     managedScope);
151                         }
152                         String managedVersion = dependency.getArtifact().getBaseVersion();
153                         String version = childNode.getArtifact().getBaseVersion();
154                         if (!managedVersion.equals(version)) {
155                             managedBits |= DependencyNode.MANAGED_VERSION;
156                             childNode.setData(
157                                     DependencyManagerUtils.NODE_DATA_PREMANAGED_VERSION,
158                                     managedVersion);
159                         }
160                         ((DefaultDependencyNode) childNode).setManagedBits(managedBits);
161                     }
162                     childNode.setChildren(Collections.<DependencyNode> emptyList());
163                     if (node.getChildren().isEmpty()) {
164                         node.setChildren(new ArrayList<DependencyNode>());
165                     }
166                     node.getChildren().add(childNode);
167                     // Mark added child as already visited to avoid expanding it
168                     // again
169                     setVisited(childNode);
170                 }
171             }
172         } catch (ArtifactDescriptorException e) {
173             AntClient.getInstance().log(e.getMessage(), e, Project.MSG_ERR);
174         } finally {
175             decTabs();
176         }
177     }
178 
179     protected void incTabs() {
180         tabs += TAB_STR;
181     }
182 
183     @Override
184     public boolean visitLeave(DependencyNode node) {
185         boolean visit = super.visitLeave(node);
186         if (!ignores.contains(node)) {
187             decTabs();
188         }
189         return visit;
190     }
191 
192     protected void decTabs() {
193         tabs = tabs.substring(0, tabs.length() - TAB_STR.length());
194     }
195 
196     protected void print(DependencyNode node)
197             throws UnsupportedEncodingException, IOException {
198         String toString = tabs + toString(node)
199                 + System.getProperty("line.separator");
200         output.write(toString.getBytes(AntBuildMojo.getInstance().getEncoding()));
201     }
202 
203     /**
204      * @return String representation depending on {@link #format}
205      * @see PrintGraphTask#FORMAT_GAV
206      * @see PrintGraphTask#FORMAT_KV_F_GAV
207      */
208     public String toString(DependencyNode node) {
209         Artifact artifact = node.getArtifact();
210         Dependency dependency = node.getDependency();
211         StringBuilder sb = new StringBuilder();
212         switch (format) {
213         case 1:
214             sb.append(artifact.getFile().getName());
215             sb.append('=');
216             // fall through
217         case 0:
218             sb.append(artifact.getGroupId());
219             sb.append(':').append(artifact.getArtifactId());
220             sb.append(':').append(artifact.getVersion());
221             sb.append(':').append(artifact.getExtension());
222             sb.append(':').append(artifact.getClassifier());
223             sb.append(':').append(dependency.getScope());
224             break;
225         default:
226             return "Unknown format: " + format + "!";
227         }
228         if (node.getDependency().isOptional()) {
229             sb.append(" [optional]");
230         }
231         String premanaged = DependencyManagerUtils.getPremanagedVersion(node);
232         if (premanaged != null && !premanaged.equals(artifact.getBaseVersion())) {
233             sb.append(" (version managed from ").append(premanaged).append(")");
234         }
235         premanaged = DependencyManagerUtils.getPremanagedScope(node);
236         if (premanaged != null && !premanaged.equals(dependency.getScope())) {
237             sb.append(" (scope managed from ").append(premanaged).append(")");
238         }
239         DependencyNode winner = (DependencyNode) node.getData().get(
240                 ConflictResolver.NODE_DATA_WINNER);
241         if (winner != null
242                 && !ArtifactIdUtils.equalsId(artifact, winner.getArtifact())) {
243             Artifact w = winner.getArtifact();
244             sb.append(" (superseded by ");
245             if (ArtifactIdUtils.toVersionlessId(artifact).equals(
246                     ArtifactIdUtils.toVersionlessId(w))) {
247                 sb.append(w.getVersion());
248             } else {
249                 sb.append(w);
250             }
251             sb.append(")");
252         }
253         return sb.toString();
254     }
255 
256 }