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  */
25 package jdk.tools.jlink.internal.plugins;
26 
27 import java.io.ByteArrayOutputStream;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.io.UncheckedIOException;
32 import java.lang.module.ModuleDescriptor;
33 import java.util.EnumSet;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.Properties;
37 import java.util.Set;
38 import java.util.function.Function;
39 import java.util.stream.Collectors;
40 import jdk.tools.jlink.internal.ModuleSorter;
41 import jdk.tools.jlink.internal.Utils;
42 import jdk.tools.jlink.plugin.PluginException;
43 import jdk.tools.jlink.plugin.ResourcePool;
44 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
45 import jdk.tools.jlink.plugin.ResourcePoolEntry;
46 import jdk.tools.jlink.plugin.ResourcePoolModule;
47 import jdk.tools.jlink.plugin.Plugin;
48 
49 /**
50  * This plugin adds/deletes information for 'release' file.
51  */
52 public 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
getType()59     public Category getType() {
60         return Category.METAINFO_ADDER;
61     }
62 
63     @Override
getName()64     public String getName() {
65         return NAME;
66     }
67 
68     @Override
getDescription()69     public String getDescription() {
70         return PluginsResourceBundle.getDescription(NAME);
71     }
72 
73     @Override
getState()74     public Set<State> getState() {
75         return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
76     }
77 
78     @Override
hasArguments()79     public boolean hasArguments() {
80         return true;
81     }
82 
83     @Override
getArgumentsDescription()84     public String getArgumentsDescription() {
85         return PluginsResourceBundle.getArgument(NAME);
86     }
87 
88     @Override
configure(Map<String, String> config)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                 String keys = config.get(KEYS);
112                 if (keys == null || keys.isEmpty()) {
113                     throw new IllegalArgumentException("No key specified for delete");
114                 }
115                 Utils.parseList(keys).stream().forEach((k) -> {
116                     release.remove(k);
117                 });
118             }
119             break;
120 
121             default: {
122                 // --release-info <file>
123                 Properties props = new Properties();
124                 try (FileInputStream fis = new FileInputStream(operation)) {
125                     props.load(fis);
126                 } catch (IOException exp) {
127                     throw new UncheckedIOException(exp);
128                 }
129                 props.forEach((k, v) -> release.put(k.toString(), v.toString()));
130             }
131             break;
132         }
133     }
134 
135     @Override
transform(ResourcePool in, ResourcePoolBuilder out)136     public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
137         in.transformAndCopy(Function.identity(), out);
138 
139         ResourcePoolModule javaBase = in.moduleView().findModule("java.base")
140                                                      .orElse(null);
141         if (javaBase == null || javaBase.targetPlatform() == null) {
142             throw new PluginException("ModuleTarget attribute is missing for java.base module");
143         }
144 
145         // fill release information available from transformed "java.base" module!
146         ModuleDescriptor desc = javaBase.descriptor();
147         desc.version().ifPresent(v -> release.put("JAVA_VERSION",
148                                                   quote(parseVersion(v))));
149 
150         // put topological sorted module names separated by space
151         release.put("MODULES",  new ModuleSorter(in.moduleView())
152                .sorted().map(ResourcePoolModule::name)
153                .collect(Collectors.joining(" ", "\"", "\"")));
154 
155         // create a TOP level ResourcePoolEntry for "release" file.
156         out.add(ResourcePoolEntry.create("/java.base/release",
157                                          ResourcePoolEntry.Type.TOP,
158                                          releaseFileContent()));
159         return out.build();
160     }
161 
162     // Parse version string and return a string that includes only version part
163     // leaving "pre", "build" information. See also: java.lang.Runtime.Version.
parseVersion(ModuleDescriptor.Version v)164     private static String parseVersion(ModuleDescriptor.Version v) {
165         return Runtime.Version.parse(v.toString())
166                       .version()
167                       .stream()
168                       .map(Object::toString)
169                       .collect(Collectors.joining("."));
170     }
171 
quote(String str)172     private static String quote(String str) {
173         return "\"" + str + "\"";
174     }
175 
releaseFileContent()176     private byte[] releaseFileContent() {
177         ByteArrayOutputStream baos = new ByteArrayOutputStream();
178         try (PrintWriter pw = new PrintWriter(baos)) {
179             release.entrySet().stream()
180                    .sorted(Map.Entry.comparingByKey())
181                    .forEach(e -> pw.format("%s=%s%n", e.getKey(), e.getValue()));
182         }
183         return baos.toByteArray();
184     }
185 }
186