1/*
2 * Copyright (c) 2016, 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.plugins;
26
27import java.io.ByteArrayOutputStream;
28import java.io.FileInputStream;
29import java.io.IOException;
30import java.io.PrintWriter;
31import java.io.UncheckedIOException;
32import java.lang.module.ModuleDescriptor;
33import java.util.EnumSet;
34import java.util.HashMap;
35import java.util.Map;
36import java.util.Properties;
37import java.util.Set;
38import java.util.function.Function;
39import java.util.stream.Collectors;
40import jdk.tools.jlink.internal.ModuleSorter;
41import jdk.tools.jlink.internal.Utils;
42import jdk.tools.jlink.plugin.PluginException;
43import jdk.tools.jlink.plugin.ResourcePool;
44import jdk.tools.jlink.plugin.ResourcePoolBuilder;
45import jdk.tools.jlink.plugin.ResourcePoolEntry;
46import jdk.tools.jlink.plugin.ResourcePoolModule;
47import jdk.tools.jlink.plugin.Plugin;
48
49/**
50 * This plugin adds/deletes information for 'release' file.
51 */
52public final class ReleaseInfoPlugin implements Plugin {
53    // option name
54    public static final String NAME = "release-info";
55    public static final String KEYS = "keys";
56    private final Map<String, String> release = new HashMap<>();
57
58    @Override
59    public Category getType() {
60        return Category.METAINFO_ADDER;
61    }
62
63    @Override
64    public String getName() {
65        return NAME;
66    }
67
68    @Override
69    public String getDescription() {
70        return PluginsResourceBundle.getDescription(NAME);
71    }
72
73    @Override
74    public Set<State> getState() {
75        return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
76    }
77
78    @Override
79    public boolean hasArguments() {
80        return true;
81    }
82
83    @Override
84    public String getArgumentsDescription() {
85        return PluginsResourceBundle.getArgument(NAME);
86    }
87
88    @Override
89    public void configure(Map<String, String> config) {
90        String operation = config.get(NAME);
91        if (operation == null) {
92            return;
93        }
94
95        switch (operation) {
96            case "add": {
97                // leave it to open-ended! source, java_version, java_full_version
98                // can be passed via this option like:
99                //
100                //     --release-info add:build_type=fastdebug,source=openjdk,java_version=9
101                // and put whatever value that was passed in command line.
102
103                config.keySet().stream()
104                      .filter(s -> !NAME.equals(s))
105                      .forEach(s -> release.put(s, config.get(s)));
106            }
107            break;
108
109            case "del": {
110                // --release-info del:keys=openjdk,java_version
111                Utils.parseList(config.get(KEYS)).stream().forEach((k) -> {
112                    release.remove(k);
113                });
114            }
115            break;
116
117            default: {
118                // --release-info <file>
119                Properties props = new Properties();
120                try (FileInputStream fis = new FileInputStream(operation)) {
121                    props.load(fis);
122                } catch (IOException exp) {
123                    throw new UncheckedIOException(exp);
124                }
125                props.forEach((k, v) -> release.put(k.toString(), v.toString()));
126            }
127            break;
128        }
129    }
130
131    @Override
132    public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
133        in.transformAndCopy(Function.identity(), out);
134
135        ResourcePoolModule javaBase = in.moduleView().findModule("java.base")
136                                                     .orElse(null);
137        if (javaBase == null || javaBase.targetPlatform() == null) {
138            throw new PluginException("ModuleTarget attribute is missing for java.base module");
139        }
140
141        // fill release information available from transformed "java.base" module!
142        ModuleDescriptor desc = javaBase.descriptor();
143        desc.version().ifPresent(v -> release.put("JAVA_VERSION",
144                                                  quote(parseVersion(v))));
145
146        // put topological sorted module names separated by space
147        release.put("MODULES",  new ModuleSorter(in.moduleView())
148               .sorted().map(ResourcePoolModule::name)
149               .collect(Collectors.joining(" ", "\"", "\"")));
150
151        // create a TOP level ResourcePoolEntry for "release" file.
152        out.add(ResourcePoolEntry.create("/java.base/release",
153                                         ResourcePoolEntry.Type.TOP,
154                                         releaseFileContent()));
155        return out.build();
156    }
157
158    // Parse version string and return a string that includes only version part
159    // leaving "pre", "build" information. See also: java.lang.Runtime.Version.
160    private static String parseVersion(ModuleDescriptor.Version v) {
161        return Runtime.Version.parse(v.toString())
162                      .version()
163                      .stream()
164                      .map(Object::toString)
165                      .collect(Collectors.joining("."));
166    }
167
168    private static String quote(String str) {
169        return "\"" + str + "\"";
170    }
171
172    private byte[] releaseFileContent() {
173        ByteArrayOutputStream baos = new ByteArrayOutputStream();
174        try (PrintWriter pw = new PrintWriter(baos)) {
175            release.entrySet().stream()
176                   .sorted(Map.Entry.comparingByKey())
177                   .forEach(e -> pw.format("%s=%s%n", e.getKey(), e.getValue()));
178        }
179        return baos.toByteArray();
180    }
181}
182