1 /* 2 * Copyright (c) 2016, 2023, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @enablePreview 27 * @modules java.base/jdk.internal.module 28 * @library /test/lib 29 * @build MultiReleaseJarTest 30 * jdk.test.lib.util.JarUtils 31 * jdk.test.lib.util.ModuleInfoWriter 32 * @run testng MultiReleaseJarTest 33 * @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest 34 * @summary Basic test of modular JARs as multi-release JARs 35 */ 36 37 import java.io.File; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.lang.module.ModuleDescriptor; 41 import java.lang.module.ModuleFinder; 42 import java.lang.module.ModuleReader; 43 import java.lang.module.ModuleReference; 44 import java.net.URI; 45 import java.net.URLConnection; 46 import java.nio.ByteBuffer; 47 import java.nio.file.Files; 48 import java.nio.file.Path; 49 import java.nio.file.Paths; 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Optional; 56 import java.util.Set; 57 import java.util.jar.Attributes; 58 import java.util.jar.Manifest; 59 60 import jdk.test.lib.util.ModuleInfoWriter; 61 import jdk.test.lib.util.JarUtils; 62 63 import org.testng.annotations.Test; 64 import static org.testng.Assert.*; 65 66 67 @Test 68 public class MultiReleaseJarTest { 69 70 private static final String MODULE_INFO = "module-info.class"; 71 72 private static final int VERSION = Runtime.version().major(); 73 74 // are multi-release JARs enabled? 75 private static final boolean MULTI_RELEASE; 76 static { 77 String s = System.getProperty("jdk.util.jar.enableMultiRelease"); 78 MULTI_RELEASE = (s == null || Boolean.parseBoolean(s)); 79 } 80 81 /** 82 * Basic test of a multi-release JAR. 83 */ 84 public void testBasic() throws Exception { 85 String name = "m1"; 86 87 ModuleDescriptor descriptor = ModuleDescriptor.newModule(name) 88 .requires("java.base") 89 .build(); 90 91 Path jar = new JarBuilder(name) 92 .moduleInfo("module-info.class", descriptor) 93 .resource("p/Main.class") 94 .resource("p/Helper.class") 95 .resource("META-INF/versions/" + VERSION + "/p/Helper.class") 96 .resource("META-INF/versions/" + VERSION + "/p/internal/Helper.class") 97 .build(); 98 99 // find the module 100 ModuleFinder finder = ModuleFinder.of(jar); 101 Optional<ModuleReference> omref = finder.find(name); 102 assertTrue((omref.isPresent())); 103 ModuleReference mref = omref.get(); 104 105 // check module packages 106 descriptor = mref.descriptor(); 107 Set<String> packages = descriptor.packages(); 108 assertTrue(packages.contains("p")); 109 if (MULTI_RELEASE) { 110 assertTrue(packages.size() == 2); 111 assertTrue(packages.contains("p.internal")); 112 } else { 113 assertTrue(packages.size() == 1); 114 } 115 } 116 117 /** 118 * Test a multi-release JAR with a module-info.class in the versioned 119 * section of the JAR. 120 */ 121 public void testModuleInfoInVersionedSection() throws Exception { 122 String name = "m1"; 123 124 ModuleDescriptor descriptor1 = ModuleDescriptor.newModule(name) 125 .requires("java.base") 126 .build(); 127 128 // module descriptor for versioned section 129 ModuleDescriptor descriptor2 = ModuleDescriptor.newModule(name) 130 .requires("java.base") 131 .requires("jdk.unsupported") 132 .build(); 133 134 Path jar = new JarBuilder(name) 135 .moduleInfo(MODULE_INFO, descriptor1) 136 .resource("p/Main.class") 137 .resource("p/Helper.class") 138 .moduleInfo("META-INF/versions/" + VERSION + "/" + MODULE_INFO, descriptor2) 139 .resource("META-INF/versions/" + VERSION + "/p/Helper.class") 140 .resource("META-INF/versions/" + VERSION + "/p/internal/Helper.class") 141 .build(); 142 143 // find the module 144 ModuleFinder finder = ModuleFinder.of(jar); 145 Optional<ModuleReference> omref = finder.find(name); 146 assertTrue((omref.isPresent())); 147 ModuleReference mref = omref.get(); 148 149 // ensure that the right module-info.class is loaded 150 ModuleDescriptor descriptor = mref.descriptor(); 151 assertEquals(descriptor.name(), name); 152 if (MULTI_RELEASE) { 153 assertEquals(descriptor.requires(), descriptor2.requires()); 154 } else { 155 assertEquals(descriptor.requires(), descriptor1.requires()); 156 } 157 } 158 159 /** 160 * Test multi-release JAR as an automatic module. 161 */ 162 public void testAutomaticModule() throws Exception { 163 String name = "m"; 164 165 Path jar = new JarBuilder(name) 166 .resource("p/Main.class") 167 .resource("p/Helper.class") 168 .resource("META-INF/versions/" + VERSION + "/p/Helper.class") 169 .resource("META-INF/versions/" + VERSION + "/p/internal/Helper.class") 170 .build(); 171 172 // find the module 173 ModuleFinder finder = ModuleFinder.of(jar); 174 Optional<ModuleReference> omref = finder.find(name); 175 assertTrue((omref.isPresent())); 176 ModuleReference mref = omref.get(); 177 178 // check module packages 179 ModuleDescriptor descriptor = mref.descriptor(); 180 Set<String> packages = descriptor.packages(); 181 if (MULTI_RELEASE) { 182 assertTrue(packages.size() == 2); 183 assertTrue(packages.contains("p.internal")); 184 } else { 185 assertTrue(packages.size() == 1); 186 } 187 } 188 189 /** 190 * Exercise ModuleReader on a multi-release JAR 191 */ 192 public void testModuleReader() throws Exception { 193 String name = "m1"; 194 195 ModuleDescriptor descriptor1 = ModuleDescriptor.newModule(name) 196 .requires("java.base") 197 .build(); 198 199 // module descriptor for versioned section 200 ModuleDescriptor descriptor2 = ModuleDescriptor.newModule(name) 201 .requires("java.base") 202 .requires("jdk.unsupported") 203 .build(); 204 205 Path jar = new JarBuilder(name) 206 .moduleInfo(MODULE_INFO, descriptor1) 207 .moduleInfo("META-INF/versions/" + VERSION + "/" + MODULE_INFO, descriptor2) 208 .build(); 209 210 // find the module 211 ModuleFinder finder = ModuleFinder.of(jar); 212 Optional<ModuleReference> omref = finder.find(name); 213 assertTrue((omref.isPresent())); 214 ModuleReference mref = omref.get(); 215 216 ModuleDescriptor expected; 217 if (MULTI_RELEASE) { 218 expected = descriptor2; 219 } else { 220 expected = descriptor1; 221 } 222 223 // test ModuleReader by reading module-info.class resource 224 try (ModuleReader reader = mref.open()) { 225 226 // open resource 227 Optional<InputStream> oin = reader.open(MODULE_INFO); 228 assertTrue(oin.isPresent()); 229 try (InputStream in = oin.get()) { 230 checkRequires(ModuleDescriptor.read(in), expected); 231 } 232 233 // read resource 234 Optional<ByteBuffer> obb = reader.read(MODULE_INFO); 235 assertTrue(obb.isPresent()); 236 ByteBuffer bb = obb.get(); 237 try { 238 checkRequires(ModuleDescriptor.read(bb), expected); 239 } finally { 240 reader.release(bb); 241 } 242 243 // find resource 244 Optional<URI> ouri = reader.find(MODULE_INFO); 245 assertTrue(ouri.isPresent()); 246 URI uri = ouri.get(); 247 248 String expectedTail = "!/"; 249 if (MULTI_RELEASE) 250 expectedTail += "META-INF/versions/" + VERSION + "/"; 251 expectedTail += MODULE_INFO; 252 assertTrue(uri.toString().endsWith(expectedTail)); 253 254 URLConnection uc = uri.toURL().openConnection(); 255 uc.setUseCaches(false); 256 try (InputStream in = uc.getInputStream()) { 257 checkRequires(ModuleDescriptor.read(in), expected); 258 } 259 260 } 261 } 262 263 /** 264 * Check that two ModuleDescriptor have the same requires 265 */ 266 static void checkRequires(ModuleDescriptor md1, ModuleDescriptor md2) { 267 assertEquals(md1.requires(), md2.requires()); 268 } 269 270 /** 271 * A builder of multi-release JAR files. 272 */ 273 static class JarBuilder { 274 private String name; 275 private Set<String> resources = new HashSet<>(); 276 private Map<String, ModuleDescriptor> descriptors = new HashMap<>(); 277 278 JarBuilder(String name) { 279 this.name = name; 280 } 281 282 /** 283 * Adds a module-info.class to the JAR file. 284 */ 285 JarBuilder moduleInfo(String name, ModuleDescriptor descriptor) { 286 descriptors.put(name, descriptor); 287 return this; 288 } 289 290 /** 291 * Adds a dummy resource to the JAR file. 292 */ 293 JarBuilder resource(String name) { 294 resources.add(name); 295 return this; 296 } 297 298 /** 299 * Create the multi-release JAR, returning its file path. 300 */ 301 Path build() throws Exception { 302 Path dir = Files.createTempDirectory(Paths.get(""), "jar"); 303 List<Path> files = new ArrayList<>(); 304 305 // write the module-info.class 306 for (Map.Entry<String, ModuleDescriptor> e : descriptors.entrySet()) { 307 String name = e.getKey(); 308 ModuleDescriptor descriptor = e.getValue(); 309 Path mi = Paths.get(name.replace('/', File.separatorChar)); 310 Path parent = dir.resolve(mi).getParent(); 311 if (parent != null) 312 Files.createDirectories(parent); 313 try (OutputStream out = Files.newOutputStream(dir.resolve(mi))) { 314 ModuleInfoWriter.write(descriptor, out); 315 } 316 files.add(mi); 317 } 318 319 // write the dummy resources 320 for (String name : resources) { 321 Path file = Paths.get(name.replace('/', File.separatorChar)); 322 // create dummy resource 323 Path parent = dir.resolve(file).getParent(); 324 if (parent != null) 325 Files.createDirectories(parent); 326 Files.createFile(dir.resolve(file)); 327 files.add(file); 328 } 329 330 Manifest man = new Manifest(); 331 Attributes attrs = man.getMainAttributes(); 332 attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 333 attrs.put(Attributes.Name.MULTI_RELEASE, "true"); 334 335 Path jarfile = Paths.get(name + ".jar"); 336 JarUtils.createJarFile(jarfile, man, dir, files.toArray(new Path[0])); 337 return jarfile; 338 } 339 } 340 }