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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.tools.jlink.internal;
26
27import java.lang.module.Configuration;
28import java.lang.module.ModuleFinder;
29import java.nio.ByteOrder;
30import java.nio.file.Path;
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.List;
34import java.util.Map;
35import java.util.Objects;
36import java.util.Set;
37
38import jdk.internal.module.ModulePath;
39import jdk.tools.jlink.plugin.Plugin;
40import jdk.tools.jlink.plugin.PluginException;
41import jdk.tools.jlink.builder.ImageBuilder;
42
43/**
44 * API to call jlink.
45 */
46public final class Jlink {
47
48    /**
49     * Create a plugin.
50     *
51     * @param name Plugin name
52     * @param configuration Plugin configuration.
53     * @param pluginsLayer Plugins Layer. null means boot layer.
54     * @return A new plugin or null if plugin is unknown.
55     */
56    public static Plugin newPlugin(String name,
57            Map<String, String> configuration, ModuleLayer pluginsLayer) {
58        Objects.requireNonNull(name);
59        Objects.requireNonNull(configuration);
60        pluginsLayer = pluginsLayer == null ? ModuleLayer.boot() : pluginsLayer;
61        return PluginRepository.newPlugin(configuration, name, pluginsLayer);
62    }
63
64    /**
65     * A complete plugin configuration. Instances of this class are used to
66     * configure jlink.
67     */
68    public static final class PluginsConfiguration {
69
70        private final List<Plugin> plugins;
71        private final ImageBuilder imageBuilder;
72        private final String lastSorterPluginName;
73
74        /**
75         * Empty plugins configuration.
76         */
77        public PluginsConfiguration() {
78            this(Collections.emptyList());
79        }
80
81        /**
82         * Plugins configuration.
83         *
84         * @param plugins List of plugins.
85         */
86        public PluginsConfiguration(List<Plugin> plugins) {
87            this(plugins, null, null);
88        }
89
90        /**
91         * Plugins configuration with a last sorter and an ImageBuilder. No
92         * sorting can occur after the last sorter plugin. The ImageBuilder is
93         * in charge to layout the image content on disk.
94         *
95         * @param plugins List of transformer plugins.
96         * @param imageBuilder Image builder.
97         * @param lastSorterPluginName Name of last sorter plugin, no sorting
98         * can occur after it.
99         */
100        public PluginsConfiguration(List<Plugin> plugins,
101                ImageBuilder imageBuilder, String lastSorterPluginName) {
102            this.plugins = plugins == null ? Collections.emptyList()
103                    : plugins;
104            this.imageBuilder = imageBuilder;
105            this.lastSorterPluginName = lastSorterPluginName;
106        }
107
108        /**
109         * @return the plugins
110         */
111        public List<Plugin> getPlugins() {
112            return plugins;
113        }
114
115        /**
116         * @return the imageBuilder
117         */
118        public ImageBuilder getImageBuilder() {
119            return imageBuilder;
120        }
121
122        /**
123         * @return the lastSorterPluginName
124         */
125        public String getLastSorterPluginName() {
126            return lastSorterPluginName;
127        }
128
129        @Override
130        public String toString() {
131            StringBuilder builder = new StringBuilder();
132            builder.append("imagebuilder=").append(imageBuilder).append("\n");
133            StringBuilder pluginsBuilder = new StringBuilder();
134            for (Plugin p : plugins) {
135                pluginsBuilder.append(p).append(",");
136            }
137            builder.append("plugins=").append(pluginsBuilder).append("\n");
138            builder.append("lastsorter=").append(lastSorterPluginName).append("\n");
139
140            return builder.toString();
141        }
142    }
143
144    /**
145     * Jlink configuration. Instances of this class are used to configure jlink.
146     */
147    public static final class JlinkConfiguration {
148
149        private final List<Path> modulepaths;
150        private final Path output;
151        private final Set<String> modules;
152        private final Set<String> limitmods;
153        private final ByteOrder endian;
154        private final ModuleFinder finder;
155
156        /**
157         * jlink configuration,
158         *
159         * @param output Output directory, must not exist.
160         * @param modulepaths Modules paths
161         * @param modules The possibly-empty set of root modules to resolve
162         * @param limitmods Limit the universe of observable modules
163         * @param endian Jimage byte order. Native order by default
164         */
165        public JlinkConfiguration(Path output,
166                                  List<Path> modulepaths,
167                                  Set<String> modules,
168                                  Set<String> limitmods,
169                                  ByteOrder endian) {
170            if (Objects.requireNonNull(modulepaths).isEmpty()) {
171                throw new IllegalArgumentException("Empty module path");
172            }
173
174            this.output = output;
175            this.modulepaths = modulepaths;
176            this.modules = Objects.requireNonNull(modules);
177            this.limitmods = Objects.requireNonNull(limitmods);
178            this.endian = Objects.requireNonNull(endian);
179            this.finder = moduleFinder();
180        }
181
182        /**
183         * @return the modulepaths
184         */
185        public List<Path> getModulepaths() {
186            return modulepaths;
187        }
188
189        /**
190         * @return the byte ordering
191         */
192        public ByteOrder getByteOrder() {
193            return endian;
194        }
195
196        /**
197         * @return the output
198         */
199        public Path getOutput() {
200            return output;
201        }
202
203        /**
204         * @return the modules
205         */
206        public Set<String> getModules() {
207            return modules;
208        }
209
210        /**
211         * @return the limitmods
212         */
213        public Set<String> getLimitmods() {
214            return limitmods;
215        }
216
217        /**
218         * Returns {@link ModuleFinder} that finds all observable modules
219         * for this jlink configuration.
220         */
221        public ModuleFinder finder() {
222            return finder;
223        }
224
225        /**
226         * Returns a {@link Configuration} of the given module path,
227         * root modules with full service binding.
228         */
229        public Configuration resolveAndBind()
230        {
231            return Configuration.empty().resolveAndBind(finder,
232                                                        ModuleFinder.of(),
233                                                        modules);
234        }
235
236        /**
237         * Returns a {@link Configuration} of the given module path,
238         * root modules with no service binding.
239         */
240        public Configuration resolve()
241        {
242            return Configuration.empty().resolve(finder,
243                                                 ModuleFinder.of(),
244                                                 modules);
245        }
246
247        private ModuleFinder moduleFinder() {
248            Path[] entries = modulepaths.toArray(new Path[0]);
249            ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries);
250            if (!limitmods.isEmpty()) {
251                finder = JlinkTask.limitFinder(finder, limitmods, modules);
252            }
253            return finder;
254        }
255
256        @Override
257        public String toString() {
258            StringBuilder builder = new StringBuilder();
259
260            builder.append("output=").append(output).append("\n");
261            StringBuilder pathsBuilder = new StringBuilder();
262            for (Path p : modulepaths) {
263                pathsBuilder.append(p).append(",");
264            }
265            builder.append("modulepaths=").append(pathsBuilder).append("\n");
266
267            StringBuilder modsBuilder = new StringBuilder();
268            for (String p : modules) {
269                modsBuilder.append(p).append(",");
270            }
271            builder.append("modules=").append(modsBuilder).append("\n");
272
273            StringBuilder limitsBuilder = new StringBuilder();
274            for (String p : limitmods) {
275                limitsBuilder.append(p).append(",");
276            }
277            builder.append("limitmodules=").append(limitsBuilder).append("\n");
278            builder.append("endian=").append(endian).append("\n");
279            return builder.toString();
280        }
281    }
282
283    /**
284     * Jlink instance constructor, if a security manager is set, the jlink
285     * permission is checked.
286     */
287    public Jlink() {
288        if (System.getSecurityManager() != null) {
289            System.getSecurityManager().
290                    checkPermission(new JlinkPermission("jlink"));
291        }
292    }
293
294    /**
295     * Build the image.
296     *
297     * @param config Jlink config, must not be null.
298     * @throws PluginException
299     */
300    public void build(JlinkConfiguration config) {
301        build(config, null);
302    }
303
304    /**
305     * Build the image with a plugin configuration.
306     *
307     * @param config Jlink config, must not be null.
308     * @param pluginsConfig Plugins config, can be null
309     * @throws PluginException
310     */
311    public void build(JlinkConfiguration config, PluginsConfiguration pluginsConfig) {
312        Objects.requireNonNull(config);
313        if (pluginsConfig == null) {
314            pluginsConfig = new PluginsConfiguration();
315        }
316
317        // add all auto-enabled plugins from boot layer
318        pluginsConfig = addAutoEnabledPlugins(pluginsConfig);
319
320        try {
321            JlinkTask.createImage(config, pluginsConfig);
322        } catch (Exception ex) {
323            throw new PluginException(ex);
324        }
325    }
326
327    private PluginsConfiguration addAutoEnabledPlugins(PluginsConfiguration pluginsConfig) {
328        List<Plugin> plugins = new ArrayList<>(pluginsConfig.getPlugins());
329        List<Plugin> bootPlugins = PluginRepository.getPlugins(ModuleLayer.boot());
330        for (Plugin bp : bootPlugins) {
331            if (Utils.isAutoEnabled(bp)) {
332                try {
333                    bp.configure(Collections.emptyMap());
334                } catch (IllegalArgumentException e) {
335                    if (JlinkTask.DEBUG) {
336                        System.err.println("Plugin " + bp.getName() + " threw exception with config: {}");
337                        e.printStackTrace();
338                    }
339                    throw e;
340                }
341                plugins.add(bp);
342            }
343        }
344        return new PluginsConfiguration(plugins, pluginsConfig.getImageBuilder(),
345            pluginsConfig.getLastSorterPluginName());
346    }
347
348    /**
349     * Post process the image with a plugin configuration.
350     *
351     * @param image Existing image.
352     * @param plugins Plugins cannot be null
353     */
354    public void postProcess(ExecutableImage image, List<Plugin> plugins) {
355        Objects.requireNonNull(image);
356        Objects.requireNonNull(plugins);
357        try {
358            JlinkTask.postProcessImage(image, plugins);
359        } catch (Exception ex) {
360            throw new PluginException(ex);
361        }
362    }
363}
364