jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java
author chegar
Wed, 27 Apr 2016 20:36:02 +0100
changeset 37676 24ef455da1b0
parent 36742 286b583dc969
child 42338 a60f280f803c
permissions -rw-r--r--
8044773: Refactor jdk.net API so that it can be moved out of the base module Reviewed-by: alanb, erikj, mchung

/*
 * 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);
    }
}