1/*
2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package toolbox;
25
26import java.io.File;
27import java.io.IOException;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.LinkedHashSet;
34import java.util.List;
35import java.util.Set;
36import java.util.stream.Collectors;
37
38/**
39 * Builder for module declarations.
40 */
41public class ModuleBuilder {
42
43    private final ToolBox tb;
44    private final String name;
45    private String comment = "";
46    private List<String> requires = new ArrayList<>();
47    private List<String> exports = new ArrayList<>();
48    private List<String> opens = new ArrayList<>();
49    private List<String> uses = new ArrayList<>();
50    private List<String> provides = new ArrayList<>();
51    private List<String> content = new ArrayList<>();
52    private Set<Path> modulePath = new LinkedHashSet<>();
53
54    /**
55     * Creates a builder for a module.
56     * @param tb a Toolbox that can be used to compile the module declaration.
57     * @param name the name of the module to be built
58     */
59    public ModuleBuilder(ToolBox tb, String name) {
60        this.tb = tb;
61        this.name = name;
62    }
63
64    /**
65     * Sets the doc comment for the declaration.
66     * @param comment the content of the comment, excluding the initial
67     *  '/**', leading whitespace and asterisks, and the final trailing '&#02a;/'.
68     * @return this builder
69     */
70    public ModuleBuilder comment(String comment) {
71        this.comment = comment;
72        return this;
73    }
74
75    /**
76     * Adds a "requires" directive to the declaration.
77     * @param module the name of the module that is required
78     * @param modulePath a path in while to locate the modules
79     *    if the declaration is compiled
80     * @return this builder
81     */
82    public ModuleBuilder requires(String module, Path... modulePath) {
83        addDirective(requires, "requires " + module + ";");
84        this.modulePath.addAll(Arrays.asList(modulePath));
85        return this;
86
87    }
88
89    /**
90     * Adds a "requires static" directive to the declaration.
91     * @param module the name of the module that is required
92     * @param modulePath a path in which to locate the modules
93     *    if the declaration is compiled
94     * @return this builder
95     */
96    public ModuleBuilder requiresStatic(String module, Path... modulePath) {
97        addDirective(requires, "requires static " + module + ";");
98        this.modulePath.addAll(Arrays.asList(modulePath));
99        return this;
100    }
101
102    /**
103     * Adds a "requires transitive" directive to the declaration.
104     * @param module the name of the module that is required
105     * @param modulePath a path in which to locate the modules
106     *    if the declaration is compiled
107     * @return this builder
108     */
109    public ModuleBuilder requiresTransitive(String module, Path... modulePath) {
110        addDirective(requires, "requires transitive " + module + ";");
111        this.modulePath.addAll(Arrays.asList(modulePath));
112        return this;
113    }
114
115    /**
116     * Adds a "requires static transitive" directive to the declaration.
117     * @param module the name of the module that is required
118     * @param modulePath a path in which to locate the modules
119     *    if the declaration is compiled
120     * @return this builder
121     */
122    public ModuleBuilder requiresStaticTransitive(String module, Path... modulePath) {
123        addDirective(requires, "requires static transitive " + module + ";");
124        this.modulePath.addAll(Arrays.asList(modulePath));
125        return this;
126    }
127
128    /**
129     * Adds an unqualified "exports" directive to the declaration.
130     * @param pkg the name of the package to be exported
131     * @return this builder
132     */
133    public ModuleBuilder exports(String pkg) {
134        return addDirective(exports, "exports " + pkg + ";");
135    }
136
137    /**
138     * Adds a qualified "exports" directive to the declaration.
139     * @param pkg the name of the package to be exported
140     * @param module the name of the module to which it is to be exported
141     * @return this builder
142     */
143    public ModuleBuilder exportsTo(String pkg, String module) {
144        return addDirective(exports, "exports " + pkg + " to " + module + ";");
145    }
146
147    /**
148     * Adds an unqualified "opens" directive to the declaration.
149     * @param pkg the name of the package to be opened
150     * @return this builder
151     */
152    public ModuleBuilder opens(String pkg) {
153        return addDirective(opens, "opens " + pkg + ";");
154    }
155
156    /**
157     * Adds a qualified "opens" directive to the declaration.
158     * @param pkg the name of the package to be opened
159     * @param module the name of the module to which it is to be opened
160     * @return this builder
161     */
162    public ModuleBuilder opensTo(String pkg, String module) {
163        return addDirective(opens, "opens " + pkg + " to " + module + ";");
164    }
165
166    /**
167     * Adds a "uses" directive to the declaration.
168     * @param service the name of the service type
169     * @return this builder
170     */
171    public ModuleBuilder uses(String service) {
172        return addDirective(uses, "uses " + service + ";");
173    }
174
175    /**
176     * Adds a "provides" directive to the declaration.
177     * @param service the name of the service type
178     * @param implementation the name of the implementation type
179     * @return this builder
180     */
181    public ModuleBuilder provides(String service, String implementation) {
182        return addDirective(provides, "provides " + service + " with " + implementation + ";");
183    }
184
185    private ModuleBuilder addDirective(List<String> directives, String directive) {
186        directives.add(directive);
187        return this;
188    }
189
190    /**
191     * Adds type definitions to the module.
192     * @param content a series of strings, each representing the content of
193     *  a compilation unit to be included with the module
194     * @return this builder
195     */
196    public ModuleBuilder classes(String... content) {
197        this.content.addAll(Arrays.asList(content));
198        return this;
199    }
200
201    /**
202     * Writes the module declaration and associated additional compilation
203     * units to a module directory within a given directory.
204     * @param srcDir the directory in which a directory will be created
205     *  to contain the source files for the module
206     * @return the directory containing the source files for the module
207     */
208    public Path write(Path srcDir) throws IOException {
209        Files.createDirectories(srcDir);
210        List<String> sources = new ArrayList<>();
211        StringBuilder sb = new StringBuilder();
212        if (!comment.isEmpty()) {
213            sb.append("/**\n * ")
214                    .append(comment.replace("\n", "\n * "))
215                    .append("\n */\n");
216        }
217        sb.append("module ").append(name).append(" {\n");
218        requires.forEach(r -> sb.append("    " + r + "\n"));
219        exports.forEach(e -> sb.append("    " + e + "\n"));
220        opens.forEach(o -> sb.append("    " + o + "\n"));
221        uses.forEach(u -> sb.append("    " + u + "\n"));
222        provides.forEach(p -> sb.append("    " + p + "\n"));
223        sb.append("}");
224        sources.add(sb.toString());
225        sources.addAll(content);
226        Path moduleSrc = srcDir.resolve(name);
227        tb.writeJavaFiles(moduleSrc, sources.toArray(new String[]{}));
228        return moduleSrc;
229    }
230
231    /**
232     * Writes the source files for the module to an interim directory,
233     * and then compiles them to a given directory.
234     * @param modules the directory in which a directory will be created
235     *    to contain the compiled class files for the module
236     * @throws IOException if an error occurs while compiling the files
237     */
238    public void build(Path modules) throws IOException {
239        build(Paths.get(modules + "Src"), modules);
240    }
241
242    /**
243     * Writes the source files for the module to a specified directory,
244     * and then compiles them to a given directory.
245     * @param srcDir the directory in which a directory will be created
246     *  to contain the source files for the module
247     * @param modules the directory in which a directory will be created
248     *    to contain the compiled class files for the module
249     * @throws IOException if an error occurs while compiling the files
250     */
251    public void build(Path src, Path modules) throws IOException {
252        Path moduleSrc = write(src);
253        String mp = modulePath.stream()
254                .map(Path::toString)
255                .collect(Collectors.joining(File.pathSeparator));
256        new JavacTask(tb)
257                .outdir(Files.createDirectories(modules.resolve(name)))
258                .options("--module-path", mp)
259                .files(tb.findJavaFiles(moduleSrc))
260                .run()
261                .writeAll();
262    }
263}
264