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 
  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 
  59 import org.testng.annotations.Test;
  60 import static org.testng.Assert.*;
  61 
  62 
  63 @Test
  64 public 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 }