1 /* 2 * Copyright (c) 2019, 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.incubator.jpackage.internal; 26 27 import java.io.*; 28 import java.nio.charset.StandardCharsets; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.nio.file.StandardCopyOption; 32 import java.text.MessageFormat; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Optional; 37 import java.util.stream.Collectors; 38 import java.util.stream.Stream; 39 import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; 40 import jdk.incubator.jpackage.internal.resources.ResourceLocator; 41 42 /** 43 * Resource file that may have the default value supplied by jpackage. It can be 44 * overridden by a file from resource directory set with {@code --resource-dir} 45 * jpackage parameter. 46 * 47 * Resource has default name and public name. Default name is the name of a file 48 * in {@code jdk.incubator.jpackage.internal.resources} package that provides the default 49 * value of the resource. 50 * 51 * Public name is a path relative to resource directory to a file with custom 52 * value of the resource. 53 * 54 * Use #setPublicName to set the public name. 55 * 56 * If #setPublicName was not called, name of file passed in #saveToFile function 57 * will be used as a public name. 58 * 59 * Use #setExternal to set arbitrary file as a source of resource. If non-null 60 * value was passed in #setExternal call that value will be used as a path to file 61 * to copy in the destination file passed in #saveToFile function call. 62 */ 63 final class OverridableResource { 64 65 OverridableResource(String defaultName) { 66 this.defaultName = defaultName; 67 } 68 69 OverridableResource setSubstitutionData(Map<String, String> v) { 70 if (v != null) { 71 // Disconnect `v` 72 substitutionData = new HashMap<>(v); 73 } else { 74 substitutionData = null; 75 } 76 return this; 77 } 78 79 OverridableResource setCategory(String v) { 80 category = v; 81 return this; 82 } 83 84 OverridableResource setResourceDir(Path v) { 85 resourceDir = v; 86 return this; 87 } 88 89 OverridableResource setResourceDir(File v) { 90 return setResourceDir(toPath(v)); 91 } 92 93 /** 94 * Set name of file to look for in resource dir. 95 * 96 * @return this 97 */ 98 OverridableResource setPublicName(Path v) { 99 publicName = v; 100 return this; 101 } 102 103 OverridableResource setPublicName(String v) { 104 return setPublicName(Path.of(v)); 105 } 106 107 OverridableResource setExternal(Path v) { 108 externalPath = v; 109 return this; 110 } 111 112 OverridableResource setExternal(File v) { 113 return setExternal(toPath(v)); 114 } 115 116 void saveToFile(Path dest) throws IOException { 117 final String printableCategory; 118 if (category != null) { 119 printableCategory = String.format("[%s]", category); 120 } else { 121 printableCategory = ""; 122 } 123 124 if (externalPath != null && externalPath.toFile().exists()) { 125 Log.verbose(MessageFormat.format(I18N.getString( 126 "message.using-custom-resource-from-file"), 127 printableCategory, 128 externalPath.toAbsolutePath().normalize())); 129 130 try (InputStream in = Files.newInputStream(externalPath)) { 131 processResourceStream(in, dest); 132 } 133 return; 134 } 135 136 final Path resourceName = Optional.ofNullable(publicName).orElse( 137 dest.getFileName()); 138 139 if (resourceDir != null) { 140 final Path customResource = resourceDir.resolve(resourceName); 141 if (customResource.toFile().exists()) { 142 Log.verbose(MessageFormat.format(I18N.getString( 143 "message.using-custom-resource"), printableCategory, 144 resourceDir.normalize().toAbsolutePath().relativize( 145 customResource.normalize().toAbsolutePath()))); 146 147 try (InputStream in = Files.newInputStream(customResource)) { 148 processResourceStream(in, dest); 149 } 150 return; 151 } 152 } 153 154 if (defaultName != null) { 155 Log.verbose(MessageFormat.format( 156 I18N.getString("message.using-default-resource"), 157 defaultName, printableCategory, resourceName)); 158 159 try (InputStream in = readDefault(defaultName)) { 160 processResourceStream(in, dest); 161 } 162 } 163 } 164 165 void saveToFile(File dest) throws IOException { 166 saveToFile(dest.toPath()); 167 } 168 169 static InputStream readDefault(String resourceName) { 170 return ResourceLocator.class.getResourceAsStream(resourceName); 171 } 172 173 static OverridableResource createResource(String defaultName, 174 Map<String, ? super Object> params) { 175 return new OverridableResource(defaultName).setResourceDir( 176 RESOURCE_DIR.fetchFrom(params)); 177 } 178 179 private static List<String> substitute(Stream<String> lines, 180 Map<String, String> substitutionData) { 181 return lines.map(line -> { 182 String result = line; 183 for (var entry : substitutionData.entrySet()) { 184 result = result.replace(entry.getKey(), Optional.ofNullable( 185 entry.getValue()).orElse("")); 186 } 187 return result; 188 }).collect(Collectors.toList()); 189 } 190 191 private static Path toPath(File v) { 192 if (v != null) { 193 return v.toPath(); 194 } 195 return null; 196 } 197 198 private void processResourceStream(InputStream rawResource, Path dest) 199 throws IOException { 200 if (substitutionData == null) { 201 Files.createDirectories(dest.getParent()); 202 Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING); 203 } else { 204 // Utf8 in and out 205 try (BufferedReader reader = new BufferedReader( 206 new InputStreamReader(rawResource, StandardCharsets.UTF_8))) { 207 Files.createDirectories(dest.getParent()); 208 Files.write(dest, substitute(reader.lines(), substitutionData)); 209 } 210 } 211 } 212 213 private Map<String, String> substitutionData; 214 private String category; 215 private Path resourceDir; 216 private Path publicName; 217 private Path externalPath; 218 private final String defaultName; 219 }