--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java Tue Mar 15 13:48:30 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java Thu Mar 17 19:04:28 2016 +0000
@@ -25,35 +25,94 @@
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.
*/
-final class Module extends Archive {
- private final String moduleName;
- private final Map<String, Boolean> requires;
- private final Map<String, Set<String>> exports;
- private final Set<String> packages;
+class Module extends Archive {
+ static final boolean traceOn = Boolean.getBoolean("jdeps.debug");
+ static void trace(String fmt, Object... args) {
+ if (traceOn) {
+ System.err.format(fmt, args);
+ }
+ }
- private Module(ClassFileReader reader, String name,
+ /*
+ * 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);
+ };
+
+ 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;
+
+ private Module(String name,
+ URI location,
+ ModuleDescriptor descriptor,
Map<String, Boolean> requires,
Map<String, Set<String>> exports,
- Set<String> packages) {
- super(name, reader);
- this.moduleName = name;
+ Set<String> packages,
+ boolean isJDK,
+ 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;
+ }
+
+ /**
+ * Returns module name
+ */
+ public String name() {
+ return descriptor.name();
+ }
+
+ public boolean isNamed() {
+ return true;
}
- public String name() {
- return moduleName;
+ public boolean isAutomatic() {
+ return descriptor.isAutomatic();
+ }
+
+ public Module getModule() {
+ return this;
+ }
+
+ public ModuleDescriptor descriptor() {
+ return descriptor;
+ }
+
+ public boolean isJDK() {
+ return isJDK;
}
public Map<String, Boolean> requires() {
@@ -64,89 +123,46 @@
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;
}
/**
- * Tests if this module can read m
- */
- public boolean canRead(Module m) {
- // ## TODO: handle "re-exported=true"
- // all JDK modules require all modules containing its direct dependences
- // should not be an issue
- return requires.containsKey(m.name());
- }
-
- /**
- * Tests if a given fully-qualified name is an exported type.
+ * Tests if the package of the given name is exported.
*/
- public boolean isExported(String cn) {
- int i = cn.lastIndexOf('.');
- String pn = i > 0 ? cn.substring(0, i) : "";
-
- return isExportedPackage(pn);
- }
-
- /**
- * Tests if a given package name is exported.
- */
- public boolean isExportedPackage(String pn) {
+ public boolean isExported(String pn) {
return exports.containsKey(pn) ? exports.get(pn).isEmpty() : false;
}
/**
- * Tests if the given classname is accessible to module m
+ * Converts this module to a strict module with the given dependences
+ *
+ * @throws IllegalArgumentException if this module is not an automatic module
*/
- public boolean isAccessibleTo(String classname, Module m) {
- int i = classname.lastIndexOf('.');
- String pn = i > 0 ? classname.substring(0, i) : "";
- if (!packages.contains(pn)) {
- throw new IllegalArgumentException(classname + " is not a member of module " + name());
+ public Module toStrictModule(Map<String, Boolean> requires) {
+ if (!isAutomatic()) {
+ throw new IllegalArgumentException(name() + " already a strict module");
}
-
- if (m != null && !m.canRead(this)) {
- trace("%s not readable by %s%n", this.name(), m.name());
- return false;
- }
-
- // exported API
- Set<String> ms = exports().get(pn);
- String mname = m != null ? m.name() : "unnamed";
- if (ms == null) {
- trace("%s not exported in %s%n", classname, this.name());
- } else if (!(ms.isEmpty() || ms.contains(mname))) {
- trace("%s not permit to %s %s%n", classname, mname, ms);
- }
- return ms != null && (ms.isEmpty() || ms.contains(mname));
+ return new StrictModule(this, requires);
}
- private static final boolean traceOn = Boolean.getBoolean("jdeps.debug");
- private void trace(String fmt, Object... args) {
- if (traceOn) {
- System.err.format(fmt, args);
- }
+ /**
+ * Tests if the package of the given name is qualifiedly exported
+ * to the target.
+ */
+ public boolean isExported(String pn, String target) {
+ return isExported(pn) || exports.containsKey(pn) && exports.get(pn).contains(target);
}
- @Override
- public boolean equals(Object ob) {
- if (!(ob instanceof Module))
- return false;
- Module that = (Module)ob;
- return (moduleName.equals(that.moduleName)
- && requires.equals(that.requires)
- && exports.equals(that.exports)
- && packages.equals(that.packages));
- }
+ private final static String JDK_UNSUPPORTED = "jdk.unsupported";
- @Override
- public int hashCode() {
- int hc = moduleName.hashCode();
- hc = hc * 43 + requires.hashCode();
- hc = hc * 43 + exports.hashCode();
- hc = hc * 43 + packages.hashCode();
- return hc;
- }
+ // temporary until jdk.unsupported module
+ private final static List<String> unsupported = Arrays.asList("sun.misc", "sun.reflect");
@Override
public String toString() {
@@ -154,22 +170,35 @@
}
public final static class Builder {
- String name;
- ClassFileReader reader;
+ 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;
+ ClassFileReader reader;
+ ModuleDescriptor descriptor;
+ URI location;
- public Builder() {
+ public Builder(String name) {
+ this(name, false);
}
- public Builder name(String n) {
- name = n;
+ public Builder(String name, boolean isJDK) {
+ this.name = name;
+ this.isJDK = isJDK;
+ }
+
+ public Builder location(URI location) {
+ this.location = location;
+ return this;
+ }
+
+ public Builder descriptor(ModuleDescriptor md) {
+ this.descriptor = md;
return this;
}
public Builder require(String d, boolean reexport) {
- // System.err.format("%s depend %s reexports %s%n", name, d, reexport);
requires.put(d, reexport);
return this;
}
@@ -185,14 +214,172 @@
exports.put(p, new HashSet<>(ms));
return this;
}
- public Builder classes(ClassFileReader.ModuleClassReader reader) {
+ public Builder classes(ClassFileReader reader) {
this.reader = reader;
return this;
}
public Module build() {
- Module m = new Module(reader, name, requires, exports, packages);
- return m;
+ if (descriptor.isAutomatic() && isJDK) {
+ throw new InternalError("JDK module: " + name + " can't be automatic module");
+ }
+
+ return new Module(name, location, descriptor, requires, exports, packages, isJDK, 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);
+ }
+
+ @Override
+ public String name() {
+ return "unnamed";
+ }
+
+ @Override
+ public boolean isNamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isAutomatic() {
+ return false;
+ }
+
+ @Override
+ public boolean isExported(String pn) {
+ return true;
+ }
+ }
+
+ 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;
+
+ /**
+ * Converts the given automatic module to a strict module.
+ *
+ * Replace this module's dependences with the given requires and also
+ * 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);
+ }
+
+ @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;
}
}
}