make/src/classes/build/tools/module/GenJdepsModulesXml.java

Print this page

        

*** 23,55 **** * questions. */ package build.tools.module; - import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; ! import java.io.InputStream; ! import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; - import java.util.Collections; - import java.util.Comparator; - import java.util.HashMap; import java.util.HashSet; - import java.util.Map; - import java.util.Objects; import java.util.Set; ! import java.util.jar.JarEntry; ! import java.util.jar.JarFile; ! import javax.xml.namespace.QName; ! import javax.xml.stream.*; ! import javax.xml.stream.events.Attribute; ! import javax.xml.stream.events.XMLEvent; /** * GenJdepsModulesXml augments the input modules.xml file(s) * to include the module membership from the given path to * the JDK exploded image. The output file is used by jdeps --- 23,43 ---- * questions. */ package build.tools.module; import java.io.File; import java.io.IOException; ! import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashSet; import java.util.Set; ! import java.util.stream.Collectors; /** * GenJdepsModulesXml augments the input modules.xml file(s) * to include the module membership from the given path to * the JDK exploded image. The output file is used by jdeps
*** 95,485 **** GenJdepsModulesXml gentool = new GenJdepsModulesXml(modulepath); Set<Module> modules = new HashSet<>(); for (; i < args.length; i++) { Path p = Paths.get(args[i]); ! try (InputStream in = new BufferedInputStream(Files.newInputStream(p))) { ! Set<Module> mods = gentool.load(in); ! modules.addAll(mods); ! } } Files.createDirectories(outfile.getParent()); ! gentool.writeXML(modules, outfile); } final Path modulepath; public GenJdepsModulesXml(Path modulepath) { this.modulepath = modulepath; } ! private static final String MODULES = "modules"; ! private static final String MODULE = "module"; ! private static final String NAME = "name"; ! private static final String DEPEND = "depend"; ! private static final String EXPORT = "export"; ! private static final String TO = "to"; ! private static final String INCLUDE = "include"; ! private static final QName REEXPORTS = new QName("re-exports"); ! private Set<Module> load(InputStream in) throws XMLStreamException, IOException { ! Set<Module> modules = new HashSet<>(); ! XMLInputFactory factory = XMLInputFactory.newInstance(); ! XMLEventReader stream = factory.createXMLEventReader(in); ! Module.Builder mb = null; ! String modulename = null; ! String pkg = null; ! Set<String> permits = new HashSet<>(); ! while (stream.hasNext()) { ! XMLEvent event = stream.nextEvent(); ! if (event.isStartElement()) { ! String startTag = event.asStartElement().getName().getLocalPart(); ! switch (startTag) { ! case MODULES: ! break; ! case MODULE: ! if (mb != null) { ! throw new RuntimeException("end tag for module is missing"); ! } ! modulename = getNextTag(stream, NAME); ! mb = new Module.Builder(); ! mb.name(modulename); ! break; ! case NAME: ! throw new RuntimeException(event.toString()); ! case DEPEND: ! boolean reexports = false; ! Attribute attr = event.asStartElement().getAttributeByName(REEXPORTS); ! if (attr != null) { ! String value = attr.getValue(); ! if (value.equals("true") || value.equals("false")) { ! reexports = Boolean.parseBoolean(value); ! } else { ! throw new RuntimeException("unexpected attribute " + attr.toString()); ! } ! } ! mb.require(getData(stream), reexports); ! break; ! case INCLUDE: ! throw new RuntimeException("unexpected " + event); ! case EXPORT: ! pkg = getNextTag(stream, NAME); ! break; ! case TO: ! permits.add(getData(stream)); ! break; ! default: ! } ! } else if (event.isEndElement()) { ! String endTag = event.asEndElement().getName().getLocalPart(); ! switch (endTag) { ! case MODULE: ! buildIncludes(mb, modulename); ! modules.add(mb.build()); ! mb = null; ! break; ! case EXPORT: ! if (pkg == null) { ! throw new RuntimeException("export-to is malformed"); ! } ! mb.exportTo(pkg, permits); ! pkg = null; ! permits.clear(); ! break; ! default: ! } ! } else if (event.isCharacters()) { ! String s = event.asCharacters().getData(); ! if (!s.trim().isEmpty()) { ! throw new RuntimeException("export-to is malformed"); ! } ! } ! } ! return modules; ! } ! ! private String getData(XMLEventReader reader) throws XMLStreamException { ! XMLEvent e = reader.nextEvent(); ! if (e.isCharacters()) { ! return e.asCharacters().getData(); ! } ! throw new RuntimeException(e.toString()); ! } ! ! private String getNextTag(XMLEventReader reader, String tag) throws XMLStreamException { ! XMLEvent e = reader.nextTag(); ! if (e.isStartElement()) { ! String t = e.asStartElement().getName().getLocalPart(); ! if (!tag.equals(t)) { ! throw new RuntimeException(e + " expected: " + tag); ! } ! return getData(reader); ! } ! throw new RuntimeException("export-to name is missing:" + e); ! } ! private void writeXML(Set<Module> modules, Path path) ! throws IOException, XMLStreamException ! { ! XMLOutputFactory xof = XMLOutputFactory.newInstance(); ! try (OutputStream out = Files.newOutputStream(path)) { ! int depth = 0; ! XMLStreamWriter xtw = xof.createXMLStreamWriter(out, "UTF-8"); ! xtw.writeStartDocument("utf-8","1.0"); ! writeStartElement(xtw, MODULES, depth); ! modules.stream() ! .sorted(Comparator.comparing(Module::name)) ! .forEach(m -> writeModuleElement(xtw, m, depth+1)); ! writeEndElement(xtw, depth); ! xtw.writeCharacters("\n"); ! xtw.writeEndDocument(); ! xtw.flush(); ! xtw.close(); ! } ! } ! ! private void writeElement(XMLStreamWriter xtw, String element, String value, int depth) { ! try { ! writeStartElement(xtw, element, depth); ! xtw.writeCharacters(value); ! xtw.writeEndElement(); ! } catch (XMLStreamException e) { ! throw new RuntimeException(e); ! } ! } ! ! private void writeDependElement(XMLStreamWriter xtw, Module.Dependence d, int depth) { ! try { ! writeStartElement(xtw, DEPEND, depth); ! if (d.reexport) { ! xtw.writeAttribute("re-exports", "true"); ! } ! xtw.writeCharacters(d.name); ! xtw.writeEndElement(); ! } catch (XMLStreamException e) { ! throw new RuntimeException(e); ! } ! } ! ! private void writeExportElement(XMLStreamWriter xtw, String pkg, int depth) { ! writeExportElement(xtw, pkg, Collections.emptySet(), depth); ! } ! ! private void writeExportElement(XMLStreamWriter xtw, String pkg, ! Set<String> permits, int depth) { ! try { ! writeStartElement(xtw, EXPORT, depth); ! writeElement(xtw, NAME, pkg, depth+1); ! if (!permits.isEmpty()) { ! permits.stream().sorted() ! .forEach(m -> writeElement(xtw, TO, m, depth + 1)); ! } ! writeEndElement(xtw, depth); ! } catch (XMLStreamException e) { ! throw new RuntimeException(e); ! } ! } ! private void writeModuleElement(XMLStreamWriter xtw, Module m, int depth) { ! try { ! writeStartElement(xtw, MODULE, depth); ! writeElement(xtw, NAME, m.name(), depth+1); ! m.requires().stream().sorted(Comparator.comparing(d -> d.name)) ! .forEach(d -> writeDependElement(xtw, d, depth+1)); ! m.exports().keySet().stream() ! .filter(pn -> m.exports().get(pn).isEmpty()) ! .sorted() ! .forEach(pn -> writeExportElement(xtw, pn, depth+1)); ! m.exports().entrySet().stream() ! .filter(e -> !e.getValue().isEmpty()) ! .sorted(Map.Entry.comparingByKey()) ! .forEach(e -> writeExportElement(xtw, e.getKey(), e.getValue(), depth+1)); ! m.packages().stream().sorted() ! .forEach(p -> writeElement(xtw, INCLUDE, p, depth+1)); ! writeEndElement(xtw, depth); ! } catch (XMLStreamException e) { ! throw new RuntimeException(e); ! ! } ! } ! ! /** Two spaces; the default indentation. */ ! public static final String DEFAULT_INDENT = " "; ! ! /** stack[depth] indicates what's been written into the current scope. */ ! private static String[] stack = new String[] { "\n", ! "\n" + DEFAULT_INDENT, ! "\n" + DEFAULT_INDENT + DEFAULT_INDENT, ! "\n" + DEFAULT_INDENT + DEFAULT_INDENT + DEFAULT_INDENT}; ! ! private void writeStartElement(XMLStreamWriter xtw, String name, int depth) ! throws XMLStreamException ! { ! xtw.writeCharacters(stack[depth]); ! xtw.writeStartElement(name); ! } ! ! private void writeEndElement(XMLStreamWriter xtw, int depth) throws XMLStreamException { ! xtw.writeCharacters(stack[depth]); ! xtw.writeEndElement(); ! } ! ! private String packageName(Path p) { return packageName(p.toString().replace(File.separatorChar, '/')); } ! private String packageName(String name) { int i = name.lastIndexOf('/'); return (i > 0) ? name.substring(0, i).replace('/', '.') : ""; } ! private boolean includes(String name) { ! return name.endsWith(".class") && !name.equals("module-info.class"); } ! public void buildIncludes(Module.Builder mb, String modulename) throws IOException { ! Path mclasses = modulepath.resolve(modulename); try { Files.find(mclasses, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr) -> includes(p.getFileName().toString())) .map(p -> packageName(mclasses.relativize(p))) .forEach(mb::include); } catch (NoSuchFileException e) { // aggregate module may not have class } ! } ! ! static class Module { ! static class Dependence { ! final String name; ! final boolean reexport; ! Dependence(String name) { ! this(name, false); ! } ! Dependence(String name, boolean reexport) { ! this.name = name; ! this.reexport = reexport; ! } ! ! @Override ! public int hashCode() { ! int hash = 5; ! hash = 11 * hash + Objects.hashCode(this.name); ! hash = 11 * hash + (this.reexport ? 1 : 0); ! return hash; ! } ! ! public boolean equals(Object o) { ! Dependence d = (Dependence)o; ! return this.name.equals(d.name) && this.reexport == d.reexport; ! } ! } ! private final String moduleName; ! private final Set<Dependence> requires; ! private final Map<String, Set<String>> exports; ! private final Set<String> packages; ! ! private Module(String name, ! Set<Dependence> requires, ! Map<String, Set<String>> exports, ! Set<String> packages) { ! this.moduleName = name; ! this.requires = Collections.unmodifiableSet(requires); ! this.exports = Collections.unmodifiableMap(exports); ! this.packages = Collections.unmodifiableSet(packages); ! } ! ! public String name() { ! return moduleName; ! } ! ! public Set<Dependence> requires() { ! return requires; ! } ! ! public Map<String, Set<String>> exports() { ! return exports; ! } ! ! public Set<String> packages() { ! return packages; ! } ! ! @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)); ! } ! ! @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; ! } ! ! @Override ! public String toString() { ! StringBuilder sb = new StringBuilder(); ! sb.append("module ").append(moduleName).append(" {").append("\n"); ! requires.stream().sorted().forEach(d -> ! sb.append(String.format(" requires %s%s%n", d.reexport ? "public " : "", d.name))); ! exports.entrySet().stream().filter(e -> e.getValue().isEmpty()) ! .sorted(Map.Entry.comparingByKey()) ! .forEach(e -> sb.append(String.format(" exports %s%n", e.getKey()))); ! exports.entrySet().stream().filter(e -> !e.getValue().isEmpty()) ! .sorted(Map.Entry.comparingByKey()) ! .forEach(e -> sb.append(String.format(" exports %s to %s%n", e.getKey(), e.getValue()))); ! packages.stream().sorted().forEach(pn -> sb.append(String.format(" includes %s%n", pn))); ! sb.append("}"); ! return sb.toString(); ! } ! ! static class Builder { ! private String name; ! private final Set<Dependence> requires = new HashSet<>(); ! private final Map<String, Set<String>> exports = new HashMap<>(); ! private final Set<String> packages = new HashSet<>(); ! ! public Builder() { ! } ! ! public Builder name(String n) { ! name = n; ! return this; ! } ! ! public Builder require(String d, boolean reexport) { ! requires.add(new Dependence(d, reexport)); ! return this; ! } ! ! public Builder include(String p) { ! packages.add(p); ! return this; ! } ! ! public Builder export(String p) { ! return exportTo(p, Collections.emptySet()); ! } ! ! public Builder exportTo(String p, Set<String> ms) { ! Objects.requireNonNull(p); ! Objects.requireNonNull(ms); ! if (exports.containsKey(p)) { ! throw new RuntimeException(name + " already exports " + p); ! } ! exports.put(p, new HashSet<>(ms)); ! return this; ! } ! ! public Module build() { ! Module m = new Module(name, requires, exports, packages); ! return m; ! } ! } } } --- 83,130 ---- GenJdepsModulesXml gentool = new GenJdepsModulesXml(modulepath); Set<Module> modules = new HashSet<>(); for (; i < args.length; i++) { Path p = Paths.get(args[i]); ! modules.addAll(ModulesXmlReader.readModules(p) ! .stream() ! .map(gentool::buildIncludes) ! .collect(Collectors.toSet())); } Files.createDirectories(outfile.getParent()); ! ModulesXmlWriter.writeModules(modules, outfile); } final Path modulepath; public GenJdepsModulesXml(Path modulepath) { this.modulepath = modulepath; } ! private static String packageName(Path p) { return packageName(p.toString().replace(File.separatorChar, '/')); } ! private static String packageName(String name) { int i = name.lastIndexOf('/'); return (i > 0) ? name.substring(0, i).replace('/', '.') : ""; } ! private static boolean includes(String name) { ! return name.endsWith(".class"); } ! public Module buildIncludes(Module module) { ! Module.Builder mb = new Module.Builder(module); ! Path mclasses = modulepath.resolve(module.name()); try { Files.find(mclasses, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr) -> includes(p.getFileName().toString())) .map(p -> packageName(mclasses.relativize(p))) .forEach(mb::include); } catch (NoSuchFileException e) { // aggregate module may not have class + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); } ! return mb.build(); } }