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
26 package com.sun.tools.jdeps;
27
28 import com.sun.tools.classfile.Dependency.Location;
29
30 import java.io.BufferedReader;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.UncheckedIOException;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.Map;
40 import java.util.Objects;
41 import java.util.Set;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44
45 /**
46 * Dependency Analyzer.
47 */
48 public class Analyzer {
49 /**
50 * Type of the dependency analysis. Appropriate level of data
51 * will be stored.
52 */
53 public enum Type {
54 SUMMARY,
55 MODULE, // equivalent to summary in addition, print module descriptor
56 PACKAGE,
57 CLASS,
58 VERBOSE
59 }
60
61 /**
62 * Filter to be applied when analyzing the dependencies from the given archives.
63 * Only the accepted dependencies are recorded.
64 */
65 interface Filter {
66 boolean accepts(Location origin, Archive originArchive,
67 Location target, Archive targetArchive);
68 }
69
70 protected final JdepsConfiguration configuration;
71 protected final Type type;
72 protected final Filter filter;
73 protected final Map<Archive, Dependences> results = new HashMap<>();
74 protected final Map<Location, Archive> locationToArchive = new HashMap<>();
75 static final Archive NOT_FOUND
76 = new Archive(JdepsTask.getMessage("artifact.not.found"));
77
78 /**
79 * Constructs an Analyzer instance.
80 *
81 * @param type Type of the dependency analysis
82 * @param filter
83 */
84 Analyzer(JdepsConfiguration config, Type type, Filter filter) {
85 this.configuration = config;
86 this.type = type;
87 this.filter = filter;
88 }
89
90 /**
91 * Performs the dependency analysis on the given archives.
92 */
93 boolean run(Iterable<? extends Archive> archives,
94 Map<Location, Archive> locationMap)
95 {
96 this.locationToArchive.putAll(locationMap);
144 return Stream.empty();
145 }
146 return results.get(source).requires()
147 .stream();
148 }
149
150 interface Visitor {
151 /**
152 * Visits a recorded dependency from origin to target which can be
153 * a fully-qualified classname, a package name, a module or
154 * archive name depending on the Analyzer's type.
155 */
156 public void visitDependence(String origin, Archive originArchive,
157 String target, Archive targetArchive);
158 }
159
160 /**
161 * Visit the dependencies of the given source.
162 * If the requested level is SUMMARY, it will visit the required archives list.
163 */
164 void visitDependences(Archive source, Visitor v, Type level) {
165 if (level == Type.SUMMARY) {
166 final Dependences result = results.get(source);
167 final Set<Archive> reqs = result.requires();
168 Stream<Archive> stream = reqs.stream();
169 if (reqs.isEmpty()) {
170 if (hasDependences(source)) {
171 // If reqs.isEmpty() and we have dependences, then it means
172 // that the dependences are from 'source' onto itself.
173 stream = Stream.of(source);
174 }
175 }
176 stream.sorted(Comparator.comparing(Archive::getName))
177 .forEach(archive -> {
178 Profile profile = result.getTargetProfile(archive);
179 v.visitDependence(source.getName(), source,
180 profile != null ? profile.profileName()
181 : archive.getName(), archive);
182 });
183 } else {
184 Dependences result = results.get(source);
185 if (level != type) {
186 // requesting different level of analysis
187 result = new Dependences(source, level);
188 source.visitDependences(result);
189 }
190 result.dependencies().stream()
191 .sorted(Comparator.comparing(Dep::origin)
192 .thenComparing(Dep::target))
193 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(),
194 d.target(), d.targetArchive()));
195 }
196 }
197
198 void visitDependences(Archive source, Visitor v) {
199 visitDependences(source, v, type);
200 }
201
202 /**
203 * Dependences contains the dependencies for an Archive that can have one or
204 * more classes.
205 */
206 class Dependences implements Archive.Visitor {
207 protected final Archive archive;
208 protected final Set<Archive> requires;
209 protected final Set<Dep> deps;
210 protected final Type level;
211 private Profile profile;
212 Dependences(Archive archive, Type level) {
213 this.archive = archive;
214 this.deps = new HashSet<>();
215 this.requires = new HashSet<>();
216 this.level = level;
217 }
218
219 Set<Dep> dependencies() {
220 return deps;
221 }
222
223 Set<Archive> requires() {
224 return requires;
225 }
226
227 Profile getTargetProfile(Archive target) {
228 if (target.getModule().isJDK()) {
229 return Profile.getProfile((Module) target);
230 } else {
231 return null;
232 }
233 }
234
235 /*
236 * Returns the archive that contains the given location.
249 .orElseGet(() -> REMOVED_JDK_INTERNALS.contains(t)
250 ? REMOVED_JDK_INTERNALS
251 : NOT_FOUND);
252 }
253 return locationToArchive.computeIfAbsent(t, _k -> target);
254 }
255
256 // return classname or package name depending on the level
257 private String getLocationName(Location o) {
258 if (level == Type.CLASS || level == Type.VERBOSE) {
259 return VersionHelper.get(o.getClassName());
260 } else {
261 String pkg = o.getPackageName();
262 return pkg.isEmpty() ? "<unnamed>" : pkg;
263 }
264 }
265
266 @Override
267 public void visit(Location o, Location t) {
268 Archive targetArchive = findArchive(t);
269 if (filter.accepts(o, archive, t, targetArchive)) {
270 addDep(o, t);
271 if (archive != targetArchive && !requires.contains(targetArchive)) {
272 requires.add(targetArchive);
273 }
274 }
275 if (targetArchive.getModule().isNamed()) {
276 Profile p = Profile.getProfile(t.getPackageName());
277 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
278 profile = p;
279 }
280 }
281 }
282
283 private Dep curDep;
284 protected Dep addDep(Location o, Location t) {
285 String origin = getLocationName(o);
286 String target = getLocationName(t);
287 Archive targetArchive = findArchive(t);
288 if (curDep != null &&
289 curDep.origin().equals(origin) &&
351 this.targetArchive == d.targetArchive;
352 }
353 return false;
354 }
355
356 @Override
357 public int hashCode() {
358 return Objects.hash(this.origin,
359 this.originArchive,
360 this.target,
361 this.targetArchive);
362 }
363
364 public String toString() {
365 return String.format("%s (%s) -> %s (%s)%n",
366 origin, originArchive.getName(),
367 target, targetArchive.getName());
368 }
369 }
370
371 static final Jdk8Internals REMOVED_JDK_INTERNALS = new Jdk8Internals();
372
373 static class Jdk8Internals extends Module {
374 private final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
375 private final Set<String> jdk8Internals;
376 private Jdk8Internals() {
377 super("JDK removed internal API");
378 try (InputStream in = JdepsTask.class.getResourceAsStream(JDK8_INTERNALS);
379 BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
380 this.jdk8Internals = reader.lines()
381 .filter(ln -> !ln.startsWith("#"))
382 .collect(Collectors.toSet());
383 } catch (IOException e) {
384 throw new UncheckedIOException(e);
385 }
386 }
387
388 public boolean contains(Location location) {
389 String cn = location.getClassName();
390 int i = cn.lastIndexOf('.');
391 String pn = i > 0 ? cn.substring(0, i) : "";
392
393 return jdk8Internals.contains(pn);
394 }
395
396 @Override
397 public String name() {
398 return getName();
399 }
400
401 @Override
402 public boolean isJDK() {
403 return true;
404 }
405
406 @Override
407 public boolean isExported(String pn) {
408 return false;
409 }
410 }
411 }
|
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
26 package com.sun.tools.jdeps;
27
28 import com.sun.tools.classfile.Dependency.Location;
29
30 import java.io.BufferedReader;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.UncheckedIOException;
35 import java.lang.module.ModuleDescriptor;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Map;
41 import java.util.Objects;
42 import java.util.Set;
43 import java.util.function.Predicate;
44 import java.util.stream.Collectors;
45 import java.util.stream.Stream;
46
47 /**
48 * Dependency Analyzer.
49 */
50 public class Analyzer {
51 /**
52 * Type of the dependency analysis. Appropriate level of data
53 * will be stored.
54 */
55 public enum Type {
56 SUMMARY,
57 MODULE, // equivalent to summary in addition, print module descriptor
58 PACKAGE,
59 CLASS,
60 VERBOSE
61 }
62
63 /**
64 * Filter to be applied when analyzing the dependencies from the given archives.
65 * Only the accepted dependencies are recorded.
66 */
67 interface Filter {
68 boolean accepts(Location origin, Archive originArchive,
69 Location target, Archive targetArchive);
70 }
71
72 protected final JdepsConfiguration configuration;
73 protected final Type type;
74 protected final Filter filter;
75 protected final Map<Archive, Dependences> results = new HashMap<>();
76 protected final Map<Location, Archive> locationToArchive = new HashMap<>();
77 static final Archive NOT_FOUND
78 = new Archive(JdepsTask.getMessage("artifact.not.found"));
79 static final Predicate<Archive> ANY = a -> true;
80
81 /**
82 * Constructs an Analyzer instance.
83 *
84 * @param type Type of the dependency analysis
85 * @param filter
86 */
87 Analyzer(JdepsConfiguration config, Type type, Filter filter) {
88 this.configuration = config;
89 this.type = type;
90 this.filter = filter;
91 }
92
93 /**
94 * Performs the dependency analysis on the given archives.
95 */
96 boolean run(Iterable<? extends Archive> archives,
97 Map<Location, Archive> locationMap)
98 {
99 this.locationToArchive.putAll(locationMap);
147 return Stream.empty();
148 }
149 return results.get(source).requires()
150 .stream();
151 }
152
153 interface Visitor {
154 /**
155 * Visits a recorded dependency from origin to target which can be
156 * a fully-qualified classname, a package name, a module or
157 * archive name depending on the Analyzer's type.
158 */
159 public void visitDependence(String origin, Archive originArchive,
160 String target, Archive targetArchive);
161 }
162
163 /**
164 * Visit the dependencies of the given source.
165 * If the requested level is SUMMARY, it will visit the required archives list.
166 */
167 void visitDependences(Archive source, Visitor v, Type level, Predicate<Archive> targetFilter) {
168 if (level == Type.SUMMARY) {
169 final Dependences result = results.get(source);
170 final Set<Archive> reqs = result.requires();
171 Stream<Archive> stream = reqs.stream();
172 if (reqs.isEmpty()) {
173 if (hasDependences(source)) {
174 // If reqs.isEmpty() and we have dependences, then it means
175 // that the dependences are from 'source' onto itself.
176 stream = Stream.of(source);
177 }
178 }
179 stream.sorted(Comparator.comparing(Archive::getName))
180 .forEach(archive -> {
181 Profile profile = result.getTargetProfile(archive);
182 v.visitDependence(source.getName(), source,
183 profile != null ? profile.profileName()
184 : archive.getName(), archive);
185 });
186 } else {
187 Dependences result = results.get(source);
188 if (level != type) {
189 // requesting different level of analysis
190 result = new Dependences(source, level, targetFilter);
191 source.visitDependences(result);
192 }
193 result.dependencies().stream()
194 .sorted(Comparator.comparing(Dep::origin)
195 .thenComparing(Dep::target))
196 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(),
197 d.target(), d.targetArchive()));
198 }
199 }
200
201 void visitDependences(Archive source, Visitor v) {
202 visitDependences(source, v, type, ANY);
203 }
204
205 void visitDependences(Archive source, Visitor v, Type level) {
206 visitDependences(source, v, level, ANY);
207 }
208
209 /**
210 * Dependences contains the dependencies for an Archive that can have one or
211 * more classes.
212 */
213 class Dependences implements Archive.Visitor {
214 protected final Archive archive;
215 protected final Set<Archive> requires;
216 protected final Set<Dep> deps;
217 protected final Type level;
218 protected final Predicate<Archive> targetFilter;
219 private Profile profile;
220 Dependences(Archive archive, Type level) {
221 this(archive, level, ANY);
222 }
223 Dependences(Archive archive, Type level, Predicate<Archive> targetFilter) {
224 this.archive = archive;
225 this.deps = new HashSet<>();
226 this.requires = new HashSet<>();
227 this.level = level;
228 this.targetFilter = targetFilter;
229 }
230
231 Set<Dep> dependencies() {
232 return deps;
233 }
234
235 Set<Archive> requires() {
236 return requires;
237 }
238
239 Profile getTargetProfile(Archive target) {
240 if (target.getModule().isJDK()) {
241 return Profile.getProfile((Module) target);
242 } else {
243 return null;
244 }
245 }
246
247 /*
248 * Returns the archive that contains the given location.
261 .orElseGet(() -> REMOVED_JDK_INTERNALS.contains(t)
262 ? REMOVED_JDK_INTERNALS
263 : NOT_FOUND);
264 }
265 return locationToArchive.computeIfAbsent(t, _k -> target);
266 }
267
268 // return classname or package name depending on the level
269 private String getLocationName(Location o) {
270 if (level == Type.CLASS || level == Type.VERBOSE) {
271 return VersionHelper.get(o.getClassName());
272 } else {
273 String pkg = o.getPackageName();
274 return pkg.isEmpty() ? "<unnamed>" : pkg;
275 }
276 }
277
278 @Override
279 public void visit(Location o, Location t) {
280 Archive targetArchive = findArchive(t);
281 if (filter.accepts(o, archive, t, targetArchive) && targetFilter.test(targetArchive)) {
282 addDep(o, t);
283 if (archive != targetArchive && !requires.contains(targetArchive)) {
284 requires.add(targetArchive);
285 }
286 }
287 if (targetArchive.getModule().isNamed()) {
288 Profile p = Profile.getProfile(t.getPackageName());
289 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
290 profile = p;
291 }
292 }
293 }
294
295 private Dep curDep;
296 protected Dep addDep(Location o, Location t) {
297 String origin = getLocationName(o);
298 String target = getLocationName(t);
299 Archive targetArchive = findArchive(t);
300 if (curDep != null &&
301 curDep.origin().equals(origin) &&
363 this.targetArchive == d.targetArchive;
364 }
365 return false;
366 }
367
368 @Override
369 public int hashCode() {
370 return Objects.hash(this.origin,
371 this.originArchive,
372 this.target,
373 this.targetArchive);
374 }
375
376 public String toString() {
377 return String.format("%s (%s) -> %s (%s)%n",
378 origin, originArchive.getName(),
379 target, targetArchive.getName());
380 }
381 }
382
383 /*
384 * Returns true if the given archive represents not found.
385 */
386 static boolean notFound(Archive archive) {
387 return archive == NOT_FOUND || archive == REMOVED_JDK_INTERNALS;
388 }
389
390 static final Jdk8Internals REMOVED_JDK_INTERNALS = new Jdk8Internals();
391
392 static class Jdk8Internals extends Module {
393 private static final String NAME = "JDK removed internal API";
394 private static final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
395 private final Set<String> jdk8Internals;
396 private Jdk8Internals() {
397 super(NAME, ModuleDescriptor.newModule("jdk8internals").build(), true);
398 try (InputStream in = JdepsTask.class.getResourceAsStream(JDK8_INTERNALS);
399 BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
400 this.jdk8Internals = reader.lines()
401 .filter(ln -> !ln.startsWith("#"))
402 .collect(Collectors.toSet());
403 } catch (IOException e) {
404 throw new UncheckedIOException(e);
405 }
406 }
407
408 public boolean contains(Location location) {
409 String cn = location.getClassName();
410 int i = cn.lastIndexOf('.');
411 String pn = i > 0 ? cn.substring(0, i) : "";
412
413 return jdk8Internals.contains(pn);
414 }
415
416 @Override
417 public boolean isJDK() {
418 return true;
419 }
420
421 @Override
422 public boolean isExported(String pn) {
423 return false;
424 }
425 }
426 }
|