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