jdk/make/src/classes/build/tools/module/Module.java
author alanb
Thu, 17 Mar 2016 19:04:16 +0000
changeset 36511 9d0388c6b336
parent 26624 e6383382c939
permissions -rw-r--r--
8142968: Module System implementation Summary: Initial integration of JEP 200, JEP 260, JEP 261, and JEP 282 Reviewed-by: alanb, mchung, naoto, rriggs, psandoz, plevart, mullan, ascarpino, vinnie, prr, sherman, dfuchs, mhaupt Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, chris.hegarty@oracle.com, alexandr.scherbatiy@oracle.com, amy.lu@oracle.com, calvin.cheung@oracle.com, daniel.fuchs@oracle.com, erik.joelsson@oracle.com, harold.seigel@oracle.com, jaroslav.bachorik@oracle.com, jean-francois.denise@oracle.com, jan.lahoda@oracle.com, james.laskey@oracle.com, lois.foltan@oracle.com, miroslav.kos@oracle.com, huaming.li@oracle.com, sean.mullan@oracle.com, naoto.sato@oracle.com, masayoshi.okutsu@oracle.com, peter.levart@gmail.com, philip.race@oracle.com, claes.redestad@oracle.com, sergey.bylokhov@oracle.com, alexandre.iline@oracle.com, volker.simonis@gmail.com, staffan.larsen@oracle.com, stuart.marks@oracle.com, semyon.sadetsky@oracle.com, serguei.spitsyn@oracle.com, sundararajan.athijegannathan@oracle.com, valerie.peng@oracle.com, vincent.x.ryan@oracle.com, weijun.wang@oracle.com, yuri.nesterenko@oracle.com, yekaterina.kantserova@oracle.com, alexander.kulyakhtin@oracle.com, felix.yang@oracle.com, andrei.eremeev@oracle.com, frank.yuan@oracle.com, sergei.pikalev@oracle.com, sibabrata.sahoo@oracle.com, tiantian.du@oracle.com, sha.jiang@oracle.com

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