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