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