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