8058118: Generate modules.list during the build

Reviewed-by: alanb, mchung
This commit is contained in:
Chris Hegarty 2014-09-16 12:06:53 +02:00
parent 797b24f7a0
commit 375020d61a
5 changed files with 698 additions and 372 deletions

View File

@ -25,29 +25,17 @@
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.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.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;
import java.util.stream.Collectors;
/**
* GenJdepsModulesXml augments the input modules.xml file(s)
@ -97,14 +85,14 @@ public final class GenJdepsModulesXml {
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);
}
modules.addAll(ModulesXmlReader.readModules(p)
.stream()
.map(gentool::buildIncludes)
.collect(Collectors.toSet()));
}
Files.createDirectories(outfile.getParent());
gentool.writeXML(modules, outfile);
ModulesXmlWriter.writeModules(modules, outfile);
}
final Path modulepath;
@ -112,228 +100,21 @@ public final class GenJdepsModulesXml {
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) {
private static String packageName(Path p) {
return packageName(p.toString().replace(File.separatorChar, '/'));
}
private String packageName(String name) {
private static 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");
private static boolean includes(String name) {
return name.endsWith(".class");
}
public void buildIncludes(Module.Builder mb, String modulename) throws IOException {
Path mclasses = modulepath.resolve(modulename);
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()))
@ -341,145 +122,9 @@ public final class GenJdepsModulesXml {
.forEach(mb::include);
} catch (NoSuchFileException e) {
// aggregate module may not have class
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
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;
}
}
return mb.build();
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2014, 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 build.tools.module;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
/**
* $ java build.tools.module.GenModulesList \
* -o modules.list \
* top/modules.xml ...
*/
public final class GenModulesList {
private final static String USAGE =
"Usage: GenModulesList -o <output file> path-to-modules-xml";
private Set<Module> modules = new HashSet<>();
private HashMap<String,Module> nameToModule = new HashMap<>();
public static void main(String[] args) throws Exception {
GenModulesList gen = new GenModulesList();
gen.run(args);
}
void run(String[] args) throws Exception {
Path outfile = null;
int i = 0;
while (i < args.length) {
String arg = args[i];
if (arg.equals("-o")) {
outfile = Paths.get(args[i+1]);
i = i+2;
} else {
break;
}
}
if (outfile == null || i >= args.length) {
System.err.println(USAGE);
System.exit(-1);
}
for (; i < args.length; i++) {
Path p = Paths.get(args[i]);
modules.addAll(ModulesXmlReader.readModules(p));
}
modules.stream()
.forEach(m -> nameToModule.put(m.name(), m));
Path parent = outfile.getParent();
if (parent != null)
Files.createDirectories(parent);
Iterable<Module> sortedModules = (new TopoSorter(modules)).result();
try (PrintWriter writer = new PrintWriter(outfile.toFile())) {
for (Module m : sortedModules) {
if (isNotAggregator(m)) {
String deps = getModuleDependences(m).stream()
.filter(GenModulesList::isNotAggregator)
.map(Module::name)
.collect(Collectors.joining(" "));
writer.format("%s: %s%n", m.name(), deps);
}
}
}
}
private Module nameToModule(String name) {
return nameToModule.get(name);
}
private Set<Module> getModuleDependences(Module m) {
return m.requires().stream()
.map(d -> d.name())
.map(this::nameToModule)
.collect(Collectors.toSet());
}
static boolean isNotAggregator(Module m) {
return isNotAggregator(m.name());
}
static boolean isNotAggregator(String name) {
return AGGREGATORS.contains(name) ? false : true;
}
static final List<String> AGGREGATORS = Arrays.asList(new String[] {
"java.se", "java.compact1", "java.compact2",
"java.compact3", "jdk.compact3"});
class TopoSorter {
final Deque<Module> result = new LinkedList<>();
final Deque<Module> nodes = new LinkedList<>();
TopoSorter(Collection<Module> nodes) {
nodes.stream()
.forEach(m -> this.nodes.add(m));
sort();
}
public Iterable<Module> result() {
return result;
}
private void sort() {
Deque<Module> visited = new LinkedList<>();
Deque<Module> done = new LinkedList<>();
Module node;
while ((node = nodes.poll()) != null) {
if (!visited.contains(node)) {
visit(node, visited, done);
}
}
}
private void visit(Module m, Deque<Module> visited, Deque<Module> done) {
if (visited.contains(m)) {
if (!done.contains(m)) {
throw new IllegalArgumentException("Cyclic detected: " +
m + " " + getModuleDependences(m));
}
return;
}
visited.add(m);
getModuleDependences(m).stream()
.forEach(x -> visit(x, visited, done));
done.add(m);
result.addLast(m);
}
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) 2014, 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 build.tools.module;
import java.util.*;
public 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;
}
public String name() {
return name;
}
@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(Module module) {
name = module.name();
requires.addAll(module.requires());
exports.putAll(module.exports());
packages.addAll(module.packages());
}
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;
}
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2014, 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 build.tools.module;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.XMLEvent;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
public class ModulesXmlReader {
private ModulesXmlReader() {}
public static Set<Module> readModules(Path modulesXml)
throws XMLStreamException, IOException
{
Set<Module> modules = new HashSet<>();
try (InputStream in = new BufferedInputStream(Files.newInputStream(modulesXml))) {
Set<Module> mods = ModulesXmlReader.load(in);
modules.addAll(mods);
}
return modules;
}
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 static 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:
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 static String getData(XMLEventReader reader)
throws XMLStreamException
{
XMLEvent e = reader.nextEvent();
if (e.isCharacters())
return e.asCharacters().getData();
throw new RuntimeException(e.toString());
}
private static 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);
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright (c) 2014, 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 build.tools.module;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
public final class ModulesXmlWriter {
private ModulesXmlWriter() {}
public static void writeModules(Set<Module> modules, Path path)
throws IOException, XMLStreamException
{
writeXML(modules, path);
}
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 static 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 static 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 static 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 static void writeExportElement(XMLStreamWriter xtw,
String pkg,
int depth) {
writeExportElement(xtw, pkg, Collections.emptySet(), depth);
}
private static 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 static 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 static void writeStartElement(XMLStreamWriter xtw,
String name,
int depth)
throws XMLStreamException
{
xtw.writeCharacters(stack[depth]);
xtw.writeStartElement(name);
}
private static void writeEndElement(XMLStreamWriter xtw, int depth)
throws XMLStreamException
{
xtw.writeCharacters(stack[depth]);
xtw.writeEndElement();
}
}