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