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