aoqi@0: /* aoqi@0: * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * @test aoqi@0: * @bug 6964768 6964461 6964469 6964487 6964460 6964481 6980021 aoqi@0: * @summary need test program to validate javac resource bundles aoqi@0: */ aoqi@0: aoqi@0: import java.io.*; aoqi@0: import java.util.*; aoqi@0: import javax.tools.*; aoqi@0: import com.sun.tools.classfile.*; aoqi@0: aoqi@0: /** aoqi@0: * Compare string constants in javac classes against keys in javac resource bundles. aoqi@0: */ aoqi@0: public class CheckResourceKeys { aoqi@0: /** aoqi@0: * Main program. aoqi@0: * Options: aoqi@0: * -finddeadkeys aoqi@0: * look for keys in resource bundles that are no longer required aoqi@0: * -findmissingkeys aoqi@0: * look for keys in resource bundles that are missing aoqi@0: * aoqi@0: * @throws Exception if invoked by jtreg and errors occur aoqi@0: */ aoqi@0: public static void main(String... args) throws Exception { aoqi@0: CheckResourceKeys c = new CheckResourceKeys(); aoqi@0: if (c.run(args)) aoqi@0: return; aoqi@0: aoqi@0: if (is_jtreg()) aoqi@0: throw new Exception(c.errors + " errors occurred"); aoqi@0: else aoqi@0: System.exit(1); aoqi@0: } aoqi@0: aoqi@0: static boolean is_jtreg() { aoqi@0: return (System.getProperty("test.src") != null); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Main entry point. aoqi@0: */ aoqi@0: boolean run(String... args) throws Exception { aoqi@0: boolean findDeadKeys = false; aoqi@0: boolean findMissingKeys = false; aoqi@0: aoqi@0: if (args.length == 0) { aoqi@0: if (is_jtreg()) { aoqi@0: findDeadKeys = true; aoqi@0: findMissingKeys = true; aoqi@0: } else { aoqi@0: System.err.println("Usage: java CheckResourceKeys "); aoqi@0: System.err.println("where options include"); aoqi@0: System.err.println(" -finddeadkeys find keys in resource bundles which are no longer required"); aoqi@0: System.err.println(" -findmissingkeys find keys in resource bundles that are required but missing"); aoqi@0: return true; aoqi@0: } aoqi@0: } else { aoqi@0: for (String arg: args) { aoqi@0: if (arg.equalsIgnoreCase("-finddeadkeys")) aoqi@0: findDeadKeys = true; aoqi@0: else if (arg.equalsIgnoreCase("-findmissingkeys")) aoqi@0: findMissingKeys = true; aoqi@0: else aoqi@0: error("bad option: " + arg); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (errors > 0) aoqi@0: return false; aoqi@0: aoqi@0: Set codeStrings = getCodeStrings(); aoqi@0: Set resourceKeys = getResourceKeys(); aoqi@0: aoqi@0: if (findDeadKeys) aoqi@0: findDeadKeys(codeStrings, resourceKeys); aoqi@0: aoqi@0: if (findMissingKeys) aoqi@0: findMissingKeys(codeStrings, resourceKeys); aoqi@0: aoqi@0: return (errors == 0); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Find keys in resource bundles which are probably no longer required. aoqi@0: * A key is probably required if there is a string fragment in the code aoqi@0: * that is part of the resource key, or if the key is well-known aoqi@0: * according to various pragmatic rules. aoqi@0: */ aoqi@0: void findDeadKeys(Set codeStrings, Set resourceKeys) { aoqi@0: String[] prefixes = { aoqi@0: "compiler.err.", "compiler.warn.", "compiler.note.", "compiler.misc.", aoqi@0: "javac." aoqi@0: }; aoqi@0: for (String rk: resourceKeys) { aoqi@0: // some keys are used directly, without a prefix. aoqi@0: if (codeStrings.contains(rk)) aoqi@0: continue; aoqi@0: aoqi@0: // remove standard prefix aoqi@0: String s = null; aoqi@0: for (int i = 0; i < prefixes.length && s == null; i++) { aoqi@0: if (rk.startsWith(prefixes[i])) { aoqi@0: s = rk.substring(prefixes[i].length()); aoqi@0: } aoqi@0: } aoqi@0: if (s == null) { aoqi@0: error("Resource key does not start with a standard prefix: " + rk); aoqi@0: continue; aoqi@0: } aoqi@0: aoqi@0: if (codeStrings.contains(s)) aoqi@0: continue; aoqi@0: aoqi@0: // keys ending in .1 are often synthesized aoqi@0: if (s.endsWith(".1") && codeStrings.contains(s.substring(0, s.length() - 2))) aoqi@0: continue; aoqi@0: aoqi@0: // verbose keys are generated by ClassReader.printVerbose aoqi@0: if (s.startsWith("verbose.") && codeStrings.contains(s.substring(8))) aoqi@0: continue; aoqi@0: aoqi@0: // mandatory warning messages are synthesized with no characteristic substring aoqi@0: if (isMandatoryWarningString(s)) aoqi@0: continue; aoqi@0: aoqi@0: // check known (valid) exceptions aoqi@0: if (knownRequired.contains(rk)) aoqi@0: continue; aoqi@0: aoqi@0: // check known suspects aoqi@0: if (needToInvestigate.contains(rk)) aoqi@0: continue; aoqi@0: aoqi@0: error("Resource key not found in code: " + rk); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * The keys for mandatory warning messages are all synthesized and do not aoqi@0: * have a significant recognizable substring to look for. aoqi@0: */ aoqi@0: private boolean isMandatoryWarningString(String s) { aoqi@0: String[] bases = { "deprecated", "unchecked", "varargs", "sunapi" }; aoqi@0: String[] tails = { ".filename", ".filename.additional", ".plural", ".plural.additional", ".recompile" }; aoqi@0: for (String b: bases) { aoqi@0: if (s.startsWith(b)) { aoqi@0: String tail = s.substring(b.length()); aoqi@0: for (String t: tails) { aoqi@0: if (tail.equals(t)) aoqi@0: return true; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: Set knownRequired = new TreeSet(Arrays.asList( aoqi@0: // See Resolve.getErrorKey aoqi@0: "compiler.err.cant.resolve.args", aoqi@0: "compiler.err.cant.resolve.args.params", aoqi@0: "compiler.err.cant.resolve.location.args", aoqi@0: "compiler.err.cant.resolve.location.args.params", aoqi@0: "compiler.misc.cant.resolve.location.args", aoqi@0: "compiler.misc.cant.resolve.location.args.params", aoqi@0: // JavaCompiler, reports #errors and #warnings aoqi@0: "compiler.misc.count.error", aoqi@0: "compiler.misc.count.error.plural", aoqi@0: "compiler.misc.count.warn", aoqi@0: "compiler.misc.count.warn.plural", aoqi@0: // Used for LintCategory aoqi@0: "compiler.warn.lintOption", aoqi@0: // Other aoqi@0: "compiler.misc.base.membership" // (sic) aoqi@0: )); aoqi@0: aoqi@0: aoqi@0: Set needToInvestigate = new TreeSet(Arrays.asList( aoqi@0: "compiler.misc.fatal.err.cant.close.loader", // Supressed by JSR308 aoqi@0: "compiler.err.cant.read.file", // UNUSED aoqi@0: "compiler.err.illegal.self.ref", // UNUSED aoqi@0: "compiler.err.io.exception", // UNUSED aoqi@0: "compiler.err.limit.pool.in.class", // UNUSED aoqi@0: "compiler.err.name.reserved.for.internal.use", // UNUSED aoqi@0: "compiler.err.no.match.entry", // UNUSED aoqi@0: "compiler.err.not.within.bounds.explain", // UNUSED aoqi@0: "compiler.err.signature.doesnt.match.intf", // UNUSED aoqi@0: "compiler.err.signature.doesnt.match.supertype", // UNUSED aoqi@0: "compiler.err.type.var.more.than.once", // UNUSED aoqi@0: "compiler.err.type.var.more.than.once.in.result", // UNUSED aoqi@0: "compiler.misc.ccf.found.later.version", // UNUSED aoqi@0: "compiler.misc.non.denotable.type", // UNUSED aoqi@0: "compiler.misc.unnamed.package", // should be required, CR 6964147 aoqi@0: "compiler.misc.verbose.retro", // UNUSED aoqi@0: "compiler.misc.verbose.retro.with", // UNUSED aoqi@0: "compiler.misc.verbose.retro.with.list", // UNUSED aoqi@0: "compiler.warn.proc.type.already.exists", // TODO in JavacFiler aoqi@0: "javac.err.invalid.arg", // UNUSED ?? aoqi@0: "javac.opt.arg.class", // UNUSED ?? aoqi@0: "javac.opt.arg.pathname", // UNUSED ?? aoqi@0: "javac.opt.moreinfo", // option commented out aoqi@0: "javac.opt.nogj", // UNUSED aoqi@0: "javac.opt.printflat", // option commented out aoqi@0: "javac.opt.printsearch", // option commented out aoqi@0: "javac.opt.prompt", // option commented out aoqi@0: "javac.opt.retrofit", // UNUSED aoqi@0: "javac.opt.s", // option commented out aoqi@0: "javac.opt.scramble", // option commented out aoqi@0: "javac.opt.scrambleall" // option commented out aoqi@0: )); aoqi@0: aoqi@0: /** aoqi@0: * For all strings in the code that look like they might be fragments of aoqi@0: * a resource key, verify that a key exists. aoqi@0: */ aoqi@0: void findMissingKeys(Set codeStrings, Set resourceKeys) { aoqi@0: for (String cs: codeStrings) { aoqi@0: if (cs.matches("[A-Za-z][^.]*\\..*")) { aoqi@0: // ignore filenames (i.e. in SourceFile attribute aoqi@0: if (cs.matches(".*\\.java")) aoqi@0: continue; aoqi@0: // ignore package and class names aoqi@0: if (cs.matches("(com|java|javax|sun)\\.[A-Za-z.]+")) aoqi@0: continue; aoqi@0: // explicit known exceptions aoqi@0: if (noResourceRequired.contains(cs)) aoqi@0: continue; aoqi@0: // look for matching resource aoqi@0: if (hasMatch(resourceKeys, cs)) aoqi@0: continue; aoqi@0: error("no match for \"" + cs + "\""); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: // where aoqi@0: private Set noResourceRequired = new HashSet(Arrays.asList( aoqi@0: // system properties aoqi@0: "application.home", // in Paths.java aoqi@0: "env.class.path", aoqi@0: "line.separator", aoqi@0: "os.name", aoqi@0: "user.dir", aoqi@0: // file names aoqi@0: "ct.sym", aoqi@0: "rt.jar", aoqi@0: "tools.jar", aoqi@0: // -XD option names aoqi@0: "process.packages", aoqi@0: "ignore.symbol.file", aoqi@0: // prefix/embedded strings aoqi@0: "compiler.", aoqi@0: "compiler.misc.", aoqi@0: "count.", aoqi@0: "illegal.", aoqi@0: "javac.", aoqi@0: "verbose." aoqi@0: )); aoqi@0: aoqi@0: /** aoqi@0: * Look for a resource that ends in this string fragment. aoqi@0: */ aoqi@0: boolean hasMatch(Set resourceKeys, String s) { aoqi@0: for (String rk: resourceKeys) { aoqi@0: if (rk.endsWith(s)) aoqi@0: return true; aoqi@0: } aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get the set of strings from (most of) the javac classfiles. aoqi@0: */ aoqi@0: Set getCodeStrings() throws IOException { aoqi@0: Set results = new TreeSet(); aoqi@0: JavaCompiler c = ToolProvider.getSystemJavaCompiler(); aoqi@0: JavaFileManager fm = c.getStandardFileManager(null, null, null); aoqi@0: JavaFileManager.Location javacLoc = findJavacLocation(fm); aoqi@0: String[] pkgs = { aoqi@0: "javax.annotation.processing", aoqi@0: "javax.lang.model", aoqi@0: "javax.tools", aoqi@0: "com.sun.source", aoqi@0: "com.sun.tools.javac" aoqi@0: }; aoqi@0: for (String pkg: pkgs) { aoqi@0: for (JavaFileObject fo: fm.list(javacLoc, aoqi@0: pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) { aoqi@0: String name = fo.getName(); aoqi@0: // ignore resource files, and files which are not really part of javac aoqi@0: if (name.matches(".*resources.[A-Za-z_0-9]+\\.class.*") aoqi@0: || name.matches(".*CreateSymbols\\.class.*")) aoqi@0: continue; aoqi@0: scan(fo, results); aoqi@0: } aoqi@0: } aoqi@0: return results; aoqi@0: } aoqi@0: aoqi@0: // depending on how the test is run, javac may be on bootclasspath or classpath aoqi@0: JavaFileManager.Location findJavacLocation(JavaFileManager fm) { aoqi@0: JavaFileManager.Location[] locns = aoqi@0: { StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH }; aoqi@0: try { aoqi@0: for (JavaFileManager.Location l: locns) { aoqi@0: JavaFileObject fo = fm.getJavaFileForInput(l, aoqi@0: "com.sun.tools.javac.Main", JavaFileObject.Kind.CLASS); aoqi@0: if (fo != null) aoqi@0: return l; aoqi@0: } aoqi@0: } catch (IOException e) { aoqi@0: throw new Error(e); aoqi@0: } aoqi@0: throw new IllegalStateException("Cannot find javac"); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get the set of strings from a class file. aoqi@0: * Only strings that look like they might be a resource key are returned. aoqi@0: */ aoqi@0: void scan(JavaFileObject fo, Set results) throws IOException { aoqi@0: InputStream in = fo.openInputStream(); aoqi@0: try { aoqi@0: ClassFile cf = ClassFile.read(in); aoqi@0: for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) { aoqi@0: if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) { aoqi@0: String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value; aoqi@0: if (v.matches("[A-Za-z0-9-_.]+")) aoqi@0: results.add(v); aoqi@0: } aoqi@0: } aoqi@0: } catch (ConstantPoolException ignore) { aoqi@0: } finally { aoqi@0: in.close(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get the set of keys from the javac resource bundles. aoqi@0: */ aoqi@0: Set getResourceKeys() { aoqi@0: Set results = new TreeSet(); aoqi@0: for (String name : new String[]{"javac", "compiler"}) { aoqi@0: ResourceBundle b = aoqi@0: ResourceBundle.getBundle("com.sun.tools.javac.resources." + name); aoqi@0: results.addAll(b.keySet()); aoqi@0: } aoqi@0: return results; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Report an error. aoqi@0: */ aoqi@0: void error(String msg) { aoqi@0: System.err.println("Error: " + msg); aoqi@0: errors++; aoqi@0: } aoqi@0: aoqi@0: int errors; aoqi@0: }