1 /*
   2  * Copyright (c) 2010, 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.
   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 8000612
  27  * @summary need test program to validate javadoc resource bundles
  28  * @modules jdk.javadoc/jdk.javadoc.internal.tool
  29  *          jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.resources:open
  30  *          jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources:open
  31  *          jdk.javadoc/jdk.javadoc.internal.tool.resources:open
  32  *          jdk.jdeps/com.sun.tools.classfile
  33  */
  34 
  35 import java.io.*;
  36 import java.util.*;
  37 import javax.tools.*;
  38 import com.sun.tools.classfile.*;
  39 
  40 /**
  41  * Compare string constants in javadoc classes against keys in javadoc resource bundles.
  42  */
  43 public class CheckResourceKeys {
  44     /**
  45      * Main program.
  46      * Options:
  47      * -finddeadkeys
  48      *      look for keys in resource bundles that are no longer required
  49      * -findmissingkeys
  50      *      look for keys in resource bundles that are missing
  51      *
  52      * @throws Exception if invoked by jtreg and errors occur
  53      */
  54     public static void main(String... args) throws Exception {
  55         CheckResourceKeys c = new CheckResourceKeys();
  56         if (c.run(args))
  57             return;
  58 
  59         if (is_jtreg())
  60             throw new Exception(c.errors + " errors occurred");
  61         else
  62             System.exit(1);
  63     }
  64 
  65     static boolean is_jtreg() {
  66         return (System.getProperty("test.src") != null);
  67     }
  68 
  69     /**
  70      * Main entry point.
  71      */
  72     boolean run(String... args) throws Exception {
  73         boolean findDeadKeys = false;
  74         boolean findMissingKeys = false;
  75 
  76         if (args.length == 0) {
  77             if (is_jtreg()) {
  78                 findDeadKeys = true;
  79                 findMissingKeys = true;
  80             } else {
  81                 System.err.println("Usage: java CheckResourceKeys <options>");
  82                 System.err.println("where options include");
  83                 System.err.println("  -finddeadkeys      find keys in resource bundles which are no longer required");
  84                 System.err.println("  -findmissingkeys   find keys in resource bundles that are required but missing");
  85                 return true;
  86             }
  87         } else {
  88             for (String arg: args) {
  89                 if (arg.equalsIgnoreCase("-finddeadkeys"))
  90                     findDeadKeys = true;
  91                 else if (arg.equalsIgnoreCase("-findmissingkeys"))
  92                     findMissingKeys = true;
  93                 else
  94                     error("bad option: " + arg);
  95             }
  96         }
  97 
  98         if (errors > 0)
  99             return false;
 100 
 101         Set<String> codeKeys = getCodeKeys();
 102         Set<String> resourceKeys = getResourceKeys();
 103 
 104         System.err.println("found " + codeKeys.size() + " keys in code");
 105         System.err.println("found " + resourceKeys.size() + " keys in resource bundles");
 106 
 107         if (findDeadKeys)
 108             findDeadKeys(codeKeys, resourceKeys);
 109 
 110         if (findMissingKeys)
 111             findMissingKeys(codeKeys, resourceKeys);
 112 
 113         usageTests(false);
 114         usageTests(true);
 115 
 116         return (errors == 0);
 117     }
 118 
 119     void usageTests(boolean xflag) {
 120         String[] argarray = { xflag ? "-X" : "-help" };
 121         StringWriter sw = new StringWriter();
 122         PrintWriter pw = new PrintWriter(sw);
 123         if (jdk.javadoc.internal.tool.Main.execute(argarray, pw) == 0) {
 124             pw.flush();
 125             String s = sw.toString();
 126             if (s.isEmpty()) {
 127                 error("no javadoc output ?");
 128                 return;
 129             }
 130             if (sw.toString().contains("<MISSING KEY>")) {
 131                 System.out.println(s);
 132                 error("missing resources in output ?");
 133             }
 134         } else {
 135             error("failed to execute javadoc");
 136         }
 137     }
 138 
 139     /**
 140      * Find keys in resource bundles which are probably no longer required.
 141      * A key is required if there is a string in the code that is a resource key,
 142      * or if the key is well-known according to various pragmatic rules.
 143      */
 144     void findDeadKeys(Set<String> codeKeys, Set<String> resourceKeys) {
 145         for (String rk: resourceKeys) {
 146             // ignore these synthesized keys, tested by usageTests
 147             if (rk.startsWith("doclet.usage.") || rk.startsWith("doclet.xusage"))
 148                 continue;
 149             // ignore these synthesized keys, tested by usageTests
 150             if (rk.matches("main\\.opt\\..*\\.(arg|desc)"))
 151                 continue;
 152             if (codeKeys.contains(rk))
 153                 continue;
 154 
 155             error("Resource key not found in code: '" + rk + '"');
 156         }
 157     }
 158 
 159     /**
 160      * For all strings in the code that look like they might be
 161      * a resource key, verify that a key exists.
 162      */
 163     void findMissingKeys(Set<String> codeKeys, Set<String> resourceKeys) {
 164         for (String ck: codeKeys) {
 165             // ignore these synthesized keys, tested by usageTests
 166             if (ck.startsWith("doclet.usage.") || ck.startsWith("doclet.xusage."))
 167                 continue;
 168             // ignore this partial key, tested by usageTests
 169             if (ck.equals("main.opt."))
 170                 continue;
 171             if (resourceKeys.contains(ck))
 172                 continue;
 173             error("No resource for \"" + ck + "\"");
 174         }
 175     }
 176 
 177     /**
 178      * Get the set of strings from (most of) the javadoc classfiles.
 179      */
 180     Set<String> getCodeKeys() throws IOException {
 181         Set<String> results = new TreeSet<String>();
 182         JavaCompiler c = ToolProvider.getSystemJavaCompiler();
 183         try (JavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 184             JavaFileManager.Location javadocLoc = findJavadocLocation(fm);
 185             String[] pkgs = {
 186                 "jdk.javadoc.internal.doclets",
 187                 "jdk.javadoc.internal.tool"
 188             };
 189             for (String pkg: pkgs) {
 190                 for (JavaFileObject fo: fm.list(javadocLoc,
 191                         pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) {
 192                     String name = fo.getName();
 193                     // ignore resource files
 194                     if (name.matches(".*resources.[A-Za-z_0-9]+\\.class.*"))
 195                         continue;
 196                     scan(fo, results);
 197                 }
 198             }
 199 
 200             // special handling for code strings synthesized in
 201             // com.sun.tools.doclets.internal.toolkit.util.Util.getTypeName
 202             String[] extras = {
 203                 "AnnotationType", "Class", "Enum", "Error", "Exception", "Interface"
 204             };
 205             for (String s: extras) {
 206                 if (results.contains("doclet." + s))
 207                     results.add("doclet." + s.toLowerCase());
 208             }
 209 
 210             // special handling for code strings synthesized in
 211             // com.sun.tools.javadoc.Messager
 212             results.add("javadoc.error.msg");
 213             results.add("javadoc.note.msg");
 214             results.add("javadoc.note.pos.msg");
 215             results.add("javadoc.warning.msg");
 216 
 217             return results;
 218         }
 219     }
 220 
 221     // depending on how the test is run, javadoc may be on bootclasspath or classpath
 222     JavaFileManager.Location findJavadocLocation(JavaFileManager fm) {
 223         JavaFileManager.Location[] locns =
 224             { StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH };
 225         try {
 226             for (JavaFileManager.Location l: locns) {
 227                 JavaFileObject fo = fm.getJavaFileForInput(l,
 228                     "jdk.javadoc.internal.tool.Main", JavaFileObject.Kind.CLASS);
 229                 if (fo != null) {
 230                     System.err.println("found javadoc in " + l);
 231                     return l;
 232                 }
 233             }
 234         } catch (IOException e) {
 235             throw new Error(e);
 236         }
 237         throw new IllegalStateException("Cannot find javadoc");
 238     }
 239 
 240     /**
 241      * Get the set of strings from a class file.
 242      * Only strings that look like they might be a resource key are returned.
 243      */
 244     void scan(JavaFileObject fo, Set<String> results) throws IOException {
 245         //System.err.println("scan " + fo.getName());
 246         InputStream in = fo.openInputStream();
 247         try {
 248             ClassFile cf = ClassFile.read(in);
 249             for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) {
 250                 if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) {
 251                     String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value;
 252                     if (v.matches("(doclet|main|javadoc|tag)\\.[A-Za-z0-9-_.]+"))
 253                         results.add(v);
 254                 }
 255             }
 256         } catch (ConstantPoolException ignore) {
 257         } finally {
 258             in.close();
 259         }
 260     }
 261 
 262     /**
 263      * Get the set of keys from the javadoc resource bundles.
 264      */
 265     Set<String> getResourceKeys() {
 266         Module jdk_javadoc = ModuleLayer.boot().findModule("jdk.javadoc").get();
 267         String[] names = {
 268                 "jdk.javadoc.internal.doclets.formats.html.resources.standard",
 269                 "jdk.javadoc.internal.doclets.toolkit.resources.doclets",
 270                 "jdk.javadoc.internal.tool.resources.javadoc",
 271         };
 272         Set<String> results = new TreeSet<String>();
 273         for (String name : names) {
 274             ResourceBundle b = ResourceBundle.getBundle(name, jdk_javadoc);
 275             results.addAll(b.keySet());
 276         }
 277         return results;
 278     }
 279 
 280     /**
 281      * Report an error.
 282      */
 283     void error(String msg) {
 284         System.err.println("Error: " + msg);
 285         errors++;
 286     }
 287 
 288     int errors;
 289 }