8153035: GenModuleInfoSource strips away the API comments

Reviewed-by: chegar
This commit is contained in:
Mandy Chung 2016-03-30 17:23:45 -07:00
parent 5b93b89a0a
commit 0a40727bca
5 changed files with 161 additions and 1022 deletions

View File

@ -25,25 +25,27 @@
package build.tools.module;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A build tool to extend the module-info.java in the source tree
* for platform-specific exports, uses, and provides and write
* to the specified output file.
* A build tool to extend the module-info.java in the source tree for
* platform-specific exports, uses, and provides and write to the specified
* output file. Injecting platform-specific requires is not supported.
*
* GenModulesList build tool currently generates the modules.list from
* the module-info.java from the source tree that will be used for
* the make target and dependences.
*
* The build currently invokes gensrc-$MODULE.gmk after modules.list
* is generated. Hence, platform-specific requires is not supported.
* The extra exports, uses, provides can be specified in module-info.java.extra
* files and GenModuleInfoSource will be invoked for each module that has
* module-info.java.extra in the source directory.
*/
public class GenModuleInfoSource {
private final static String USAGE =
@ -57,17 +59,32 @@ public class GenModuleInfoSource {
public static void main(String... args) throws Exception {
Path outfile = null;
Path moduleInfoJava = null;
Map<String, Set<String>> options = new HashMap<>();
GenModuleInfoSource genModuleInfo = new GenModuleInfoSource();
// validate input arguments
for (int i = 0; i < args.length; i++){
String option = args[i];
if (option.startsWith("-")) {
String arg = args[++i];
if (option.equals("-exports") ||
option.equals("-uses") ||
option.equals("-provides")) {
options.computeIfAbsent(option, _k -> new HashSet<>()).add(arg);
if (option.equals("-exports")) {
int index = arg.indexOf('/');
if (index > 0) {
String pn = arg.substring(0, index);
String mn = arg.substring(index + 1, arg.length());
genModuleInfo.exportTo(pn, mn);
} else {
genModuleInfo.export(arg);
}
} else if (option.equals("-uses")) {
genModuleInfo.use(arg);
} else if (option.equals("-provides")) {
int index = arg.indexOf('/');
if (index <= 0) {
throw new IllegalArgumentException("invalid -provide argument: " + arg);
}
String service = arg.substring(0, index);
String impl = arg.substring(index + 1, arg.length());
genModuleInfo.provide(service, impl);
} else if (option.equals("-o")) {
outfile = Paths.get(arg);
} else {
@ -87,48 +104,145 @@ public class GenModuleInfoSource {
System.err.println(USAGE);
System.exit(-1);
}
// read module-info.java
Module.Builder builder = ModuleInfoReader.builder(moduleInfoJava);
augment(builder, options);
// generate new module-info.java
Module module = builder.build();
genModuleInfo.generate(moduleInfoJava, outfile);
}
private final Set<String> exports = new HashSet<>();
private final Map<String, Set<String>> exportsTo = new HashMap<>();
private final Set<String> uses = new HashSet<>();
private final Map<String, Set<String>> provides = new HashMap<>();
GenModuleInfoSource() {
}
private void export(String p) {
Objects.requireNonNull(p);
if (exports.contains(p) || exportsTo.containsKey(p)) {
throw new RuntimeException("duplicated exports: " + p);
}
exports.add(p);
}
private void exportTo(String p, String mn) {
Objects.requireNonNull(p);
Objects.requireNonNull(mn);
if (exports.contains(p)) {
throw new RuntimeException("unqualified exports already exists: " + p);
}
exportsTo.computeIfAbsent(p, _k -> new HashSet<>()).add(mn);
}
private void use(String service) {
uses.add(service);
}
private void provide(String s, String impl) {
provides.computeIfAbsent(s, _k -> new HashSet<>()).add(impl);
}
private void generate(Path sourcefile, Path outfile) throws IOException {
Path parent = outfile.getParent();
if (parent != null)
Files.createDirectories(parent);
try (BufferedWriter writer = Files.newBufferedWriter(outfile)) {
writer.write(module.toString());
}
}
List<String> lines = Files.readAllLines(sourcefile);
try (BufferedWriter bw = Files.newBufferedWriter(outfile);
PrintWriter writer = new PrintWriter(bw)) {
int lineNumber = 0;
for (String l : lines) {
lineNumber++;
String[] s = l.trim().split("\\s+");
String keyword = s[0].trim();
int nextIndex = keyword.length();
String exp = null;
int n = l.length();
switch (keyword) {
case "exports":
boolean inExportsTo = false;
// assume package name immediately after exports
exp = s[1].trim();
if (s.length >= 3) {
nextIndex = l.indexOf(exp, nextIndex) + exp.length();
if (s[2].trim().equals("to")) {
inExportsTo = true;
n = l.indexOf("to", nextIndex) + "to".length();
} else {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed: " + s[2]);
}
}
private static void augment(Module.Builder builder, Map<String, Set<String>> options) {
for (String opt : options.keySet()) {
if (opt.equals("-exports")) {
for (String arg : options.get(opt)) {
int index = arg.indexOf('/');
if (index > 0) {
String pn = arg.substring(0, index);
String mn = arg.substring(index + 1, arg.length());
builder.exportTo(pn, mn);
} else {
builder.export(arg);
}
}
} else if (opt.equals("-uses")) {
options.get(opt).stream()
.forEach(builder::use);
} else if (opt.equals("-provides")) {
for (String arg : options.get(opt)) {
int index = arg.indexOf('/');
if (index <= 0) {
throw new IllegalArgumentException("invalid -provide argument: " + arg);
}
String service = arg.substring(0, index);
String impl = arg.substring(index + 1, arg.length());
builder.provide(service, impl);
// inject the extra targets after "to"
if (inExportsTo) {
writer.println(injectExportTargets(exp, l, n));
} else {
writer.println(l);
}
break;
case "to":
if (exp == null) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed");
}
n = l.indexOf("to", nextIndex) + "to".length();
writer.println(injectExportTargets(exp, l, n));
break;
case "}":
doAugments(writer);
// fall through
default:
writer.println(l);
// reset exports
exp = null;
}
}
}
}
private String injectExportTargets(String pn, String exp, int pos) {
Set<String> targets = exportsTo.remove(pn);
if (targets != null) {
StringBuilder sb = new StringBuilder();
// inject the extra targets after the given pos
sb.append(exp.substring(0, pos))
.append("\n\t")
.append(targets.stream()
.collect(Collectors.joining(",", "", ",")))
.append(" /* injected */");
if (pos < exp.length()) {
// print the remaining statement followed "to"
sb.append("\n\t")
.append(exp.substring(pos+1, exp.length()));
}
return sb.toString();
} else {
return exp;
}
}
private void doAugments(PrintWriter writer) {
if ((exports.size() + exportsTo.size() + uses.size() + provides.size()) == 0)
return;
writer.println(" // augmented from module-info.java.extra");
exports.stream()
.sorted()
.forEach(e -> writer.format(" exports %s;%n", e));
// remaining injected qualified exports
exportsTo.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> String.format(" exports %s to%n%s;", e.getKey(),
e.getValue().stream().sorted()
.map(mn -> String.format(" %s", mn))
.collect(Collectors.joining(",\n"))))
.forEach(writer::println);
uses.stream().sorted()
.forEach(s -> writer.format(" uses %s;%n", s));
provides.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.flatMap(e -> e.getValue().stream().sorted()
.map(impl -> String.format(" provides %s with %s;",
e.getKey(), impl)))
.forEach(writer::println);
}
}

View File

@ -1,280 +0,0 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Module {
public static class Dependence implements Comparable<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;
}
public boolean reexport(){
return 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;
}
@Override
public int compareTo(Dependence o) {
int rc = this.name.compareTo(o.name);
return rc != 0 ? rc : Boolean.compare(this.reexport, o.reexport);
}
@Override
public String toString() {
return String.format("requires %s%s;",
reexport ? "public " : "", name);
}
}
private final String moduleName;
private final Set<Dependence> requires;
private final Map<String, Set<String>> exports;
private final Set<String> uses;
private final Map<String, Set<String>> provides;
private Module(String name,
Set<Dependence> requires,
Map<String, Set<String>> exports,
Set<String> uses,
Map<String, Set<String>> provides) {
this.moduleName = name;
this.requires = Collections.unmodifiableSet(requires);
this.exports = Collections.unmodifiableMap(exports);
this.uses = Collections.unmodifiableSet(uses);
this.provides = Collections.unmodifiableMap(provides);
}
public String name() {
return moduleName;
}
public Set<Dependence> requires() {
return requires;
}
public Map<String, Set<String>> exports() {
return exports;
}
public Set<String> uses() {
return uses;
}
public Map<String, Set<String>> provides() {
return provides;
}
@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));
}
@Override
public int hashCode() {
int hc = moduleName.hashCode();
hc = hc * 43 + requires.hashCode();
hc = hc * 43 + exports.hashCode();
return hc;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("module %s {%n", moduleName));
requires.stream()
.sorted()
.map(d -> String.format(" requires %s%s;%n", d.reexport ? "public " : "", d.name))
.forEach(sb::append);
exports.entrySet().stream()
.filter(e -> e.getValue().isEmpty())
.sorted(Map.Entry.comparingByKey())
.map(e -> String.format(" exports %s;%n", e.getKey()))
.forEach(sb::append);
exports.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.sorted(Map.Entry.comparingByKey())
.map(e -> String.format(" exports %s to%n%s;%n", e.getKey(),
e.getValue().stream().sorted()
.map(mn -> String.format(" %s", mn))
.collect(Collectors.joining(",\n"))))
.forEach(sb::append);
uses.stream().sorted()
.map(s -> String.format(" uses %s;%n", s))
.forEach(sb::append);
provides.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.flatMap(e -> e.getValue().stream().sorted()
.map(impl -> String.format(" provides %s with %s;%n", e.getKey(), impl)))
.forEach(sb::append);
sb.append("}").append("\n");
return sb.toString();
}
/**
* Module Builder
*/
static class Builder {
private String name;
final Set<Dependence> requires = new HashSet<>();
final Map<String, Set<String>> exports = new HashMap<>();
final Set<String> uses = new HashSet<>();
final Map<String, Set<String>> provides = new HashMap<>();
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 export(String p) {
Objects.requireNonNull(p);
if (exports.containsKey(p)) {
throw new RuntimeException(name + " already exports " + p +
" " + exports.get(p));
}
return exportTo(p, Collections.emptySet());
}
public Builder exportTo(String p, String mn) {
Objects.requireNonNull(p);
Objects.requireNonNull(mn);
Set<String> ms = exports.get(p);
if (ms != null && ms.isEmpty()) {
throw new RuntimeException(name + " already has unqualified exports " + p);
}
exports.computeIfAbsent(p, _k -> new HashSet<>()).add(mn);
return this;
}
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.get(p));
}
exports.put(p, new HashSet<>(ms));
return this;
}
public Builder use(String cn) {
uses.add(cn);
return this;
}
public Builder provide(String s, String impl) {
provides.computeIfAbsent(s, _k -> new HashSet<>()).add(impl);
return this;
}
public Builder merge(Module m1, Module m2) {
if (!m1.name().equals(m2.name())) {
throw new IllegalArgumentException(m1.name() + " != " + m2.name());
}
name = m1.name();
// ## reexports
requires.addAll(m1.requires());
requires.addAll(m2.requires());
Stream.concat(m1.exports().keySet().stream(), m2.exports().keySet().stream())
.distinct()
.forEach(pn -> {
Set<String> s1 = m2.exports().get(pn);
Set<String> s2 = m2.exports().get(pn);
if (s1 == null || s2 == null) {
exportTo(pn, s1 != null ? s1 : s2);
} else if (s1.isEmpty() || s2.isEmpty()) {
// unqualified exports
export(pn);
} else {
exportTo(pn, Stream.concat(s1.stream(), s2.stream())
.collect(Collectors.toSet()));
}
});
uses.addAll(m1.uses());
uses.addAll(m2.uses());
m1.provides().keySet().stream()
.forEach(s -> m1.provides().get(s).stream()
.forEach(impl -> provide(s, impl)));
m2.provides().keySet().stream()
.forEach(s -> m2.provides().get(s).stream()
.forEach(impl -> provide(s, impl)));
return this;
}
public Module build() {
Module m = new Module(name, requires, exports, uses, provides);
return m;
}
@Override
public String toString() {
return name != null ? name : "Unknown";
}
}
}

View File

@ -1,357 +0,0 @@
/*
* Copyright (c) 2015, 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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import build.tools.module.Module.Builder;
/**
* Source reader of module-info.java
*/
public class ModuleInfoReader {
private final Path sourcefile;
private final Builder builder;
private ModuleInfoReader(Path file) {
this.sourcefile = file;
this.builder = new Builder();
}
public static Builder builder(Path file) throws IOException {
ModuleInfoReader reader = new ModuleInfoReader(file);
reader.readFile();
return reader.builder;
}
/**
* Reads the source file.
*/
void readFile() throws IOException {
List<String> lines = Files.readAllLines(sourcefile);
boolean done = false;
int lineNumber = 0;
boolean inBlockComment = false;
boolean inRequires = false;
boolean reexports = false;
boolean inProvides = false;
boolean inWith = false;
String serviceIntf = null;
String providerClass = null;
boolean inUses = false;
boolean inExports = false;
boolean inExportsTo = false;
String qualifiedExports = null;
Counter counter = new Counter();
for (String line : lines) {
lineNumber++;
if (inBlockComment) {
int c = line.indexOf("*/");
if (c >= 0) {
line = line.substring(c + 2, line.length());
inBlockComment = false;
} else {
// skip lines until end of comment block
continue;
}
}
inBlockComment = beginBlockComment(line);
line = trimComment(line).trim();
// ignore empty lines
if (line.length() == 0) {
continue;
}
String values;
if (inRequires || inExports | inUses | (inWith && providerClass == null)) {
values = line;
} else {
String[] s = line.split("\\s+");
String keyword = s[0].trim();
int nextIndex = keyword.length();
switch (keyword) {
case "module":
if (s.length != 3 || !s[2].trim().equals("{")) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed");
}
builder.name(s[1].trim());
continue; // next line
case "requires":
inRequires = true;
counter.numRequires++;
if (s.length >= 2) {
String ss = s[1].trim();
if (ss.equals("public")) {
nextIndex = line.indexOf(ss) + ss.length();
reexports = true;
}
}
break;
case "exports":
inExports = true;
inExportsTo = false;
counter.numExports++;
qualifiedExports = null;
if (s.length >= 3) {
qualifiedExports = s[1].trim();
nextIndex = line.indexOf(qualifiedExports, nextIndex)
+ qualifiedExports.length();
if (s[2].trim().equals("to")) {
inExportsTo = true;
nextIndex = line.indexOf("to", nextIndex) + "to".length();
} else {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed: " + s[2]);
}
}
break;
case "to":
if (!inExports || qualifiedExports == null) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed");
}
inExportsTo = true;
break;
case "uses":
inUses = true;
counter.numUses++;
break;
case "provides":
inProvides = true;
inWith = false;
counter.numProvides++;
serviceIntf = null;
providerClass = null;
if (s.length >= 2) {
serviceIntf = s[1].trim();
nextIndex = line.indexOf(serviceIntf) + serviceIntf.length();
}
if (s.length >= 3) {
if (s[2].trim().equals("with")) {
inWith = true;
nextIndex = line.indexOf("with") + "with".length();
} else {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed: " + s[2]);
}
}
break;
case "with":
if (!inProvides || serviceIntf == null) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed");
}
inWith = true;
nextIndex = line.indexOf("with") + "with".length();
break;
case "}":
counter.validate(builder);
done = true;
continue; // next line
default:
throw new RuntimeException(sourcefile + ", \"" +
keyword + "\" on line " +
lineNumber + ", is not recognized");
}
values = line.substring(nextIndex, line.length()).trim();
}
int len = values.length();
if (len == 0) {
continue; // next line
}
char lastchar = values.charAt(len - 1);
if (lastchar != ',' && lastchar != ';') {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", is malformed:" +
" ',' or ';' is missing.");
}
values = values.substring(0, len - 1).trim();
// parse the values specified for a keyword specified
for (String s : values.split(",")) {
s = s.trim();
if (s.length() > 0) {
if (inRequires) {
if (builder.requires.contains(s)) {
throw new RuntimeException(sourcefile + ", line "
+ lineNumber + " duplicated requires: \"" + s + "\"");
}
builder.require(s, reexports);
} else if (inExports) {
if (!inExportsTo && qualifiedExports == null) {
builder.export(s);
} else {
builder.exportTo(qualifiedExports, s);
}
} else if (inUses) {
builder.use(s);
} else if (inProvides) {
if (!inWith) {
serviceIntf = s;
} else {
providerClass = s;
builder.provide(serviceIntf, providerClass);
}
}
}
}
if (lastchar == ';') {
inRequires = false;
reexports = false;
inExports = false;
inExportsTo = false;
inProvides = false;
inWith = false;
inUses = false;
}
}
if (inBlockComment) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", missing \"*/\" to end a block comment");
}
if (!done) {
throw new RuntimeException(sourcefile + ", line " +
lineNumber + ", missing \"}\" to end module definition" +
" for \"" + builder + "\"");
}
return;
}
// the naming convention for the module names without dashes
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("[\\w\\.\\*_$/]+");
private static boolean beginBlockComment(String line) {
int pos = 0;
while (pos >= 0 && pos < line.length()) {
int c = line.indexOf("/*", pos);
if (c < 0) {
return false;
}
if (c > 0 && !Character.isWhitespace(line.charAt(c - 1))) {
return false;
}
int c1 = line.indexOf("//", pos);
if (c1 >= 0 && c1 < c) {
return false;
}
int c2 = line.indexOf("*/", c + 2);
if (c2 < 0) {
return true;
}
pos = c + 2;
}
return false;
}
private static String trimComment(String line) {
StringBuilder sb = new StringBuilder();
int pos = 0;
while (pos >= 0 && pos < line.length()) {
int c1 = line.indexOf("//", pos);
if (c1 > 0 && !Character.isWhitespace(line.charAt(c1 - 1))) {
// not a comment
c1 = -1;
}
int c2 = line.indexOf("/*", pos);
if (c2 > 0 && !Character.isWhitespace(line.charAt(c2 - 1))) {
// not a comment
c2 = -1;
}
int c = line.length();
int n = line.length();
if (c1 >= 0 || c2 >= 0) {
if (c1 >= 0) {
c = c1;
}
if (c2 >= 0 && c2 < c) {
c = c2;
}
int c3 = line.indexOf("*/", c2 + 2);
if (c == c2 && c3 > c2) {
n = c3 + 2;
}
}
if (c > 0) {
if (sb.length() > 0) {
// add a whitespace if multiple comments on one line
sb.append(" ");
}
sb.append(line.substring(pos, c));
}
pos = n;
}
return sb.toString();
}
static class Counter {
int numRequires;
int numExports;
int numUses;
int numProvides;
void validate(Builder builder) {
assertEquals("requires", numRequires, builder.requires.size(),
() -> builder.requires.stream()
.map(Module.Dependence::toString));
assertEquals("exports", numExports, builder.exports.size(),
() -> builder.exports.entrySet().stream()
.map(e -> "exports " + e.getKey() + " to " + e.getValue()));
assertEquals("uses", numUses, builder.uses.size(),
() -> builder.uses.stream());
assertEquals("provides", numProvides,
(int)builder.provides.values().stream()
.flatMap(s -> s.stream())
.count(),
() -> builder.provides.entrySet().stream()
.map(e -> "provides " + e.getKey() + " with " + e.getValue()));
}
private static void assertEquals(String msg, int expected, int got,
Supplier<Stream<String>> supplier) {
if (expected != got){
System.err.println("ERROR: mismatched " + msg +
" expected: " + expected + " got: " + got );
supplier.get().sorted()
.forEach(System.err::println);
throw new AssertionError("mismatched " + msg +
" expected: " + expected + " got: " + got + " ");
}
}
}
}

View File

@ -1,162 +0,0 @@
/*
* 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 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 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

@ -1,176 +0,0 @@
/*
* 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 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));
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();
}
}