test/tools/javac/diags/CheckResourceKeys.java

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

mercurial