/
githubmirror
/
jdk17u-dev
Обзор
Документация
Войти
/
githubmirror
/
jdk17u-dev
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
make/jdk/src/classes/build/tools/module/GenModuleInfoSource.java
786 строк
29 KB
Erik Joelsson
8226346: Build better binary builders
13 дек 2019, 19:27
13 дек 2019, 19:27
4df99aa
Код
Авторство
О чём код?
/* * Copyright (c) 2015, 2018, 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.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.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.*; /** * A build tool to extend the module-info.java in the source tree for * platform-specific exports, opens, uses, and provides and write to * the specified output file. * * GenModuleInfoSource will be invoked for each module that has * module-info.java.extra in the source directory. * * The extra exports, opens, uses, provides can be specified * in module-info.java.extra. * Injecting platform-specific requires is not supported. * * @see build.tools.module.ModuleInfoExtraTest for basic testing */ public class GenModuleInfoSource { private final static String USAGE = "Usage: GenModuleInfoSource \n" + " [-d]\n" + " -o <output file>\n" + " --source-file <module-info-java>\n" + " --modules <module-name>[,<module-name>...]\n" + " <module-info.java.extra> ...\n"; static boolean debug = false; static boolean verbose = false; public static void main(String... args) throws Exception { Path outfile = null; Path moduleInfoJava = null; Set<String> modules = Collections.emptySet(); List<Path> extras = new ArrayList<>(); // validate input arguments for (int i = 0; i < args.length; i++){ String option = args[i]; String arg = i+1 < args.length ? args[i+1] : null; switch (option) { case "-d": debug = true; break; case "-o": outfile = Paths.get(arg); i++; break; case "--source-file": moduleInfoJava = Paths.get(arg); if (Files.notExists(moduleInfoJava)) { throw new IllegalArgumentException(moduleInfoJava + " not exist"); } i++; break; case "--modules": modules = Arrays.stream(arg.split(",")) .collect(toSet()); i++; break; case "-v": verbose = true; break; default: Path file = Paths.get(option); if (Files.notExists(file)) { throw new IllegalArgumentException(file + " not exist"); } extras.add(file); } } if (moduleInfoJava == null || outfile == null || modules.isEmpty() || extras.isEmpty()) { System.err.println(USAGE); System.exit(-1); } GenModuleInfoSource genModuleInfo = new GenModuleInfoSource(moduleInfoJava, extras, modules); // generate new module-info.java genModuleInfo.generate(outfile); } final Path sourceFile; final List<Path> extraFiles; final ModuleInfo extras; final Set<String> modules; final ModuleInfo moduleInfo; GenModuleInfoSource(Path sourceFile, List<Path> extraFiles, Set<String> modules) throws IOException { this.sourceFile = sourceFile; this.extraFiles = extraFiles; this.modules = modules; this.moduleInfo = new ModuleInfo(); this.moduleInfo.parse(sourceFile); // parse module-info.java.extra this.extras = new ModuleInfo(); for (Path file : extraFiles) { extras.parseExtra(file); } // merge with module-info.java.extra moduleInfo.augmentModuleInfo(extras, modules); } void generate(Path output) throws IOException { List<String> lines = Files.readAllLines(sourceFile); try (BufferedWriter bw = Files.newBufferedWriter(output); PrintWriter writer = new PrintWriter(bw)) { // write the copyright header and lines up to module declaration for (String l : lines) { writer.println(l); if (l.trim().startsWith("module ")) { if (debug) { // print URI rather than file path to avoid escape writer.format(" // source file: %s%n", sourceFile.toUri()); for (Path file : extraFiles) { writer.format(" // %s%n", file.toUri()); } } break; } } // requires for (String l : lines) { if (l.trim().startsWith("requires")) writer.println(l); } // write exports, opens, uses, and provides moduleInfo.print(writer); // close writer.println("}"); } } class ModuleInfo { final Map<String, Statement> exports = new HashMap<>(); final Map<String, Statement> opens = new HashMap<>(); final Map<String, Statement> uses = new HashMap<>(); final Map<String, Statement> provides = new HashMap<>(); Statement getStatement(String directive, String name) { Objects.requireNonNull(name); switch (directive) { case "exports": if (moduleInfo.exports.containsKey(name) && moduleInfo.exports.get(name).isUnqualified()) { throw new IllegalArgumentException(sourceFile + " already has " + directive + " " + name); } return exports.computeIfAbsent(name, _n -> new Statement("exports", "to", name)); case "opens": if (moduleInfo.opens.containsKey(name) && moduleInfo.opens.get(name).isUnqualified()) { throw new IllegalArgumentException(sourceFile + " already has " + directive + " " + name); } if (moduleInfo.opens.containsKey(name)) { throw new IllegalArgumentException(sourceFile + " already has " + directive + " " + name); } return opens.computeIfAbsent(name, _n -> new Statement("opens", "to", name)); case "uses": return uses.computeIfAbsent(name, _n -> new Statement("uses", "", name)); case "provides": return provides.computeIfAbsent(name, _n -> new Statement("provides", "with", name, true)); default: throw new IllegalArgumentException(directive); } } /* * Augment this ModuleInfo with module-info.java.extra */ void augmentModuleInfo(ModuleInfo extraFiles, Set<String> modules) { // API package exported in the original module-info.java extraFiles.exports.entrySet() .stream() .filter(e -> exports.containsKey(e.getKey()) && e.getValue().filter(modules)) .forEach(e -> mergeExportsOrOpens(exports.get(e.getKey()), e.getValue(), modules)); // add exports that are not defined in the original module-info.java extraFiles.exports.entrySet() .stream() .filter(e -> !exports.containsKey(e.getKey()) && e.getValue().filter(modules)) .forEach(e -> addTargets(getStatement("exports", e.getKey()), e.getValue(), modules)); // API package opened in the original module-info.java extraFiles.opens.entrySet() .stream() .filter(e -> opens.containsKey(e.getKey()) && e.getValue().filter(modules)) .forEach(e -> mergeExportsOrOpens(opens.get(e.getKey()), e.getValue(), modules)); // add opens that are not defined in the original module-info.java extraFiles.opens.entrySet() .stream() .filter(e -> !opens.containsKey(e.getKey()) && e.getValue().filter(modules)) .forEach(e -> addTargets(getStatement("opens", e.getKey()), e.getValue(), modules)); // provides extraFiles.provides.keySet() .stream() .filter(service -> provides.containsKey(service)) .forEach(service -> mergeProvides(service, extraFiles.provides.get(service))); extraFiles.provides.keySet() .stream() .filter(service -> !provides.containsKey(service)) .forEach(service -> provides.put(service, extraFiles.provides.get(service))); // uses extraFiles.uses.keySet() .stream() .filter(service -> !uses.containsKey(service)) .forEach(service -> uses.put(service, extraFiles.uses.get(service))); } // add qualified exports or opens to known modules only private void addTargets(Statement statement, Statement extra, Set<String> modules) { extra.targets.stream() .filter(mn -> modules.contains(mn)) .forEach(mn -> statement.addTarget(mn)); } private void mergeExportsOrOpens(Statement statement, Statement extra, Set<String> modules) { String pn = statement.name; if (statement.isUnqualified() && extra.isQualified()) { throw new RuntimeException("can't add qualified exports to " + "unqualified exports " + pn); } Set<String> mods = extra.targets.stream() .filter(mn -> statement.targets.contains(mn)) .collect(toSet()); if (mods.size() > 0) { throw new RuntimeException("qualified exports " + pn + " to " + mods.toString() + " already declared in " + sourceFile); } // add qualified exports or opens to known modules only addTargets(statement, extra, modules); } private void mergeProvides(String service, Statement extra) { Statement statement = provides.get(service); Set<String> mods = extra.targets.stream() .filter(mn -> statement.targets.contains(mn)) .collect(toSet()); if (mods.size() > 0) { throw new RuntimeException("qualified exports " + service + " to " + mods.toString() + " already declared in " + sourceFile); } extra.targets.stream() .forEach(mn -> statement.addTarget(mn)); } void print(PrintWriter writer) { // print unqualified exports exports.entrySet().stream() .filter(e -> e.getValue().targets.isEmpty()) .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); // print qualified exports exports.entrySet().stream() .filter(e -> !e.getValue().targets.isEmpty()) .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); // print unqualified opens opens.entrySet().stream() .filter(e -> e.getValue().targets.isEmpty()) .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); // print qualified opens opens.entrySet().stream() .filter(e -> !e.getValue().targets.isEmpty()) .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); // uses and provides writer.println(); uses.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); provides.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .forEach(e -> writer.println(e.getValue())); } private void parse(Path file) throws IOException { Parser parser = new Parser(file); parser.run(); if (verbose) { parser.dump(); } process(parser, false); } private void parseExtra(Path file) throws IOException { Parser parser = new Parser(file); parser.run(); if (verbose) { parser.dump(); } process(parser, true); } private void process(Parser parser, boolean extraFile) throws IOException { // no duplicate statement local in each file Map<String, Statement> exports = new HashMap<>(); Map<String, Statement> opens = new HashMap<>(); Map<String, Statement> uses = new HashMap<>(); Map<String, Statement> provides = new HashMap<>(); String token = null; boolean hasCurlyBracket = false; while ((token = parser.nextToken()) != null) { if (token.equals("module")) { String modulename = nextIdentifier(parser); if (extraFile) { throw parser.newError("cannot declare module in " + parser.sourceFile); } skipTokenOrThrow(parser, "{", "missing {"); hasCurlyBracket = true; } else if (token.equals("requires")) { token = nextIdentifier(parser); if (token.equals("transitive")) { token = nextIdentifier(parser); } if (extraFile) { throw parser.newError("cannot declare requires in " + parser.sourceFile); } skipTokenOrThrow(parser, ";", "missing semicolon"); } else if (isExportsOpensProvidesUses(token)) { // new statement String keyword = token; String name = nextIdentifier(parser); Statement statement = getStatement(keyword, name); switch (keyword) { case "exports": if (exports.containsKey(name)) { throw parser.newError("multiple " + keyword + " " + name); } exports.put(name, statement); break; case "opens": if (opens.containsKey(name)) { throw parser.newError("multiple " + keyword + " " + name); } opens.put(name, statement); break; case "uses": if (uses.containsKey(name)) { throw parser.newError("multiple " + keyword + " " + name); } uses.put(name, statement); break; case "provides": if (provides.containsKey(name)) { throw parser.newError("multiple " + keyword + " " + name); } provides.put(name, statement); break; } String lookAhead = lookAhead(parser); if (lookAhead.equals(statement.qualifier)) { parser.nextToken(); // skip qualifier while ((lookAhead = parser.peekToken()) != null) { // add target name name = nextIdentifier(parser); statement.addTarget(name); lookAhead = lookAhead(parser); if (lookAhead.equals(",") || lookAhead.equals(";")) { parser.nextToken(); } else { throw parser.newError("missing semicolon"); } if (lookAhead.equals(";")) { break; } } } else { skipTokenOrThrow(parser, ";", "missing semicolon"); } } else if (token.equals(";")) { continue; } else if (hasCurlyBracket && token.equals("}")) { hasCurlyBracket = false; if (parser.peekToken() != null) { // must be EOF throw parser.newError("is malformed"); } } else { throw parser.newError("missing keyword"); } } if (hasCurlyBracket) { parser.newError("missing }"); } } private boolean isExportsOpensProvidesUses(String word) { switch (word) { case "exports": case "opens": case "provides": case "uses": return true; default: return false; } } private String lookAhead(Parser parser) { String lookAhead = parser.peekToken(); if (lookAhead == null) { // EOF throw parser.newError("reach end of file"); } return lookAhead; } private String nextIdentifier(Parser parser) { String lookAhead = parser.peekToken(); boolean maybeIdentifier = true; switch (lookAhead) { case "module": case "requires": case "exports": case "opens": case "provides": case "uses": case "to": case "with": case ",": case ";": case "{": case "}": maybeIdentifier = false; } if (lookAhead == null || !maybeIdentifier) { throw parser.newError("<identifier> missing"); } return parser.nextToken(); } private String skipTokenOrThrow(Parser parser, String token, String msg) { // look ahead to report the proper line number String lookAhead = parser.peekToken(); if (!token.equals(lookAhead)) { throw parser.newError(msg); } return parser.nextToken(); } } static class Statement { final String directive; final String qualifier; final String name; final Set<String> targets = new LinkedHashSet<>(); final boolean ordered; Statement(String directive, String qualifier, String name) { this(directive, qualifier, name, false); } Statement(String directive, String qualifier, String name, boolean ordered) { this.directive = directive; this.qualifier = qualifier; this.name = name; this.ordered = ordered; } Statement addTarget(String mn) { if (mn.isEmpty()) throw new IllegalArgumentException("empty module name"); targets.add(mn); return this; } boolean isQualified() { return targets.size() > 0; } boolean isUnqualified() { return targets.isEmpty(); } /** * Returns true if this statement is unqualified or it has * at least one target in the given names. */ boolean filter(Set<String> names) { if (isUnqualified()) { return true; } else { return targets.stream() .filter(mn -> names.contains(mn)) .findAny().isPresent(); } } @Override public String toString() { StringBuilder sb = new StringBuilder(" "); sb.append(directive).append(" ").append(name); if (targets.isEmpty()) { sb.append(";"); } else if (targets.size() == 1) { sb.append(" ").append(qualifier) .append(orderedTargets().collect(joining(",", " ", ";"))); } else { sb.append(" ").append(qualifier) .append(orderedTargets() .map(target -> String.format(" %s", target)) .collect(joining(",\n", "\n", ";"))); } return sb.toString(); } public Stream<String> orderedTargets() { return ordered ? targets.stream() : targets.stream().sorted(); } } static void trace(String fmt, Object... params) { if (verbose) { System.out.format(fmt, params); } } static class Parser { private static final List<String> EMPTY = List.of(); private final Path sourceFile; private boolean inCommentBlock = false; private List<List<String>> tokens = new ArrayList<>(); private int lineNumber = 1; private int index = 0; Parser(Path file) { this.sourceFile = file; } void run() throws IOException { List<String> lines = Files.readAllLines(sourceFile); for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { String l = lines.get(lineNumber - 1).trim(); tokenize(l); } } /* * Tokenize the given string. Comments are skipped. */ List<String> tokenize(String l) { while (!l.isEmpty()) { if (inCommentBlock) { int comment = l.indexOf("*/"); if (comment == -1) return emptyTokens(); // end comment block inCommentBlock = false; if ((comment + 2) >= l.length()) { return emptyTokens(); } l = l.substring(comment + 2, l.length()).trim(); } // skip comment int comment = l.indexOf("//"); if (comment >= 0) { l = l.substring(0, comment).trim(); if (l.isEmpty()) return emptyTokens(); } if (l.isEmpty()) { return emptyTokens(); } int beginComment = l.indexOf("/*"); int endComment = l.indexOf("*/"); if (beginComment == -1) return tokens(l); String s1 = l.substring(0, beginComment).trim(); if (endComment > 0) { String s2 = l.substring(endComment + 2, l.length()).trim(); if (s1.isEmpty()) { l = s2; } else if (s2.isEmpty()) { l = s1; } else { l = s1 + " " + s2; } } else { inCommentBlock = true; return tokens(s1); } } return tokens(l); } private List<String> emptyTokens() { this.tokens.add(EMPTY); return EMPTY; } private List<String> tokens(String l) { List<String> tokens = new ArrayList<>(); for (String s : l.split("\\s+")) { int pos=0; s = s.trim(); if (s.isEmpty()) continue; int i = s.indexOf(',', pos); int j = s.indexOf(';', pos); while ((i >= 0 && i < s.length()) || (j >= 0 && j < s.length())) { if (j == -1 || (i >= 0 && i < j)) { String n = s.substring(pos, i).trim(); if (!n.isEmpty()) { tokens.add(n); } tokens.add(s.substring(i, i + 1)); pos = i + 1; i = s.indexOf(',', pos); } else { String n = s.substring(pos, j).trim(); if (!n.isEmpty()) { tokens.add(n); } tokens.add(s.substring(j, j + 1)); pos = j + 1; j = s.indexOf(';', pos); } } String n = s.substring(pos).trim(); if (!n.isEmpty()) { tokens.add(n); } } this.tokens.add(tokens); return tokens; } /* * Returns next token. */ String nextToken() { while (lineNumber <= tokens.size()) { List<String> l = tokens.get(lineNumber-1); if (index < l.size()) { return l.get(index++); } else { lineNumber++; index = 0; } } return null; } /* * Peeks next token. */ String peekToken() { int ln = lineNumber; int i = index; while (ln <= tokens.size()) { List<String> l = tokens.get(ln-1); if (i < l.size()) { return l.get(i++); } else { ln++; i = 0; } } return null; } Error newError(String msg) { if (lineNumber <= tokens.size()) { throw new Error(sourceFile + ", line " + lineNumber + ", " + msg + " \"" + lineAt(lineNumber) + "\""); } else { throw new Error(sourceFile + ", line " + lineNumber + ", " + msg); } } void dump() { for (int i = 1; i <= tokens.size(); i++) { System.out.format("%d: %s%n", i, lineAt(i)); } } private String lineAt(int i) { return tokens.get(i-1).stream().collect(Collectors.joining(" ")); } } }