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 }