test/tools/javac/diags/CheckResourceKeys.java

Wed, 06 Apr 2011 20:33:44 -0700

author
ohair
date
Wed, 06 Apr 2011 20:33:44 -0700
changeset 962
0ff2bbd38f10
parent 916
cb9493a80341
child 1230
b14d9583ce92
permissions
-rw-r--r--

7033660: Update copyright year to 2011 on any files changed in 2011
Reviewed-by: dholmes

jjg@597 1 /*
ohair@962 2 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
jjg@597 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jjg@597 4 *
jjg@597 5 * This code is free software; you can redistribute it and/or modify it
jjg@597 6 * under the terms of the GNU General Public License version 2 only, as
jjg@597 7 * published by the Free Software Foundation.
jjg@597 8 *
jjg@597 9 * This code is distributed in the hope that it will be useful, but WITHOUT
jjg@597 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jjg@597 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jjg@597 12 * version 2 for more details (a copy is included in the LICENSE file that
jjg@597 13 * accompanied this code).
jjg@597 14 *
jjg@597 15 * You should have received a copy of the GNU General Public License version
jjg@597 16 * 2 along with this work; if not, write to the Free Software Foundation,
jjg@597 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jjg@597 18 *
jjg@597 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jjg@597 20 * or visit www.oracle.com if you need additional information or have any
jjg@597 21 * questions.
jjg@597 22 */
jjg@597 23
jjg@597 24 /*
jjg@597 25 * @test
jjg@916 26 * @bug 6964768 6964461 6964469 6964487 6964460 6964481 6980021
jjg@597 27 * @summary need test program to validate javac resource bundles
jjg@597 28 */
jjg@597 29
jjg@597 30 import java.io.*;
jjg@597 31 import java.util.*;
jjg@597 32 import javax.tools.*;
jjg@597 33 import com.sun.tools.classfile.*;
jjg@597 34
jjg@597 35 /**
jjg@597 36 * Compare string constants in javac classes against keys in javac resource bundles.
jjg@597 37 */
jjg@597 38 public class CheckResourceKeys {
jjg@597 39 /**
jjg@597 40 * Main program.
jjg@597 41 * Options:
jjg@597 42 * -finddeadkeys
jjg@597 43 * look for keys in resource bundles that are no longer required
jjg@597 44 * -findmissingkeys
jjg@597 45 * look for keys in resource bundles that are missing
jjg@597 46 *
jjg@597 47 * @throws Exception if invoked by jtreg and errors occur
jjg@597 48 */
jjg@597 49 public static void main(String... args) throws Exception {
jjg@597 50 CheckResourceKeys c = new CheckResourceKeys();
jjg@597 51 if (c.run(args))
jjg@597 52 return;
jjg@597 53
jjg@597 54 if (is_jtreg())
jjg@597 55 throw new Exception(c.errors + " errors occurred");
jjg@597 56 else
jjg@597 57 System.exit(1);
jjg@597 58 }
jjg@597 59
jjg@597 60 static boolean is_jtreg() {
jjg@597 61 return (System.getProperty("test.src") != null);
jjg@597 62 }
jjg@597 63
jjg@597 64 /**
jjg@597 65 * Main entry point.
jjg@597 66 */
jjg@597 67 boolean run(String... args) throws Exception {
jjg@597 68 boolean findDeadKeys = false;
jjg@597 69 boolean findMissingKeys = false;
jjg@597 70
jjg@597 71 if (args.length == 0) {
jjg@597 72 if (is_jtreg()) {
jjg@597 73 findDeadKeys = true;
jjg@597 74 findMissingKeys = true;
jjg@597 75 } else {
jjg@597 76 System.err.println("Usage: java CheckResourceKeys <options>");
jjg@597 77 System.err.println("where options include");
jjg@597 78 System.err.println(" -finddeadkeys find keys in resource bundles which are no longer required");
jjg@597 79 System.err.println(" -findmissingkeys find keys in resource bundles that are required but missing");
jjg@597 80 return true;
jjg@597 81 }
jjg@597 82 } else {
jjg@597 83 for (String arg: args) {
jjg@597 84 if (arg.equalsIgnoreCase("-finddeadkeys"))
jjg@597 85 findDeadKeys = true;
jjg@597 86 else if (arg.equalsIgnoreCase("-findmissingkeys"))
jjg@597 87 findMissingKeys = true;
jjg@597 88 else
jjg@597 89 error("bad option: " + arg);
jjg@597 90 }
jjg@597 91 }
jjg@597 92
jjg@597 93 if (errors > 0)
jjg@597 94 return false;
jjg@597 95
jjg@597 96 Set<String> codeStrings = getCodeStrings();
jjg@597 97 Set<String> resourceKeys = getResourceKeys();
jjg@597 98
jjg@597 99 if (findDeadKeys)
jjg@597 100 findDeadKeys(codeStrings, resourceKeys);
jjg@597 101
jjg@597 102 if (findMissingKeys)
jjg@597 103 findMissingKeys(codeStrings, resourceKeys);
jjg@597 104
jjg@597 105 return (errors == 0);
jjg@597 106 }
jjg@597 107
jjg@597 108 /**
jjg@597 109 * Find keys in resource bundles which are probably no longer required.
jjg@597 110 * A key is probably required if there is a string fragment in the code
jjg@597 111 * that is part of the resource key, or if the key is well-known
jjg@597 112 * according to various pragmatic rules.
jjg@597 113 */
jjg@597 114 void findDeadKeys(Set<String> codeStrings, Set<String> resourceKeys) {
jjg@597 115 String[] prefixes = {
jjg@597 116 "compiler.err.", "compiler.warn.", "compiler.note.", "compiler.misc.",
jjg@597 117 "javac."
jjg@597 118 };
jjg@597 119 for (String rk: resourceKeys) {
jjg@597 120 // some keys are used directly, without a prefix.
jjg@597 121 if (codeStrings.contains(rk))
jjg@597 122 continue;
jjg@597 123
jjg@597 124 // remove standard prefix
jjg@597 125 String s = null;
jjg@597 126 for (int i = 0; i < prefixes.length && s == null; i++) {
jjg@597 127 if (rk.startsWith(prefixes[i])) {
jjg@597 128 s = rk.substring(prefixes[i].length());
jjg@597 129 }
jjg@597 130 }
jjg@597 131 if (s == null) {
jjg@597 132 error("Resource key does not start with a standard prefix: " + rk);
jjg@597 133 continue;
jjg@597 134 }
jjg@597 135
jjg@597 136 if (codeStrings.contains(s))
jjg@597 137 continue;
jjg@597 138
jjg@597 139 // keys ending in .1 are often synthesized
jjg@597 140 if (s.endsWith(".1") && codeStrings.contains(s.substring(0, s.length() - 2)))
jjg@597 141 continue;
jjg@597 142
jjg@597 143 // verbose keys are generated by ClassReader.printVerbose
jjg@597 144 if (s.startsWith("verbose.") && codeStrings.contains(s.substring(8)))
jjg@597 145 continue;
jjg@597 146
jjg@597 147 // mandatory warning messages are synthesized with no characteristic substring
jjg@597 148 if (isMandatoryWarningString(s))
jjg@597 149 continue;
jjg@597 150
jjg@597 151 // check known (valid) exceptions
jjg@597 152 if (knownRequired.contains(rk))
jjg@597 153 continue;
jjg@597 154
jjg@597 155 // check known suspects
jjg@597 156 if (needToInvestigate.contains(rk))
jjg@597 157 continue;
jjg@597 158
jjg@597 159 error("Resource key not found in code: " + rk);
jjg@597 160 }
jjg@597 161 }
jjg@597 162
jjg@597 163 /**
jjg@597 164 * The keys for mandatory warning messages are all synthesized and do not
jjg@597 165 * have a significant recognizable substring to look for.
jjg@597 166 */
jjg@597 167 private boolean isMandatoryWarningString(String s) {
jjg@597 168 String[] bases = { "deprecated", "unchecked", "varargs", "sunapi" };
jjg@597 169 String[] tails = { ".filename", ".filename.additional", ".plural", ".plural.additional", ".recompile" };
jjg@597 170 for (String b: bases) {
jjg@597 171 if (s.startsWith(b)) {
jjg@597 172 String tail = s.substring(b.length());
jjg@597 173 for (String t: tails) {
jjg@597 174 if (tail.equals(t))
jjg@597 175 return true;
jjg@597 176 }
jjg@597 177 }
jjg@597 178 }
jjg@597 179 return false;
jjg@597 180 }
jjg@597 181
jjg@597 182 Set<String> knownRequired = new TreeSet<String>(Arrays.asList(
jjg@597 183 // See Resolve.getErrorKey
jjg@597 184 "compiler.err.cant.resolve.args",
jjg@597 185 "compiler.err.cant.resolve.args.params",
jjg@597 186 "compiler.err.cant.resolve.location.args",
jjg@597 187 "compiler.err.cant.resolve.location.args.params",
jjg@597 188 // JavaCompiler, reports #errors and #warnings
jjg@597 189 "compiler.misc.count.error",
jjg@597 190 "compiler.misc.count.error.plural",
jjg@597 191 "compiler.misc.count.warn",
jjg@597 192 "compiler.misc.count.warn.plural",
jjg@597 193 // Used for LintCategory
jjg@597 194 "compiler.warn.lintOption",
jjg@597 195 // Other
jjg@597 196 "compiler.misc.base.membership" // (sic)
jjg@597 197 ));
jjg@597 198
jjg@597 199
jjg@597 200 Set<String> needToInvestigate = new TreeSet<String>(Arrays.asList(
jjg@597 201 "compiler.err.cant.read.file", // UNUSED
jjg@597 202 "compiler.err.illegal.self.ref", // UNUSED
jjg@597 203 "compiler.err.io.exception", // UNUSED
jjg@597 204 "compiler.err.limit.pool.in.class", // UNUSED
jjg@597 205 "compiler.err.name.reserved.for.internal.use", // UNUSED
jjg@597 206 "compiler.err.no.match.entry", // UNUSED
jjg@597 207 "compiler.err.not.within.bounds.explain", // UNUSED
jjg@597 208 "compiler.err.signature.doesnt.match.intf", // UNUSED
jjg@597 209 "compiler.err.signature.doesnt.match.supertype", // UNUSED
jjg@597 210 "compiler.err.type.var.more.than.once", // UNUSED
jjg@597 211 "compiler.err.type.var.more.than.once.in.result", // UNUSED
jjg@597 212 "compiler.misc.ccf.found.later.version", // UNUSED
jjg@597 213 "compiler.misc.non.denotable.type", // UNUSED
jjg@597 214 "compiler.misc.unnamed.package", // should be required, CR 6964147
jjg@597 215 "compiler.misc.verbose.retro", // UNUSED
jjg@597 216 "compiler.misc.verbose.retro.with", // UNUSED
jjg@597 217 "compiler.misc.verbose.retro.with.list", // UNUSED
jjg@597 218 "compiler.warn.proc.type.already.exists", // TODO in JavacFiler
jjg@597 219 "javac.err.invalid.arg", // UNUSED ??
jjg@597 220 "javac.opt.arg.class", // UNUSED ??
jjg@597 221 "javac.opt.arg.pathname", // UNUSED ??
jjg@597 222 "javac.opt.moreinfo", // option commented out
jjg@597 223 "javac.opt.nogj", // UNUSED
jjg@597 224 "javac.opt.printflat", // option commented out
jjg@597 225 "javac.opt.printsearch", // option commented out
jjg@597 226 "javac.opt.prompt", // option commented out
jjg@597 227 "javac.opt.retrofit", // UNUSED
jjg@597 228 "javac.opt.s", // option commented out
jjg@597 229 "javac.opt.scramble", // option commented out
jjg@597 230 "javac.opt.scrambleall" // option commented out
jjg@597 231 ));
jjg@597 232
jjg@597 233 /**
jjg@597 234 * For all strings in the code that look like they might be fragments of
jjg@597 235 * a resource key, verify that a key exists.
jjg@597 236 */
jjg@597 237 void findMissingKeys(Set<String> codeStrings, Set<String> resourceKeys) {
jjg@597 238 for (String cs: codeStrings) {
jjg@597 239 if (cs.matches("[A-Za-z][^.]*\\..*")) {
jjg@597 240 // ignore filenames (i.e. in SourceFile attribute
jjg@597 241 if (cs.matches(".*\\.java"))
jjg@597 242 continue;
jjg@597 243 // ignore package and class names
jjg@597 244 if (cs.matches("(com|java|javax|sun)\\.[A-Za-z.]+"))
jjg@597 245 continue;
jjg@597 246 // explicit known exceptions
jjg@597 247 if (noResourceRequired.contains(cs))
jjg@597 248 continue;
jjg@597 249 // look for matching resource
jjg@597 250 if (hasMatch(resourceKeys, cs))
jjg@597 251 continue;
jjg@597 252 error("no match for \"" + cs + "\"");
jjg@597 253 }
jjg@597 254 }
jjg@597 255 }
jjg@597 256 // where
jjg@597 257 private Set<String> noResourceRequired = new HashSet<String>(Arrays.asList(
jjg@597 258 // system properties
jjg@597 259 "application.home", // in Paths.java
jjg@597 260 "env.class.path",
jjg@597 261 "line.separator",
jjg@597 262 "user.dir",
jjg@597 263 // file names
jjg@597 264 "ct.sym",
jjg@597 265 "rt.jar",
jjg@597 266 "tools.jar",
jjg@597 267 // -XD option names
jjg@597 268 "process.packages",
jjg@597 269 "ignore.symbol.file",
jjg@597 270 // prefix/embedded strings
jjg@597 271 "compiler.",
jjg@597 272 "compiler.misc.",
jjg@597 273 "count.",
jjg@597 274 "illegal.",
jjg@597 275 "javac.",
jjg@597 276 "verbose."
jjg@597 277 ));
jjg@597 278
jjg@597 279 /**
jjg@597 280 * Look for a resource that ends in this string fragment.
jjg@597 281 */
jjg@597 282 boolean hasMatch(Set<String> resourceKeys, String s) {
jjg@597 283 for (String rk: resourceKeys) {
jjg@597 284 if (rk.endsWith(s))
jjg@597 285 return true;
jjg@597 286 }
jjg@597 287 return false;
jjg@597 288 }
jjg@597 289
jjg@597 290 /**
jjg@597 291 * Get the set of strings from (most of) the javac classfiles.
jjg@597 292 */
jjg@597 293 Set<String> getCodeStrings() throws IOException {
jjg@597 294 Set<String> results = new TreeSet<String>();
jjg@597 295 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
jjg@597 296 JavaFileManager fm = c.getStandardFileManager(null, null, null);
jjg@605 297 JavaFileManager.Location javacLoc = findJavacLocation(fm);
jjg@597 298 String[] pkgs = {
jjg@597 299 "javax.annotation.processing",
jjg@597 300 "javax.lang.model",
jjg@597 301 "javax.tools",
jjg@597 302 "com.sun.source",
jjg@597 303 "com.sun.tools.javac"
jjg@597 304 };
jjg@597 305 for (String pkg: pkgs) {
jjg@605 306 for (JavaFileObject fo: fm.list(javacLoc,
jjg@597 307 pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) {
jjg@597 308 String name = fo.getName();
jjg@597 309 // ignore resource files, and files which are not really part of javac
jjg@597 310 if (name.contains("resources")
jjg@597 311 || name.contains("Launcher.class")
jjg@597 312 || name.contains("CreateSymbols.class"))
jjg@597 313 continue;
jjg@597 314 scan(fo, results);
jjg@597 315 }
jjg@597 316 }
jjg@597 317 return results;
jjg@597 318 }
jjg@597 319
jjg@605 320 // depending on how the test is run, javac may be on bootclasspath or classpath
jjg@605 321 JavaFileManager.Location findJavacLocation(JavaFileManager fm) {
jjg@605 322 JavaFileManager.Location[] locns =
jjg@605 323 { StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH };
jjg@605 324 try {
jjg@605 325 for (JavaFileManager.Location l: locns) {
jjg@605 326 JavaFileObject fo = fm.getJavaFileForInput(l,
jjg@605 327 "com.sun.tools.javac.Main", JavaFileObject.Kind.CLASS);
jjg@605 328 if (fo != null)
jjg@605 329 return l;
jjg@605 330 }
jjg@605 331 } catch (IOException e) {
jjg@605 332 throw new Error(e);
jjg@605 333 }
jjg@605 334 throw new IllegalStateException("Cannot find javac");
jjg@605 335 }
jjg@605 336
jjg@597 337 /**
jjg@597 338 * Get the set of strings from a class file.
jjg@597 339 * Only strings that look like they might be a resource key are returned.
jjg@597 340 */
jjg@597 341 void scan(JavaFileObject fo, Set<String> results) throws IOException {
jjg@597 342 InputStream in = fo.openInputStream();
jjg@597 343 try {
jjg@597 344 ClassFile cf = ClassFile.read(in);
jjg@597 345 for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) {
jjg@597 346 if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) {
jjg@597 347 String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value;
jjg@597 348 if (v.matches("[A-Za-z0-9-_.]+"))
jjg@597 349 results.add(v);
jjg@597 350 }
jjg@597 351 }
jjg@597 352 } catch (ConstantPoolException ignore) {
jjg@597 353 } finally {
jjg@597 354 in.close();
jjg@597 355 }
jjg@597 356 }
jjg@597 357
jjg@597 358 /**
jjg@597 359 * Get the set of keys from the javac resource bundles.
jjg@597 360 */
jjg@597 361 Set<String> getResourceKeys() {
jjg@597 362 Set<String> results = new TreeSet<String>();
jjg@597 363 for (String name : new String[]{"javac", "compiler"}) {
jjg@597 364 ResourceBundle b =
jjg@597 365 ResourceBundle.getBundle("com.sun.tools.javac.resources." + name);
jjg@597 366 results.addAll(b.keySet());
jjg@597 367 }
jjg@597 368 return results;
jjg@597 369 }
jjg@597 370
jjg@597 371 /**
jjg@597 372 * Report an error.
jjg@597 373 */
jjg@597 374 void error(String msg) {
jjg@597 375 System.err.println("Error: " + msg);
jjg@597 376 errors++;
jjg@597 377 }
jjg@597 378
jjg@597 379 int errors;
jjg@597 380 }

mercurial