CheckResourceKeys.java revision 3573:c4a18ee691c4
150764Smarkm/*
250764Smarkm * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
3233294Sstas * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4233294Sstas *
5233294Sstas * This code is free software; you can redistribute it and/or modify it
650764Smarkm * under the terms of the GNU General Public License version 2 only, as
7233294Sstas * published by the Free Software Foundation.
8233294Sstas *
9233294Sstas * This code is distributed in the hope that it will be useful, but WITHOUT
1050764Smarkm * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11233294Sstas * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12233294Sstas * version 2 for more details (a copy is included in the LICENSE file that
1350764Smarkm * accompanied this code).
14233294Sstas *
15233294Sstas * You should have received a copy of the GNU General Public License version
16233294Sstas * 2 along with this work; if not, write to the Free Software Foundation,
1750764Smarkm * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18233294Sstas *
19233294Sstas * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20233294Sstas * or visit www.oracle.com if you need additional information or have any
2150764Smarkm * questions.
22233294Sstas */
23233294Sstas
24233294Sstas/*
25233294Sstas * @test
26233294Sstas * @bug 8000612
27233294Sstas * @summary need test program to validate javadoc resource bundles
28233294Sstas * @modules jdk.jdeps/com.sun.tools.classfile
29233294Sstas */
30233294Sstas
31233294Sstasimport java.io.*;
32233294Sstasimport java.lang.reflect.Layer;
3350764Smarkmimport java.lang.reflect.Module;
3450764Smarkmimport java.util.*;
3550764Smarkmimport javax.tools.*;
36127807Snectarimport com.sun.tools.classfile.*;
3750764Smarkm
3850764Smarkm/**
39127807Snectar * Compare string constants in javadoc classes against keys in javadoc resource bundles.
4050764Smarkm */
4150764Smarkmpublic class CheckResourceKeys {
4250764Smarkm    /**
4350764Smarkm     * Main program.
4450764Smarkm     * Options:
4550764Smarkm     * -finddeadkeys
4650764Smarkm     *      look for keys in resource bundles that are no longer required
4750764Smarkm     * -findmissingkeys
4850764Smarkm     *      look for keys in resource bundles that are missing
49233294Sstas     *
50233294Sstas     * @throws Exception if invoked by jtreg and errors occur
51233294Sstas     */
5250764Smarkm    public static void main(String... args) throws Exception {
5350764Smarkm        CheckResourceKeys c = new CheckResourceKeys();
5450764Smarkm        if (c.run(args))
5550764Smarkm            return;
5650764Smarkm
5750764Smarkm        if (is_jtreg())
5850764Smarkm            throw new Exception(c.errors + " errors occurred");
5950764Smarkm        else
6050764Smarkm            System.exit(1);
6150764Smarkm    }
6250764Smarkm
6350764Smarkm    static boolean is_jtreg() {
6450764Smarkm        return (System.getProperty("test.src") != null);
65233294Sstas    }
6650764Smarkm
6750764Smarkm    /**
6850764Smarkm     * Main entry point.
6950764Smarkm     */
7050764Smarkm    boolean run(String... args) throws Exception {
7150764Smarkm        boolean findDeadKeys = false;
7250764Smarkm        boolean findMissingKeys = false;
7350764Smarkm
7450764Smarkm        if (args.length == 0) {
7550764Smarkm            if (is_jtreg()) {
7650764Smarkm                findDeadKeys = true;
7750764Smarkm                findMissingKeys = true;
7850764Smarkm            } else {
7950764Smarkm                System.err.println("Usage: java CheckResourceKeys <options>");
8050764Smarkm                System.err.println("where options include");
81178846Sdfr                System.err.println("  -finddeadkeys      find keys in resource bundles which are no longer required");
82178846Sdfr                System.err.println("  -findmissingkeys   find keys in resource bundles that are required but missing");
8350764Smarkm                return true;
8450764Smarkm            }
8550764Smarkm        } else {
8650764Smarkm            for (String arg: args) {
87178846Sdfr                if (arg.equalsIgnoreCase("-finddeadkeys"))
88178846Sdfr                    findDeadKeys = true;
8950764Smarkm                else if (arg.equalsIgnoreCase("-findmissingkeys"))
9050764Smarkm                    findMissingKeys = true;
9150764Smarkm                else
9250764Smarkm                    error("bad option: " + arg);
9350764Smarkm            }
9450764Smarkm        }
9550764Smarkm
9650764Smarkm        if (errors > 0)
9750764Smarkm            return false;
98233294Sstas
9950764Smarkm        Set<String> codeKeys = getCodeKeys();
10050764Smarkm        Set<String> resourceKeys = getResourceKeys();
10150764Smarkm
10250764Smarkm        System.err.println("found " + codeKeys.size() + " keys in code");
10350764Smarkm        System.err.println("found " + resourceKeys.size() + " keys in resource bundles");
104178846Sdfr
105178846Sdfr        if (findDeadKeys)
106178846Sdfr            findDeadKeys(codeKeys, resourceKeys);
107178846Sdfr
10850764Smarkm        if (findMissingKeys)
10950764Smarkm            findMissingKeys(codeKeys, resourceKeys);
11050764Smarkm
11150764Smarkm        return (errors == 0);
11250764Smarkm    }
113178846Sdfr
114178846Sdfr    /**
11550764Smarkm     * Find keys in resource bundles which are probably no longer required.
11650764Smarkm     * A key is required if there is a string in the code that is a resource key,
11750764Smarkm     * or if the key is well-known according to various pragmatic rules.
11850764Smarkm     */
11950764Smarkm    void findDeadKeys(Set<String> codeKeys, Set<String> resourceKeys) {
120233294Sstas        for (String rk: resourceKeys) {
121178846Sdfr            if (codeKeys.contains(rk))
122178846Sdfr                continue;
12350764Smarkm
12450764Smarkm            error("Resource key not found in code: '" + rk + "'");
12550764Smarkm        }
12650764Smarkm    }
12750764Smarkm
128178846Sdfr    /**
129178846Sdfr     * For all strings in the code that look like they might be
13050764Smarkm     * a resource key, verify that a key exists.
13150764Smarkm     */
13250764Smarkm    void findMissingKeys(Set<String> codeKeys, Set<String> resourceKeys) {
13350764Smarkm        for (String ck: codeKeys) {
13450764Smarkm            if (resourceKeys.contains(ck))
13550764Smarkm                continue;
13650764Smarkm            error("No resource for \"" + ck + "\"");
13750764Smarkm        }
13850764Smarkm    }
13950764Smarkm
14050764Smarkm    /**
14150764Smarkm     * Get the set of strings from (most of) the javadoc classfiles.
14250764Smarkm     */
14350764Smarkm    Set<String> getCodeKeys() throws IOException {
14450764Smarkm        Set<String> results = new TreeSet<String>();
145127807Snectar        JavaCompiler c = ToolProvider.getSystemJavaCompiler();
14650764Smarkm        try (JavaFileManager fm = c.getStandardFileManager(null, null, null)) {
14750764Smarkm            JavaFileManager.Location javadocLoc = findJavadocLocation(fm);
14850764Smarkm            String[] pkgs = {
149178846Sdfr                "com.sun.tools.doclets",
15050764Smarkm                "com.sun.tools.javadoc"
15150764Smarkm            };
15250764Smarkm            for (String pkg: pkgs) {
15350764Smarkm                for (JavaFileObject fo: fm.list(javadocLoc,
15450764Smarkm                        pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) {
15550764Smarkm                    String name = fo.getName();
15650764Smarkm                    // ignore resource files
15750764Smarkm                    if (name.matches(".*resources.[A-Za-z_0-9]+\\.class.*"))
15850764Smarkm                        continue;
15950764Smarkm                    scan(fo, results);
16050764Smarkm                }
16150764Smarkm            }
162178846Sdfr
16350764Smarkm            // special handling for code strings synthesized in
164178846Sdfr            // com.sun.tools.doclets.internal.toolkit.util.Util.getTypeName
165178846Sdfr            String[] extras = {
166178846Sdfr                "AnnotationType", "Class", "Enum", "Error", "Exception", "Interface"
167178846Sdfr            };
16850764Smarkm            for (String s: extras) {
16950764Smarkm                if (results.contains("doclet." + s))
17050764Smarkm                    results.add("doclet." + s.toLowerCase());
17150764Smarkm            }
17250764Smarkm
173233294Sstas            // special handling for code strings synthesized in
17450764Smarkm            // 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}
253