Path: blob/master/test/langtools/jdk/javadoc/tool/CheckResourceKeys.java
40957 views
/*1* Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 8000612 8254627 824799426* @summary need test program to validate javadoc resource bundles27* @modules jdk.javadoc/jdk.javadoc.internal.tool28* jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.resources:open29* jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources:open30* jdk.javadoc/jdk.javadoc.internal.tool.resources:open31* jdk.jdeps/com.sun.tools.classfile32*/3334import java.io.*;35import java.util.*;36import java.util.regex.Matcher;37import java.util.regex.Pattern;38import javax.tools.*;39import com.sun.tools.classfile.*;4041/**42* Compare string constants in javadoc classes against keys in javadoc resource bundles.43*/44public class CheckResourceKeys {45/**46* Main program.47* Options:48* -finddeadkeys49* look for keys in resource bundles that are no longer required50* -findmissingkeys51* look for keys in resource bundles that are missing52*53* @throws Exception if invoked by jtreg and errors occur54*/55public static void main(String... args) throws Exception {56CheckResourceKeys c = new CheckResourceKeys();57if (c.run(args))58return;5960if (is_jtreg())61throw new Exception(c.errors + " errors occurred");62else63System.exit(1);64}6566static boolean is_jtreg() {67return (System.getProperty("test.src") != null);68}6970/**71* Main entry point.72*/73boolean run(String... args) throws Exception {74boolean findDeadKeys = false;75boolean findMissingKeys = false;7677if (args.length == 0) {78if (is_jtreg()) {79findDeadKeys = true;80findMissingKeys = true;81} else {82System.err.println("Usage: java CheckResourceKeys <options>");83System.err.println("where options include");84System.err.println(" -finddeadkeys find keys in resource bundles which are no longer required");85System.err.println(" -findmissingkeys find keys in resource bundles that are required but missing");86return true;87}88} else {89for (String arg: args) {90if (arg.equalsIgnoreCase("-finddeadkeys"))91findDeadKeys = true;92else if (arg.equalsIgnoreCase("-findmissingkeys"))93findMissingKeys = true;94else95error("bad option: " + arg);96}97}9899if (errors > 0)100return false;101102Set<String> codeKeys = getCodeKeys();103Set<String> resourceKeys = getResourceKeys();104105System.err.println("found " + codeKeys.size() + " keys in code");106System.err.println("found " + resourceKeys.size() + " keys in resource bundles");107108if (findDeadKeys)109findDeadKeys(codeKeys, resourceKeys);110111if (findMissingKeys)112findMissingKeys(codeKeys, resourceKeys);113114usageTests(false);115usageTests(true);116117return (errors == 0);118}119120void usageTests(boolean xflag) {121String[] argarray = { xflag ? "-X" : "--help" };122StringWriter sw = new StringWriter();123PrintWriter pw = new PrintWriter(sw);124if (jdk.javadoc.internal.tool.Main.execute(argarray, pw) == 0) {125pw.flush();126String s = sw.toString();127if (s.isEmpty()) {128error("no javadoc output ?");129return;130}131if (sw.toString().contains("<MISSING KEY>")) {132System.out.println(s);133error("missing resources in output ?");134}135} else {136error("failed to execute javadoc");137}138}139140/**141* Find keys in resource bundles which are probably no longer required.142* A key is required if there is a string in the code that is a resource key,143* or if the key is well-known according to various pragmatic rules.144*/145void findDeadKeys(Set<String> codeKeys, Set<String> resourceKeys) {146for (String rk: resourceKeys) {147// ignore these synthesized keys, tested by usageTests148if (rk.startsWith("doclet.usage.") || rk.startsWith("doclet.xusage"))149continue;150// ignore these synthesized keys, tested by usageTests151if (rk.matches("main\\.opt\\..*\\.(arg|desc)"))152continue;153// ignore this partial key154if (rk.startsWith("doclet.Declared_Using_Preview."))155continue;156if (codeKeys.contains(rk))157continue;158159error("Resource key not found in code: '" + rk + '"');160}161}162163/**164* For all strings in the code that look like they might be165* a resource key, verify that a key exists.166*/167void findMissingKeys(Set<String> codeKeys, Set<String> resourceKeys) {168for (String ck: codeKeys) {169// ignore these synthesized keys, tested by usageTests170if (ck.startsWith("doclet.usage.") || ck.startsWith("doclet.xusage."))171continue;172// ignore this partial key, tested by usageTests173if (ck.equals("main.opt."))174continue;175// ignore these system property names176if (ck.equals("javadoc.internal.show.taglets") || ck.equals("javadoc.legal-notices"))177continue;178if (resourceKeys.contains(ck))179continue;180error("No resource for \"" + ck + "\"");181}182}183184/**185* Get the set of strings from (most of) the javadoc classfiles.186*/187Set<String> getCodeKeys() throws IOException {188Set<String> results = new TreeSet<String>();189JavaCompiler c = ToolProvider.getSystemJavaCompiler();190try (JavaFileManager fm = c.getStandardFileManager(null, null, null)) {191JavaFileManager.Location javadocLoc = findJavadocLocation(fm);192String[] pkgs = {193"jdk.javadoc.internal.doclets",194"jdk.javadoc.internal.tool"195};196for (String pkg: pkgs) {197for (JavaFileObject fo: fm.list(javadocLoc,198pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) {199String name = fo.getName();200// ignore resource files201if (name.matches(".*resources.[A-Za-z_0-9]+\\.class.*"))202continue;203scan(fo, results);204}205}206207// special handling for strings in search.js.template208FileObject fo = fm.getFileForInput(javadocLoc,209"jdk.javadoc.internal.doclets.formats.html",210"resources/search.js.template");211CharSequence search_js = fo.getCharContent(true);212Pattern p = Pattern.compile("##REPLACE:(?<key>[A-Za-z0-9._]+)##");213Matcher m = p.matcher(search_js);214while (m.find()) {215results.add(m.group("key"));216}217218// special handling for code strings synthesized in219// jdk.javadoc.internal.doclets.toolkit.util.Utils.getTypeName220String[] extras = {221"AnnotationType", "Class", "Enum", "EnumClass", "Error", "Exception", "Interface", "RecordClass"222};223for (String s: extras) {224if (results.contains("doclet." + s))225results.add("doclet." + s.toLowerCase());226}227228// special handling for code strings synthesized in229// jdk.javadoc.internal.tool.Messager230results.add("javadoc.error.msg");231results.add("javadoc.note.msg");232results.add("javadoc.note.pos.msg");233results.add("javadoc.warning.msg");234235results.add("javadoc.err.message");236results.add("javadoc.warn.message");237results.add("javadoc.note.message");238239return results;240}241}242243// depending on how the test is run, javadoc may be on bootclasspath or classpath244JavaFileManager.Location findJavadocLocation(JavaFileManager fm) {245JavaFileManager.Location[] locns =246{ StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH };247try {248for (JavaFileManager.Location l: locns) {249JavaFileObject fo = fm.getJavaFileForInput(l,250"jdk.javadoc.internal.tool.Main", JavaFileObject.Kind.CLASS);251if (fo != null) {252System.err.println("found javadoc in " + l);253return l;254}255}256} catch (IOException e) {257throw new Error(e);258}259throw new IllegalStateException("Cannot find javadoc");260}261262/**263* Get the set of strings from a class file.264* Only strings that look like they might be a resource key are returned.265*/266void scan(JavaFileObject fo, Set<String> results) throws IOException {267//System.err.println("scan " + fo.getName());268InputStream in = fo.openInputStream();269try {270ClassFile cf = ClassFile.read(in);271for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) {272if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) {273String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value;274if (v.matches("(doclet|main|javadoc|tag)\\.[A-Za-z0-9-_.]+"))275results.add(v);276}277}278} catch (ConstantPoolException ignore) {279} finally {280in.close();281}282}283284/**285* Get the set of keys from the javadoc resource bundles.286*/287Set<String> getResourceKeys() {288Module jdk_javadoc = ModuleLayer.boot().findModule("jdk.javadoc").get();289String[] names = {290"jdk.javadoc.internal.doclets.formats.html.resources.standard",291"jdk.javadoc.internal.doclets.toolkit.resources.doclets",292"jdk.javadoc.internal.tool.resources.javadoc",293};294Set<String> results = new TreeSet<String>();295for (String name : names) {296ResourceBundle b = ResourceBundle.getBundle(name, jdk_javadoc);297results.addAll(b.keySet());298}299return results;300}301302/**303* Report an error.304*/305void error(String msg) {306System.err.println("Error: " + msg);307errors++;308}309310int errors;311}312313314