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 /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