8156680: jdeps implementation refresh

Reviewed-by: dfuchs
This commit is contained in:
Mandy Chung 2016-05-19 10:55:33 -07:00
parent d027cbbffb
commit bbc75367c7
47 changed files with 4474 additions and 2286 deletions

@ -25,23 +25,24 @@
package com.sun.tools.jdeps;
import java.io.PrintStream;
import java.util.ArrayList;
import static com.sun.tools.jdeps.JdepsConfiguration.*;
import com.sun.tools.classfile.Dependency.Location;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.sun.tools.classfile.Dependency.Location;
/**
* Dependency Analyzer.
*/
@ -52,6 +53,7 @@ public class Analyzer {
*/
public enum Type {
SUMMARY,
MODULE, // equivalent to summary in addition, print module descriptor
PACKAGE,
CLASS,
VERBOSE
@ -62,9 +64,11 @@ public class Analyzer {
* Only the accepted dependencies are recorded.
*/
interface Filter {
boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
boolean accepts(Location origin, Archive originArchive,
Location target, Archive targetArchive);
}
protected final JdepsConfiguration configuration;
protected final Type type;
protected final Filter filter;
protected final Map<Archive, Dependences> results = new HashMap<>();
@ -78,7 +82,8 @@ public class Analyzer {
* @param type Type of the dependency analysis
* @param filter
*/
public Analyzer(Type type, Filter filter) {
Analyzer(JdepsConfiguration config, Type type, Filter filter) {
this.configuration = config;
this.type = type;
this.filter = filter;
}
@ -86,16 +91,10 @@ public class Analyzer {
/**
* Performs the dependency analysis on the given archives.
*/
public boolean run(Stream<? extends Archive> archives) {
return run(archives.collect(Collectors.toList()));
}
/**
* Performs the dependency analysis on the given archives.
*/
public boolean run(Iterable<? extends Archive> archives) {
// build a map from Location to Archive
buildLocationArchiveMap(archives);
boolean run(Iterable<? extends Archive> archives,
Map<Location, Archive> locationMap)
{
this.locationToArchive.putAll(locationMap);
// traverse and analyze all dependencies
for (Archive archive : archives) {
@ -106,40 +105,50 @@ public class Analyzer {
return true;
}
protected void buildLocationArchiveMap(Iterable<? extends Archive> archives) {
// build a map from Location to Archive
for (Archive archive: archives) {
archive.getClasses()
.forEach(l -> locationToArchive.putIfAbsent(l, archive));
}
/**
* Returns the analyzed archives
*/
Set<Archive> archives() {
return results.keySet();
}
public boolean hasDependences(Archive archive) {
/**
* Returns true if the given archive has dependences.
*/
boolean hasDependences(Archive archive) {
if (results.containsKey(archive)) {
return results.get(archive).dependencies().size() > 0;
}
return false;
}
public Set<String> dependences(Archive source) {
/**
* Returns the dependences, either class name or package name
* as specified in the given verbose level, from the given source.
*/
Set<String> dependences(Archive source) {
if (!results.containsKey(source)) {
return Collections.emptySet();
}
Dependences result = results.get(source);
return result.dependencies().stream()
.map(Dep::target)
.collect(Collectors.toSet());
return results.get(source).dependencies()
.stream()
.map(Dep::target)
.collect(Collectors.toSet());
}
public Stream<Archive> requires(Archive source) {
/**
* Returns the direct dependences of the given source
*/
Stream<Archive> requires(Archive source) {
if (!results.containsKey(source)) {
return Stream.empty();
}
Dependences result = results.get(source);
return result.requires().stream().filter(a -> !a.isEmpty());
return results.get(source).requires()
.stream();
}
public interface Visitor {
interface Visitor {
/**
* Visits a recorded dependency from origin to target which can be
* a fully-qualified classname, a package name, a module or
@ -153,7 +162,7 @@ public class Analyzer {
* Visit the dependencies of the given source.
* If the requested level is SUMMARY, it will visit the required archives list.
*/
public void visitDependences(Archive source, Visitor v, Type level) {
void visitDependences(Archive source, Visitor v, Type level) {
if (level == Type.SUMMARY) {
final Dependences result = results.get(source);
final Set<Archive> reqs = result.requires();
@ -187,7 +196,7 @@ public class Analyzer {
}
}
public void visitDependences(Archive source, Visitor v) {
void visitDependences(Archive source, Visitor v) {
visitDependences(source, v, type);
}
@ -224,14 +233,28 @@ public class Analyzer {
}
}
/*
* Returns the archive that contains the given location.
*/
Archive findArchive(Location t) {
// local in this archive
if (archive.getClasses().contains(t))
return archive;
return locationToArchive.computeIfAbsent(t, _k -> NOT_FOUND);
Archive target;
if (locationToArchive.containsKey(t)) {
target = locationToArchive.get(t);
} else {
// special case JDK removed API
target = configuration.findClass(t)
.orElseGet(() -> REMOVED_JDK_INTERNALS.contains(t)
? REMOVED_JDK_INTERNALS
: NOT_FOUND);
}
return locationToArchive.computeIfAbsent(t, _k -> target);
}
// return classname or package name depedning on the level
// return classname or package name depending on the level
private String getLocationName(Location o) {
if (level == Type.CLASS || level == Type.VERBOSE) {
return o.getClassName();
@ -345,4 +368,66 @@ public class Analyzer {
target, targetArchive.getName());
}
}
private static final JdkInternals REMOVED_JDK_INTERNALS = new JdkInternals();
private static class JdkInternals extends Module {
private final String BUNDLE = "com.sun.tools.jdeps.resources.jdkinternals";
private final Set<String> jdkinternals;
private final Set<String> jdkUnsupportedClasses;
private JdkInternals() {
super("JDK removed internal API");
try {
ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
this.jdkinternals = rb.keySet();
} catch (MissingResourceException e) {
throw new InternalError("Cannot find jdkinternals resource bundle");
}
this.jdkUnsupportedClasses = getUnsupportedClasses();
}
public boolean contains(Location location) {
if (jdkUnsupportedClasses.contains(location.getName() + ".class")) {
return false;
}
String cn = location.getClassName();
int i = cn.lastIndexOf('.');
String pn = i > 0 ? cn.substring(0, i) : "";
return jdkinternals.contains(cn) || jdkinternals.contains(pn);
}
@Override
public String name() {
return getName();
}
@Override
public boolean isExported(String pn) {
return false;
}
private Set<String> getUnsupportedClasses() {
// jdk.unsupported may not be observable
Optional<Module> om = Profile.FULL_JRE.findModule(JDK_UNSUPPORTED);
if (om.isPresent()) {
return om.get().reader().entries();
}
// find from local run-time image
SystemModuleFinder system = new SystemModuleFinder();
if (system.find(JDK_UNSUPPORTED).isPresent()) {
try {
return system.getClassReader(JDK_UNSUPPORTED).entries();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return Collections.emptySet();
}
}
}

@ -38,6 +38,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/**
* Represents the source of the class files.
@ -86,6 +87,10 @@ public class Archive {
return Module.UNNAMED_MODULE;
}
public boolean contains(String entry) {
return reader.entries().contains(entry);
}
public void addClass(Location origin) {
deps.computeIfAbsent(origin, _k -> new HashSet<>());
}
@ -98,6 +103,15 @@ public class Archive {
return deps.keySet();
}
public Stream<Location> getDependencies() {
return deps.values().stream()
.flatMap(Set::stream);
}
public boolean hasDependences() {
return getDependencies().count() > 0;
}
public void visitDependences(Visitor v) {
for (Map.Entry<Location,Set<Location>> e: deps.entrySet()) {
for (Location target : e.getValue()) {

@ -173,7 +173,7 @@ public class ClassFileReader {
static boolean isClass(Path file) {
String fn = file.getFileName().toString();
return fn.endsWith(".class") && !fn.equals(MODULE_INFO);
return fn.endsWith(".class");
}
class FileIterator implements Iterator<ClassFile> {
@ -306,7 +306,7 @@ public class ClassFileReader {
protected Set<String> scan() {
try (JarFile jf = new JarFile(path.toFile())) {
return jf.stream().map(JarEntry::getName)
.filter(n -> n.endsWith(".class") && !n.endsWith(MODULE_INFO))
.filter(n -> n.endsWith(".class"))
.collect(Collectors.toSet());
} catch (IOException e) {
throw new UncheckedIOException(e);
@ -409,7 +409,7 @@ public class ClassFileReader {
while (entries.hasMoreElements()) {
JarEntry e = entries.nextElement();
String name = e.getName();
if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
if (name.endsWith(".class")) {
return e;
}
}

@ -24,360 +24,285 @@
*/
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Module.*;
import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
import static java.util.stream.Collectors.*;
import com.sun.tools.classfile.AccessFlags;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.ConstantPoolException;
import com.sun.tools.classfile.Dependencies;
import com.sun.tools.classfile.Dependency;
import com.sun.tools.classfile.Dependency.Location;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.io.UncheckedIOException;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.sun.tools.jdeps.Module.*;
import static com.sun.tools.jdeps.ModulePaths.SystemModulePath.JAVA_BASE;
/**
* Parses class files and finds dependences
*/
class DependencyFinder {
private static Finder API_FINDER = new Finder(true);
private static Finder CLASS_FINDER = new Finder(false);
public class DependencyFinder {
private final List<Archive> roots = new ArrayList<>();
private final List<Archive> classpaths = new ArrayList<>();
private final List<Module> modulepaths = new ArrayList<>();
private final List<String> classes = new ArrayList<>();
private final boolean compileTimeView;
private final JdepsConfiguration configuration;
private final JdepsFilter filter;
DependencyFinder(boolean compileTimeView) {
this.compileTimeView = compileTimeView;
private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>();
private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>();
private final ExecutorService pool = Executors.newFixedThreadPool(2);
private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>();
DependencyFinder(JdepsConfiguration configuration,
JdepsFilter filter) {
this.configuration = configuration;
this.filter = filter;
this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>());
this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>());
}
/*
* Adds a class name to the root set
*/
void addClassName(String cn) {
classes.add(cn);
}
/*
* Adds the archive of the given path to the root set
*/
void addRoot(Path path) {
addRoot(Archive.getInstance(path));
}
/*
* Adds the given archive to the root set
*/
void addRoot(Archive archive) {
Objects.requireNonNull(archive);
if (!roots.contains(archive))
roots.add(archive);
Map<Location, Archive> locationToArchive() {
return parsedClasses;
}
/**
* Add an archive specified in the classpath.
* Returns the modules of all dependencies found
*/
void addClassPathArchive(Path path) {
addClassPathArchive(Archive.getInstance(path));
Stream<Archive> getDependences(Archive source) {
return source.getDependencies()
.map(this::locationToArchive)
.filter(a -> a != source);
}
/**
* Add an archive specified in the classpath.
*/
void addClassPathArchive(Archive archive) {
Objects.requireNonNull(archive);
classpaths.add(archive);
}
/**
* Add an archive specified in the modulepath.
*/
void addModule(Module m) {
Objects.requireNonNull(m);
modulepaths.add(m);
}
/**
* Returns the root set.
*/
List<Archive> roots() {
return roots;
}
/**
* Returns a stream of all archives including the root set, module paths,
* and classpath.
* Returns the location to archive map; or NOT_FOUND.
*
* This only returns the archives with classes parsed.
* Location represents a parsed class.
*/
Stream<Archive> archives() {
Stream<Archive> archives = Stream.concat(roots.stream(), modulepaths.stream());
archives = Stream.concat(archives, classpaths.stream());
return archives.filter(a -> !a.isEmpty())
.distinct();
Archive locationToArchive(Location location) {
return parsedClasses.containsKey(location)
? parsedClasses.get(location)
: configuration.findClass(location).orElse(NOT_FOUND);
}
/**
* Finds dependencies
*
* @param apiOnly API only
* @param maxDepth depth of transitive dependency analysis; zero indicates
* @throws IOException
* Returns a map from an archive to its required archives
*/
void findDependencies(JdepsFilter filter, boolean apiOnly, int maxDepth)
throws IOException
{
Dependency.Finder finder =
apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
: Dependencies.getClassDependencyFinder();
Map<Archive, Set<Archive>> dependences() {
Map<Archive, Set<Archive>> map = new HashMap<>();
parsedArchives.values().stream()
.flatMap(Deque::stream)
.filter(a -> !a.isEmpty())
.forEach(source -> {
Set<Archive> deps = getDependences(source).collect(toSet());
if (!deps.isEmpty()) {
map.put(source, deps);
}
});
return map;
}
// list of archives to be analyzed
Set<Archive> roots = new LinkedHashSet<>(this.roots);
boolean isParsed(Location location) {
return parsedClasses.containsKey(location);
}
// include java.base in root set
roots.add(JAVA_BASE);
/**
* Parses all class files from the given archive stream and returns
* all target locations.
*/
public Set<Location> parse(Stream<? extends Archive> archiveStream) {
archiveStream.forEach(archive -> parse(archive, CLASS_FINDER));
return waitForTasksCompleted();
}
// If -include pattern specified, classes may be in module path or class path.
// To get compile time view analysis, all classes are analyzed.
// add all modules except JDK modules to root set
modulepaths.stream()
.filter(filter::matches)
.forEach(roots::add);
/**
* Parses the exported API class files from the given archive stream and
* returns all target locations.
*/
public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) {
archiveStream.forEach(archive -> parse(archive, API_FINDER));
return waitForTasksCompleted();
}
// add classpath to the root set
classpaths.stream()
.filter(filter::matches)
.forEach(roots::add);
// transitive dependency
int depth = maxDepth > 0 ? maxDepth : Integer.MAX_VALUE;
// Work queue of names of classfiles to be searched.
// Entries will be unique, and for classes that do not yet have
// dependencies in the results map.
ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
ConcurrentSkipListSet<String> doneClasses = new ConcurrentSkipListSet<>();
TaskExecutor executor = new TaskExecutor(finder, filter, apiOnly, deque, doneClasses);
/**
* Parses the named class from the given archive and
* returns all target locations the named class references.
*/
public Set<Location> parse(Archive archive, String name) {
try {
// get the immediate dependencies of the input files
for (Archive source : roots) {
executor.task(source, deque);
}
executor.waitForTasksCompleted();
List<Archive> archives = Stream.concat(Stream.concat(roots.stream(),
modulepaths.stream()),
classpaths.stream())
.collect(Collectors.toList());
// Additional pass to find archive where dependences are identified
// and also any specified classes, if any.
// If -R is specified, perform transitive dependency analysis.
Deque<String> unresolved = new LinkedList<>(classes);
do {
String name;
while ((name = unresolved.poll()) != null) {
if (doneClasses.contains(name)) {
continue;
}
if (compileTimeView) {
final String cn = name + ".class";
// parse all classes in the source archive
Optional<Archive> source = archives.stream()
.filter(a -> a.reader().entries().contains(cn))
.findFirst();
trace("%s compile time view %s%n", name, source.map(Archive::getName).orElse(" not found"));
if (source.isPresent()) {
executor.runTask(source.get(), deque);
}
}
ClassFile cf = null;
for (Archive archive : archives) {
cf = archive.reader().getClassFile(name);
if (cf != null) {
String classFileName;
try {
classFileName = cf.getName();
} catch (ConstantPoolException e) {
throw new Dependencies.ClassFileError(e);
}
if (!doneClasses.contains(classFileName)) {
// if name is a fully-qualified class name specified
// from command-line, this class might already be parsed
doneClasses.add(classFileName);
for (Dependency d : finder.findDependencies(cf)) {
if (depth == 0) {
// ignore the dependency
archive.addClass(d.getOrigin());
break;
} else if (filter.accepts(d) && filter.accept(archive)) {
// continue analysis on non-JDK classes
archive.addClass(d.getOrigin(), d.getTarget());
String cn = d.getTarget().getName();
if (!doneClasses.contains(cn) && !deque.contains(cn)) {
deque.add(cn);
}
} else {
// ensure that the parsed class is added the archive
archive.addClass(d.getOrigin());
}
}
}
break;
}
}
if (cf == null) {
doneClasses.add(name);
}
}
unresolved = deque;
deque = new ConcurrentLinkedDeque<>();
} while (!unresolved.isEmpty() && depth-- > 0);
} finally {
executor.shutdown();
return parse(archive, CLASS_FINDER, name);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
/**
* TaskExecutor creates FutureTask to analyze all classes in a given archive
* Parses the exported API of the named class from the given archive and
* returns all target locations the named class references.
*/
private class TaskExecutor {
final ExecutorService pool;
final Dependency.Finder finder;
final JdepsFilter filter;
final boolean apiOnly;
final Set<String> doneClasses;
final Map<Archive, FutureTask<Void>> tasks = new HashMap<>();
TaskExecutor(Dependency.Finder finder,
JdepsFilter filter,
boolean apiOnly,
ConcurrentLinkedDeque<String> deque,
Set<String> doneClasses) {
this.pool = Executors.newFixedThreadPool(2);
this.finder = finder;
this.filter = filter;
this.apiOnly = apiOnly;
this.doneClasses = doneClasses;
public Set<Location> parseExportedAPIs(Archive archive, String name)
{
try {
return parse(archive, API_FINDER, name);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Creates a new task to analyze class files in the given archive.
* The dependences are added to the given deque for analysis.
*/
FutureTask<Void> task(Archive archive, final ConcurrentLinkedDeque<String> deque) {
trace("parsing %s %s%n", archive.getName(), archive.path());
FutureTask<Void> task = new FutureTask<Void>(new Callable<Void>() {
public Void call() throws Exception {
for (ClassFile cf : archive.reader().getClassFiles()) {
String classFileName;
try {
classFileName = cf.getName();
} catch (ConstantPoolException e) {
throw new Dependencies.ClassFileError(e);
}
private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) {
if (parsedArchives.get(finder).contains(archive))
return Optional.empty();
// tests if this class matches the -include
String cn = classFileName.replace('/', '.');
if (!filter.matches(cn))
continue;
parsedArchives.get(finder).add(archive);
// if -apionly is specified, analyze only exported and public types
if (apiOnly && !(isExported(archive, cn) && cf.access_flags.is(AccessFlags.ACC_PUBLIC)))
continue;
if (!doneClasses.contains(classFileName)) {
doneClasses.add(classFileName);
}
for (Dependency d : finder.findDependencies(cf)) {
if (filter.accepts(d) && filter.accept(archive)) {
String name = d.getTarget().getName();
if (!doneClasses.contains(name) && !deque.contains(name)) {
deque.add(name);
}
archive.addClass(d.getOrigin(), d.getTarget());
} else {
// ensure that the parsed class is added the archive
archive.addClass(d.getOrigin());
}
}
trace("parsing %s %s%n", archive.getName(), archive.path());
FutureTask<Set<Location>> task = new FutureTask<>(new Callable<>() {
public Set<Location> call() throws Exception {
Set<Location> targets = new HashSet<>();
for (ClassFile cf : archive.reader().getClassFiles()) {
String classFileName;
try {
classFileName = cf.getName();
} catch (ConstantPoolException e) {
throw new Dependencies.ClassFileError(e);
}
return null;
}
});
tasks.put(archive, task);
pool.submit(task);
return task;
}
/*
* This task will parse all class files of the given archive, if it's a new task.
* This method waits until the task is completed.
*/
void runTask(Archive archive, final ConcurrentLinkedDeque<String> deque) {
if (tasks.containsKey(archive))
return;
FutureTask<Void> task = task(archive, deque);
try {
// wait for completion
task.get();
} catch (InterruptedException|ExecutionException e) {
throw new Error(e);
}
}
/*
* Waits until all submitted tasks are completed.
*/
void waitForTasksCompleted() {
try {
for (FutureTask<Void> t : tasks.values()) {
if (t.isDone())
// filter source class/archive
String cn = classFileName.replace('/', '.');
if (!finder.accept(archive, cn, cf.access_flags))
continue;
// wait for completion
t.get();
// tests if this class matches the -include
if (!filter.matches(cn))
continue;
for (Dependency d : finder.findDependencies(cf)) {
if (filter.accepts(d)) {
archive.addClass(d.getOrigin(), d.getTarget());
targets.add(d.getTarget());
} else {
// ensure that the parsed class is added the archive
archive.addClass(d.getOrigin());
}
parsedClasses.putIfAbsent(d.getOrigin(), archive);
}
}
} catch (InterruptedException|ExecutionException e) {
throw new Error(e);
return targets;
}
});
tasks.add(task);
pool.submit(task);
return Optional.of(task);
}
private Set<Location> parse(Archive archive, Finder finder, String name)
throws IOException
{
ClassFile cf = archive.reader().getClassFile(name);
if (cf == null) {
throw new IllegalArgumentException(archive.getName() + " does not contain " + name);
}
/*
* Shutdown the executor service.
*/
void shutdown() {
pool.shutdown();
Set<Location> targets = new HashSet<>();
String cn;
try {
cn = cf.getName().replace('/', '.');
} catch (ConstantPoolException e) {
throw new Dependencies.ClassFileError(e);
}
/**
* Tests if the given class name is exported by the given archive.
*
* All packages are exported in unnamed module.
*/
private boolean isExported(Archive archive, String classname) {
int i = classname.lastIndexOf('.');
String pn = i > 0 ? classname.substring(0, i) : "";
return archive.getModule().isExported(pn);
if (!finder.accept(archive, cn, cf.access_flags))
return targets;
// tests if this class matches the -include
if (!filter.matches(cn))
return targets;
// skip checking filter.matches
for (Dependency d : finder.findDependencies(cf)) {
if (filter.accepts(d)) {
targets.add(d.getTarget());
archive.addClass(d.getOrigin(), d.getTarget());
} else {
// ensure that the parsed class is added the archive
archive.addClass(d.getOrigin());
}
parsedClasses.putIfAbsent(d.getOrigin(), archive);
}
return targets;
}
/*
* Waits until all submitted tasks are completed.
*/
private Set<Location> waitForTasksCompleted() {
try {
Set<Location> targets = new HashSet<>();
FutureTask<Set<Location>> task;
while ((task = tasks.poll()) != null) {
// wait for completion
if (!task.isDone())
targets.addAll(task.get());
}
return targets;
} catch (InterruptedException|ExecutionException e) {
throw new Error(e);
}
}
/*
* Shutdown the executor service.
*/
void shutdown() {
pool.shutdown();
}
private interface SourceFilter {
boolean accept(Archive archive, String cn, AccessFlags accessFlags);
}
private static class Finder implements Dependency.Finder, SourceFilter {
private final Dependency.Finder finder;
private final boolean apiOnly;
Finder(boolean apiOnly) {
this.apiOnly = apiOnly;
this.finder = apiOnly
? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
: Dependencies.getClassDependencyFinder();
}
@Override
public boolean accept(Archive archive, String cn, AccessFlags accessFlags) {
int i = cn.lastIndexOf('.');
String pn = i > 0 ? cn.substring(0, i) : "";
// if -apionly is specified, analyze only exported and public types
// All packages are exported in unnamed module.
return apiOnly ? archive.getModule().isExported(pn) &&
accessFlags.is(AccessFlags.ACC_PUBLIC)
: true;
}
@Override
public Iterable<? extends Dependency> findDependencies(ClassFile classfile) {
return finder.findDependencies(classfile);
}
}
}

@ -0,0 +1,390 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.jdeps;
import com.sun.tools.classfile.Dependency.Location;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.sun.tools.jdeps.Analyzer.Type.CLASS;
import static com.sun.tools.jdeps.Analyzer.Type.VERBOSE;
import static com.sun.tools.jdeps.Module.trace;
import static java.util.stream.Collectors.*;
/**
* Dependency Analyzer.
*
* Type of filters:
* source filter: -include <pattern>
* target filter: -package, -regex, -requires
*
* The initial archive set for analysis includes
* 1. archives specified in the command line arguments
* 2. observable modules matching the source filter
* 3. classpath archives matching the source filter or target filter
* 4. -addmods and -m root modules
*/
public class DepsAnalyzer {
final JdepsConfiguration configuration;
final JdepsFilter filter;
final JdepsWriter writer;
final Analyzer.Type verbose;
final boolean apiOnly;
final DependencyFinder finder;
final Analyzer analyzer;
final List<Archive> rootArchives = new ArrayList<>();
// parsed archives
final Set<Archive> archives = new LinkedHashSet<>();
public DepsAnalyzer(JdepsConfiguration config,
JdepsFilter filter,
JdepsWriter writer,
Analyzer.Type verbose,
boolean apiOnly) {
this.configuration = config;
this.filter = filter;
this.writer = writer;
this.verbose = verbose;
this.apiOnly = apiOnly;
this.finder = new DependencyFinder(config, filter);
this.analyzer = new Analyzer(configuration, verbose, filter);
// determine initial archives to be analyzed
this.rootArchives.addAll(configuration.initialArchives());
// if -include pattern is specified, add the matching archives on
// classpath to the root archives
if (filter.hasIncludePattern() || filter.hasTargetFilter()) {
configuration.getModules().values().stream()
.filter(source -> filter.include(source) && filter.matches(source))
.forEach(this.rootArchives::add);
}
// class path archives
configuration.classPathArchives().stream()
.filter(filter::matches)
.forEach(this.rootArchives::add);
// Include the root modules for analysis
this.rootArchives.addAll(configuration.rootModules());
trace("analyze root archives: %s%n", this.rootArchives);
}
/*
* Perform runtime dependency analysis
*/
public boolean run() throws IOException {
return run(false, 1);
}
/**
* Perform compile-time view or run-time view dependency analysis.
*
* @param compileTimeView
* @param maxDepth depth of recursive analysis. depth == 0 if -R is set
*/
public boolean run(boolean compileTimeView, int maxDepth) throws IOException {
try {
// parse each packaged module or classpath archive
if (apiOnly) {
finder.parseExportedAPIs(rootArchives.stream());
} else {
finder.parse(rootArchives.stream());
}
archives.addAll(rootArchives);
int depth = maxDepth > 0 ? maxDepth : Integer.MAX_VALUE;
// transitive analysis
if (depth > 1) {
if (compileTimeView)
transitiveArchiveDeps(depth-1);
else
transitiveDeps(depth-1);
}
Set<Archive> archives = archives();
// analyze the dependencies collected
analyzer.run(archives, finder.locationToArchive());
writer.generateOutput(archives, analyzer);
} finally {
finder.shutdown();
}
return true;
}
/**
* Returns the archives for reporting that has matching dependences.
*
* If -requires is set, they should be excluded.
*/
Set<Archive> archives() {
if (filter.requiresFilter().isEmpty()) {
return archives.stream()
.filter(filter::include)
.filter(Archive::hasDependences)
.collect(Collectors.toSet());
} else {
// use the archives that have dependences and not specified in -requires
return archives.stream()
.filter(filter::include)
.filter(source -> !filter.requiresFilter().contains(source))
.filter(source ->
source.getDependencies()
.map(finder::locationToArchive)
.anyMatch(a -> a != source))
.collect(Collectors.toSet());
}
}
/**
* Returns the dependences, either class name or package name
* as specified in the given verbose level.
*/
Stream<String> dependences() {
return analyzer.archives().stream()
.map(analyzer::dependences)
.flatMap(Set::stream)
.distinct();
}
/**
* Returns the archives that contains the given locations and
* not parsed and analyzed.
*/
private Set<Archive> unresolvedArchives(Stream<Location> locations) {
return locations.filter(l -> !finder.isParsed(l))
.distinct()
.map(configuration::findClass)
.flatMap(Optional::stream)
.filter(filter::include)
.collect(toSet());
}
/*
* Recursively analyzes entire module/archives.
*/
private void transitiveArchiveDeps(int depth) throws IOException {
Stream<Location> deps = archives.stream()
.flatMap(Archive::getDependencies);
// start with the unresolved archives
Set<Archive> unresolved = unresolvedArchives(deps);
do {
// parse all unresolved archives
Set<Location> targets = apiOnly
? finder.parseExportedAPIs(unresolved.stream())
: finder.parse(unresolved.stream());
archives.addAll(unresolved);
// Add dependencies to the next batch for analysis
unresolved = unresolvedArchives(targets.stream());
} while (!unresolved.isEmpty() && depth-- > 0);
}
/*
* Recursively analyze the class dependences
*/
private void transitiveDeps(int depth) throws IOException {
Stream<Location> deps = archives.stream()
.flatMap(Archive::getDependencies);
Deque<Location> unresolved = deps.collect(Collectors.toCollection(LinkedList::new));
ConcurrentLinkedDeque<Location> deque = new ConcurrentLinkedDeque<>();
do {
Location target;
while ((target = unresolved.poll()) != null) {
if (finder.isParsed(target))
continue;
Archive archive = configuration.findClass(target).orElse(null);
if (archive != null && filter.include(archive)) {
archives.add(archive);
String name = target.getName();
Set<Location> targets = apiOnly
? finder.parseExportedAPIs(archive, name)
: finder.parse(archive, name);
// build unresolved dependencies
targets.stream()
.filter(t -> !finder.isParsed(t))
.forEach(deque::add);
}
}
unresolved = deque;
deque = new ConcurrentLinkedDeque<>();
} while (!unresolved.isEmpty() && depth-- > 0);
}
// ----- for testing purpose -----
public static enum Info {
REQUIRES,
REQUIRES_PUBLIC,
EXPORTED_API,
MODULE_PRIVATE,
QUALIFIED_EXPORTED_API,
INTERNAL_API,
JDK_INTERNAL_API
}
public static class Node {
public final String name;
public final String source;
public final Info info;
Node(String name, Info info) {
this(name, name, info);
}
Node(String name, String source, Info info) {
this.name = name;
this.source = source;
this.info = info;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (info != Info.REQUIRES && info != Info.REQUIRES_PUBLIC)
sb.append(source).append("/");
sb.append(name);
if (info == Info.QUALIFIED_EXPORTED_API)
sb.append(" (qualified)");
else if (info == Info.JDK_INTERNAL_API)
sb.append(" (JDK internal)");
else if (info == Info.INTERNAL_API)
sb.append(" (internal)");
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Node))
return false;
Node other = (Node)o;
return this.name.equals(other.name) &&
this.source.equals(other.source) &&
this.info.equals(other.info);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + source.hashCode();
result = 31 * result + info.hashCode();
return result;
}
}
/**
* Returns a graph of module dependences.
*
* Each Node represents a module and each edge is a dependence.
* No analysis on "requires public".
*/
public Graph<Node> moduleGraph() {
Graph.Builder<Node> builder = new Graph.Builder<>();
archives().stream()
.forEach(m -> {
Node u = new Node(m.getName(), Info.REQUIRES);
builder.addNode(u);
analyzer.requires(m)
.map(req -> new Node(req.getName(), Info.REQUIRES))
.forEach(v -> builder.addEdge(u, v));
});
return builder.build();
}
/**
* Returns a graph of dependences.
*
* Each Node represents a class or package per the specified verbose level.
* Each edge indicates
*/
public Graph<Node> dependenceGraph() {
Graph.Builder<Node> builder = new Graph.Builder<>();
archives().stream()
.map(analyzer.results::get)
.filter(deps -> !deps.dependencies().isEmpty())
.flatMap(deps -> deps.dependencies().stream())
.forEach(d -> addEdge(builder, d));
return builder.build();
}
private void addEdge(Graph.Builder<Node> builder, Analyzer.Dep dep) {
Archive source = dep.originArchive();
Archive target = dep.targetArchive();
String pn = dep.target();
if ((verbose == CLASS || verbose == VERBOSE)) {
int i = dep.target().lastIndexOf('.');
pn = i > 0 ? dep.target().substring(0, i) : "";
}
final Info info;
if (source == target) {
info = Info.MODULE_PRIVATE;
} else if (!target.getModule().isNamed()) {
info = Info.EXPORTED_API;
} else if (target.getModule().isExported(pn)) {
info = Info.EXPORTED_API;
} else {
Module module = target.getModule();
if (!source.getModule().isJDK() && module.isJDK())
info = Info.JDK_INTERNAL_API;
// qualified exports or inaccessible
else if (module.isExported(pn, source.getModule().name()))
info = Info.QUALIFIED_EXPORTED_API;
else
info = Info.INTERNAL_API;
}
Node u = new Node(dep.origin(), source.getName(), info);
Node v = new Node(dep.target(), target.getName(), info);
builder.addEdge(u, v);
}
}

@ -0,0 +1,401 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.jdeps;
import java.io.PrintWriter;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class Graph<T> {
private final Set<T> nodes;
private final Map<T, Set<T>> edges;
public Graph(Set<T> nodes, Map<T, Set<T>> edges) {
this.nodes = Collections.unmodifiableSet(nodes);
this.edges = Collections.unmodifiableMap(edges);
}
public Set<T> nodes() {
return nodes;
}
public Map<T, Set<T>> edges() {
return edges;
}
public Set<T> adjacentNodes(T u) {
return edges.get(u);
}
public boolean contains(T u) {
return nodes.contains(u);
}
public Set<Edge<T>> edgesFrom(T u) {
return edges.get(u).stream()
.map(v -> new Edge<T>(u, v))
.collect(Collectors.toSet());
}
/**
* Returns a new Graph after transitive reduction
*/
public Graph<T> reduce() {
Builder<T> builder = new Builder<>();
nodes.stream()
.forEach(u -> {
builder.addNode(u);
edges.get(u).stream()
.filter(v -> !pathExists(u, v, false))
.forEach(v -> builder.addEdge(u, v));
});
return builder.build();
}
/**
* Returns a new Graph after transitive reduction. All edges in
* the given g takes precedence over this graph.
*
* @throw IllegalArgumentException g must be a subgraph this graph
*/
public Graph<T> reduce(Graph<T> g) {
boolean subgraph = nodes.containsAll(g.nodes) &&
g.edges.keySet().stream()
.allMatch(u -> adjacentNodes(u).containsAll(g.adjacentNodes(u)));
if (!subgraph) {
throw new IllegalArgumentException(g + " is not a subgraph of " + this);
}
Builder<T> builder = new Builder<>();
nodes.stream()
.forEach(u -> {
builder.addNode(u);
// filter the edge if there exists a path from u to v in the given g
// or there exists another path from u to v in this graph
edges.get(u).stream()
.filter(v -> !g.pathExists(u, v) && !pathExists(u, v, false))
.forEach(v -> builder.addEdge(u, v));
});
// add the overlapped edges from this graph and the given g
g.edges().keySet().stream()
.forEach(u -> g.adjacentNodes(u).stream()
.filter(v -> isAdjacent(u, v))
.forEach(v -> builder.addEdge(u, v)));
return builder.build();
}
/**
* Returns nodes sorted in topological order.
*/
public Stream<T> orderedNodes() {
TopoSorter<T> sorter = new TopoSorter<>(this);
return sorter.result.stream();
}
/**
* Traverse this graph and performs the given action in topological order
*/
public void ordered(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.ordered(action);
}
/**
* Traverses this graph and performs the given action in reverse topological order
*/
public void reverse(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.reverse(action);
}
/**
* Returns a transposed graph from this graph
*/
public Graph<T> transpose() {
Builder<T> builder = new Builder<>();
builder.addNodes(nodes);
// reverse edges
edges.keySet().forEach(u -> {
edges.get(u).stream()
.forEach(v -> builder.addEdge(v, u));
});
return builder.build();
}
/**
* Returns all nodes reachable from the given set of roots.
*/
public Set<T> dfs(Set<T> roots) {
Deque<T> deque = new LinkedList<>(roots);
Set<T> visited = new HashSet<>();
while (!deque.isEmpty()) {
T u = deque.pop();
if (!visited.contains(u)) {
visited.add(u);
if (contains(u)) {
adjacentNodes(u).stream()
.filter(v -> !visited.contains(v))
.forEach(deque::push);
}
}
}
return visited;
}
private boolean isAdjacent(T u, T v) {
return edges.containsKey(u) && edges.get(u).contains(v);
}
private boolean pathExists(T u, T v) {
return pathExists(u, v, true);
}
/**
* Returns true if there exists a path from u to v in this graph.
* If includeAdjacent is false, it returns true if there exists
* another path from u to v of distance > 1
*/
private boolean pathExists(T u, T v, boolean includeAdjacent) {
if (!nodes.contains(u) || !nodes.contains(v)) {
return false;
}
if (includeAdjacent && isAdjacent(u, v)) {
return true;
}
Deque<T> stack = new LinkedList<>();
Set<T> visited = new HashSet<>();
stack.push(u);
while (!stack.isEmpty()) {
T node = stack.pop();
if (node.equals(v)) {
return true;
}
if (!visited.contains(node)) {
visited.add(node);
edges.get(node).stream()
.filter(e -> includeAdjacent || !node.equals(u) || !e.equals(v))
.forEach(e -> stack.push(e));
}
}
assert !visited.contains(v);
return false;
}
public void printGraph(PrintWriter out) {
out.println("graph for " + nodes);
nodes.stream()
.forEach(u -> adjacentNodes(u).stream()
.forEach(v -> out.format(" %s -> %s%n", u, v)));
}
@Override
public String toString() {
return nodes.toString();
}
static class Edge<T> {
final T u;
final T v;
Edge(T u, T v) {
this.u = u;
this.v = v;
}
@Override
public String toString() {
return String.format("%s -> %s", u, v);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Edge))
return false;
@SuppressWarnings("unchecked")
Edge<T> edge = (Edge<T>) o;
return u.equals(edge.u) && v.equals(edge.v);
}
@Override
public int hashCode() {
int result = u.hashCode();
result = 31 * result + v.hashCode();
return result;
}
}
static class Builder<T> {
final Set<T> nodes = new HashSet<>();
final Map<T, Set<T>> edges = new HashMap<>();
public void addNode(T node) {
if (nodes.contains(node)) {
return;
}
nodes.add(node);
edges.computeIfAbsent(node, _e -> new HashSet<>());
}
public void addNodes(Set<T> nodes) {
nodes.addAll(nodes);
}
public void addEdge(T u, T v) {
addNode(u);
addNode(v);
edges.get(u).add(v);
}
public Graph<T> build() {
return new Graph<T>(nodes, edges);
}
}
/**
* Topological sort
*/
static class TopoSorter<T> {
final Deque<T> result = new LinkedList<>();
final Deque<T> nodes;
final Graph<T> graph;
TopoSorter(Graph<T> graph) {
this.graph = graph;
this.nodes = new LinkedList<>(graph.nodes);
sort();
}
public void ordered(Consumer<T> action) {
result.iterator().forEachRemaining(action);
}
public void reverse(Consumer<T> action) {
result.descendingIterator().forEachRemaining(action);
}
private void sort() {
Deque<T> visited = new LinkedList<>();
Deque<T> done = new LinkedList<>();
T node;
while ((node = nodes.poll()) != null) {
if (!visited.contains(node)) {
visit(node, visited, done);
}
}
}
private void visit(T node, Deque<T> visited, Deque<T> done) {
if (visited.contains(node)) {
if (!done.contains(node)) {
throw new IllegalArgumentException("Cyclic detected: " +
node + " " + graph.edges().get(node));
}
return;
}
visited.add(node);
graph.edges().get(node).stream()
.forEach(x -> visit(x, visited, done));
done.add(node);
result.addLast(node);
}
}
public static class DotGraph {
static final String ORANGE = "#e76f00";
static final String BLUE = "#437291";
static final String GRAY = "#dddddd";
static final String REEXPORTS = "";
static final String REQUIRES = "style=\"dashed\"";
static final String REQUIRES_BASE = "color=\"" + GRAY + "\"";
static final Set<String> javaModules = modules(name ->
(name.startsWith("java.") && !name.equals("java.smartcardio")));
static final Set<String> jdkModules = modules(name ->
(name.startsWith("java.") ||
name.startsWith("jdk.") ||
name.startsWith("javafx.")) && !javaModules.contains(name));
private static Set<String> modules(Predicate<String> predicate) {
return ModuleFinder.ofSystem().findAll()
.stream()
.map(ModuleReference::descriptor)
.map(ModuleDescriptor::name)
.filter(predicate)
.collect(Collectors.toSet());
}
static void printAttributes(PrintWriter out) {
out.format(" size=\"25,25\";%n");
out.format(" nodesep=.5;%n");
out.format(" ranksep=1.5;%n");
out.format(" pencolor=transparent;%n");
out.format(" node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n");
out.format(" edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n");
}
static void printNodes(PrintWriter out, Graph<String> graph) {
out.format(" subgraph se {%n");
graph.nodes().stream()
.filter(javaModules::contains)
.forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n",
mn, ORANGE, "java"));
out.format(" }%n");
graph.nodes().stream()
.filter(jdkModules::contains)
.forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n",
mn, BLUE, "jdk"));
graph.nodes().stream()
.filter(mn -> !javaModules.contains(mn) && !jdkModules.contains(mn))
.forEach(mn -> out.format(" \"%s\";%n", mn));
}
static void printEdges(PrintWriter out, Graph<String> graph,
String node, Set<String> requiresPublic) {
graph.adjacentNodes(node).forEach(dn -> {
String attr = dn.equals("java.base") ? REQUIRES_BASE
: (requiresPublic.contains(dn) ? REEXPORTS : REQUIRES);
out.format(" \"%s\" -> \"%s\" [%s];%n", node, dn, attr);
});
}
}
}

@ -0,0 +1,586 @@
/*
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Module.trace;
import static java.util.stream.Collectors.*;
import com.sun.tools.classfile.Dependency;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class JdepsConfiguration {
private static final String MODULE_INFO = "module-info.class";
private final SystemModuleFinder system;
private final ModuleFinder finder;
private final Map<String, Module> nameToModule = new LinkedHashMap<>();
private final Map<String, Module> packageToModule = new HashMap<>();
private final Map<String, List<Archive>> packageToUnnamedModule = new HashMap<>();
private final List<Archive> classpathArchives = new ArrayList<>();
private final List<Archive> initialArchives = new ArrayList<>();
private final Set<Module> rootModules = new HashSet<>();
private final Configuration configuration;
private JdepsConfiguration(SystemModuleFinder systemModulePath,
ModuleFinder finder,
Set<String> roots,
List<Path> classpaths,
List<Archive> initialArchives)
throws IOException
{
trace("root: %s%n", roots);
this.system = systemModulePath;
this.finder = finder;
// build root set
Set<String> mods = new HashSet<>();
if (initialArchives.isEmpty() && classpaths.isEmpty() && !roots.isEmpty()) {
// named module as root. No initial unnamed module
mods.addAll(roots);
} else {
// unnamed module
mods.addAll(roots);
mods.addAll(systemModulePath.defaultSystemRoots());
}
this.configuration = Configuration.empty()
.resolveRequires(finder, ModuleFinder.of(), mods);
this.configuration.modules().stream()
.map(ResolvedModule::reference)
.forEach(this::addModuleReference);
// packages in unnamed module
initialArchives.forEach(archive -> {
addPackagesInUnnamedModule(archive);
this.initialArchives.add(archive);
});
// classpath archives
for (Path p : classpaths) {
if (Files.exists(p)) {
Archive archive = Archive.getInstance(p);
addPackagesInUnnamedModule(archive);
classpathArchives.add(archive);
}
}
// all roots specified in -addmods or -m are included
// as the initial set for analysis.
roots.stream()
.map(nameToModule::get)
.forEach(this.rootModules::add);
initProfiles();
trace("resolved modules: %s%n", nameToModule.keySet().stream()
.sorted().collect(joining("\n", "\n", "")));
}
private void initProfiles() {
// other system modules are not observed and not added in nameToModule map
Map<String, Module> systemModules =
system.moduleNames()
.collect(toMap(Function.identity(), (mn) -> {
Module m = nameToModule.get(mn);
if (m == null) {
ModuleReference mref = finder.find(mn).get();
m = toModule(mref);
}
return m;
}));
Profile.init(systemModules);
}
private void addModuleReference(ModuleReference mref) {
Module module = toModule(mref);
nameToModule.put(mref.descriptor().name(), module);
mref.descriptor().packages()
.forEach(pn -> packageToModule.putIfAbsent(pn, module));
}
private void addPackagesInUnnamedModule(Archive archive) {
archive.reader().entries().stream()
.filter(e -> e.endsWith(".class") && !e.equals(MODULE_INFO))
.map(this::toPackageName)
.distinct()
.forEach(pn -> packageToUnnamedModule
.computeIfAbsent(pn, _n -> new ArrayList<>()).add(archive));
}
private String toPackageName(String name) {
int i = name.lastIndexOf('/');
return i > 0 ? name.replace('/', '.').substring(0, i) : "";
}
public Optional<Module> findModule(String name) {
Objects.requireNonNull(name);
Module m = nameToModule.get(name);
return m!= null ? Optional.of(m) : Optional.empty();
}
public Optional<ModuleDescriptor> findModuleDescriptor(String name) {
Objects.requireNonNull(name);
Module m = nameToModule.get(name);
return m!= null ? Optional.of(m.descriptor()) : Optional.empty();
}
boolean isSystem(Module m) {
return system.find(m.name()).isPresent();
}
/**
* Returns the modules that the given module can read
*/
public Stream<Module> reads(Module module) {
return configuration.findModule(module.name()).get()
.reads().stream()
.map(ResolvedModule::name)
.map(nameToModule::get);
}
/**
* Returns the list of packages that split between resolved module and
* unnamed module
*/
public Map<String, Set<String>> splitPackages() {
Set<String> splitPkgs = packageToModule.keySet().stream()
.filter(packageToUnnamedModule::containsKey)
.collect(toSet());
if (splitPkgs.isEmpty())
return Collections.emptyMap();
return splitPkgs.stream().collect(toMap(Function.identity(), (pn) -> {
Set<String> sources = new LinkedHashSet<>();
sources.add(packageToModule.get(pn).getModule().location().toString());
packageToUnnamedModule.get(pn).stream()
.map(Archive::getPathName)
.forEach(sources::add);
return sources;
}));
}
/**
* Returns an optional archive containing the given Location
*/
public Optional<Archive> findClass(Dependency.Location location) {
String name = location.getName();
int i = name.lastIndexOf('/');
String pn = i > 0 ? name.substring(0, i).replace('/', '.') : "";
Archive archive = packageToModule.get(pn);
if (archive != null) {
return archive.contains(name + ".class")
? Optional.of(archive)
: Optional.empty();
}
if (packageToUnnamedModule.containsKey(pn)) {
return packageToUnnamedModule.get(pn).stream()
.filter(a -> a.contains(name + ".class"))
.findFirst();
}
return Optional.empty();
}
/**
* Returns the list of Modules that can be found in the specified
* module paths.
*/
public Map<String, Module> getModules() {
return nameToModule;
}
public Stream<Module> resolve(Set<String> roots) {
if (roots.isEmpty()) {
return nameToModule.values().stream();
} else {
return Configuration.empty()
.resolveRequires(finder, ModuleFinder.of(), roots)
.modules().stream()
.map(ResolvedModule::name)
.map(nameToModule::get);
}
}
public List<Archive> classPathArchives() {
return classpathArchives;
}
public List<Archive> initialArchives() {
return initialArchives;
}
public Set<Module> rootModules() {
return rootModules;
}
public Module toModule(ModuleReference mref) {
try {
String mn = mref.descriptor().name();
URI location = mref.location().orElseThrow(FileNotFoundException::new);
ModuleDescriptor md = mref.descriptor();
Module.Builder builder = new Module.Builder(md, system.find(mn).isPresent());
final ClassFileReader reader;
if (location.getScheme().equals("jrt")) {
reader = system.getClassReader(mn);
} else {
reader = ClassFileReader.newInstance(Paths.get(location));
}
builder.classes(reader);
builder.location(location);
return builder.build();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
static class SystemModuleFinder implements ModuleFinder {
private static final String JAVA_SE = "java.se";
private final FileSystem fileSystem;
private final Path root;
private final Map<String, ModuleReference> systemModules;
SystemModuleFinder() {
this(System.getProperty("java.home"));
}
SystemModuleFinder(String javaHome) {
final FileSystem fs;
final Path root;
final Map<String, ModuleReference> systemModules;
if (javaHome != null) {
if (Files.isRegularFile(Paths.get(javaHome, "lib", "modules"))) {
try {
// jrt file system
fs = FileSystems.getFileSystem(URI.create("jrt:/"));
root = fs.getPath("/modules");
systemModules = Files.walk(root, 1)
.filter(path -> !path.equals(root))
.map(this::toModuleReference)
.collect(toMap(mref -> mref.descriptor().name(),
Function.identity()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} else {
// exploded image
fs = FileSystems.getDefault();
root = Paths.get(javaHome, "modules");
systemModules = ModuleFinder.ofSystem().findAll().stream()
.collect(toMap(mref -> mref.descriptor().name(), Function.identity()));
}
} else {
fs = null;
root = null;
systemModules = Collections.emptyMap();
}
this.fileSystem = fs;
this.root = root;
this.systemModules = systemModules;
}
private ModuleReference toModuleReference(Path path) {
Path minfo = path.resolve(MODULE_INFO);
try (InputStream in = Files.newInputStream(minfo);
BufferedInputStream bin = new BufferedInputStream(in)) {
ModuleDescriptor descriptor = dropHashes(ModuleDescriptor.read(bin));
String mn = descriptor.name();
URI uri = URI.create("jrt:/" + path.getFileName().toString());
Supplier<ModuleReader> readerSupplier = new Supplier<>() {
@Override
public ModuleReader get() {
return new ModuleReader() {
@Override
public Optional<URI> find(String name) throws IOException {
return name.equals(mn)
? Optional.of(uri) : Optional.empty();
}
@Override
public void close() throws IOException {
}
};
}
};
return new ModuleReference(descriptor, uri, readerSupplier);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private ModuleDescriptor dropHashes(ModuleDescriptor md) {
ModuleDescriptor.Builder builder = new ModuleDescriptor.Builder(md.name());
md.requires().forEach(builder::requires);
md.exports().forEach(builder::exports);
md.provides().values().stream().forEach(builder::provides);
md.uses().stream().forEach(builder::uses);
builder.conceals(md.conceals());
return builder.build();
}
@Override
public Set<ModuleReference> findAll() {
return systemModules.values().stream().collect(toSet());
}
@Override
public Optional<ModuleReference> find(String mn) {
return systemModules.containsKey(mn)
? Optional.of(systemModules.get(mn)) : Optional.empty();
}
public Stream<String> moduleNames() {
return systemModules.values().stream()
.map(mref -> mref.descriptor().name());
}
public ClassFileReader getClassReader(String modulename) throws IOException {
Path mp = root.resolve(modulename);
if (Files.exists(mp) && Files.isDirectory(mp)) {
return ClassFileReader.newInstance(fileSystem, mp);
} else {
throw new FileNotFoundException(mp.toString());
}
}
public Set<String> defaultSystemRoots() {
Set<String> roots = new HashSet<>();
boolean hasJava = false;
if (systemModules.containsKey(JAVA_SE)) {
// java.se is a system module
hasJava = true;
roots.add(JAVA_SE);
}
for (ModuleReference mref : systemModules.values()) {
String mn = mref.descriptor().name();
if (hasJava && mn.startsWith("java."))
continue;
// add as root if observable and exports at least one package
ModuleDescriptor descriptor = mref.descriptor();
for (ModuleDescriptor.Exports e : descriptor.exports()) {
if (!e.isQualified()) {
roots.add(mn);
break;
}
}
}
return roots;
}
}
public static class Builder {
// the token for "all modules on the module path"
private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
final SystemModuleFinder systemModulePath;
final Set<String> rootModules = new HashSet<>();
final List<Archive> initialArchives = new ArrayList<>();
final List<Path> paths = new ArrayList<>();
final List<Path> classPaths = new ArrayList<>();
ModuleFinder upgradeModulePath;
ModuleFinder appModulePath;
boolean addAllApplicationModules;
public Builder() {
this(System.getProperty("java.home"));
}
public Builder(String systemModulePath) {
this.systemModulePath = new SystemModuleFinder(systemModulePath);;
}
public Builder upgradeModulePath(String upgradeModulePath) {
this.upgradeModulePath = createModulePathFinder(upgradeModulePath);
return this;
}
public Builder appModulePath(String modulePath) {
this.appModulePath = createModulePathFinder(modulePath);
return this;
}
public Builder addmods(Set<String> addmods) {
for (String mn : addmods) {
switch (mn) {
case ALL_MODULE_PATH:
this.addAllApplicationModules = true;
break;
default:
this.rootModules.add(mn);
}
}
return this;
}
/*
* This method is for -check option to find all target modules specified
* in qualified exports.
*
* Include all system modules and modules found on modulepath
*/
public Builder allModules() {
systemModulePath.moduleNames()
.forEach(this.rootModules::add);
this.addAllApplicationModules = true;
return this;
}
public Builder addRoot(Path path) {
Archive archive = Archive.getInstance(path);
if (archive.contains(MODULE_INFO)) {
paths.add(path);
} else {
initialArchives.add(archive);
}
return this;
}
public Builder addClassPath(String classPath) {
this.classPaths.addAll(getClassPaths(classPath));
return this;
}
public JdepsConfiguration build() throws IOException {
ModuleFinder finder = systemModulePath;
if (upgradeModulePath != null) {
finder = ModuleFinder.compose(upgradeModulePath, systemModulePath);
}
if (appModulePath != null) {
finder = ModuleFinder.compose(finder, appModulePath);
}
if (!paths.isEmpty()) {
ModuleFinder otherModulePath = ModuleFinder.of(paths.toArray(new Path[0]));
finder = ModuleFinder.compose(finder, otherModulePath);
// add modules specified on command-line (convenience) as root set
otherModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(rootModules::add);
}
if (addAllApplicationModules && appModulePath != null) {
appModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(rootModules::add);
}
return new JdepsConfiguration(systemModulePath,
finder,
rootModules,
classPaths,
initialArchives);
}
private static ModuleFinder createModulePathFinder(String mpaths) {
if (mpaths == null) {
return null;
} else {
String[] dirs = mpaths.split(File.pathSeparator);
Path[] paths = new Path[dirs.length];
int i = 0;
for (String dir : dirs) {
paths[i++] = Paths.get(dir);
}
return ModuleFinder.of(paths);
}
}
/*
* Returns the list of Archive specified in cpaths and not included
* initialArchives
*/
private List<Path> getClassPaths(String cpaths) {
if (cpaths.isEmpty()) {
return Collections.emptyList();
}
List<Path> paths = new ArrayList<>();
for (String p : cpaths.split(File.pathSeparator)) {
if (p.length() > 0) {
// wildcard to parse all JAR files e.g. -classpath dir/*
int i = p.lastIndexOf(".*");
if (i > 0) {
Path dir = Paths.get(p.substring(0, i));
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
for (Path entry : stream) {
paths.add(entry);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} else {
paths.add(Paths.get(p));
}
}
}
return paths;
}
}
}

@ -41,22 +41,27 @@ import java.util.stream.Stream;
* 2. -filter:package to filter out same-package dependencies
* This filter is applied when jdeps parses the class files
* and filtered dependencies are not stored in the Analyzer.
* 3. -module specifies to match target dependencies from the given module
* 3. -requires specifies to match target dependence from the given module
* This gets expanded into package lists to be filtered.
* 4. -filter:archive to filter out same-archive dependencies
* This filter is applied later in the Analyzer as the
* containing archive of a target class may not be known until
* the entire archive
*/
class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
public static final JdepsFilter DEFAULT_FILTER =
new JdepsFilter.Builder().filter(true, true).build();
private final Dependency.Filter filter;
private final Pattern filterPattern;
private final boolean filterSamePackage;
private final boolean filterSameArchive;
private final boolean findJDKInternals;
private final Pattern includePattern;
private final Set<String> includePackages;
private final Set<String> excludeModules;
private final Pattern includeSystemModules;
private final Set<String> requires;
private JdepsFilter(Dependency.Filter filter,
Pattern filterPattern,
@ -64,16 +69,16 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
boolean filterSameArchive,
boolean findJDKInternals,
Pattern includePattern,
Set<String> includePackages,
Set<String> excludeModules) {
Pattern includeSystemModules,
Set<String> requires) {
this.filter = filter;
this.filterPattern = filterPattern;
this.filterSamePackage = filterSamePackage;
this.filterSameArchive = filterSameArchive;
this.findJDKInternals = findJDKInternals;
this.includePattern = includePattern;
this.includePackages = includePackages;
this.excludeModules = excludeModules;
this.includeSystemModules = includeSystemModules;
this.requires = requires;
}
/**
@ -82,12 +87,7 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
* @param cn fully-qualified name
*/
public boolean matches(String cn) {
if (includePackages.isEmpty() && includePattern == null)
return true;
int i = cn.lastIndexOf('.');
String pn = i > 0 ? cn.substring(0, i) : "";
if (includePackages.contains(pn))
if (includePattern == null)
return true;
if (includePattern != null)
@ -97,29 +97,39 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
}
/**
* Tests if the given source includes classes specified in includePattern
* or includePackages filters.
* Tests if the given source includes classes specified in -include option
*
* This method can be used to determine if the given source should eagerly
* be processed.
*/
public boolean matches(Archive source) {
if (!includePackages.isEmpty() && source.getModule().isNamed()) {
boolean found = source.getModule().packages()
.stream()
.filter(pn -> includePackages.contains(pn))
.findAny().isPresent();
if (found)
return true;
if (includePattern != null) {
return source.reader().entries().stream()
.map(name -> name.replace('/', '.'))
.filter(name -> !name.equals("module-info.class"))
.anyMatch(this::matches);
}
if (!includePackages.isEmpty() || includePattern != null) {
return source.reader().entries()
.stream()
.map(name -> name.replace('/', '.'))
.filter(this::matches)
.findAny().isPresent();
}
return false;
return hasTargetFilter();
}
public boolean include(Archive source) {
Module module = source.getModule();
// skip system module by default; or if includeSystemModules is set
// only include the ones matching the pattern
return !module.isSystem() || (includeSystemModules != null &&
includeSystemModules.matcher(module.name()).matches());
}
public boolean hasIncludePattern() {
return includePattern != null || includeSystemModules != null;
}
public boolean hasTargetFilter() {
return filter != null;
}
public Set<String> requiresFilter() {
return requires;
}
// ----- Dependency.Filter -----
@ -164,42 +174,35 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
return true;
}
/**
* Returns true if dependency should be recorded for the given source.
*/
public boolean accept(Archive source) {
return !excludeModules.contains(source.getName());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("exclude modules: ")
.append(excludeModules.stream().sorted().collect(Collectors.joining(",")))
.append("\n");
sb.append("include pattern: ").append(includePattern).append("\n");
sb.append("filter same archive: ").append(filterSameArchive).append("\n");
sb.append("filter same package: ").append(filterSamePackage).append("\n");
sb.append("requires: ").append(requires).append("\n");
return sb.toString();
}
static class Builder {
Dependency.Filter filter;
public static class Builder {
static Pattern SYSTEM_MODULE_PATTERN = Pattern.compile("java\\..*|jdk\\..*|javafx\\..*");
Pattern filterPattern;
Pattern regex;
boolean filterSamePackage;
boolean filterSameArchive;
boolean findJDKInterals;
// source filters
Pattern includePattern;
Set<String> includePackages = new HashSet<>();
Set<String> includeModules = new HashSet<>();
Set<String> excludeModules = new HashSet<>();
Pattern includeSystemModules;
Set<String> requires = new HashSet<>();
Set<String> targetPackages = new HashSet<>();
public Builder packages(Set<String> packageNames) {
this.filter = Dependencies.getPackageFilter(packageNames, false);
this.targetPackages.addAll(packageNames);
return this;
}
public Builder regex(Pattern regex) {
this.filter = Dependencies.getRegexFilter(regex);
this.regex = regex;
return this;
}
public Builder filter(Pattern regex) {
@ -211,6 +214,13 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
this.filterSameArchive = sameArchive;
return this;
}
public Builder requires(String name, Set<String> packageNames) {
this.requires.add(name);
this.targetPackages.addAll(packageNames);
includeIfSystemModule(name);
return this;
}
public Builder findJDKInternals(boolean value) {
this.findJDKInterals = value;
return this;
@ -219,30 +229,33 @@ class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
this.includePattern = regex;
return this;
}
public Builder includePackage(String pn) {
this.includePackages.add(pn);
public Builder includeSystemModules(Pattern regex) {
this.includeSystemModules = regex;
return this;
}
public Builder includeModules(Set<String> includes) {
this.includeModules.addAll(includes);
return this;
}
public Builder excludeModules(Set<String> excludes) {
this.excludeModules.addAll(excludes);
public Builder includeIfSystemModule(String name) {
if (includeSystemModules == null &&
SYSTEM_MODULE_PATTERN.matcher(name).matches()) {
this.includeSystemModules = SYSTEM_MODULE_PATTERN;
}
return this;
}
JdepsFilter build() {
public JdepsFilter build() {
Dependency.Filter filter = null;
if (regex != null)
filter = Dependencies.getRegexFilter(regex);
else if (!targetPackages.isEmpty()) {
filter = Dependencies.getPackageFilter(targetPackages, false);
}
return new JdepsFilter(filter,
filterPattern,
filterSamePackage,
filterSameArchive,
findJDKInterals,
includePattern,
includePackages,
excludeModules.stream()
.filter(mn -> !includeModules.contains(mn))
.collect(Collectors.toSet()));
includeSystemModules,
requires);
}
}

@ -25,31 +25,26 @@
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
import static com.sun.tools.jdeps.Analyzer.Type.*;
import static com.sun.tools.jdeps.JdepsWriter.*;
import static com.sun.tools.jdeps.ModuleAnalyzer.Graph;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.module.ResolutionException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -58,7 +53,12 @@ import java.util.stream.Stream;
* Implementation for the jdeps tool for static class dependency analysis.
*/
class JdepsTask {
static class BadArgs extends Exception {
static interface BadArguments {
String getKey();
Object[] getArgs();
boolean showUsage();
}
static class BadArgs extends Exception implements BadArguments {
static final long serialVersionUID = 8765093759964640721L;
BadArgs(String key, Object... args) {
super(JdepsTask.getMessage(key, args));
@ -73,6 +73,44 @@ class JdepsTask {
final String key;
final Object[] args;
boolean showUsage;
@Override
public String getKey() {
return key;
}
@Override
public Object[] getArgs() {
return args;
}
@Override
public boolean showUsage() {
return showUsage;
}
}
static class UncheckedBadArgs extends RuntimeException implements BadArguments {
static final long serialVersionUID = -1L;
final BadArgs cause;
UncheckedBadArgs(BadArgs cause) {
super(cause);
this.cause = cause;
}
@Override
public String getKey() {
return cause.key;
}
@Override
public Object[] getArgs() {
return cause.args;
}
@Override
public boolean showUsage() {
return cause.showUsage;
}
}
static abstract class Option {
@ -126,7 +164,7 @@ class JdepsTask {
if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
throw new BadArgs("err.invalid.path", arg);
}
task.options.dotOutputDir = arg;
task.options.dotOutputDir = Paths.get(arg);;
}
},
new Option(false, "-s", "-summary") {
@ -136,8 +174,9 @@ class JdepsTask {
}
},
new Option(false, "-v", "-verbose",
"-verbose:package",
"-verbose:class") {
"-verbose:module",
"-verbose:package",
"-verbose:class") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
switch (opt) {
case "-v":
@ -146,6 +185,9 @@ class JdepsTask {
task.options.filterSameArchive = false;
task.options.filterSamePackage = false;
break;
case "-verbose:module":
task.options.verbose = MODULE;
break;
case "-verbose:package":
task.options.verbose = PACKAGE;
break;
@ -157,6 +199,61 @@ class JdepsTask {
}
}
},
new Option(false, "-apionly") {
void process(JdepsTask task, String opt, String arg) {
task.options.apiOnly = true;
}
},
new Option(true, "-check") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
Set<String> mods = Set.of(arg.split(","));
task.options.checkModuleDeps = mods;
task.options.addmods.addAll(mods);
}
},
new Option(true, "-genmoduleinfo") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
Path p = Paths.get(arg);
if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
throw new BadArgs("err.invalid.path", arg);
}
task.options.genModuleInfo = Paths.get(arg);
}
},
new Option(false, "-jdkinternals") {
void process(JdepsTask task, String opt, String arg) {
task.options.findJDKInternals = true;
task.options.verbose = CLASS;
if (task.options.includePattern == null) {
task.options.includePattern = Pattern.compile(".*");
}
}
},
// ---- paths option ----
new Option(true, "-cp", "-classpath") {
void process(JdepsTask task, String opt, String arg) {
task.options.classpath = arg;
}
},
new Option(true, "-mp", "-modulepath") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.modulePath = arg;
}
},
new Option(true, "-upgrademodulepath") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.upgradeModulePath = arg;
}
},
new Option(true, "-m") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.rootModule = arg;
task.options.addmods.add(arg);
}
},
// ---- Target filtering options ----
new Option(true, "-p", "-package") {
void process(JdepsTask task, String opt, String arg) {
task.options.packageNames.add(arg);
@ -198,26 +295,24 @@ class JdepsTask {
}
}
},
// ---- Source filtering options ----
new Option(true, "-include") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.includePattern = Pattern.compile(arg);
}
},
// Another alternative to list modules in -addmods option
new HiddenOption(true, "-include-system-modules") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.includeSystemModulePattern = Pattern.compile(arg);
}
},
new Option(false, "-P", "-profile") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.showProfile = true;
task.options.showModule = false;
}
},
new Option(false, "-M") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.showModule = true;
task.options.showProfile = false;
}
},
new Option(false, "-apionly") {
void process(JdepsTask task, String opt, String arg) {
task.options.apiOnly = true;
}
},
new Option(false, "-R", "-recursive") {
@ -228,15 +323,6 @@ class JdepsTask {
task.options.filterSamePackage = false;
}
},
new Option(true, "-genmoduleinfo") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
Path p = Paths.get(arg);
if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
throw new BadArgs("err.invalid.path", arg);
}
task.options.genModuleInfo = arg;
}
},
new Option(false, "-ct", "-compile-time") {
void process(JdepsTask task, String opt, String arg) {
task.options.compileTimeView = true;
@ -245,62 +331,11 @@ class JdepsTask {
task.options.depth = 0;
}
},
new Option(false, "-jdkinternals") {
void process(JdepsTask task, String opt, String arg) {
task.options.findJDKInternals = true;
task.options.verbose = CLASS;
if (task.options.includePattern == null) {
task.options.includePattern = Pattern.compile(".*");
}
}
},
new Option(true, "-cp", "-classpath") {
void process(JdepsTask task, String opt, String arg) {
task.options.classpath = arg;
}
},
new Option(true, "-mp", "-modulepath") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.modulePath = arg;
task.options.showModule = true;
}
},
new Option(true, "-upgrademodulepath") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.upgradeModulePath = arg;
task.options.showModule = true;
}
},
new Option(true, "-m") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.rootModule = arg;
task.options.includes.add(arg);
task.options.showModule = true;
}
},
new Option(false, "-check") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.checkModuleDeps = true;
}
},
new HiddenOption(true, "-include-modules") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
Arrays.stream(arg.split(","))
.forEach(task.options.includes::add);
task.options.showModule = true;
}
},
new HiddenOption(true, "-exclude-modules") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
Arrays.stream(arg.split(","))
.forEach(task.options.excludes::add);
task.options.showModule = true;
}
},
new Option(false, "-q", "-quiet") {
void process(JdepsTask task, String opt, String arg) {
task.options.nowarning = true;
}
task.options.nowarning = true;
}
},
new Option(false, "-version") {
@ -318,7 +353,11 @@ class JdepsTask {
task.options.showLabel = true;
}
},
new HiddenOption(false, "-hide-module") {
void process(JdepsTask task, String opt, String arg) {
task.options.showModule = false;
}
},
new HiddenOption(true, "-depth") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
try {
@ -332,7 +371,7 @@ class JdepsTask {
private static final String PROGNAME = "jdeps";
private final Options options = new Options();
private final List<String> classes = new ArrayList<>();
private final List<String> inputArgs = new ArrayList<>();
private PrintWriter log;
void setLog(PrintWriter out) {
@ -342,13 +381,13 @@ class JdepsTask {
/**
* Result codes.
*/
static final int EXIT_OK = 0, // Completed with no errors.
EXIT_ERROR = 1, // Completed but reported errors.
EXIT_CMDERR = 2, // Bad command-line arguments
EXIT_SYSERR = 3, // System error or resource exhaustion.
EXIT_ABNORMAL = 4;// terminated abnormally
static final int EXIT_OK = 0, // Completed with no errors.
EXIT_ERROR = 1, // Completed but reported errors.
EXIT_CMDERR = 2, // Bad command-line arguments
EXIT_SYSERR = 3, // System error or resource exhaustion.
EXIT_ABNORMAL = 4; // terminated abnormally
int run(String[] args) {
int run(String... args) {
if (log == null) {
log = new PrintWriter(System.out);
}
@ -360,15 +399,11 @@ class JdepsTask {
if (options.version || options.fullVersion) {
showVersion(options.fullVersion);
}
if (options.rootModule != null && !classes.isEmpty()) {
reportError("err.invalid.module.option", options.rootModule, classes);
return EXIT_CMDERR;
if (!inputArgs.isEmpty() && options.rootModule != null) {
reportError("err.invalid.arg.for.option", "-m");
}
if (options.checkModuleDeps && options.rootModule == null) {
reportError("err.root.module.not.set");
return EXIT_CMDERR;
}
if (classes.isEmpty() && options.rootModule == null && options.includePattern == null) {
if (inputArgs.isEmpty() && options.addmods.isEmpty() && options.includePattern == null
&& options.includeSystemModulePattern == null && options.checkModuleDeps == null) {
if (options.help || options.version || options.fullVersion) {
return EXIT_OK;
} else {
@ -377,19 +412,10 @@ class JdepsTask {
}
}
if (options.genModuleInfo != null) {
if (options.dotOutputDir != null || !options.classpath.isEmpty() || options.hasFilter()) {
if (options.dotOutputDir != null || options.classpath != null || options.hasFilter()) {
showHelp();
return EXIT_CMDERR;
}
// default to compile time view analysis
options.compileTimeView = true;
for (String fn : classes) {
Path p = Paths.get(fn);
if (!Files.exists(p) || !fn.endsWith(".jar")) {
reportError("err.genmoduleinfo.not.jarfile", fn);
return EXIT_CMDERR;
}
}
}
if (options.numFilters() > 1) {
@ -405,12 +431,15 @@ class JdepsTask {
showHelp();
return EXIT_CMDERR;
}
if (options.checkModuleDeps != null && !inputArgs.isEmpty()) {
reportError("err.invalid.module.option", inputArgs, "-check");
}
boolean ok = run();
return ok ? EXIT_OK : EXIT_ERROR;
} catch (BadArgs e) {
reportError(e.key, e.args);
if (e.showUsage) {
} catch (BadArgs|UncheckedBadArgs e) {
reportError(e.getKey(), e.getArgs());
if (e.showUsage()) {
log.println(getMessage("main.usage.summary", PROGNAME));
}
return EXIT_CMDERR;
@ -419,176 +448,178 @@ class JdepsTask {
return EXIT_CMDERR;
} catch (IOException e) {
e.printStackTrace();
return EXIT_ABNORMAL;
return EXIT_CMDERR;
} finally {
log.flush();
}
}
private ModulePaths modulePaths;
private boolean run() throws BadArgs, IOException {
DependencyFinder dependencyFinder =
new DependencyFinder(options.compileTimeView);
boolean run() throws IOException {
JdepsConfiguration config = buildConfig();
buildArchive(dependencyFinder);
// detect split packages
config.splitPackages().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> System.out.format("split package: %s %s%n", e.getKey(),
e.getValue().toString()));
if (options.rootModule != null &&
(options.checkModuleDeps || (options.dotOutputDir != null &&
options.verbose == SUMMARY))) {
// -dotfile -s prints the configuration of the given root
// -checkModuleDeps prints the suggested module-info.java
return analyzeModules(dependencyFinder);
}
// check if any module specified in -module is missing
Stream.concat(options.addmods.stream(), options.requires.stream())
.forEach(mn -> config.findModule(mn).orElseThrow(() ->
new UncheckedBadArgs(new BadArgs("err.module.not.found", mn))));
// otherwise analyze the dependencies
// -genmoduleinfo
if (options.genModuleInfo != null) {
return genModuleInfo(dependencyFinder);
} else {
return analyzeDeps(dependencyFinder);
return genModuleInfo(config);
}
// -check
if (options.checkModuleDeps != null) {
return new ModuleAnalyzer(config, log, options.checkModuleDeps).run();
}
if (options.dotOutputDir != null &&
(options.verbose == SUMMARY || options.verbose == MODULE) &&
!options.addmods.isEmpty() && inputArgs.isEmpty()) {
return new ModuleAnalyzer(config, log).genDotFiles(options.dotOutputDir);
}
return analyzeDeps(config);
}
private void buildArchive(DependencyFinder dependencyFinder)
throws BadArgs, IOException
{
// If -genmoduleinfo is specified, the input arguments must be JAR files
// Treat them as automatic modules for analysis
List<Path> jarfiles = options.genModuleInfo != null
? classes.stream().map(Paths::get)
.collect(Collectors.toList())
: Collections.emptyList();
// Set module paths
this.modulePaths = new ModulePaths(options.upgradeModulePath, options.modulePath, jarfiles);
private JdepsConfiguration buildConfig() throws IOException {
JdepsConfiguration.Builder builder =
new JdepsConfiguration.Builder(options.systemModulePath);
// add modules to dependency finder for analysis
Map<String, Module> modules = modulePaths.getModules();
modules.values().stream()
.forEach(dependencyFinder::addModule);
builder.upgradeModulePath(options.upgradeModulePath)
.appModulePath(options.modulePath)
.addmods(options.addmods);
// If -m option is set, add the specified module and its transitive dependences
// to the root set
if (options.rootModule != null) {
modulePaths.dependences(options.rootModule)
.forEach(dependencyFinder::addRoot);
if (options.checkModuleDeps != null) {
// check all system modules in the image
builder.allModules();
}
// check if any module specified in -requires is missing
Optional<String> req = options.requires.stream()
.filter(mn -> !modules.containsKey(mn))
.findFirst();
if (req.isPresent()) {
throw new BadArgs("err.module.not.found", req.get());
}
if (options.classpath != null)
builder.addClassPath(options.classpath);
// classpath
for (Path p : getClassPaths(options.classpath)) {
// build the root set of archives to be analyzed
for (String s : inputArgs) {
Path p = Paths.get(s);
if (Files.exists(p)) {
dependencyFinder.addClassPathArchive(p);
builder.addRoot(p);
}
}
// if -genmoduleinfo is not set, the input arguments are considered as
// unnamed module. Add them to the root set
if (options.genModuleInfo == null) {
// add root set
for (String s : classes) {
Path p = Paths.get(s);
if (Files.exists(p)) {
// add to the initial root set
dependencyFinder.addRoot(p);
} else {
if (isValidClassName(s)) {
dependencyFinder.addClassName(s);
} else {
warning("warn.invalid.arg", s);
}
}
}
}
return builder.build();
}
private boolean analyzeDeps(DependencyFinder dependencyFinder) throws IOException {
JdepsFilter filter = dependencyFilter();
// parse classfiles and find all dependencies
findDependencies(dependencyFinder, filter, options.apiOnly);
// analyze the dependencies collected
Analyzer analyzer = new Analyzer(options.verbose, filter);
analyzer.run(dependencyFinder.archives());
private boolean analyzeDeps(JdepsConfiguration config) throws IOException {
// output result
final JdepsWriter writer;
if (options.dotOutputDir != null) {
Path dir = Paths.get(options.dotOutputDir);
Files.createDirectories(dir);
writer = new DotFileWriter(dir, options.verbose,
writer = new DotFileWriter(options.dotOutputDir,
options.verbose,
options.showProfile,
options.showModule,
options.showLabel);
} else {
writer = new SimpleWriter(log, options.verbose,
writer = new SimpleWriter(log,
options.verbose,
options.showProfile,
options.showModule);
}
// Targets for reporting - include the root sets and other analyzed archives
final List<Archive> targets;
if (options.rootModule == null) {
// no module as the root set
targets = dependencyFinder.archives()
.filter(filter::accept)
.filter(a -> !a.getModule().isNamed())
.collect(Collectors.toList());
} else {
// named modules in topological order
Stream<Module> modules = dependencyFinder.archives()
.filter(a -> a.getModule().isNamed())
.map(Archive::getModule);
Graph<Module> graph = ModuleAnalyzer.graph(modulePaths, modules.toArray(Module[]::new));
// then add unnamed module
targets = graph.orderedNodes()
.filter(filter::accept)
.collect(Collectors.toList());
// analyze the dependencies
DepsAnalyzer analyzer = new DepsAnalyzer(config,
dependencyFilter(config),
writer,
options.verbose,
options.apiOnly);
// in case any reference not found
dependencyFinder.archives()
.filter(a -> !a.getModule().isNamed())
.forEach(targets::add);
}
boolean ok = analyzer.run(options.compileTimeView, options.depth);
// print skipped entries, if any
analyzer.analyzer.archives()
.forEach(archive -> archive.reader()
.skippedEntries().stream()
.forEach(name -> warning("warn.skipped.entry",
name, archive.getPathName())));
writer.generateOutput(targets, analyzer);
if (options.findJDKInternals && !options.nowarning) {
showReplacements(targets, analyzer);
Map<String, String> jdkInternals = analyzer.dependences()
.collect(Collectors.toMap(Function.identity(), this::replacementFor));
if (!jdkInternals.isEmpty()) {
log.println();
warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
if (jdkInternals.values().stream().anyMatch(repl -> repl != null)) {
log.println();
log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
log.format("%-40s %s%n", "----------------", "---------------------");
jdkInternals.entrySet().stream()
.filter(e -> e.getValue() != null)
.sorted(Map.Entry.comparingByKey())
.forEach(e -> log.format("%-40s %s%n", e.getKey(), e.getValue()));
}
}
}
return true;
return ok;
}
private JdepsFilter dependencyFilter() {
private boolean genModuleInfo(JdepsConfiguration config) throws IOException {
ModuleInfoBuilder builder
= new ModuleInfoBuilder(config, inputArgs, options.genModuleInfo);
boolean ok = builder.run();
builder.modules().forEach(module -> {
if (module.packages().contains("")) {
reportError("ERROR: %s contains unnamed package. " +
"module-info.java not generated%n", module.getPathName());
}
});
if (!ok && !options.nowarning) {
log.println("Missing dependencies");
builder.visitMissingDeps(
new Analyzer.Visitor() {
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
if (targetArchive == NOT_FOUND)
log.format(" %-50s -> %-50s %s%n",
origin, target, targetArchive.getName());
}
});
log.println("ERROR: missing dependencies (check \"requires NOT_FOUND;\")");
}
return ok;
}
/**
* Returns a filter used during dependency analysis
*/
private JdepsFilter dependencyFilter(JdepsConfiguration config) {
// Filter specified by -filter, -package, -regex, and -module options
JdepsFilter.Builder builder = new JdepsFilter.Builder();
// Exclude JDK modules from analysis and reporting if -m specified.
modulePaths.getModules().values().stream()
.filter(m -> m.isJDK())
.map(Module::name)
.forEach(options.excludes::add);
// source filters
builder.includePattern(options.includePattern);
builder.includeModules(options.includes);
builder.excludeModules(options.excludes);
builder.includeSystemModules(options.includeSystemModulePattern);
builder.filter(options.filterSamePackage, options.filterSameArchive);
builder.findJDKInternals(options.findJDKInternals);
// -module
if (!options.requires.isEmpty()) {
Map<String, Module> modules = modulePaths.getModules();
builder.packages(options.requires.stream()
.map(modules::get)
.flatMap(m -> m.packages().stream())
.collect(Collectors.toSet()));
options.requires.stream()
.forEach(mn -> {
Module m = config.findModule(mn).get();
builder.requires(mn, m.packages());
});
}
// -regex
if (options.regex != null)
@ -600,62 +631,14 @@ class JdepsTask {
if (options.filterRegex != null)
builder.filter(options.filterRegex);
// check if system module is set
config.rootModules().stream()
.map(Module::name)
.forEach(builder::includeIfSystemModule);
return builder.build();
}
private void findDependencies(DependencyFinder dependencyFinder,
JdepsFilter filter,
boolean apiOnly)
throws IOException
{
dependencyFinder.findDependencies(filter, apiOnly, options.depth);
// print skipped entries, if any
for (Archive a : dependencyFinder.roots()) {
for (String name : a.reader().skippedEntries()) {
warning("warn.skipped.entry", name, a.getPathName());
}
}
}
private boolean genModuleInfo(DependencyFinder dependencyFinder) throws IOException {
ModuleInfoBuilder builder = new ModuleInfoBuilder(modulePaths, dependencyFinder);
boolean result = builder.run(options.verbose, options.nowarning);
builder.build(Paths.get(options.genModuleInfo));
return result;
}
private boolean analyzeModules(DependencyFinder dependencyFinder)
throws IOException
{
ModuleAnalyzer analyzer = new ModuleAnalyzer(modulePaths,
dependencyFinder,
options.rootModule);
if (options.checkModuleDeps) {
return analyzer.run();
}
if (options.dotOutputDir != null && options.verbose == SUMMARY) {
Path dir = Paths.get(options.dotOutputDir);
Files.createDirectories(dir);
analyzer.genDotFile(dir);
return true;
}
return false;
}
private boolean isValidClassName(String name) {
if (!Character.isJavaIdentifierStart(name.charAt(0))) {
return false;
}
for (int i=1; i < name.length(); i++) {
char c = name.charAt(i);
if (c != '.' && !Character.isJavaIdentifierPart(c)) {
return false;
}
}
return true;
}
public void handleOptions(String[] args) throws BadArgs {
// process options
for (int i=0; i < args.length; i++) {
@ -684,7 +667,7 @@ class JdepsTask {
if (name.charAt(0) == '-') {
throw new BadArgs("err.option.after.class", name).showUsage(true);
}
classes.add(name);
inputArgs.add(name);
}
}
}
@ -703,7 +686,7 @@ class JdepsTask {
log.println(getMessage("error.prefix") + " " + getMessage(key, args));
}
private void warning(String key, Object... args) {
void warning(String key, Object... args) {
log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
}
@ -749,7 +732,7 @@ class JdepsTask {
boolean version;
boolean fullVersion;
boolean showProfile;
boolean showModule;
boolean showModule = true;
boolean showSummary;
boolean apiOnly;
boolean showLabel;
@ -761,22 +744,22 @@ class JdepsTask {
boolean filterSamePackage = true;
boolean filterSameArchive = false;
Pattern filterRegex;
String dotOutputDir;
String genModuleInfo;
String classpath = "";
Path dotOutputDir;
Path genModuleInfo;
String classpath;
int depth = 1;
Set<String> requires = new HashSet<>();
Set<String> packageNames = new HashSet<>();
Pattern regex; // apply to the dependences
Pattern includePattern; // apply to classes
Pattern includePattern;
Pattern includeSystemModulePattern;
boolean compileTimeView = false;
boolean checkModuleDeps = false;
Set<String> checkModuleDeps;
String systemModulePath = System.getProperty("java.home");
String upgradeModulePath;
String modulePath;
String rootModule;
// modules to be included or excluded
Set<String> includes = new HashSet<>();
Set<String> excludes = new HashSet<>();
Set<String> addmods = new HashSet<>();
boolean hasFilter() {
return numFilters() > 0;
@ -789,11 +772,8 @@ class JdepsTask {
if (packageNames.size() > 0) count++;
return count;
}
boolean isRootModule() {
return rootModule != null;
}
}
private static class ResourceBundleHelper {
static final ResourceBundle versionRB;
static final ResourceBundle bundle;
@ -819,35 +799,6 @@ class JdepsTask {
}
}
/*
* Returns the list of Archive specified in cpaths and not included
* initialArchives
*/
private List<Path> getClassPaths(String cpaths) throws IOException
{
if (cpaths.isEmpty()) {
return Collections.emptyList();
}
List<Path> paths = new ArrayList<>();
for (String p : cpaths.split(File.pathSeparator)) {
if (p.length() > 0) {
// wildcard to parse all JAR files e.g. -classpath dir/*
int i = p.lastIndexOf(".*");
if (i > 0) {
Path dir = Paths.get(p.substring(0, i));
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
for (Path entry : stream) {
paths.add(entry);
}
}
} else {
paths.add(Paths.get(p));
}
}
}
return paths;
}
/**
* Returns the recommended replacement API for the given classname;
* or return null if replacement API is not known.
@ -865,32 +816,5 @@ class JdepsTask {
}
}
return value;
};
private void showReplacements(List<Archive> archives, Analyzer analyzer) {
Map<String,String> jdkinternals = new TreeMap<>();
boolean useInternals = false;
for (Archive source : archives) {
useInternals = useInternals || analyzer.hasDependences(source);
for (String cn : analyzer.dependences(source)) {
String repl = replacementFor(cn);
if (repl != null) {
jdkinternals.putIfAbsent(cn, repl);
}
}
}
if (useInternals) {
log.println();
warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
}
if (!jdkinternals.isEmpty()) {
log.println();
log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
log.format("%-40s %s%n", "----------------", "---------------------");
for (Map.Entry<String,String> e : jdkinternals.entrySet()) {
log.format("%-40s %s%n", e.getKey(), e.getValue());
}
}
}
}

@ -24,23 +24,33 @@
*/
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Analyzer.Type.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor.Requires;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import static com.sun.tools.jdeps.Analyzer.Type.*;
public abstract class JdepsWriter {
public static JdepsWriter newDotWriter(Path outputdir, Analyzer.Type type) {
return new DotFileWriter(outputdir, type, false, true, false);
}
public static JdepsWriter newSimpleWriter(PrintWriter writer, Analyzer.Type type) {
return new SimpleWriter(writer, type, false, true);
}
final Analyzer.Type type;
final boolean showProfile;
final boolean showModule;
JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
private JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
this.type = type;
this.showProfile = showProfile;
this.showModule = showModule;
@ -48,7 +58,7 @@ public abstract class JdepsWriter {
abstract void generateOutput(Collection<Archive> archives, Analyzer analyzer) throws IOException;
public static class DotFileWriter extends JdepsWriter {
static class DotFileWriter extends JdepsWriter {
final boolean showLabel;
final Path outputDir;
DotFileWriter(Path dir, Analyzer.Type type,
@ -62,8 +72,10 @@ public abstract class JdepsWriter {
void generateOutput(Collection<Archive> archives, Analyzer analyzer)
throws IOException
{
Files.createDirectories(outputDir);
// output individual .dot file for each archive
if (type != SUMMARY) {
if (type != SUMMARY && type != MODULE) {
archives.stream()
.filter(analyzer::hasDependences)
.forEach(archive -> {
@ -85,13 +97,13 @@ public abstract class JdepsWriter {
{
// If verbose mode (-v or -verbose option),
// the summary.dot file shows package-level dependencies.
Analyzer.Type summaryType =
(type == PACKAGE || type == SUMMARY) ? SUMMARY : PACKAGE;
boolean isSummary = type == PACKAGE || type == SUMMARY || type == MODULE;
Analyzer.Type summaryType = isSummary ? SUMMARY : PACKAGE;
Path summary = outputDir.resolve("summary.dot");
try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
for (Archive archive : archives) {
if (type == PACKAGE || type == SUMMARY) {
if (isSummary) {
if (showLabel) {
// build labels listing package-level dependencies
analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
@ -208,19 +220,22 @@ public abstract class JdepsWriter {
void generateOutput(Collection<Archive> archives, Analyzer analyzer) {
RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
for (Archive archive : archives) {
// print summary
if (showModule && archive.getModule().isNamed()) {
summaryFormatter.showModuleRequires(archive.getModule());
} else {
archives.stream()
.filter(analyzer::hasDependences)
.sorted(Comparator.comparing(Archive::getName))
.forEach(archive -> {
if (showModule && archive.getModule().isNamed() && type != SUMMARY) {
// print module-info except -summary
summaryFormatter.printModuleDescriptor(archive.getModule());
}
// print summary
analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
}
if (analyzer.hasDependences(archive) && type != SUMMARY) {
// print the class-level or package-level dependences
analyzer.visitDependences(archive, depFormatter);
}
}
if (analyzer.hasDependences(archive) && type != SUMMARY) {
// print the class-level or package-level dependences
analyzer.visitDependences(archive, depFormatter);
}
});
}
class RawOutputFormatter implements Analyzer.Visitor {
@ -269,20 +284,16 @@ public abstract class JdepsWriter {
writer.format("%n");
}
public void showModuleRequires(Module module) {
public void printModuleDescriptor(Module module) {
if (!module.isNamed())
return;
writer.format("module %s", module.name());
if (module.isAutomatic())
writer.format(" (automatic)");
writer.println();
module.requires().keySet()
writer.format("%s%s%n", module.name(), module.isAutomatic() ? " automatic" : "");
writer.format(" [%s]%n", module.location());
module.descriptor().requires()
.stream()
.sorted()
.forEach(req -> writer.format(" requires %s%s%n",
module.requires.get(req) ? "public " : "",
req));
.sorted(Comparator.comparing(Requires::name))
.forEach(req -> writer.format(" requires %s%n", req));
}
}
}
@ -307,7 +318,7 @@ public abstract class JdepsWriter {
}
// exported API
boolean jdkunsupported = Module.isJDKUnsupported(module, pn);
boolean jdkunsupported = Module.JDK_UNSUPPORTED.equals(module.name());
if (module.isExported(pn) && !jdkunsupported) {
return showProfileOrModule(module);
}

@ -25,67 +25,56 @@
package com.sun.tools.jdeps;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
/**
* JDeps internal representation of module for dependency analysis.
* Jdeps internal representation of module for dependency analysis.
*/
class Module extends Archive {
static final boolean traceOn = Boolean.getBoolean("jdeps.debug");
static final Module UNNAMED_MODULE = new UnnamedModule();
static final String JDK_UNSUPPORTED = "jdk.unsupported";
static final boolean DEBUG = Boolean.getBoolean("jdeps.debug");
static void trace(String fmt, Object... args) {
trace(DEBUG, fmt, args);
}
static void trace(boolean traceOn, String fmt, Object... args) {
if (traceOn) {
System.err.format(fmt, args);
}
}
/*
* Returns true if the given package name is JDK critical internal API
* in jdk.unsupported module
*/
static boolean isJDKUnsupported(Module m, String pn) {
return JDK_UNSUPPORTED.equals(m.name()) || unsupported.contains(pn);
};
private final ModuleDescriptor descriptor;
private final Map<String, Set<String>> exports;
private final boolean isSystem;
private final URI location;
protected final ModuleDescriptor descriptor;
protected final Map<String, Boolean> requires;
protected final Map<String, Set<String>> exports;
protected final Set<String> packages;
protected final boolean isJDK;
protected final URI location;
protected Module(String name) {
super(name);
this.descriptor = null;
this.location = null;
this.exports = Collections.emptyMap();
this.isSystem = true;
}
private Module(String name,
URI location,
ModuleDescriptor descriptor,
Map<String, Boolean> requires,
Map<String, Set<String>> exports,
Set<String> packages,
boolean isJDK,
boolean isSystem,
ClassFileReader reader) {
super(name, location, reader);
this.descriptor = descriptor;
this.location = location;
this.requires = Collections.unmodifiableMap(requires);
this.exports = Collections.unmodifiableMap(exports);
this.packages = Collections.unmodifiableSet(packages);
this.isJDK = isJDK;
this.isSystem = isSystem;
}
/**
@ -111,31 +100,35 @@ class Module extends Archive {
return descriptor;
}
public boolean isJDK() {
return isJDK;
public URI location() {
return location;
}
public Map<String, Boolean> requires() {
return requires;
public boolean isJDK() {
String mn = name();
return isSystem &&
(mn.startsWith("java.") || mn.startsWith("jdk.") || mn.startsWith("javafx."));
}
public boolean isSystem() {
return isSystem;
}
public Map<String, Set<String>> exports() {
return exports;
}
public Map<String, Set<String>> provides() {
return descriptor.provides().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().providers()));
}
public Set<String> packages() {
return packages;
return descriptor.packages();
}
/**
* Tests if the package of the given name is exported.
*/
public boolean isExported(String pn) {
if (JDK_UNSUPPORTED.equals(this.name())) {
return false;
}
return exports.containsKey(pn) ? exports.get(pn).isEmpty() : false;
}
@ -159,11 +152,6 @@ class Module extends Archive {
return isExported(pn) || exports.containsKey(pn) && exports.get(pn).contains(target);
}
private final static String JDK_UNSUPPORTED = "jdk.unsupported";
// temporary until jdk.unsupported module
private final static List<String> unsupported = Arrays.asList("sun.misc", "sun.reflect");
@Override
public String toString() {
return name();
@ -171,21 +159,19 @@ class Module extends Archive {
public final static class Builder {
final String name;
final Map<String, Boolean> requires = new HashMap<>();
final Map<String, Set<String>> exports = new HashMap<>();
final Set<String> packages = new HashSet<>();
final boolean isJDK;
final ModuleDescriptor descriptor;
final boolean isSystem;
ClassFileReader reader;
ModuleDescriptor descriptor;
URI location;
public Builder(String name) {
this(name, false);
public Builder(ModuleDescriptor md) {
this(md, false);
}
public Builder(String name, boolean isJDK) {
this.name = name;
this.isJDK = isJDK;
public Builder(ModuleDescriptor md, boolean isSystem) {
this.name = md.name();
this.descriptor = md;
this.isSystem = isSystem;
}
public Builder location(URI location) {
@ -193,48 +179,30 @@ class Module extends Archive {
return this;
}
public Builder descriptor(ModuleDescriptor md) {
this.descriptor = md;
return this;
}
public Builder require(String d, boolean reexport) {
requires.put(d, reexport);
return this;
}
public Builder packages(Set<String> pkgs) {
packages.addAll(pkgs);
return this;
}
public Builder export(String p, Set<String> ms) {
Objects.requireNonNull(p);
Objects.requireNonNull(ms);
exports.put(p, new HashSet<>(ms));
return this;
}
public Builder classes(ClassFileReader reader) {
this.reader = reader;
return this;
}
public Module build() {
if (descriptor.isAutomatic() && isJDK) {
if (descriptor.isAutomatic() && isSystem) {
throw new InternalError("JDK module: " + name + " can't be automatic module");
}
return new Module(name, location, descriptor, requires, exports, packages, isJDK, reader);
Map<String, Set<String>> exports = new HashMap<>();
descriptor.exports().stream()
.forEach(exp -> exports.computeIfAbsent(exp.source(), _k -> new HashSet<>())
.addAll(exp.targets()));
return new Module(name, location, descriptor, exports, isSystem, reader);
}
}
final static Module UNNAMED_MODULE = new UnnamedModule();
private static class UnnamedModule extends Module {
private UnnamedModule() {
super("unnamed", null, null,
Collections.emptyMap(),
Collections.emptyMap(),
Collections.emptySet(),
false, null);
}
@ -260,10 +228,7 @@ class Module extends Archive {
}
private static class StrictModule extends Module {
private static final String SERVICES_PREFIX = "META-INF/services/";
private final Map<String, Set<String>> provides;
private final Module module;
private final JarFile jarfile;
private final ModuleDescriptor md;
/**
* Converts the given automatic module to a strict module.
@ -272,114 +237,26 @@ class Module extends Archive {
* declare service providers, if specified in META-INF/services configuration file
*/
private StrictModule(Module m, Map<String, Boolean> requires) {
super(m.name(), m.location, m.descriptor, requires, m.exports, m.packages, m.isJDK, m.reader());
this.module = m;
try {
this.jarfile = new JarFile(m.path().toFile(), false);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
this.provides = providers(jarfile);
super(m.name(), m.location, m.descriptor, m.exports, m.isSystem, m.reader());
ModuleDescriptor.Builder builder = new ModuleDescriptor.Builder(m.name());
requires.keySet().forEach(mn -> {
if (requires.get(mn).equals(Boolean.TRUE)) {
builder.requires(ModuleDescriptor.Requires.Modifier.PUBLIC, mn);
} else {
builder.requires(mn);
}
});
m.descriptor.exports().forEach(e -> builder.exports(e));
m.descriptor.uses().forEach(s -> builder.uses(s));
m.descriptor.provides().values().forEach(p -> builder.provides(p));
builder.conceals(m.descriptor.conceals());
this.md = builder.build();
}
@Override
public Map<String, Set<String>> provides() {
return provides;
}
private Map<String, Set<String>> providers(JarFile jf) {
Map<String, Set<String>> provides = new HashMap<>();
// map names of service configuration files to service names
Set<String> serviceNames = jf.stream()
.map(e -> e.getName())
.filter(e -> e.startsWith(SERVICES_PREFIX))
.distinct()
.map(this::toServiceName)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
// parse each service configuration file
for (String sn : serviceNames) {
JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
Set<String> providerClasses = new HashSet<>();
try (InputStream in = jf.getInputStream(entry)) {
BufferedReader reader
= new BufferedReader(new InputStreamReader(in, "UTF-8"));
String cn;
while ((cn = nextLine(reader)) != null) {
if (isJavaIdentifier(cn)) {
providerClasses.add(cn);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
if (!providerClasses.isEmpty())
provides.put(sn, providerClasses);
}
return provides;
}
/**
* Returns a container with the service type corresponding to the name of
* a services configuration file.
*
* For example, if called with "META-INF/services/p.S" then this method
* returns a container with the value "p.S".
*/
private Optional<String> toServiceName(String cf) {
assert cf.startsWith(SERVICES_PREFIX);
int index = cf.lastIndexOf("/") + 1;
if (index < cf.length()) {
String prefix = cf.substring(0, index);
if (prefix.equals(SERVICES_PREFIX)) {
String sn = cf.substring(index);
if (isJavaIdentifier(sn))
return Optional.of(sn);
}
}
return Optional.empty();
}
/**
* Reads the next line from the given reader and trims it of comments and
* leading/trailing white space.
*
* Returns null if the reader is at EOF.
*/
private String nextLine(BufferedReader reader) throws IOException {
String ln = reader.readLine();
if (ln != null) {
int ci = ln.indexOf('#');
if (ci >= 0)
ln = ln.substring(0, ci);
ln = ln.trim();
}
return ln;
}
/**
* Returns {@code true} if the given identifier is a legal Java identifier.
*/
private static boolean isJavaIdentifier(String id) {
int n = id.length();
if (n == 0)
return false;
if (!Character.isJavaIdentifierStart(id.codePointAt(0)))
return false;
int cp = id.codePointAt(0);
int i = Character.charCount(cp);
for (; i < n; i += Character.charCount(cp)) {
cp = id.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && id.charAt(i) != '.')
return false;
}
if (cp == '.')
return false;
return true;
public ModuleDescriptor descriptor() {
return md;
}
}
}

@ -24,15 +24,24 @@
*/
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Analyzer.Type.CLASS;
import static com.sun.tools.jdeps.JdepsTask.*;
import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
import static com.sun.tools.jdeps.Module.trace;
import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleFinder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@ -40,168 +49,159 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModuleInfoBuilder {
final ModulePaths modulePaths;
final JdepsConfiguration configuration;
final Path outputdir;
final DependencyFinder dependencyFinder;
final JdepsFilter filter;
final Analyzer analyzer;
final Map<Module, Module> strictModules = new HashMap<>();
ModuleInfoBuilder(ModulePaths modulePaths, DependencyFinder finder) {
this.modulePaths = modulePaths;
this.dependencyFinder = finder;
this.filter = new JdepsFilter.Builder().filter(true, true).build();
this.analyzer = new Analyzer(CLASS, filter);
final Map<Module, Module> strictModules;
public ModuleInfoBuilder(JdepsConfiguration configuration,
List<String> args,
Path outputdir) {
this.configuration = configuration;
this.outputdir = outputdir;
this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
this.analyzer = new Analyzer(configuration, Analyzer.Type.CLASS, DEFAULT_FILTER);
// add targets to modulepath if it has module-info.class
List<Path> paths = args.stream()
.map(fn -> Paths.get(fn))
.collect(Collectors.toList());
// automatic module to convert to strict module
this.strictModules = ModuleFinder.of(paths.toArray(new Path[0]))
.findAll().stream()
.map(configuration::toModule)
.collect(Collectors.toMap(Function.identity(), Function.identity()));
Optional<Module> om = strictModules.keySet().stream()
.filter(m -> !m.descriptor().isAutomatic())
.findAny();
if (om.isPresent()) {
throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",
om.get().getPathName()));
}
if (strictModules.isEmpty()) {
throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));
}
}
private Stream<Module> automaticModules() {
return modulePaths.getModules().values()
.stream()
.filter(Module::isAutomatic);
public boolean run() throws IOException {
try {
// pass 1: find API dependencies
Map<Archive, Set<Archive>> requiresPublic = computeRequiresPublic();
// pass 2: analyze all class dependences
dependencyFinder.parse(automaticModules().stream());
analyzer.run(automaticModules(), dependencyFinder.locationToArchive());
// computes requires and requires public
automaticModules().forEach(m -> {
Map<String, Boolean> requires;
if (requiresPublic.containsKey(m)) {
requires = requiresPublic.get(m).stream()
.map(Archive::getModule)
.collect(Collectors.toMap(Module::name, (v) -> Boolean.TRUE));
} else {
requires = new HashMap<>();
}
analyzer.requires(m)
.map(Archive::getModule)
.forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));
strictModules.put(m, m.toStrictModule(requires));
});
// generate module-info.java
descriptors().forEach(md -> writeModuleInfo(outputdir, md));
// find any missing dependences
return automaticModules().stream()
.flatMap(analyzer::requires)
.allMatch(m -> !m.equals(NOT_FOUND));
} finally {
dependencyFinder.shutdown();
}
}
/**
* Returns the stream of resulting modules
*/
Stream<Module> modules() {
return strictModules.values().stream();
}
/**
* Returns the stream of resulting ModuleDescriptors
*/
public Stream<ModuleDescriptor> descriptors() {
return strictModules.values().stream().map(Module::descriptor);
}
void visitMissingDeps(Analyzer.Visitor visitor) {
automaticModules().stream()
.filter(m -> analyzer.requires(m).anyMatch(d -> d.equals(NOT_FOUND)))
.forEach(m -> {
analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
});
}
void writeModuleInfo(Path dir, ModuleDescriptor descriptor) {
String mn = descriptor.name();
Path srcFile = dir.resolve(mn).resolve("module-info.java");
try {
Files.createDirectories(srcFile.getParent());
System.out.println("writing to " + srcFile);
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(srcFile))) {
printModuleInfo(pw, descriptor);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void printModuleInfo(PrintWriter writer, ModuleDescriptor descriptor) {
writer.format("module %s {%n", descriptor.name());
Map<String, Module> modules = configuration.getModules();
// first print the JDK modules
descriptor.requires().stream()
.filter(req -> !req.name().equals("java.base")) // implicit requires
.sorted(Comparator.comparing(Requires::name))
.forEach(req -> writer.format(" requires %s;%n", req));
descriptor.exports().stream()
.peek(exp -> {
if (exp.targets().size() > 0)
throw new InternalError(descriptor.name() + " qualified exports: " + exp);
})
.sorted(Comparator.comparing(Exports::source))
.forEach(exp -> writer.format(" exports %s;%n", exp.source()));
descriptor.provides().values().stream()
.sorted(Comparator.comparing(Provides::service))
.forEach(p -> p.providers().stream()
.sorted()
.forEach(impl -> writer.format(" provides %s with %s;%n", p.service(), impl)));
writer.println("}");
}
private Set<Module> automaticModules() {
return strictModules.keySet();
}
/**
* Compute 'requires public' dependences by analyzing API dependencies
*/
Map<Module, Set<Module>> computeRequiresPublic() throws IOException {
dependencyFinder.findDependencies(filter, true /* api only */, 1);
Analyzer pass1 = new Analyzer(Analyzer.Type.CLASS, filter);
private Map<Archive, Set<Archive>> computeRequiresPublic() throws IOException {
// parse the input modules
dependencyFinder.parseExportedAPIs(automaticModules().stream());
pass1.run(dependencyFinder.archives());
return automaticModules().collect(Collectors.toMap(Function.identity(),
source -> pass1.requires(source)
.map(Archive::getModule)
.collect(Collectors.toSet())));
}
boolean run(Analyzer.Type verbose, boolean quiet) throws IOException {
// add all automatic modules to the root set
automaticModules().forEach(dependencyFinder::addRoot);
// pass 1: find API dependencies
Map<Module, Set<Module>> requiresPublic = computeRequiresPublic();
// pass 2: analyze all class dependences
dependencyFinder.findDependencies(filter, false /* all classes */, 1);
analyzer.run(dependencyFinder.archives());
// computes requires and requires public
automaticModules().forEach(m -> {
Map<String, Boolean> requires;
if (requiresPublic.containsKey(m)) {
requires = requiresPublic.get(m)
.stream()
.collect(Collectors.toMap(Archive::getName, (v) -> Boolean.TRUE));
} else {
requires = new HashMap<>();
}
analyzer.requires(m)
.forEach(d -> requires.putIfAbsent(d.getName(), Boolean.FALSE));
trace("strict module %s requires %s%n", m.name(), requires);
strictModules.put(m, m.toStrictModule(requires));
});
// find any missing dependences
Optional<Module> missingDeps = automaticModules()
.filter(this::missingDep)
.findAny();
if (missingDeps.isPresent()) {
automaticModules()
.filter(this::missingDep)
.forEach(m -> {
System.err.format("Missing dependencies from %s%n", m.name());
analyzer.visitDependences(m,
new Analyzer.Visitor() {
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
if (targetArchive == NOT_FOUND)
System.err.format(" %-50s -> %-50s %s%n",
origin, target, targetArchive.getName());
}
}, verbose);
System.err.println();
});
System.err.println("ERROR: missing dependencies (check \"requires NOT_FOUND;\")");
}
return missingDeps.isPresent() ? false : true;
}
private boolean missingDep(Archive m) {
return analyzer.requires(m).filter(a -> a.equals(NOT_FOUND))
.findAny().isPresent();
}
void build(Path dir) throws IOException {
ModuleInfoWriter writer = new ModuleInfoWriter(dir);
writer.generateOutput(strictModules.values(), analyzer);
}
private class ModuleInfoWriter {
private final Path outputDir;
ModuleInfoWriter(Path dir) {
this.outputDir = dir;
}
void generateOutput(Iterable<Module> modules, Analyzer analyzer) throws IOException {
// generate module-info.java file for each archive
for (Module m : modules) {
if (m.packages().contains("")) {
System.err.format("ERROR: %s contains unnamed package. " +
"module-info.java not generated%n", m.getPathName());
continue;
}
String mn = m.getName();
Path srcFile = outputDir.resolve(mn).resolve("module-info.java");
Files.createDirectories(srcFile.getParent());
System.out.println("writing to " + srcFile);
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(srcFile))) {
printModuleInfo(pw, m);
}
}
}
private void printModuleInfo(PrintWriter writer, Module m) {
writer.format("module %s {%n", m.name());
Map<String, Module> modules = modulePaths.getModules();
Map<String, Boolean> requires = m.requires();
// first print the JDK modules
requires.keySet().stream()
.filter(mn -> !mn.equals("java.base")) // implicit requires
.filter(mn -> modules.containsKey(mn) && modules.get(mn).isJDK())
.sorted()
.forEach(mn -> {
String modifier = requires.get(mn) ? "public " : "";
writer.format(" requires %s%s;%n", modifier, mn);
});
// print requires non-JDK modules
requires.keySet().stream()
.filter(mn -> !modules.containsKey(mn) || !modules.get(mn).isJDK())
.sorted()
.forEach(mn -> {
String modifier = requires.get(mn) ? "public " : "";
writer.format(" requires %s%s;%n", modifier, mn);
});
m.packages().stream()
.sorted()
.forEach(pn -> writer.format(" exports %s;%n", pn));
m.provides().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> {
String service = e.getKey();
e.getValue().stream()
.sorted()
.forEach(impl -> writer.format(" provides %s with %s;%n", service, impl));
});
writer.println("}");
}
return dependencyFinder.dependences();
}
}

@ -1,206 +0,0 @@
/*
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.jdeps;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderNotFoundException;
import java.util.*;
import java.util.stream.Collectors;
import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
public class ModulePaths {
final ModuleFinder finder;
final Map<String, Module> modules = new LinkedHashMap<>();
public ModulePaths(String upgradeModulePath, String modulePath) {
this(upgradeModulePath, modulePath, Collections.emptyList());
}
public ModulePaths(String upgradeModulePath, String modulePath, List<Path> jars) {
ModuleFinder finder = ModuleFinder.ofSystem();
if (upgradeModulePath != null) {
finder = ModuleFinder.compose(createModulePathFinder(upgradeModulePath), finder);
}
if (jars.size() > 0) {
finder = ModuleFinder.compose(finder, ModuleFinder.of(jars.toArray(new Path[0])));
}
if (modulePath != null) {
finder = ModuleFinder.compose(finder, createModulePathFinder(modulePath));
}
this.finder = finder;
// add modules from modulepaths
finder.findAll().stream().forEach(mref ->
modules.computeIfAbsent(mref.descriptor().name(), mn -> toModule(mn, mref))
);
}
/**
* Returns the list of Modules that can be found in the specified
* module paths.
*/
Map<String, Module> getModules() {
return modules;
}
Set<Module> dependences(String... roots) {
Configuration cf = configuration(roots);
return cf.modules().stream()
.map(ResolvedModule::name)
.map(modules::get)
.collect(Collectors.toSet());
}
Configuration configuration(String... roots) {
return Configuration.empty().resolveRequires(finder, ModuleFinder.empty(), Set.of(roots));
}
private static ModuleFinder createModulePathFinder(String mpaths) {
if (mpaths == null) {
return null;
} else {
String[] dirs = mpaths.split(File.pathSeparator);
Path[] paths = new Path[dirs.length];
int i = 0;
for (String dir : dirs) {
paths[i++] = Paths.get(dir);
}
return ModuleFinder.of(paths);
}
}
private static Module toModule(String mn, ModuleReference mref) {
return SystemModulePath.find(mn)
.orElse(toModule(new Module.Builder(mn), mref));
}
private static Module toModule(Module.Builder builder, ModuleReference mref) {
ModuleDescriptor md = mref.descriptor();
builder.descriptor(md);
for (ModuleDescriptor.Requires req : md.requires()) {
builder.require(req.name(), req.modifiers().contains(PUBLIC));
}
for (ModuleDescriptor.Exports exp : md.exports()) {
builder.export(exp.source(), exp.targets());
}
builder.packages(md.packages());
try {
URI location = mref.location()
.orElseThrow(FileNotFoundException::new);
builder.location(location);
builder.classes(getClassReader(location, md.name()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return builder.build();
}
static class SystemModulePath {
final static Module JAVA_BASE;
private final static FileSystem fs;
private final static Path root;
private final static Map<String, Module> installed = new HashMap<>();
static {
if (isJrtAvailable()) {
// jrt file system
fs = FileSystems.getFileSystem(URI.create("jrt:/"));
root = fs.getPath("/modules");
} else {
// exploded image
String javahome = System.getProperty("java.home");
fs = FileSystems.getDefault();
root = Paths.get(javahome, "modules");
}
ModuleFinder.ofSystem().findAll().stream()
.forEach(mref ->
installed.computeIfAbsent(mref.descriptor().name(),
mn -> toModule(new Module.Builder(mn, true), mref))
);
JAVA_BASE = installed.get("java.base");
Profile.init(installed);
}
private static boolean isJrtAvailable() {
try {
FileSystems.getFileSystem(URI.create("jrt:/"));
return true;
} catch (ProviderNotFoundException | FileSystemNotFoundException e) {
return false;
}
}
public static Optional<Module> find(String mn) {
return installed.containsKey(mn) ? Optional.of(installed.get(mn))
: Optional.empty();
}
public static boolean contains(Module m) {
return installed.containsValue(m);
}
public static ClassFileReader getClassReader(String modulename) throws IOException {
Path mp = root.resolve(modulename);
if (Files.exists(mp) && Files.isDirectory(mp)) {
return ClassFileReader.newInstance(fs, mp);
} else {
throw new FileNotFoundException(mp.toString());
}
}
}
/**
* Returns a ModuleClassReader that only reads classes for the given modulename.
*/
public static ClassFileReader getClassReader(URI location, String modulename)
throws IOException {
if (location.getScheme().equals("jrt")) {
return SystemModulePath.getClassReader(modulename);
} else {
Path path = Paths.get(location);
return ClassFileReader.newInstance(path);
}
}
}

@ -26,9 +26,13 @@
package com.sun.tools.jdeps;
import java.io.IOException;
import java.lang.module.ModuleDescriptor;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
@ -44,17 +48,18 @@ enum Profile {
// need a way to determine JRE modules
SE_JRE("Java SE JRE", 4, "java.se", "jdk.charsets",
"jdk.crypto.ec", "jdk.crypto.pkcs11",
"jdk.crypto.mscapi", "jdk.crypto.ucrypto", "jdk.jvmstat",
"jdk.crypto.mscapi", "jdk.crypto.ucrypto",
"jdk.localedata", "jdk.scripting.nashorn", "jdk.zipfs"),
FULL_JRE("Full JRE", 5, "java.se.ee", "jdk.charsets",
"jdk.crypto.ec", "jdk.crypto.pkcs11",
"jdk.crypto.mscapi", "jdk.crypto.ucrypto", "jdk.jvmstat",
"jdk.localedata", "jdk.scripting.nashorn", "jdk.zipfs");
"jdk.localedata", "jdk.scripting.nashorn",
"jdk.unsupported", "jdk.zipfs");
final String name;
final int profile;
final String[] mnames;
final Set<Module> modules = new HashSet<>();
final Map<String, Module> modules = new HashMap<>();
Profile(String name, int profile, String... mnames) {
this.name = name;
@ -75,12 +80,18 @@ enum Profile {
return JDK.isEmpty() ? 0 : Profile.values().length;
}
Optional<Module> findModule(String name) {
return modules.containsKey(name)
? Optional.of(modules.get(name))
: Optional.empty();
}
/**
* Returns the Profile for the given package name; null if not found.
*/
public static Profile getProfile(String pn) {
for (Profile p : Profile.values()) {
for (Module m : p.modules) {
for (Module m : p.modules.values()) {
if (m.packages().contains(pn)) {
return p;
}
@ -94,7 +105,7 @@ enum Profile {
*/
public static Profile getProfile(Module m) {
for (Profile p : Profile.values()) {
if (p.modules.contains(m)) {
if (p.modules.containsValue(m)) {
return p;
}
}
@ -102,34 +113,28 @@ enum Profile {
}
private final static Set<Module> JDK = new HashSet<>();
static synchronized void init(Map<String, Module> installed) {
for (Profile p : Profile.values()) {
for (String mn : p.mnames) {
// this includes platform-dependent module that may not exist
Module m = installed.get(mn);
if (m != null) {
p.addModule(installed, m);
}
}
}
static synchronized void init(Map<String, Module> systemModules) {
Arrays.stream(Profile.values()).forEach(p ->
// this includes platform-dependent module that may not exist
Arrays.stream(p.mnames)
.filter(systemModules::containsKey)
.map(systemModules::get)
.forEach(m -> p.addModule(systemModules, m)));
// JDK modules should include full JRE plus other jdk.* modules
// Just include all installed modules. Assume jdeps is running
// in JDK image
JDK.addAll(installed.values());
JDK.addAll(systemModules.values());
}
private void addModule(Map<String, Module> installed, Module m) {
modules.add(m);
for (String n : m.requires().keySet()) {
Module d = installed.get(n);
if (d == null) {
throw new InternalError("module " + n + " required by " +
m.name() + " doesn't exist");
}
modules.add(d);
}
private void addModule(Map<String, Module> systemModules, Module module) {
modules.put(module.name(), module);
module.descriptor().requires().stream()
.map(ModuleDescriptor.Requires::name)
.map(systemModules::get)
.forEach(m -> modules.put(m.name(), m));
}
// for debugging
public static void main(String[] args) throws IOException {
// find platform modules
@ -139,14 +144,6 @@ enum Profile {
for (Profile p : Profile.values()) {
String profileName = p.name;
System.out.format("%2d: %-10s %s%n", p.profile, profileName, p.modules);
for (Module m: p.modules) {
System.out.format("module %s%n", m.name());
System.out.format(" requires %s%n", m.requires());
for (Map.Entry<String,Set<String>> e: m.exports().entrySet()) {
System.out.format(" exports %s %s%n", e.getKey(),
e.getValue().isEmpty() ? "" : "to " + e.getValue());
}
}
}
System.out.println("All JDK modules:-");
JDK.stream().sorted(Comparator.comparing(Module::name))

@ -1,12 +1,10 @@
main.usage.summary=\
Usage: {0} <options> [-m <module-name> | <classes...>]\n\
Usage: {0} <options> <path ...>]\n\
use -h, -? or -help for a list of possible options
main.usage=\
Usage: {0} <options> [-m <module-name> | <classes...>]\n\
If -m <module-name> is specified, the specified module will be analyzed\n\
otherwise, <classes> can be a pathname to a .class file, a directory,\n\
a JAR file, or a fully-qualified class name.\n\
Usage: {0} <options> <path ...>]\n\
<path> can be a pathname to a .class file, a directory, a JAR file.\n\
\n\
Possible options include:
@ -14,134 +12,138 @@ error.prefix=Error:
warn.prefix=Warning:
main.opt.h=\
\ -h -? -help Print this usage message
\ -h -? -help Print this usage message
main.opt.version=\
\ -version Version information
\ -version Version information
main.opt.v=\
\ -v -verbose Print all class level dependencies\n\
\ Equivalent to -verbose:class -filter:none.\n\
\ -verbose:package Print package-level dependencies excluding\n\
\ dependencies within the same package by default\n\
\ -verbose:class Print class-level dependencies excluding\n\
\ dependencies within the same package by default
main.opt.f=\
\ -f <regex> -filter <regex> Filter dependences matching the given pattern\n\
\ If given multiple times, the last one will be used.\n\
\ -filter:package Filter dependences within the same package (default)\n\
\ -filter:module Filter dependences within the same module\n\
\ -filter:archive Filter dependences within the same archive\n\
\ -filter:none No -filter:package and -filter:archive filtering\n\
\ Filtering specified via the -filter option still applies.
\ -v -verbose Print all class level dependencies\n\
\ Equivalent to -verbose:class -filter:none.\n\
\ -verbose:package Print package-level dependencies excluding\n\
\ dependencies within the same package by default\n\
\ -verbose:class Print class-level dependencies excluding\n\
\ dependencies within the same package by default
main.opt.s=\
\ -s -summary Print dependency summary only.\n\
\ If -s option is used with -m, the module descriptor of\n\
\ the given module will be read to generate the graph.
\ -s -summary Print dependency summary only.
main.opt.p=\
\ -p <pkgname> Finds dependences matching the given package name\n\
\ -package <pkgname> (may be given multiple times).
main.opt.f=\
\ -f <regex> -filter <regex> Filter dependences matching the given\n\
\ pattern. If given multiple times, the last\n\
\ one will be used.\n\
\ -filter:package Filter dependences within the same package.\n\
\ This is the default.\n\
\ -filter:archive Filter dependences within the same archive.\n\
\ -filter:module Filter dependences within the same module.\n\
\ -filter:none No -filter:package and -filter:archive\n\
\ filtering. Filtering specified via the\n\
\ -filter option still applies.\n\
main.opt.p=\n\
\Options to filter dependencies:\n\
\ -p <pkgname> -package <pkgname> Finds dependences matching the given package\n\
\ name (may be given multiple times).
main.opt.e=\
\ -e <regex>\n\
\ -regex <regex> Finds dependences matching the given pattern.
\ -e <regex> -regex <regex> Finds dependences matching the given pattern.
main.opt.module=\
\ -module <module-name> Finds dependences matching the given module name\n\
\ (may be given multiple times).\n\
\ -package, -regex, -requires are mutual exclusive.
\ -module <module-name> Finds dependences matching the given module\n\
\ name (may be given multiple times).\n\
\ -package, -regex, -module are mutual exclusive.
main.opt.include=\
\ -include <regex> Restrict analysis to classes matching pattern\n\
\ This option filters the list of classes to\n\
\ be analyzed. It can be used together with\n\
\ -p and -e which apply pattern to the dependences
main.opt.include=\n\
\Options to filter classes to be analyzed:\n\
\ -include <regex> Restrict analysis to classes matching pattern\n\
\ This option filters the list of classes to\n\
\ be analyzed. It can be used together with\n\
\ -p and -e which apply pattern to the dependences
main.opt.P=\
\ -P -profile Show profile containing a package
main.opt.M=\
\ -M Show module containing a package
\ -P -profile Show profile containing a package
main.opt.cp=\
\ -cp <path> -classpath <path> Specify where to find class files
\ -cp <path> -classpath <path> Specify where to find class files
main.opt.mp=\
\ -mp <module path>...\n\
\ -modulepath <module path>... Specify module path
\ -modulepath <module path>... Specify module path
main.opt.upgrademodulepath=\
\ -upgrademodulepath <module path>... Specify upgrade module path
main.opt.m=\
\ -m <module-name> Specify the name of the module and its transitive\n\
\ dependences to be analyzed.
\ -m <module-name> Specify the root module for analysis
main.opt.R=\
\ -R -recursive Recursively traverse all run-time dependencies.\n\
\ The -R option implies -filter:none. If -p, -e, -f\n\
\ option is specified, only the matching dependences\n\
\ are analyzed.
\ -R -recursive Recursively traverse all run-time dependencies.\n\
\ The -R option implies -filter:none. If -p,\n\
\ -e, -foption is specified, only the matching\n\
\ dependences are analyzed.
main.opt.ct=\
\ -ct -compile-time Compile-time view of transitive dependencies\n\
\ i.e. compile-time view of -R option. If a dependence\n\
\ is found from a directory, a JAR file or a module,\n\
\ all class files in that containing archive are analyzed.
\ -ct -compile-time Compile-time view of transitive dependencies\n\
\ i.e. compile-time view of -R option.\n\
\ Analyzes the dependences per other given options\n\
\ If a dependence is found from a directory,\n\
\ a JAR file or a module, all classes in that \n\
\ containing archive are analyzed.
main.opt.apionly=\
\ -apionly Restrict analysis to APIs i.e. dependences\n\
\ from the signature of public and protected\n\
\ members of public classes including field\n\
\ type, method parameter types, returned type,\n\
\ checked exception types etc
\ -apionly Restrict analysis to APIs i.e. dependences\n\
\ from the signature of public and protected\n\
\ members of public classes including field\n\
\ type, method parameter types, returned type,\n\
\ checked exception types etc.
main.opt.genmoduleinfo=\
\ -genmoduleinfo <dir> Generate module-info.java under the specified directory.\n\
\ The specified JAR files will be analyzed.\n\
\ This option cannot be used with -dotoutput or -cp.
\ -genmoduleinfo <dir> Generate module-info.java under the specified\n\
\ directory. The specified JAR files will be\n\
\ analyzed. This option cannot be used with\n\
\ -dotoutput or -cp.
main.opt.check=\
\ -check Analyze the dependence of a given module specified via\n\
\ -m option. It prints out the resulting module dependency\n\
\ graph after transition reduction and also identifies any\n\
\ unused qualified exports.
\ -check <module-name>[,<module-name>...\n\
\ Analyze the dependence of the specified modules\n\
\ It prints the module descriptor, the resulting\n\
\ module dependences after analysis and the\n\
\ graph after transition reduction. It also\n\
\ identifies any unused qualified exports.
main.opt.dotoutput=\
\ -dotoutput <dir> Destination directory for DOT file output
\ -dotoutput <dir> Destination directory for DOT file output
main.opt.jdkinternals=\
\ -jdkinternals Finds class-level dependences on JDK internal APIs.\n\
\ By default, it analyzes all classes on -classpath\n\
\ and input files unless -include option is specified.\n\
\ This option cannot be used with -p, -e and -s options.\n\
\ WARNING: JDK internal APIs may not be accessible in\n\
\ the next release.
\ -jdkinternals Finds class-level dependences on JDK internal\n\
\ APIs. By default, it analyzes all classes\n\
\ on -classpath and input files unless -include\n\
\ option is specified. This option cannot be\n\
\ used with -p, -e and -s options.\n\
\ WARNING: JDK internal APIs are inaccessible.
main.opt.depth=\
\ -depth=<depth> Specify the depth of the transitive\n\
\ dependency analysis
\ -depth=<depth> Specify the depth of the transitive\n\
\ dependency analysis
main.opt.q=\
\ -q -quiet Do not show missing dependencies from -genmoduleinfo output.
\ -q -quiet Do not show missing dependencies from \n\
\ -genmoduleinfo output.
err.unknown.option=unknown option: {0}
err.missing.arg=no value given for {0}
err.invalid.arg.for.option=invalid argument for option: {0}
err.option.after.class=option must be specified before classes: {0}
err.genmoduleinfo.not.jarfile={0} not valid for -genmoduleinfo option (must be JAR file)
err.genmoduleinfo.not.jarfile={0} not valid for -genmoduleinfo option (must be non-modular JAR file)
err.profiles.msg=No profile information
err.exception.message={0}
err.invalid.path=invalid path: {0}
err.invalid.module.option=-m {0} is set but {1} is specified.
err.invalid.filters=Only one of -package (-p), -regex (-e), -requires option can be set
err.invalid.module.option=Cannot set {0} with {1} option.
err.invalid.filters=Only one of -package (-p), -regex (-e), -module option can be set
err.module.not.found=module not found: {0}
err.root.module.not.set=-m is not set
warn.invalid.arg=Invalid classname or pathname not exist: {0}
err.root.module.not.set=root module set empty
warn.invalid.arg=Path not exist: {0}
warn.split.package=package {0} defined in {1} {2}
warn.replace.useJDKInternals=\
JDK internal APIs are unsupported and private to JDK implementation that are\n\

@ -1,6 +1,5 @@
// No translation needed
# No translation needed
com.sun.crypto.provider.SunJCE=Use java.security.Security.getProvider(provider-name) @since 1.3
com.sun.image.codec=Use javax.imageio @since 1.4
com.sun.org.apache.xml.internal.security=Use java.xml.crypto @since 1.6
com.sun.org.apache.xml.internal.security.utils.Base64=Use java.util.Base64 @since 1.8
com.sun.org.apache.xml.internal.resolver=Use javax.xml.catalog @since 9
@ -9,17 +8,34 @@ com.sun.net.ssl.internal.ssl.Provider=Use java.security.Security.getProvider(pro
com.sun.rowset=Use javax.sql.rowset.RowSetProvider @since 1.7
com.sun.tools.javac.tree=Use com.sun.source @since 1.6
com.sun.tools.javac=Use javax.tools and javax.lang.model @since 1.6
java.awt.peer=Should not use. See https://bugs.openjdk.java.net/browse/JDK-8037739
java.awt.dnd.peer=Should not use. See https://bugs.openjdk.java.net/browse/JDK-8037739
jdk.internal.ref.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9
sun.awt.image.codec=Use javax.imageio @since 1.4
sun.misc.BASE64Encoder=Use java.util.Base64 @since 1.8
sun.misc.BASE64Decoder=Use java.util.Base64 @since 1.8
sun.misc.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9
sun.misc.Service=Use java.util.ServiceLoader @since 1.6
sun.awt.CausedFocusEvent=Use java.awt.event.FocusEvent::getCause @since 9
sun.font.FontUtilities=See java.awt.Font.textRequiresLayout @since 9
sun.reflect.Reflection=See StackWalker API @since 9
sun.reflect.ReflectionFactory=See http://openjdk.java.net/jeps/260
sun.misc.Unsafe=See http://openjdk.java.net/jeps/260
sun.misc.Signal=See http://openjdk.java.net/jeps/260
sun.misc.SignalHandler=See http://openjdk.java.net/jeps/260
sun.security.action=Use java.security.PrivilegedAction @since 1.1
sun.security.krb5=Use com.sun.security.jgss
sun.security.provider.PolicyFile=Use java.security.Policy.getInstance("JavaPolicy", new URIParameter(uri)) @since 1.6
sun.security.provider.Sun=Use java.security.Security.getProvider(provider-name) @since 1.3
sun.security.util.SecurityConstants=Use appropriate java.security.Permission subclass @since 1.1
sun.security.x509.X500Name=Use javax.security.auth.x500.X500Principal @since 1.4
sun.tools.jar=Use java.util.jar or jar tool @since 1.2
jdk.internal.ref.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9
sun.tools.jar=Use java.util.jar or jar tool @since 1.2\
# Internal APIs removed in JDK 9
com.apple.eawt=Use java.awt.desktop and JEP 272 @since 9
com.apple.concurrent=Removed. See https://bugs.openjdk.java.net/browse/JDK-8148187
com.sun.image.codec=Use javax.imageio @since 1.4
sun.misc.BASE64Encoder=Use java.util.Base64 @since 1.8
sun.misc.BASE64Decoder=Use java.util.Base64 @since 1.8
sun.misc.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9
sun.misc.Service=Use java.util.ServiceLoader @since 1.6
sun.misc=Removed. See http://openjdk.java.net/jeps/260
sun.reflect=Removed. See http://openjdk.java.net/jeps/260

@ -25,6 +25,7 @@
* @test
* @bug 8015912 8029216 8048063 8050804
* @summary Test -apionly and -jdkinternals options
* @library lib
* @modules java.base/sun.security.x509
* java.management
* jdk.jdeps/com.sun.tools.classfile
@ -154,7 +155,8 @@ public class APIDeps {
Map<String,String> jdeps(String... args) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + Arrays.toString(args));
System.err.println("jdeps " + Arrays.stream(args)
.collect(Collectors.joining(" ")));
int rc = com.sun.tools.jdeps.Main.run(args, pw);
pw.close();
String out = sw.toString();

@ -39,6 +39,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;
import static java.nio.file.StandardCopyOption.*;
public class Basic {
@ -157,7 +159,7 @@ public class Basic {
Map<String,String> jdeps(String... args) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + Arrays.toString(args));
System.err.println("jdeps " + Arrays.stream(args).collect(Collectors.joining(" ")));
int rc = com.sun.tools.jdeps.Main.run(args, pw);
pw.close();
String out = sw.toString();

@ -41,6 +41,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;
public class DotFileTest {
public static void main(String... args) throws Exception {
@ -182,7 +183,7 @@ public class DotFileTest {
// invoke jdeps
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + args);
System.err.println("jdeps " + args.stream().collect(Collectors.joining(" ")));
int rc = com.sun.tools.jdeps.Main.run(args.toArray(new String[0]), pw);
pw.close();
String out = sw.toString();

@ -0,0 +1,234 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import com.sun.tools.jdeps.Analyzer;
import com.sun.tools.jdeps.DepsAnalyzer;
import com.sun.tools.jdeps.JdepsConfiguration;
import com.sun.tools.jdeps.JdepsFilter;
import com.sun.tools.jdeps.JdepsWriter;
import com.sun.tools.jdeps.ModuleAnalyzer;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Utilities to run jdeps command
*/
public final class JdepsUtil {
/*
* Runs jdeps with the given arguments
*/
public static String[] jdeps(String... args) {
String lineSep = System.getProperty("line.separator");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + Arrays.stream(args).collect(Collectors.joining(" ")));
int rc = com.sun.tools.jdeps.Main.run(args, pw);
pw.close();
String out = sw.toString();
if (!out.isEmpty())
System.err.println(out);
if (rc != 0)
throw new Error("jdeps failed: rc=" + rc);
return out.split(lineSep);
}
public static Command newCommand(String cmd) {
return new Command(cmd);
}
public static class Command {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
final JdepsFilter.Builder filter = new JdepsFilter.Builder().filter(true, true);
final JdepsConfiguration.Builder builder = new JdepsConfiguration.Builder();
final Set<String> requires = new HashSet<>();
Analyzer.Type verbose = Analyzer.Type.PACKAGE;
boolean apiOnly = false;
public Command(String cmd) {
System.err.println("============ ");
System.err.println(cmd);
}
public Command verbose(String verbose) {
switch (verbose) {
case "-verbose":
this.verbose = Analyzer.Type.VERBOSE;
filter.filter(false, false);
break;
case "-verbose:package":
this.verbose = Analyzer.Type.PACKAGE;
break;
case "-verbose:class":
this.verbose = Analyzer.Type.CLASS;
break;
case "-summary":
this.verbose = Analyzer.Type.SUMMARY;
break;
default:
throw new IllegalArgumentException(verbose);
}
return this;
}
public Command filter(String value) {
switch (value) {
case "-filter:package":
filter.filter(true, false);
break;
case "-filter:archive":
case "-filter:module":
filter.filter(false, true);
break;
default:
throw new IllegalArgumentException(value);
}
return this;
}
public Command addClassPath(String classpath) {
builder.addClassPath(classpath);
return this;
}
public Command addRoot(Path path) {
builder.addRoot(path);
return this;
}
public Command appModulePath(String modulePath) {
builder.appModulePath(modulePath);
return this;
}
public Command addmods(Set<String> mods) {
builder.addmods(mods);
return this;
}
public Command requires(Set<String> mods) {
requires.addAll(mods);
return this;
}
public Command matchPackages(Set<String> pkgs) {
filter.packages(pkgs);
return this;
}
public Command regex(String regex) {
filter.regex(Pattern.compile(regex));
return this;
}
public Command include(String regex) {
filter.includePattern(Pattern.compile(regex));
return this;
}
public Command includeSystemMoudles(String regex) {
filter.includeSystemModules(Pattern.compile(regex));
return this;
}
public Command apiOnly() {
this.apiOnly = true;
return this;
}
public JdepsConfiguration configuration() throws IOException {
JdepsConfiguration config = builder.build();
requires.forEach(name -> {
ModuleDescriptor md = config.findModuleDescriptor(name).get();
filter.requires(name, md.packages());
});
return config;
}
private JdepsWriter writer() {
return JdepsWriter.newSimpleWriter(pw, verbose);
}
public DepsAnalyzer getDepsAnalyzer() throws IOException {
return new DepsAnalyzer(configuration(), filter.build(), writer(),
verbose, apiOnly);
}
public ModuleAnalyzer getModuleAnalyzer(Set<String> mods) throws IOException {
// if -check is set, add to the root set and all modules are observable
addmods(mods);
builder.allModules();
return new ModuleAnalyzer(configuration(), pw, mods);
}
public void dumpOutput(PrintStream out) {
out.println(sw.toString());
}
}
/**
* Create a jar file using the list of files provided.
*/
public static void createJar(Path jarfile, Path root, Stream<Path> files)
throws IOException {
Path dir = jarfile.getParent();
if (dir != null && Files.notExists(dir)) {
Files.createDirectories(dir);
}
try (JarOutputStream target = new JarOutputStream(
Files.newOutputStream(jarfile))) {
files.forEach(file -> add(root.relativize(file), file, target));
}
}
private static void add(Path path, Path source, JarOutputStream target) {
try {
String name = path.toString().replace(File.separatorChar, '/');
JarEntry entry = new JarEntry(name);
entry.setTime(source.toFile().lastModified());
target.putNextEntry(entry);
Files.copy(source, target);
target.closeEntry();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

@ -30,4 +30,3 @@ public class Foo extends Bar implements c.I {
setF(new f.F());
}
}

@ -0,0 +1,162 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Tests split packages
* @library ../lib
* @build CompilerUtils JdepsUtil
* @modules jdk.jdeps/com.sun.tools.jdeps
* @run testng CheckModuleTest
*/
import java.lang.module.ModuleDescriptor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import com.sun.tools.jdeps.ModuleAnalyzer;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertEquals;
public class CheckModuleTest {
private static final String TEST_SRC = System.getProperty("test.src");
private static final String TEST_CLASSES = System.getProperty("test.classes");
private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
private static final Path MODS_DIR = Paths.get("mods");
// m4 and m5 are analyzed. Others are compiled to make sure they are present
// on the module path for analysis
private static final Set<String> modules = Set.of("unsafe", "m4", "m5", "m6", "m7", "m8");
private static final String JAVA_BASE = "java.base";
/**
* Compiles classes used by the test
*/
@BeforeTest
public void compileAll() throws Exception {
CompilerUtils.cleanDir(MODS_DIR);
modules.forEach(mn ->
assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, mn)));
}
@DataProvider(name = "javaBase")
public Object[][] base() {
return new Object[][] {
{ JAVA_BASE, new ModuleMetaData(JAVA_BASE)
},
};
};
@Test(dataProvider = "javaBase")
public void testJavaBase(String name, ModuleMetaData data) throws Exception {
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -check %s -mp %s%n", name, MODS_DIR)
);
jdeps.appModulePath(MODS_DIR.toString());
ModuleAnalyzer analyzer = jdeps.getModuleAnalyzer(Set.of(name));
assertTrue(analyzer.run());
jdeps.dumpOutput(System.err);
ModuleDescriptor[] descriptors = analyzer.descriptors(name);
for (int i=0; i < 3; i++) {
descriptors[i].requires().stream()
.forEach(req -> data.checkRequires(req));
}
}
@DataProvider(name = "modules")
public Object[][] unnamed() {
return new Object[][]{
{ "m4", new ModuleMetaData[] {
// original
new ModuleMetaData("m4")
.requiresPublic("java.compiler")
.requires("java.logging")
// unnused exports
.exports("p4.internal", Set.of("m6", "m7")),
// suggested version
new ModuleMetaData("m4")
.requires("java.compiler"),
// reduced version
new ModuleMetaData("m4")
.requires("java.compiler")
}
},
{ "m5", new ModuleMetaData[] {
// original
new ModuleMetaData("m5")
.requiresPublic("java.compiler")
.requiresPublic("java.logging")
.requires("java.sql")
.requiresPublic("m4"),
// suggested version
new ModuleMetaData("m5")
.requiresPublic("java.compiler")
.requires("java.logging")
.requiresPublic("java.sql")
.requiresPublic("m4"),
// reduced version
new ModuleMetaData("m5")
.requiresPublic("java.compiler")
.requiresPublic("java.sql")
.requiresPublic("m4"),
}
},
};
}
@Test(dataProvider = "modules")
public void modularTest(String name, ModuleMetaData[] data) throws Exception {
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -check %s -mp %s%n", name, MODS_DIR)
);
jdeps.appModulePath(MODS_DIR.toString());
ModuleAnalyzer analyzer = jdeps.getModuleAnalyzer(Set.of(name));
assertTrue(analyzer.run());
jdeps.dumpOutput(System.err);
// compare the module descriptors and the suggested versions
ModuleDescriptor[] descriptors = analyzer.descriptors(name);
for (int i=0; i < 3; i++) {
ModuleMetaData metaData = data[i];
descriptors[i].requires().stream()
.forEach(req -> metaData.checkRequires(req));
}
Map<String, Set<String>> unused = analyzer.unusedQualifiedExports(name);
// verify unuused qualified exports
assertEquals(unused, data[0].exports);
}
}

@ -24,8 +24,8 @@
/*
* @test
* @summary Tests jdeps -genmoduleinfo option
* @library ..
* @build CompilerUtils
* @library ../lib
* @build CompilerUtils JdepsUtil
* @modules jdk.jdeps/com.sun.tools.jdeps
* @run testng GenModuleInfo
*/
@ -39,16 +39,12 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.testng.annotations.DataProvider;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@ -86,7 +82,7 @@ public class GenModuleInfo {
for (String mn : modules) {
Path root = MODS_DIR.resolve(mn);
createJar(LIBS_DIR.resolve(mn + ".jar"), root,
JdepsUtil.createJar(LIBS_DIR.resolve(mn + ".jar"), root,
Files.walk(root, Integer.MAX_VALUE)
.filter(f -> {
String fn = f.getFileName().toString();
@ -100,7 +96,7 @@ public class GenModuleInfo {
Stream<String> files = Arrays.stream(modules)
.map(mn -> LIBS_DIR.resolve(mn + ".jar"))
.map(Path::toString);
jdeps(Stream.concat(Stream.of("-cp"), files).toArray(String[]::new));
JdepsUtil.jdeps(Stream.concat(Stream.of("-cp"), files).toArray(String[]::new));
}
@Test
@ -109,7 +105,7 @@ public class GenModuleInfo {
.map(mn -> LIBS_DIR.resolve(mn + ".jar"))
.map(Path::toString);
jdeps(Stream.concat(Stream.of("-genmoduleinfo", DEST_DIR.toString()),
JdepsUtil.jdeps(Stream.concat(Stream.of("-genmoduleinfo", DEST_DIR.toString()),
files)
.toArray(String[]::new));
@ -148,7 +144,7 @@ public class GenModuleInfo {
try (InputStream in1 = Files.newInputStream(p1);
InputStream in2 = Files.newInputStream(p2)) {
verify(ModuleDescriptor.read(in1),
ModuleDescriptor.read(in2, () -> packages(MODS_DIR.resolve(mn))));
ModuleDescriptor.read(in2, () -> packages(MODS_DIR.resolve(mn))));
}
}
}
@ -188,46 +184,4 @@ public class GenModuleInfo {
}
}
/*
* Runs jdeps with the given arguments
*/
public static String[] jdeps(String... args) {
String lineSep = System.getProperty("line.separator");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + Arrays.toString(args));
int rc = com.sun.tools.jdeps.Main.run(args, pw);
pw.close();
String out = sw.toString();
if (!out.isEmpty())
System.err.println(out);
if (rc != 0)
throw new Error("jdeps failed: rc=" + rc);
return out.split(lineSep);
}
/**
* Create a jar file using the list of files provided.
*/
public static void createJar(Path jarfile, Path root, Stream<Path> files)
throws IOException {
try (JarOutputStream target = new JarOutputStream(
Files.newOutputStream(jarfile))) {
files.forEach(file -> add(root.relativize(file), file, target));
}
}
private static void add(Path path, Path source, JarOutputStream target) {
try {
String name = path.toString().replace(File.separatorChar, '/');
JarEntry entry = new JarEntry(name);
entry.setTime(source.toFile().lastModified());
target.putNextEntry(entry);
Files.copy(source, target);
target.closeEntry();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

@ -0,0 +1,209 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import com.sun.tools.jdeps.DepsAnalyzer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static com.sun.tools.jdeps.DepsAnalyzer.Info.*;
import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
import static java.lang.module.ModuleDescriptor.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class ModuleMetaData {
public static final String JAVA_BASE = "java.base";
static final String INTERNAL = "(internal)";
static final String QUALIFIED = "(qualified)";
static final String JDK_INTERNAL = "JDK internal API";
final String moduleName;
final boolean isNamed;
final Map<String, ModuleRequires> requires = new LinkedHashMap<>();
final Map<String, Dependence> references = new LinkedHashMap<>();
final Map<String, Set<String>> exports = new LinkedHashMap<>();
ModuleMetaData(String name) {
this(name, true);
}
ModuleMetaData(String name, boolean isNamed) {
this.moduleName = name;
this.isNamed = isNamed;
requires(JAVA_BASE); // implicit requires
}
String name() {
return moduleName;
}
ModuleMetaData requires(String name) {
requires.put(name, new ModuleRequires(name));
return this;
}
ModuleMetaData requiresPublic(String name) {
requires.put(name, new ModuleRequires(name, PUBLIC));
return this;
}
// for unnamed module
ModuleMetaData depends(String name) {
requires.put(name, new ModuleRequires(name));
return this;
}
ModuleMetaData reference(String origin, String target, String module) {
return dependence(origin, target, module, "");
}
ModuleMetaData internal(String origin, String target, String module) {
return dependence(origin, target, module, INTERNAL);
}
ModuleMetaData qualified(String origin, String target, String module) {
return dependence(origin, target, module, QUALIFIED);
}
ModuleMetaData jdkInternal(String origin, String target, String module) {
return dependence(origin, target, module, JDK_INTERNAL);
}
ModuleMetaData exports(String pn, Set<String> targets) {
exports.put(pn, targets);
return this;
}
private ModuleMetaData dependence(String origin, String target, String module, String access) {
references.put(key(origin, target), new Dependence(origin, target, module, access));
return this;
}
String key(String origin, String target) {
return origin + ":" + target;
}
void checkRequires(String name, Set<DepsAnalyzer.Node> adjacentNodes) {
// System.err.format("%s: Expected %s Found %s %n", name, requires, adjacentNodes);
adjacentNodes.stream()
.forEach(v -> checkRequires(v.name));
assertEquals(adjacentNodes.size(), requires.size());
}
void checkRequires(String name) {
ModuleRequires req = requires.get(name);
if (req == null)
System.err.println(moduleName + ": unexpected requires " + name);
assertTrue(requires.containsKey(name));
}
void checkRequires(Requires require) {
String name = require.name();
if (name.equals(JAVA_BASE))
return;
ModuleRequires req = requires.get(name);
if (req == null)
System.err.format("%s: unexpected dependence %s%n", moduleName, name);
assertTrue(requires.containsKey(name));
assertEquals(require.modifiers(), req.modifiers());
}
void checkDependences(String name, Set<DepsAnalyzer.Node> adjacentNodes) {
// System.err.format("%s: Expected %s Found %s %n", name, references, adjacentNodes);
adjacentNodes.stream()
.forEach(v -> checkDependence(name, v.name, v.source, v.info));
assertEquals(adjacentNodes.size(), references.size());
}
void checkDependence(String origin, String target, String module, DepsAnalyzer.Info info) {
String key = key(origin, target);
Dependence dep = references.get(key);
String access = "";
if (info == QUALIFIED_EXPORTED_API)
access = QUALIFIED;
else if (info == JDK_INTERNAL_API)
access = JDK_INTERNAL;
else if (info == INTERNAL_API)
access = INTERNAL;
assertTrue(references.containsKey(key));
assertEquals(dep.access, access);
assertEquals(dep.module, module);
}
public static class ModuleRequires {
final String name;
final Requires.Modifier mod;
ModuleRequires(String name) {
this.name = name;
this.mod = null;
}
ModuleRequires(String name, Requires.Modifier mod) {
this.name = name;
this.mod = mod;
}
Set<Requires.Modifier> modifiers() {
return mod != null ? Set.of(mod) : Collections.emptySet();
}
@Override
public String toString() {
return name;
}
}
public static class Dependence {
final String origin;
final String target;
final String module;
final String access;
Dependence(String origin, String target, String module, String access) {
this.origin = origin;
this.target = target;
this.module = module;
this.access = access;
}
@Override
public String toString() {
return String.format("%s -> %s (%s) %s", origin, target, module, access);
}
}
}

@ -24,30 +24,28 @@
/*
* @test
* @summary Tests jdeps -m and -mp options on named modules and unnamed modules
* @library ..
* @build CompilerUtils
* @library ../lib
* @build CompilerUtils JdepsUtil
* @modules jdk.jdeps/com.sun.tools.jdeps
* @run testng ModuleTest
*/
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Requires.Modifier;
import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import com.sun.tools.jdeps.DepsAnalyzer;
import com.sun.tools.jdeps.Graph;
import org.testng.annotations.DataProvider;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
public class ModuleTest {
@ -56,6 +54,7 @@ public class ModuleTest {
private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
private static final Path MODS_DIR = Paths.get("mods");
private static final Path UNNAMED_DIR = Paths.get("unnamed");
// the names of the modules in this test
private static final String UNSUPPORTED = "unsupported";
@ -66,62 +65,68 @@ public class ModuleTest {
@BeforeTest
public void compileAll() throws Exception {
CompilerUtils.cleanDir(MODS_DIR);
CompilerUtils.cleanDir(UNNAMED_DIR);
assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, UNSUPPORTED,
"-XaddExports:java.base/jdk.internal.perf=" + UNSUPPORTED));
// m4 is not referenced
Arrays.asList("m1", "m2", "m3", "m4")
.forEach(mn -> assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, mn)));
assertTrue(CompilerUtils.compile(SRC_DIR.resolve("m3"), UNNAMED_DIR, "-mp", MODS_DIR.toString()));
Files.delete(UNNAMED_DIR.resolve("module-info.class"));
}
@DataProvider(name = "modules")
public Object[][] expected() {
return new Object[][]{
{ "m3", new Data("m3").requiresPublic("java.sql")
.requiresPublic("m2")
.requires("java.logging")
.requiresPublic("m1")
.reference("p3", "java.lang", "java.base")
.reference("p3", "java.sql", "java.sql")
.reference("p3", "java.util.logging", "java.logging")
.reference("p3", "p1", "m1")
.reference("p3", "p2", "m2")
.qualified("p3", "p2.internal", "m2")
{ "m3", new ModuleMetaData("m3").requiresPublic("java.sql")
.requiresPublic("m2")
.requires("java.logging")
.requiresPublic("m1")
.reference("p3", "java.lang", "java.base")
.reference("p3", "java.sql", "java.sql")
.reference("p3", "java.util.logging", "java.logging")
.reference("p3", "p1", "m1")
.reference("p3", "p2", "m2")
.qualified("p3", "p2.internal", "m2")
},
{ "m2", new Data("m2").requiresPublic("m1")
.reference("p2", "java.lang", "java.base")
.reference("p2", "p1", "m1")
.reference("p2.internal", "java.lang", "java.base")
.reference("p2.internal", "java.io", "java.base")
{ "m2", new ModuleMetaData("m2").requiresPublic("m1")
.reference("p2", "java.lang", "java.base")
.reference("p2", "p1", "m1")
.reference("p2.internal", "java.lang", "java.base")
.reference("p2.internal", "java.io", "java.base")
},
{ "m1", new Data("m1").requires("unsupported")
.reference("p1", "java.lang", "java.base")
.reference("p1.internal", "java.lang", "java.base")
.reference("p1.internal", "p1", "m1")
.reference("p1.internal", "q", "unsupported")
{ "m1", new ModuleMetaData("m1").requires("unsupported")
.reference("p1", "java.lang", "java.base")
.reference("p1.internal", "java.lang", "java.base")
.reference("p1.internal", "p1", "m1")
.reference("p1.internal", "q", "unsupported")
},
{ "unsupported", new Data("unsupported")
.reference("q", "java.lang", "java.base")
.jdkInternal("q", "jdk.internal.perf", "(java.base)")
{ "unsupported", new ModuleMetaData("unsupported")
.reference("q", "java.lang", "java.base")
.jdkInternal("q", "jdk.internal.perf", "java.base")
},
};
}
@Test(dataProvider = "modules")
public void modularTest(String name, Data data) {
// print only the specified module
String excludes = Arrays.stream(modules)
.filter(mn -> !mn.endsWith(name))
.collect(Collectors.joining(","));
String[] result = jdeps("-exclude-modules", excludes,
"-mp", MODS_DIR.toString(),
"-m", name);
assertTrue(data.check(result));
public void modularTest(String name, ModuleMetaData data) throws IOException {
// jdeps -modulepath mods -m <name>
runTest(data, MODS_DIR.toString(), Set.of(name));
// jdeps -modulepath libs/m1.jar:.... -m <name>
String mp = Arrays.stream(modules)
.filter(mn -> !mn.equals(name))
.map(mn -> MODS_DIR.resolve(mn).toString())
.collect(Collectors.joining(File.pathSeparator));
runTest(data, mp, Collections.emptySet(), MODS_DIR.resolve(name));
}
@DataProvider(name = "unnamed")
public Object[][] unnamed() {
return new Object[][]{
{ "m3", new Data("m3", false)
{ "unnamed", new ModuleMetaData("unnamed", false)
.depends("java.sql")
.depends("java.logging")
.depends("m1")
@ -133,178 +138,43 @@ public class ModuleTest {
.reference("p3", "p2", "m2")
.internal("p3", "p2.internal", "m2")
},
{ "unsupported", new Data("unsupported", false)
.reference("q", "java.lang", "java.base")
.jdkInternal("q", "jdk.internal.perf", "(java.base)")
},
};
}
@Test(dataProvider = "unnamed")
public void unnamedTest(String name, Data data) {
String[] result = jdeps("-mp", MODS_DIR.toString(), MODS_DIR.resolve(name).toString());
assertTrue(data.check(result));
public void unnamedTest(String name, ModuleMetaData data) throws IOException {
runTest(data, MODS_DIR.toString(), Set.of("m1", "m2"), UNNAMED_DIR);
}
/*
* Runs jdeps with the given arguments
*/
public static String[] jdeps(String... args) {
String lineSep = System.getProperty("line.separator");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
System.err.println("jdeps " + Arrays.toString(args));
int rc = com.sun.tools.jdeps.Main.run(args, pw);
pw.close();
String out = sw.toString();
if (!out.isEmpty())
System.err.println(out);
if (rc != 0)
throw new Error("jdeps failed: rc=" + rc);
return out.split(lineSep);
}
private void runTest(ModuleMetaData data, String modulepath,
Set<String> roots, Path... paths)
throws IOException
{
// jdeps -modulepath <modulepath> -m root paths
static class Data {
static final String INTERNAL = "(internal)";
static final String QUALIFIED = "(qualified)";
static final String JDK_INTERNAL = "JDK internal API";
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -modulepath %s -addmods %s %s%n", MODS_DIR,
roots.stream().collect(Collectors.joining(",")), paths)
);
jdeps.appModulePath(modulepath)
.addmods(roots);
Arrays.stream(paths).forEach(jdeps::addRoot);
final String moduleName;
final boolean isNamed;
final Map<String, ModuleRequires> requires = new LinkedHashMap<>();
final Map<String, Dependence> references = new LinkedHashMap<>();
Data(String name) {
this(name, true);
}
Data(String name, boolean isNamed) {
this.moduleName = name;
this.isNamed = isNamed;
requires("java.base"); // implicit requires
}
// run the analyzer
DepsAnalyzer analyzer = jdeps.getDepsAnalyzer();
assertTrue(analyzer.run());
Data requires(String name) {
requires.put(name, new ModuleRequires(name));
return this;
}
Data requiresPublic(String name) {
requires.put(name, new ModuleRequires(name, PUBLIC));
return this;
}
// for unnamed module
Data depends(String name) {
requires.put(name, new ModuleRequires(name));
return this;
}
Data reference(String origin, String target, String module) {
return dependence(origin, target, module, "");
}
Data internal(String origin, String target, String module) {
return dependence(origin, target, module, INTERNAL);
}
Data qualified(String origin, String target, String module) {
return dependence(origin, target, module, QUALIFIED);
}
Data jdkInternal(String origin, String target, String module) {
return dependence(origin, target, module, JDK_INTERNAL);
}
private Data dependence(String origin, String target, String module, String access) {
references.put(key(origin, target), new Dependence(origin, target, module, access));
return this;
}
// analyze result
Graph<DepsAnalyzer.Node> g1 = analyzer.moduleGraph();
g1.nodes().stream()
.filter(u -> u.name.equals(data.moduleName))
.forEach(u -> data.checkRequires(u.name, g1.adjacentNodes(u)));
String key(String origin, String target) {
return origin+":"+target;
}
boolean check(String[] lines) {
System.out.format("verifying module %s%s%n", moduleName, isNamed ? "" : " (unnamed module)");
for (String l : lines) {
String[] tokens = l.trim().split("\\s+");
System.out.println(" " + Arrays.stream(tokens).collect(Collectors.joining(" ")));
switch (tokens[0]) {
case "module":
assertEquals(tokens.length, 2);
assertEquals(moduleName, tokens[1]);
break;
case "requires":
String name = tokens.length == 2 ? tokens[1] : tokens[2];
Modifier modifier = null;
if (tokens.length == 3) {
assertEquals("public", tokens[1]);
modifier = PUBLIC;
}
checkRequires(name, modifier);
break;
default:
if (tokens.length == 3) {
// unnamed module requires
assertFalse(isNamed);
assertEquals(moduleName, tokens[0]);
String mn = tokens[2];
checkRequires(mn, null);
} else {
checkDependence(tokens);
}
}
}
return true;
}
Graph<DepsAnalyzer.Node> g2 = analyzer.dependenceGraph();
g2.nodes().stream()
.filter(u -> u.name.equals(data.moduleName))
.forEach(u -> data.checkDependences(u.name, g2.adjacentNodes(u)));
private void checkRequires(String name, Modifier modifier) {
assertTrue(requires.containsKey(name));
ModuleRequires req = requires.get(name);
assertEquals(req.mod, modifier);
}
private void checkDependence(String[] tokens) {
assertTrue(tokens.length >= 4);
String origin = tokens[0];
String target = tokens[2];
String module = tokens[3];
String key = key(origin, target);
assertTrue(references.containsKey(key));
Dependence dep = references.get(key);
if (tokens.length == 4) {
assertEquals(dep.access, "");
} else if (tokens.length == 5) {
assertEquals(dep.access, tokens[4]);
} else {
// JDK internal API
module = tokens[6];
assertEquals(tokens.length, 7);
assertEquals(tokens[3], "JDK");
assertEquals(tokens[4], "internal");
assertEquals(tokens[5], "API");
}
assertEquals(dep.module, module);
}
public static class ModuleRequires {
final String name;
final ModuleDescriptor.Requires.Modifier mod;
ModuleRequires(String name) {
this.name = name;
this.mod = null;
}
ModuleRequires(String name, ModuleDescriptor.Requires.Modifier mod) {
this.name = name;
this.mod = mod;
}
}
public static class Dependence {
final String origin;
final String target;
final String module;
final String access;
Dependence(String origin, String target, String module, String access) {
this.origin = origin;
this.target = target;
this.module = module;
this.access = access;
}
}
jdeps.dumpOutput(System.err);
}
}

@ -0,0 +1,105 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Tests split packages
* @library ../lib
* @build CompilerUtils
* @modules jdk.jdeps/com.sun.tools.jdeps
* @run testng SplitPackage
*/
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.sun.tools.jdeps.DepsAnalyzer;
import com.sun.tools.jdeps.JdepsConfiguration;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
public class SplitPackage {
private static final String TEST_SRC = System.getProperty("test.src");
private static final Path CLASSES_DIR = Paths.get("classes");
private static final String SPLIT_PKG_NAME = "javax.annotation";
private static final String JAVA_ANNOTATIONS_COMMON = "java.annotations.common";
/**
* Compiles classes used by the test
*/
@BeforeTest
public void compileAll() throws Exception {
CompilerUtils.cleanDir(CLASSES_DIR);
assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "patches"), CLASSES_DIR));
}
@Test
public void runTest() throws Exception {
// Test jdeps classes
runTest(null);
// Test jdeps -addmods
runTest(JAVA_ANNOTATIONS_COMMON, SPLIT_PKG_NAME);
}
private void runTest(String root, String... splitPackages) throws Exception {
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -verbose:class -addmods %s %s%n",
root, CLASSES_DIR)
);
jdeps.verbose("-verbose:class")
.addRoot(CLASSES_DIR);
if (root != null)
jdeps.addmods(Set.of(root));
JdepsConfiguration config = jdeps.configuration();
Map<String, Set<String>> pkgs = config.splitPackages();
final Set<String> expected;
if (splitPackages != null) {
expected = Arrays.stream(splitPackages).collect(Collectors.toSet());
} else {
expected = Collections.emptySet();
}
if (!pkgs.keySet().equals(expected)) {
throw new RuntimeException(splitPackages.toString());
}
// java.annotations.common is not observable
DepsAnalyzer analyzer = jdeps.getDepsAnalyzer();
assertTrue(analyzer.run());
jdeps.dumpOutput(System.err);
}
}

@ -0,0 +1,325 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Tests jdeps -m and -mp options on named modules and unnamed modules
* @library ../lib
* @build CompilerUtils JdepsUtil
* @modules jdk.jdeps/com.sun.tools.jdeps
* @run testng TransitiveDeps
*/
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.sun.tools.jdeps.DepsAnalyzer;
import com.sun.tools.jdeps.Graph;
import org.testng.annotations.DataProvider;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
public class TransitiveDeps {
private static final String TEST_SRC = System.getProperty("test.src");
private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
private static final Path MODS_DIR = Paths.get("mods");
private static final Path LIBS_DIR = Paths.get("libs");
// the names of the modules in this test
private static String[] modules = new String[] {"unsafe", "m6", "m7"};
/**
* Compiles all modules used by the test
*/
@BeforeTest
public void compileAll() throws Exception {
CompilerUtils.cleanDir(MODS_DIR);
CompilerUtils.cleanDir(LIBS_DIR);
for (String mn : modules) {
// compile a module
assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, mn));
// create JAR files with no module-info.class
Path root = MODS_DIR.resolve(mn);
JdepsUtil.createJar(LIBS_DIR.resolve(mn + ".jar"), root,
Files.walk(root, Integer.MAX_VALUE)
.filter(f -> {
String fn = f.getFileName().toString();
return fn.endsWith(".class") && !fn.equals("module-info.class");
}));
}
}
@DataProvider(name = "modules")
public Object[][] expected1() {
return new Object[][]{
{ "m7",
List.of(new ModuleMetaData("m7")
.requires("m6")
.requires("unsafe")
.reference("p7.Main", "java.lang.Object", "java.base")
.reference("p7.Main", "java.lang.String", "java.base")
.reference("p7.Main", "org.safe.Lib", "unsafe")
.reference("p7.Main", "p6.safe.Lib", "m6"),
new ModuleMetaData("m6")
.requires("unsafe")
.reference("p6.indirect.UnsafeRef", "java.lang.Object", "java.base")
.reference("p6.indirect.UnsafeRef", "org.unsafe.UseUnsafe ", "unsafe")
.reference("p6.safe.Lib", "java.io.PrintStream", "java.base")
.reference("p6.safe.Lib", "java.lang.Class", "java.base")
.reference("p6.safe.Lib", "java.lang.Object", "java.base")
.reference("p6.safe.Lib", "java.lang.String", "java.base")
.reference("p6.safe.Lib", "java.lang.System", "java.base")
.reference("p6.safe.Lib", "org.safe.Lib", "unsafe"),
new ModuleMetaData("unsafe")
.requires("jdk.unsupported")
.reference("org.indirect.UnsafeRef", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.io.PrintStream", "java.base")
.reference("org.safe.Lib", "java.lang.Class", "java.base")
.reference("org.safe.Lib", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.lang.String", "java.base")
.reference("org.safe.Lib", "java.lang.System", "java.base")
.reference("org.unsafe.UseUnsafe", "java.lang.Object", "java.base")
.jdkInternal("org.unsafe.UseUnsafe", "sun.misc.Unsafe", "java.base")
)
},
};
}
@Test(dataProvider = "modules")
public void testModulePath(String name, List<ModuleMetaData> data) throws IOException {
Set<String> roots = Set.of("m6", "unsafe");
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -modulepath %s -addmods %s -m %s%n", MODS_DIR,
roots.stream().collect(Collectors.joining(",")), name)
);
jdeps.verbose("-verbose:class")
.appModulePath(MODS_DIR.toString())
.addmods(roots)
.addmods(Set.of(name));
runJdeps(jdeps, data);
// run automatic modules
roots = Set.of("ALL-MODULE-PATH", "jdk.unsupported");
jdeps = JdepsUtil.newCommand(
String.format("jdeps -modulepath %s -addmods %s -m %s%n", LIBS_DIR,
roots.stream().collect(Collectors.joining(",")), name)
);
jdeps.verbose("-verbose:class")
.appModulePath(LIBS_DIR.toString())
.addmods(roots)
.addmods(Set.of(name));
runJdeps(jdeps, data);
}
@DataProvider(name = "jars")
public Object[][] expected2() {
return new Object[][]{
{ "m7", List.of(new ModuleMetaData("m7.jar")
.requires("m6.jar")
.requires("unsafe.jar")
.reference("p7.Main", "java.lang.Object", "java.base")
.reference("p7.Main", "java.lang.String", "java.base")
.reference("p7.Main", "org.safe.Lib", "unsafe.jar")
.reference("p7.Main", "p6.safe.Lib", "m6.jar"))
},
};
}
@Test(dataProvider = "jars")
public void testClassPath(String name, List<ModuleMetaData> data) throws IOException {
String cpath = Arrays.stream(modules)
.filter(mn -> !mn.equals(name))
.map(mn -> LIBS_DIR.resolve(mn + ".jar").toString())
.collect(Collectors.joining(File.pathSeparator));
Path jarfile = LIBS_DIR.resolve(name + ".jar");
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -classpath %s %s%n", cpath, jarfile)
);
jdeps.verbose("-verbose:class")
.addClassPath(cpath)
.addRoot(jarfile);
runJdeps(jdeps, data);
}
@DataProvider(name = "compileTimeView")
public Object[][] expected3() {
return new Object[][] {
{"m7",
List.of(new ModuleMetaData("m7.jar")
.requires("m6.jar")
.requires("unsafe.jar")
.reference("p7.Main", "java.lang.Object", "java.base")
.reference("p7.Main", "java.lang.String", "java.base")
.reference("p7.Main", "org.safe.Lib", "unsafe.jar")
.reference("p7.Main", "p6.safe.Lib", "m6.jar"),
new ModuleMetaData("m6.jar")
.requires("unsafe.jar")
.reference("p6.indirect.UnsafeRef", "java.lang.Object", "java.base")
.reference("p6.indirect.UnsafeRef", "org.unsafe.UseUnsafe ", "unsafe.jar")
.reference("p6.safe.Lib", "java.io.PrintStream", "java.base")
.reference("p6.safe.Lib", "java.lang.Class", "java.base")
.reference("p6.safe.Lib", "java.lang.Object", "java.base")
.reference("p6.safe.Lib", "java.lang.String", "java.base")
.reference("p6.safe.Lib", "java.lang.System", "java.base")
.reference("p6.safe.Lib", "org.safe.Lib", "unsafe.jar"),
new ModuleMetaData("unsafe.jar")
.requires("jdk.unsupported")
.reference("org.indirect.UnsafeRef", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.io.PrintStream", "java.base")
.reference("org.safe.Lib", "java.lang.Class", "java.base")
.reference("org.safe.Lib", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.lang.String", "java.base")
.reference("org.safe.Lib", "java.lang.System", "java.base")
.reference("org.unsafe.UseUnsafe", "java.lang.Object", "java.base")
.jdkInternal("org.unsafe.UseUnsafe", "sun.misc.Unsafe", "java.base")
)
},
};
}
@Test(dataProvider = "compileTimeView")
public void compileTimeView(String name, List<ModuleMetaData> data) throws IOException {
String cpath = Arrays.stream(modules)
.filter(mn -> !mn.equals(name))
.map(mn -> LIBS_DIR.resolve(mn + ".jar").toString())
.collect(Collectors.joining(File.pathSeparator));
Path jarfile = LIBS_DIR.resolve(name + ".jar");
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -ct -classpath %s %s%n", cpath, jarfile)
);
jdeps.verbose("-verbose:class")
.addClassPath(cpath)
.addRoot(jarfile);
runJdeps(jdeps, data, true, 0 /* -recursive */);
}
@DataProvider(name = "recursiveDeps")
public Object[][] expected4() {
return new Object[][] {
{"m7",
List.of(new ModuleMetaData("m7.jar")
.requires("m6.jar")
.requires("unsafe.jar")
.reference("p7.Main", "java.lang.Object", "java.base")
.reference("p7.Main", "java.lang.String", "java.base")
.reference("p7.Main", "org.safe.Lib", "unsafe.jar")
.reference("p7.Main", "p6.safe.Lib", "m6.jar"),
new ModuleMetaData("m6.jar")
.requires("unsafe.jar")
.reference("p6.safe.Lib", "java.io.PrintStream", "java.base")
.reference("p6.safe.Lib", "java.lang.Class", "java.base")
.reference("p6.safe.Lib", "java.lang.Object", "java.base")
.reference("p6.safe.Lib", "java.lang.String", "java.base")
.reference("p6.safe.Lib", "java.lang.System", "java.base")
.reference("p6.safe.Lib", "org.safe.Lib", "unsafe.jar"),
new ModuleMetaData("unsafe.jar")
.requires("jdk.unsupported")
.reference("org.indirect.UnsafeRef", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.io.PrintStream", "java.base")
.reference("org.safe.Lib", "java.lang.Class", "java.base")
.reference("org.safe.Lib", "java.lang.Object", "java.base")
.reference("org.safe.Lib", "java.lang.String", "java.base")
.reference("org.safe.Lib", "java.lang.System", "java.base")
)
},
};
}
@Test(dataProvider = "recursiveDeps")
public void recursiveDeps(String name, List<ModuleMetaData> data) throws IOException {
String cpath = Arrays.stream(modules)
.filter(mn -> !mn.equals(name))
.map(mn -> LIBS_DIR.resolve(mn + ".jar").toString())
.collect(Collectors.joining(File.pathSeparator));
Path jarfile = LIBS_DIR.resolve(name + ".jar");
JdepsUtil.Command jdeps = JdepsUtil.newCommand(
String.format("jdeps -R -classpath %s %s%n", cpath, jarfile)
);
jdeps.verbose("-verbose:class").filter("-filter:archive")
.addClassPath(cpath)
.addRoot(jarfile);
runJdeps(jdeps, data, true, 0 /* -recursive */);
}
private void runJdeps(JdepsUtil.Command jdeps, List<ModuleMetaData> data)
throws IOException
{
runJdeps(jdeps, data, false, 1 /* depth */);
}
private void runJdeps(JdepsUtil.Command jdeps, List<ModuleMetaData> data,
boolean compileTimeView, int depth)
throws IOException
{
// run the analyzer
DepsAnalyzer analyzer = jdeps.getDepsAnalyzer();
assertTrue(analyzer.run(compileTimeView, depth));
jdeps.dumpOutput(System.err);
// analyze result
Graph<DepsAnalyzer.Node> g1 = analyzer.moduleGraph();
Map<String, ModuleMetaData> dataMap = data.stream()
.collect(Collectors.toMap(ModuleMetaData::name, Function.identity()));
// the returned graph contains all nodes such as java.base and jdk.unsupported
g1.nodes().stream()
.filter(u -> dataMap.containsKey(u.name))
.forEach(u -> {
ModuleMetaData md = dataMap.get(u.name);
md.checkRequires(u.name, g1.adjacentNodes(u));
});
Graph<DepsAnalyzer.Node> g2 = analyzer.dependenceGraph();
g2.nodes().stream()
.filter(u -> dataMap.containsKey(u.name))
.forEach(u -> {
ModuleMetaData md = dataMap.get(u.name);
md.checkDependences(u.name, g2.adjacentNodes(u));
});
}
}

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Documented
@Target({FIELD, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NonNull {
}

@ -22,8 +22,14 @@
*/
module m4 {
// not used in signature
requires public java.compiler;
// unused dependence
requires java.logging;
exports p4;
exports p4.internal to m1,m2,m3;
// unuused qualified exports
exports p4.internal to m6,m7;
}

@ -23,9 +23,14 @@
package p4.internal;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class Impl {
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
public String name() {
return Impl.class.getName();
}
}

@ -0,0 +1,39 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module m5 {
// m4 requires public java.compilerr
requires public m4;
requires public java.compiler;
// java.sql should be requires public
requires java.sql;
// java.logging is used for implementation only
requires public java.logging;
exports p5;
// m8 is not in the resolved graph but used by m8
exports p5.internal to m8;
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p5;
import java.sql.Driver;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class Main {
public void run(Driver driver) throws Exception {
driver.getParentLogger().config("test");
}
public p4.Lib getLib() {
return new p4.Lib();
}
public JavaCompiler getCompiler() {
return ToolProvider.getSystemJavaCompiler();
}
}

@ -0,0 +1,27 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p5.internal;
public class T {
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module m6 {
requires unsafe;
// no dependency on sun.misc.Unsafe directly or indirectly
exports p6.safe;
// direct dependency on org.unsafe
// hence indirect dependency on sun.misc.Unsafe
exports p6.indirect;
}

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p6.indirect;
// indirectly depend on sun.misc.Unsafe
public class UnsafeRef {
public static org.unsafe.UseUnsafe get() {
return new org.unsafe.UseUnsafe();
}
}

@ -0,0 +1,32 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p6.safe;
// no direct or indirect dependency on sun.misc.Unsafe
public class Lib {
public static void doit() {
System.out.println(Lib.class.getName());
org.safe.Lib.doit();
}
}

@ -0,0 +1,29 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module m7 {
// only use classes that have no direct or indirect dependency
// to sun.misc.Unsafe
requires unsafe;
requires m6;
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p7;
// Only use classes in unsafe and m6 modules with no
// direct or indirect dependency on sun.misc.Unsafe
public class Main {
public static void main(String... args) {
p6.safe.Lib.doit();
org.safe.Lib.doit();
}
}

@ -0,0 +1,28 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module m8 {
requires m5;
// use p5.internal
}

@ -0,0 +1,32 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p8;
import p5.internal.T;
public class Main {
public static void main() {
T t = new T();
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module unsafe {
requires jdk.unsupported;
// direct dependency on sun.misc.Unsafe
exports org.unsafe;
// no dependency on sun.misc.Unsafe directly or indirectly
exports org.safe;
// indirect dependency on sun.misc.Unsafe
exports org.indirect;
}

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.indirect;
// indirectly depend on sun.misc.Unsafe
public class UnsafeRef {
public static org.unsafe.UseUnsafe get() {
return new org.unsafe.UseUnsafe();
}
}

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.safe;
// no direct or indirect dependency on sun.misc.Unsafe
public class Lib {
public static void doit() {
System.out.println(Lib.class.getName());
}
}

@ -0,0 +1,30 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.unsafe;
import sun.misc.Unsafe;
public class UseUnsafe {
static Unsafe unsafe = Unsafe.getUnsafe();
}

@ -62,7 +62,7 @@ public class JDKUnsupportedTest {
public void test(String filename, String[][] expected) {
Path path = Paths.get(TEST_CLASSES, filename);
Map<String, String> result = jdeps("-M", path.toString());
Map<String, String> result = jdeps(path.toString());
for (String[] e : expected) {
String pn = e[0];
String module = e[1];