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