| /* |
| * 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.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. Injecting 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 = |
| "Usage: GenModuleInfoSource [option] -o <output file> <module-info-java>\n" + |
| "Options are:\n" + |
| " -exports <package-name>\n" + |
| " -exports <package-name>[/<module-name>]\n" + |
| " -uses <service>\n" + |
| " -provides <service>/<provider-impl-classname>\n"; |
| |
| public static void main(String... args) throws Exception { |
| Path outfile = null; |
| Path moduleInfoJava = null; |
| 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")) { |
| 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 { |
| throw new IllegalArgumentException("invalid option: " + option); |
| } |
| } else if (moduleInfoJava != null) { |
| throw new IllegalArgumentException("more than one module-info.java"); |
| } else { |
| moduleInfoJava = Paths.get(option); |
| if (Files.notExists(moduleInfoJava)) { |
| throw new IllegalArgumentException(option + " not exist"); |
| } |
| } |
| } |
| |
| if (moduleInfoJava == null || outfile == null) { |
| System.err.println(USAGE); |
| System.exit(-1); |
| } |
| |
| // generate new module-info.java |
| 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); |
| |
| 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]); |
| } |
| } |
| |
| // 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); |
| } |
| } |