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