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