1/*
2 * Copyright (c) 2015, 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 * @bug 8132734 8144062 8165723
27 * @summary Test the extended API and the aliasing additions in JarFile that
28 *          support multi-release jar files
29 * @library /lib/testlibrary/java/util/jar /test/lib
30 * @build jdk.test.lib.RandomFactory
31 *        Compiler JarBuilder CreateMultiReleaseTestJars
32 * @run testng MultiReleaseJarAPI
33 */
34
35import java.io.File;
36import java.io.IOException;
37import java.io.InputStream;
38import java.nio.charset.StandardCharsets;
39import java.nio.file.Files;
40import java.util.Arrays;
41import java.util.Map;
42import java.util.Random;
43import java.util.concurrent.atomic.AtomicInteger;
44import java.util.jar.JarFile;
45import java.util.zip.ZipEntry;
46import java.util.zip.ZipFile;
47
48import jdk.test.lib.RandomFactory;
49
50import org.testng.Assert;
51import org.testng.annotations.AfterClass;
52import org.testng.annotations.BeforeClass;
53import org.testng.annotations.DataProvider;
54import org.testng.annotations.Test;
55
56public class MultiReleaseJarAPI {
57
58    private static final Random RANDOM = RandomFactory.getRandom();
59
60    String userdir = System.getProperty("user.dir",".");
61    CreateMultiReleaseTestJars creator =  new CreateMultiReleaseTestJars();
62    File unversioned = new File(userdir, "unversioned.jar");
63    File multirelease = new File(userdir, "multi-release.jar");
64    File signedmultirelease = new File(userdir, "signed-multi-release.jar");
65
66
67    @BeforeClass
68    public void initialize() throws Exception {
69        creator.compileEntries();
70        creator.buildUnversionedJar();
71        creator.buildMultiReleaseJar();
72        creator.buildSignedMultiReleaseJar();
73    }
74
75    @AfterClass
76    public void close() throws IOException {
77        Files.delete(unversioned.toPath());
78        Files.delete(multirelease.toPath());
79        Files.delete(signedmultirelease.toPath());
80    }
81
82    @Test
83    public void isMultiReleaseJar() throws Exception {
84        try (JarFile jf = new JarFile(unversioned)) {
85            Assert.assertFalse(jf.isMultiRelease());
86        }
87
88        try (JarFile jf = new JarFile(unversioned, true, ZipFile.OPEN_READ, Runtime.version())) {
89            Assert.assertFalse(jf.isMultiRelease());
90        }
91
92        try (JarFile jf = new JarFile(multirelease)) {
93            Assert.assertTrue(jf.isMultiRelease());
94        }
95
96        try (JarFile jf = new JarFile(multirelease, true, ZipFile.OPEN_READ, Runtime.version())) {
97            Assert.assertTrue(jf.isMultiRelease());
98        }
99
100        testCustomMultiReleaseValue("true", true);
101        testCustomMultiReleaseValue("true\r\nOther: value", true);
102        testCustomMultiReleaseValue("true\nOther: value", true);
103        testCustomMultiReleaseValue("true\rOther: value", true);
104
105        testCustomMultiReleaseValue("false", false);
106        testCustomMultiReleaseValue(" true", false);
107        testCustomMultiReleaseValue("true ", false);
108        testCustomMultiReleaseValue("true\n ", false);
109        testCustomMultiReleaseValue("true\r ", false);
110        testCustomMultiReleaseValue("true\n true", false);
111        testCustomMultiReleaseValue("true\r\n true", false);
112
113        // generate "random" Strings to use as extra attributes, and
114        // verify that Multi-Release: true is always properly matched
115        for (int i = 0; i < 100; i++) {
116            byte[] keyBytes = new byte[RANDOM.nextInt(70) + 1];
117            Arrays.fill(keyBytes, (byte)('a' + RANDOM.nextInt(24)));
118            byte[] valueBytes = new byte[RANDOM.nextInt(70) + 1];
119            Arrays.fill(valueBytes, (byte)('a' + RANDOM.nextInt(24)));
120
121            String key = new String(keyBytes, StandardCharsets.UTF_8);
122            String value = new String(valueBytes, StandardCharsets.UTF_8);
123            // test that Multi-Release: true anywhere in the manifest always
124            // return true
125            testCustomMultiReleaseValue("true", Map.of(key, value), true);
126
127            // test that we don't get any false positives
128            testCustomMultiReleaseValue("false", Map.of(key, value), false);
129        }
130    }
131
132    private void testCustomMultiReleaseValue(String value, boolean expected)
133            throws Exception {
134        testCustomMultiReleaseValue(value, Map.of(), expected);
135    }
136
137    private static final AtomicInteger JAR_COUNT = new AtomicInteger(0);
138
139    private void testCustomMultiReleaseValue(String value,
140            Map<String, String> extraAttributes, boolean expected)
141            throws Exception {
142        String fileName = "custom-mr" + JAR_COUNT.incrementAndGet() + ".jar";
143        creator.buildCustomMultiReleaseJar(fileName, value, extraAttributes);
144        File custom = new File(userdir, fileName);
145        try (JarFile jf = new JarFile(custom, true, ZipFile.OPEN_READ, Runtime.version())) {
146            Assert.assertEquals(jf.isMultiRelease(), expected);
147        }
148        Files.delete(custom.toPath());
149    }
150
151    @DataProvider(name = "versions")
152    public Object[][] createVersionData() throws Exception {
153        return new Object[][]{
154                {JarFile.baseVersion(), 8},
155                {JarFile.runtimeVersion(), Runtime.version().major()},
156                {Runtime.version(), Runtime.version().major()},
157                {Runtime.Version.parse("7.1"), JarFile.baseVersion().major()},
158                {Runtime.Version.parse("9"), 9},
159                {Runtime.Version.parse("9.1.5-ea+200"), 9}
160        };
161    }
162
163    @Test(dataProvider="versions")
164    public void testVersioning(Runtime.Version value, int xpected) throws Exception {
165        Runtime.Version expected = Runtime.Version.parse(String.valueOf(xpected));
166        Runtime.Version base = JarFile.baseVersion();
167
168        // multi-release jar, opened as unversioned
169        try (JarFile jar = new JarFile(multirelease)) {
170            Assert.assertEquals(jar.getVersion(), base);
171        }
172
173        System.err.println("test versioning for Release " + value);
174        try (JarFile jf = new JarFile(multirelease, true, ZipFile.OPEN_READ, value)) {
175            Assert.assertEquals(jf.getVersion(), expected);
176        }
177
178        // regular, unversioned, jar
179        try (JarFile jf = new JarFile(unversioned, true, ZipFile.OPEN_READ, value)) {
180            Assert.assertEquals(jf.getVersion(), base);
181        }
182    }
183
184    @Test(dataProvider="versions")
185    public void testAliasing(Runtime.Version version, int xpected) throws Exception {
186        int n = Math.max(version.major(), JarFile.baseVersion().major());
187        Runtime.Version value = Runtime.Version.parse(String.valueOf(n));
188        System.err.println("test aliasing for Release " + version);
189        String prefix;
190        if (JarFile.baseVersion().equals(value)) {
191            prefix = "";
192        } else {
193            prefix = "META-INF/versions/" + value.major() + "/";
194        }
195        // test both multi-release jars
196        readAndCompare(multirelease, value, "README", prefix + "README");
197        readAndCompare(multirelease, value, "version/Version.class", prefix + "version/Version.class");
198        // and signed multi-release jars
199        readAndCompare(signedmultirelease, value, "README", prefix + "README");
200        readAndCompare(signedmultirelease, value, "version/Version.class", prefix + "version/Version.class");
201    }
202
203    private void readAndCompare(File jar, Runtime.Version version, String name, String realName) throws Exception {
204        byte[] baseBytes;
205        byte[] versionedBytes;
206        try (JarFile jf = new JarFile(jar, true, ZipFile.OPEN_READ, JarFile.baseVersion())) {
207            ZipEntry ze = jf.getEntry(realName);
208            try (InputStream is = jf.getInputStream(ze)) {
209                baseBytes = is.readAllBytes();
210            }
211        }
212        assert baseBytes.length > 0;
213
214        try (JarFile jf = new JarFile(jar, true, ZipFile.OPEN_READ, version)) {
215            ZipEntry ze = jf.getEntry(name);
216            try (InputStream is = jf.getInputStream(ze)) {
217                versionedBytes = is.readAllBytes();
218            }
219        }
220        assert versionedBytes.length > 0;
221
222        Assert.assertTrue(Arrays.equals(baseBytes, versionedBytes));
223    }
224
225    @Test
226    public void testNames() throws Exception {
227        String rname = "version/Version.class";
228        String vname = "META-INF/versions/9/version/Version.class";
229        ZipEntry ze1;
230        ZipEntry ze2;
231        try (JarFile jf = new JarFile(multirelease)) {
232            ze1 = jf.getEntry(vname);
233        }
234        Assert.assertEquals(ze1.getName(), vname);
235        try (JarFile jf = new JarFile(multirelease, true, ZipFile.OPEN_READ, Runtime.Version.parse("9"))) {
236            ze2 = jf.getEntry(rname);
237        }
238        Assert.assertEquals(ze2.getName(), rname);
239        Assert.assertNotEquals(ze1.getName(), ze2.getName());
240    }
241}
242