Thu, 17 Jul 2014 15:23:08 -0700
8029548: (jdeps) use @jdk.Exported to determine supported vs JDK internal API
8031092: jdeps does not recognize --help option.
8048063: (jdeps) Add filtering capability
Reviewed-by: alanb, dfuchs
1 /*
2 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.sun.tools.jdeps;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.SortedMap;
34 import java.util.SortedSet;
35 import java.util.TreeMap;
36 import java.util.TreeSet;
38 import com.sun.tools.classfile.Dependency.Location;
39 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
41 /**
42 * Dependency Analyzer.
43 */
44 public class Analyzer {
45 /**
46 * Type of the dependency analysis. Appropriate level of data
47 * will be stored.
48 */
49 public enum Type {
50 SUMMARY,
51 PACKAGE,
52 CLASS,
53 VERBOSE
54 };
56 /**
57 * Filter to be applied when analyzing the dependencies from the given archives.
58 * Only the accepted dependencies are recorded.
59 */
60 interface Filter {
61 boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
62 }
64 private final Type type;
65 private final Filter filter;
66 private final Map<Archive, ArchiveDeps> results = new HashMap<>();
67 private final Map<Location, Archive> map = new HashMap<>();
68 private final Archive NOT_FOUND
69 = new Archive(JdepsTask.getMessage("artifact.not.found"));
71 /**
72 * Constructs an Analyzer instance.
73 *
74 * @param type Type of the dependency analysis
75 * @param filter
76 */
77 public Analyzer(Type type, Filter filter) {
78 this.type = type;
79 this.filter = filter;
80 }
82 /**
83 * Performs the dependency analysis on the given archives.
84 */
85 public void run(List<Archive> archives) {
86 // build a map from Location to Archive
87 buildLocationArchiveMap(archives);
89 // traverse and analyze all dependencies
90 for (Archive archive : archives) {
91 ArchiveDeps deps = new ArchiveDeps(archive, type);
92 archive.visitDependences(deps);
93 results.put(archive, deps);
94 }
95 }
97 private void buildLocationArchiveMap(List<Archive> archives) {
98 // build a map from Location to Archive
99 for (Archive archive: archives) {
100 for (Location l: archive.getClasses()) {
101 if (!map.containsKey(l)) {
102 map.put(l, archive);
103 } else {
104 // duplicated class warning?
105 }
106 }
107 }
108 }
110 public boolean hasDependences(Archive archive) {
111 if (results.containsKey(archive)) {
112 return results.get(archive).dependencies().size() > 0;
113 }
114 return false;
115 }
117 public interface Visitor {
118 /**
119 * Visits a recorded dependency from origin to target which can be
120 * a fully-qualified classname, a package name, a module or
121 * archive name depending on the Analyzer's type.
122 */
123 public void visitDependence(String origin, Archive originArchive,
124 String target, Archive targetArchive);
125 }
127 /**
128 * Visit the dependencies of the given source.
129 * If the requested level is SUMMARY, it will visit the required archives list.
130 */
131 public void visitDependences(Archive source, Visitor v, Type level) {
132 if (level == Type.SUMMARY) {
133 final ArchiveDeps result = results.get(source);
134 SortedMap<String, Archive> sorted = new TreeMap<>();
135 for (Archive a : result.requires()) {
136 sorted.put(a.getName(), a);
137 }
138 for (Archive archive : sorted.values()) {
139 Profile profile = result.getTargetProfile(archive);
140 v.visitDependence(source.getName(), source,
141 profile != null ? profile.profileName() : archive.getName(), archive);
142 }
143 } else {
144 ArchiveDeps result = results.get(source);
145 if (level != type) {
146 // requesting different level of analysis
147 result = new ArchiveDeps(source, level);
148 source.visitDependences(result);
149 }
150 SortedSet<Dep> sorted = new TreeSet<>(result.dependencies());
151 for (Dep d : sorted) {
152 v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive());
153 }
154 }
155 }
157 public void visitDependences(Archive source, Visitor v) {
158 visitDependences(source, v, type);
159 }
161 /**
162 * ArchiveDeps contains the dependencies for an Archive that can have one or
163 * more classes.
164 */
165 class ArchiveDeps implements Archive.Visitor {
166 protected final Archive archive;
167 protected final Set<Archive> requires;
168 protected final Set<Dep> deps;
169 protected final Type level;
170 private Profile profile;
171 ArchiveDeps(Archive archive, Type level) {
172 this.archive = archive;
173 this.deps = new HashSet<>();
174 this.requires = new HashSet<>();
175 this.level = level;
176 }
178 Set<Dep> dependencies() {
179 return deps;
180 }
182 Set<Archive> requires() {
183 return requires;
184 }
186 Profile getTargetProfile(Archive target) {
187 return JDKArchive.isProfileArchive(target) ? profile : null;
188 }
190 Archive findArchive(Location t) {
191 Archive target = archive.getClasses().contains(t) ? archive : map.get(t);
192 if (target == null) {
193 map.put(t, target = NOT_FOUND);
194 }
195 return target;
196 }
198 // return classname or package name depedning on the level
199 private String getLocationName(Location o) {
200 if (level == Type.CLASS || level == Type.VERBOSE) {
201 return o.getClassName();
202 } else {
203 String pkg = o.getPackageName();
204 return pkg.isEmpty() ? "<unnamed>" : pkg;
205 }
206 }
208 @Override
209 public void visit(Location o, Location t) {
210 Archive targetArchive = findArchive(t);
211 if (filter.accepts(o, archive, t, targetArchive)) {
212 addDep(o, t);
213 if (!requires.contains(targetArchive)) {
214 requires.add(targetArchive);
215 }
216 }
217 if (targetArchive instanceof JDKArchive) {
218 Profile p = Profile.getProfile(t.getPackageName());
219 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
220 profile = p;
221 }
222 }
223 }
225 private Dep curDep;
226 protected Dep addDep(Location o, Location t) {
227 String origin = getLocationName(o);
228 String target = getLocationName(t);
229 Archive targetArchive = findArchive(t);
230 if (curDep != null &&
231 curDep.origin().equals(origin) &&
232 curDep.originArchive() == archive &&
233 curDep.target().equals(target) &&
234 curDep.targetArchive() == targetArchive) {
235 return curDep;
236 }
238 Dep e = new Dep(origin, archive, target, targetArchive);
239 if (deps.contains(e)) {
240 for (Dep e1 : deps) {
241 if (e.equals(e1)) {
242 curDep = e1;
243 }
244 }
245 } else {
246 deps.add(e);
247 curDep = e;
248 }
249 return curDep;
250 }
251 }
253 /*
254 * Class-level or package-level dependency
255 */
256 class Dep implements Comparable<Dep> {
257 final String origin;
258 final Archive originArchive;
259 final String target;
260 final Archive targetArchive;
262 Dep(String origin, Archive originArchive, String target, Archive targetArchive) {
263 this.origin = origin;
264 this.originArchive = originArchive;
265 this.target = target;
266 this.targetArchive = targetArchive;
267 }
269 String origin() {
270 return origin;
271 }
273 Archive originArchive() {
274 return originArchive;
275 }
277 String target() {
278 return target;
279 }
281 Archive targetArchive() {
282 return targetArchive;
283 }
285 @Override
286 @SuppressWarnings("unchecked")
287 public boolean equals(Object o) {
288 if (o instanceof Dep) {
289 Dep d = (Dep) o;
290 return this.origin.equals(d.origin) &&
291 this.originArchive == d.originArchive &&
292 this.target.equals(d.target) &&
293 this.targetArchive == d.targetArchive;
294 }
295 return false;
296 }
298 @Override
299 public int hashCode() {
300 int hash = 7;
301 hash = 67*hash + Objects.hashCode(this.origin)
302 + Objects.hashCode(this.originArchive)
303 + Objects.hashCode(this.target)
304 + Objects.hashCode(this.targetArchive);
305 return hash;
306 }
308 @Override
309 public int compareTo(Dep o) {
310 if (this.origin.equals(o.origin)) {
311 if (this.target.equals(o.target)) {
312 if (this.originArchive == o.originArchive &&
313 this.targetArchive == o.targetArchive) {
314 return 0;
315 } else if (this.originArchive == o.originArchive) {
316 return this.targetArchive.getPathName().compareTo(o.targetArchive.getPathName());
317 } else {
318 return this.originArchive.getPathName().compareTo(o.originArchive.getPathName());
319 }
320 } else {
321 return this.target.compareTo(o.target);
322 }
323 }
324 return this.origin.compareTo(o.origin);
325 }
326 }
327 }