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