8156680: jdeps implementation refresh
Reviewed-by: dfuchs
This commit is contained in:
parent
d027cbbffb
commit
bbc75367c7
langtools
src/jdk.jdeps/share/classes/com/sun/tools/jdeps
Analyzer.javaArchive.javaClassFileReader.javaDependencyFinder.javaDepsAnalyzer.javaGraph.javaJdepsConfiguration.javaJdepsFilter.javaJdepsTask.javaJdepsWriter.javaModule.javaModuleAnalyzer.javaModuleInfoBuilder.javaModulePaths.javaProfile.java
resources
test/tools/jdeps
APIDeps.javaBasic.javaDotFileTest.java
lib
m
modules
CheckModuleTest.javaGenModuleInfo.javaModuleMetaData.javaModuleTest.javaSplitPackage.javaTransitiveDeps.java
patches/javax/annotation
src
m4
m5
m6
m7
m8
unsafe
unsupported
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
234
langtools/test/tools/jdeps/lib/JdepsUtil.java
Normal file
234
langtools/test/tools/jdeps/lib/JdepsUtil.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
162
langtools/test/tools/jdeps/modules/CheckModuleTest.java
Normal file
162
langtools/test/tools/jdeps/modules/CheckModuleTest.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
209
langtools/test/tools/jdeps/modules/ModuleMetaData.java
Normal file
209
langtools/test/tools/jdeps/modules/ModuleMetaData.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
105
langtools/test/tools/jdeps/modules/SplitPackage.java
Normal file
105
langtools/test/tools/jdeps/modules/SplitPackage.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
325
langtools/test/tools/jdeps/modules/TransitiveDeps.java
Normal file
325
langtools/test/tools/jdeps/modules/TransitiveDeps.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
39
langtools/test/tools/jdeps/modules/src/m5/module-info.java
Normal file
39
langtools/test/tools/jdeps/modules/src/m5/module-info.java
Normal file
@ -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;
|
||||
}
|
44
langtools/test/tools/jdeps/modules/src/m5/p5/Main.java
Normal file
44
langtools/test/tools/jdeps/modules/src/m5/p5/Main.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
27
langtools/test/tools/jdeps/modules/src/m5/p5/internal/T.java
Normal file
27
langtools/test/tools/jdeps/modules/src/m5/p5/internal/T.java
Normal file
@ -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 {
|
||||
}
|
33
langtools/test/tools/jdeps/modules/src/m6/module-info.java
Normal file
33
langtools/test/tools/jdeps/modules/src/m6/module-info.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
32
langtools/test/tools/jdeps/modules/src/m6/p6/safe/Lib.java
Normal file
32
langtools/test/tools/jdeps/modules/src/m6/p6/safe/Lib.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
29
langtools/test/tools/jdeps/modules/src/m7/module-info.java
Normal file
29
langtools/test/tools/jdeps/modules/src/m7/module-info.java
Normal file
@ -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;
|
||||
}
|
33
langtools/test/tools/jdeps/modules/src/m7/p7/Main.java
Normal file
33
langtools/test/tools/jdeps/modules/src/m7/p7/Main.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
28
langtools/test/tools/jdeps/modules/src/m8/module-info.java
Normal file
28
langtools/test/tools/jdeps/modules/src/m8/module-info.java
Normal file
@ -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
|
||||
}
|
32
langtools/test/tools/jdeps/modules/src/m8/p8/Main.java
Normal file
32
langtools/test/tools/jdeps/modules/src/m8/p8/Main.java
Normal file
@ -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];
|
||||
|
Loading…
x
Reference in New Issue
Block a user