1 /*
   2  * Copyright (c) 2016, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package build.tools.listjdkinternals;
  27 
  28 import java.io.IOException;
  29 import java.io.OutputStream;
  30 import java.io.PrintStream;
  31 import java.io.UncheckedIOException;
  32 import java.lang.module.ModuleDescriptor;
  33 import java.lang.module.ModuleFinder;
  34 import java.lang.module.ModuleReference;
  35 import java.net.URI;
  36 import java.nio.file.FileSystem;
  37 import java.nio.file.FileSystems;
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 import java.nio.file.attribute.BasicFileAttributes;
  42 import java.time.LocalDateTime;
  43 import java.util.ArrayList;
  44 import java.util.Arrays;
  45 import java.util.HashMap;
  46 import java.util.HashSet;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.Set;
  50 import java.util.jar.JarEntry;
  51 import java.util.jar.JarFile;
  52 import java.util.stream.Collectors;
  53 
  54 /**
  55  * Run this tool to generate the JDK internal APIs in the previous releases
  56  * including platform-specific internal APIs.
  57  */
  58 public class ListJDKInternals {
  59     // Filter non-interesting JAR files
  60     private final static List<String> excludes = Arrays.asList(
  61         "deploy.jar",
  62         "javaws.jar",
  63         "plugin.jar",
  64         "cldrdata.jar",
  65         "localedata.jar"
  66     );
  67     private static void usage() {
  68         System.out.println("ListJDKInternals [-o <outfile>] <javaHome> [<javaHome>]*");
  69     }
  70 
  71     private static final Set<String> EXPORTED_PACKAGES = new HashSet<>();
  72 
  73     public static void main(String... args) throws IOException {
  74         List<Path> paths = new ArrayList<>();
  75         Path outFile = null;
  76         int i=0;
  77         while (i < args.length) {
  78             String arg = args[i++];
  79             if (arg.equals("-o")) {
  80                 outFile = Paths.get(args[i++]);
  81             } else {
  82                 Path p = Paths.get(arg);
  83                 if (Files.notExists(p))
  84                     throw new IllegalArgumentException(p + " not exist");
  85                 paths.add(p);
  86             }
  87         }
  88         if (paths.isEmpty()) {
  89             usage();
  90             System.exit(1);
  91         }
  92 
  93         // Get the exported APIs from the current JDK releases
  94         Path javaHome = Paths.get(System.getProperty("java.home"));
  95         ModuleFinder.ofSystem().findAll()
  96             .stream()
  97             .map(ModuleReference::descriptor)
  98             .filter(md -> !md.name().equals("jdk.unsupported"))
  99             .map(ModuleDescriptor::exports)
 100             .flatMap(Set::stream)
 101             .filter(exp -> !exp.isQualified())
 102             .map(ModuleDescriptor.Exports::source)
 103             .forEach(EXPORTED_PACKAGES::add);
 104 
 105         ListJDKInternals listJDKInternals = new ListJDKInternals(paths);
 106         if (outFile != null) {
 107             try (OutputStream out = Files.newOutputStream(outFile);
 108                  PrintStream pw = new PrintStream(out)) {
 109                 listJDKInternals.write(pw);
 110             }
 111         } else {
 112             listJDKInternals.write(System.out);
 113         }
 114     }
 115 
 116     private final Set<String> packages = new HashSet<>();
 117     ListJDKInternals(List<Path> dirs) throws IOException {
 118         for (Path p : dirs) {
 119             packages.addAll(list(p));
 120         }
 121     }
 122 
 123     private void write(PrintStream pw) {
 124         pw.println("# This file is auto-generated by ListJDKInternals tool on " +
 125                    LocalDateTime.now().toString());
 126         packages.stream().sorted()
 127                 .forEach(pw::println);
 128     }
 129 
 130     private Set<String> list(Path javaHome) throws IOException {
 131         Path jrt = javaHome.resolve("lib").resolve("modules");
 132         Path jre = javaHome.resolve("jre");
 133 
 134         if (Files.exists(jrt)) {
 135             return listModularRuntime(javaHome);
 136         } else if (Files.exists(jre.resolve("lib").resolve("rt.jar"))) {
 137             return listLegacyRuntime(javaHome);
 138         }
 139         throw new IllegalArgumentException("invalid " + javaHome);
 140     }
 141 
 142     private Set<String> listModularRuntime(Path javaHome) throws IOException {
 143         Map<String, String> env = new HashMap<>();
 144         env.put("java.home", javaHome.toString());
 145         FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), env);
 146         Path root = fs.getPath("packages");
 147         return Files.walk(root, 1)
 148                     .map(Path::getFileName)
 149                     .map(Path::toString)
 150                     .filter(pn -> !EXPORTED_PACKAGES.contains(pn))
 151                     .collect(Collectors.toSet());
 152     }
 153 
 154     private Set<String> listLegacyRuntime(Path javaHome) throws IOException {
 155         List<Path> dirs = new ArrayList<>();
 156         Path jre = javaHome.resolve("jre");
 157         Path lib = javaHome.resolve("lib");
 158 
 159         dirs.add(jre.resolve("lib"));
 160         dirs.add(jre.resolve("lib").resolve("ext"));
 161         dirs.add(lib.resolve("tools.jar"));
 162         dirs.add(lib.resolve("jconsole.jar"));
 163         Set<String> packages = new HashSet<>();
 164         for (Path d : dirs) {
 165             Files.find(d, 1, (Path p, BasicFileAttributes attr)
 166                     -> p.getFileName().toString().endsWith(".jar") &&
 167                        !excludes.contains(p.getFileName().toString()))
 168                 .map(ListJDKInternals::walkJarFile)
 169                 .flatMap(Set::stream)
 170                 .filter(pn -> !EXPORTED_PACKAGES.contains(pn))
 171                 .forEach(packages::add);
 172         }
 173         return packages;
 174     }
 175 
 176     static Set<String> walkJarFile(Path jarfile) {
 177         try (JarFile jf = new JarFile(jarfile.toFile())) {
 178             return jf.stream()
 179                      .map(JarEntry::getName)
 180                      .filter(n -> n.endsWith(".class"))
 181                      .map(ListJDKInternals::toPackage)
 182                 .collect(Collectors.toSet());
 183         } catch (IOException e) {
 184             throw new UncheckedIOException(e);
 185         }
 186     }
 187 
 188     static String toPackage(String name) {
 189         int i = name.lastIndexOf('/');
 190         if (i < 0) {
 191             System.err.format("Warning: unnamed package %s%n", name);
 192         }
 193         return i >= 0 ? name.substring(0, i).replace("/", ".") : "";
 194     }
 195 }