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