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 * @bug 8163798
27 * @summary basic tests for multi-release jar versioned streams
28 * @library /test/lib
29 * @modules jdk.jartool/sun.tools.jar java.base/jdk.internal.util.jar
30 * @build jdk.test.lib.Platform
31 *        jdk.test.lib.util.FileUtils
32 * @run testng TestVersionedStream
33 */
34
35import org.testng.Assert;
36import org.testng.annotations.AfterClass;
37import org.testng.annotations.DataProvider;
38import org.testng.annotations.Test;
39
40import java.io.File;
41import java.io.IOException;
42import java.io.InputStream;
43import java.io.UncheckedIOException;
44import java.nio.file.Files;
45import java.nio.file.Path;
46import java.nio.file.Paths;
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.HashMap;
50import java.util.Iterator;
51import java.util.LinkedHashSet;
52import java.util.List;
53import java.util.Map;
54import java.util.Set;
55import java.util.jar.JarEntry;
56import java.util.jar.JarFile;
57import java.util.stream.Collectors;
58import java.util.stream.Stream;
59import java.util.zip.ZipFile;
60
61import jdk.test.lib.util.FileUtils;
62
63public class TestVersionedStream {
64    private final Path userdir;
65    private final Set<String> unversionedEntryNames;
66
67    public TestVersionedStream() throws IOException {
68        userdir = Paths.get(System.getProperty("user.dir", "."));
69
70        // These are not real class files even though they end with .class.
71        // They are resource files so jar tool validation won't reject them.
72        // But they are what we want to test, especially q/Bar.class that
73        // could be in a concealed package if this was a modular multi-release
74        // jar.
75        createFiles(
76                "base/p/Bar.class",
77                "base/p/Foo.class",
78                "base/p/Main.class",
79                "v9/p/Foo.class",
80                "v10/p/Foo.class",
81                "v10/q/Bar.class",
82                "v11/p/Bar.class",
83                "v11/p/Foo.class"
84        );
85
86        jar("cf mmr.jar -C base . --release 9 -C v9 . " +
87                "--release 10 -C v10 . --release 11 -C v11 .");
88
89        System.out.println("Contents of mmr.jar\n=======");
90
91        try(JarFile jf = new JarFile("mmr.jar")) {
92            unversionedEntryNames = jf.stream()
93                    .map(je -> je.getName())
94                    .peek(System.out::println)
95                    .map(nm -> nm.startsWith("META-INF/versions/")
96                            ? nm.replaceFirst("META-INF/versions/\\d+/", "")
97                            : nm)
98                    .collect(Collectors.toCollection(LinkedHashSet::new));
99        }
100
101        System.out.println("=======");
102    }
103
104    @AfterClass
105    public void close() throws IOException {
106        Files.walk(userdir, 1)
107                .filter(p -> !p.equals(userdir))
108                .forEach(p -> {
109                    try {
110                        if (Files.isDirectory(p)) {
111                            FileUtils.deleteFileTreeWithRetry(p);
112                        } else {
113                            FileUtils.deleteFileIfExistsWithRetry(p);
114                        }
115                    } catch (IOException x) {
116                        throw new UncheckedIOException(x);
117                    }
118                });
119    }
120
121    @DataProvider
122    public Object[][] data() {
123        return new Object[][] {
124            {Runtime.Version.parse("8")},
125            {Runtime.Version.parse("9")},
126            {Runtime.Version.parse("10")},
127            {Runtime.Version.parse("11")},
128            {JarFile.baseVersion()},
129            {JarFile.runtimeVersion()}
130        };
131    }
132
133    @Test(dataProvider="data")
134    public void test(Runtime.Version version) throws Exception {
135        try (JarFile jf = new JarFile(new File("mmr.jar"), false, ZipFile.OPEN_READ, version);
136             Stream<JarEntry> jes = jdk.internal.util.jar.VersionedStream.stream(jf))
137        {
138            Assert.assertNotNull(jes);
139
140            // put versioned entries in list so we can reuse them
141            List<JarEntry> versionedEntries = jes.collect(Collectors.toList());
142
143            Assert.assertTrue(versionedEntries.size() > 0);
144
145            // also keep the names
146            List<String> versionedNames = new ArrayList<>(versionedEntries.size());
147
148            // verify the correct order while building enames
149            Iterator<String> allIt = unversionedEntryNames.iterator();
150            Iterator<JarEntry> verIt = versionedEntries.iterator();
151            boolean match = false;
152
153            while (verIt.hasNext()) {
154                match = false;
155                if (!allIt.hasNext()) break;
156                String name = verIt.next().getName();
157                versionedNames.add(name);
158                while (allIt.hasNext()) {
159                    if (name.equals(allIt.next())) {
160                        match = true;
161                        break;
162                    }
163                }
164            }
165            if (!match) {
166                Assert.fail("versioned entries not in same order as unversioned entries");
167            }
168
169            // verify the contents
170            Map<String,String> contents = new HashMap<>();
171            contents.put("p/Bar.class", "base/p/Bar.class");
172            contents.put("p/Main.class", "base/p/Main.class");
173            switch (version.major()) {
174                case 8:
175                    contents.put("p/Foo.class", "base/p/Foo.class");
176                    break;
177                case 9:
178                    contents.put("p/Foo.class", "v9/p/Foo.class");
179                    break;
180                case 10:
181                    contents.put("p/Foo.class", "v10/p/Foo.class");
182                    contents.put("q/Bar.class", "v10/q/Bar.class");
183                    break;
184                case 11:
185                    contents.put("p/Bar.class", "v11/p/Bar.class");
186                    contents.put("p/Foo.class", "v11/p/Foo.class");
187                    contents.put("q/Bar.class", "v10/q/Bar.class");
188                    break;
189                default:
190                    Assert.fail("Test out of date, please add more cases");
191            }
192
193            contents.entrySet().stream().forEach(e -> {
194                String name = e.getKey();
195                int i = versionedNames.indexOf(name);
196                Assert.assertTrue(i != -1, name + " not in enames");
197                JarEntry je = versionedEntries.get(i);
198                try (InputStream is = jf.getInputStream(je)) {
199                    String s = new String(is.readAllBytes()).replaceAll(System.lineSeparator(), "");
200                    Assert.assertTrue(s.endsWith(e.getValue()), s);
201                } catch (IOException x) {
202                    throw new UncheckedIOException(x);
203                }
204            });
205        }
206    }
207
208    private void createFiles(String... files) {
209        ArrayList<String> list = new ArrayList();
210        Arrays.stream(files)
211                .map(f -> Paths.get(userdir.toAbsolutePath().toString(), f))
212                .forEach(p -> {
213                    try {
214                        Files.createDirectories(p.getParent());
215                        Files.createFile(p);
216                        list.clear();
217                        list.add(p.toString().replace(File.separatorChar, '/'));
218                        Files.write(p, list);
219                    } catch (IOException x) {
220                        throw new UncheckedIOException(x);
221                    }});
222    }
223
224    private void jar(String args) {
225        new sun.tools.jar.Main(System.out, System.err, "jar")
226                .run(args.split(" +"));
227    }
228}
229