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