8050804: (jdeps) Recommend supported API to replace use of JDK internal API

Reviewed-by: dfuchs
diff --git a/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java b/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java
index 04d6e71..afaa38e 100644
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java
@@ -114,6 +114,11 @@
         return false;
     }
 
+    public Set<String> dependences(Archive source) {
+        ArchiveDeps result = results.get(source);
+        return result.targetDependences();
+    }
+
     public interface Visitor {
         /**
          * Visits a recorded dependency from origin to target which can be
@@ -179,6 +184,14 @@
             return deps;
         }
 
+        Set<String> targetDependences() {
+            Set<String> targets = new HashSet<>();
+            for (Dep d : deps) {
+                targets.add(d.target());
+            }
+            return targets;
+        }
+
         Set<Archive> requires() {
             return requires;
         }
diff --git a/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
index c3110e7..e75e9c7 100644
--- a/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
@@ -236,6 +236,11 @@
                 task.options.showLabel = true;
             }
         },
+        new HiddenOption(false, "-q", "-quiet") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.nowarning = true;
+            }
+        },
         new HiddenOption(true, "-depth") {
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
                 try {
@@ -249,7 +254,7 @@
 
     private static final String PROGNAME = "jdeps";
     private final Options options = new Options();
-    private final List<String> classes = new ArrayList<String>();
+    private final List<String> classes = new ArrayList<>();
 
     private PrintWriter log;
     void setLog(PrintWriter out) {
@@ -320,7 +325,9 @@
 
         Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
             @Override
-            public boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive) {
+            public boolean accepts(Location origin, Archive originArchive,
+                                   Location target, Archive targetArchive)
+            {
                 if (options.findJDKInternals) {
                     // accepts target that is JDK class but not exported
                     return isJDKArchive(targetArchive) &&
@@ -344,6 +351,10 @@
         } else {
             printRawOutput(log, analyzer);
         }
+
+        if (options.findJDKInternals && !options.nowarning) {
+            showReplacements(analyzer);
+        }
         return true;
     }
 
@@ -693,6 +704,7 @@
         boolean apiOnly;
         boolean showLabel;
         boolean findJDKInternals;
+        boolean nowarning;
         // default is to show package-level dependencies
         // and filter references from same package
         Analyzer.Type verbose = PACKAGE;
@@ -709,6 +721,7 @@
     private static class ResourceBundleHelper {
         static final ResourceBundle versionRB;
         static final ResourceBundle bundle;
+        static final ResourceBundle jdkinternals;
 
         static {
             Locale locale = Locale.getDefault();
@@ -722,6 +735,11 @@
             } catch (MissingResourceException e) {
                 throw new InternalError("version.resource.missing");
             }
+            try {
+                jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals");
+            } catch (MissingResourceException e) {
+                throw new InternalError("Cannot find jdkinternals resource bundle");
+            }
         }
     }
 
@@ -935,4 +953,50 @@
         }
         return Profile.getProfile(pn);
     }
+
+    /**
+     * Returns the recommended replacement API for the given classname;
+     * or return null if replacement API is not known.
+     */
+    private String replacementFor(String cn) {
+        String name = cn;
+        String value = null;
+        while (value == null && name != null) {
+            try {
+                value = ResourceBundleHelper.jdkinternals.getString(name);
+            } catch (MissingResourceException e) {
+                // go up one subpackage level
+                int i = name.lastIndexOf('.');
+                name = i > 0 ? name.substring(0, i) : null;
+            }
+        }
+        return value;
+    };
+
+    private void showReplacements(Analyzer analyzer) {
+        Map<String,String> jdkinternals = new TreeMap<>();
+        boolean useInternals = false;
+        for (Archive source : sourceLocations) {
+            useInternals = useInternals || analyzer.hasDependences(source);
+            for (String cn : analyzer.dependences(source)) {
+                String repl = replacementFor(cn);
+                if (repl != null && !jdkinternals.containsKey(cn)) {
+                    jdkinternals.put(cn, repl);
+                }
+            }
+        }
+        if (useInternals) {
+            log.println();
+            warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
+        }
+        if (!jdkinternals.isEmpty()) {
+            log.println();
+            log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
+            log.format("%-40s %s%n", "----------------", "---------------------");
+            for (Map.Entry<String,String> e : jdkinternals.entrySet()) {
+                log.format("%-40s %s%n", e.getKey(), e.getValue());
+            }
+        }
+
+    }
 }
diff --git a/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
index 7cc433e..ccd14ab 100644
--- a/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
@@ -93,5 +93,12 @@
 err.invalid.path=invalid path: {0}
 warn.invalid.arg=Invalid classname or pathname not exist: {0}
 warn.split.package=package {0} defined in {1} {2}
+warn.replace.useJDKInternals=\
+JDK internal APIs are unsupported and private to JDK implementation that are\n\
+subject to be removed or changed incompatibly and could break your application.\n\
+Please modify your code to eliminate dependency on any JDK internal APIs.\n\
+For the most recent update on JDK internal API replacements, please check:\n\
+{0}
 
 artifact.not.found=not found
+jdeps.wiki.url=https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
diff --git a/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdkinternals.properties b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdkinternals.properties
new file mode 100644
index 0000000..56d7d0a
--- /dev/null
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdkinternals.properties
@@ -0,0 +1,22 @@
+// No translation needed
+com.sun.crypto.provider.SunJCE=Use java.security.Security.getProvider(provider-name) @since 1.3
+com.sun.image.codec=Use javax.imageio @since 1.4
+com.sun.org.apache.xml.internal.security=Use java.xml.crypto @since 1.6
+com.sun.org.apache.xml.internal.security.utils.Base64=Use java.util.Base64 @since 1.8
+com.sun.net.ssl=Use javax.net.ssl @since 1.4
+com.sun.net.ssl.internal.ssl.Provider=Use java.security.Security.getProvider(provider-name) @since 1.3
+com.sun.rowset=Use javax.sql.rowset.RowSetProvider @since 1.7
+com.sun.tools.javac.tree=Use com.sun.source @since 1.6
+com.sun.tools.javac=Use javax.tools and javax.lang.model @since 1.6
+sun.awt.image.codec=Use javax.imageio @since 1.4
+sun.misc.BASE64Encoder=Use java.util.Base64 @since 1.8
+sun.misc.BASE64Decoder=Use java.util.Base64 @since 1.8
+sun.misc.Cleaner=Use java.lang.ref.PhantomReference @since 1.2
+sun.misc.Service=Use java.util.ServiceLoader @since 1.6
+sun.security.action=Use java.security.PrivilegedAction @since 1.1
+sun.security.krb5=Use com.sun.security.jgss
+sun.security.provider.PolicyFile=Use java.security.Policy.getInstance("JavaPolicy", new URIParameter(uri)) @since 1.6
+sun.security.provider.Sun=Use java.security.Security.getProvider(provider-name) @since 1.3
+sun.security.util.SecurityConstants=Use appropriate java.security.Permission subclass @since 1.1
+sun.security.x509.X500Name=Use javax.security.auth.x500.X500Principal @since 1.4
+sun.tools.jar=Use java.util.jar or jar tool @since 1.2
diff --git a/langtools/test/tools/jdeps/APIDeps.java b/langtools/test/tools/jdeps/APIDeps.java
index bf01a77..c3d2c99 100644
--- a/langtools/test/tools/jdeps/APIDeps.java
+++ b/langtools/test/tools/jdeps/APIDeps.java
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8015912 8029216 8048063
+ * @bug 8015912 8029216 8048063 8050804
  * @summary Test -apionly and -jdkinternals options
  * @build m.Bar m.Foo m.Gee b.B c.C c.I d.D e.E f.F g.G
  * @run main APIDeps