|
1 /* |
|
2 * Copyright (c) 2010, 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 |
|
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 // JavaCompiler, reports #errors and #warnings |
|
189 "compiler.misc.count.error", |
|
190 "compiler.misc.count.error.plural", |
|
191 "compiler.misc.count.warn", |
|
192 "compiler.misc.count.warn.plural", |
|
193 // Used for LintCategory |
|
194 "compiler.warn.lintOption", |
|
195 // Other |
|
196 "compiler.misc.base.membership" // (sic) |
|
197 )); |
|
198 |
|
199 |
|
200 Set<String> needToInvestigate = new TreeSet<String>(Arrays.asList( |
|
201 "compiler.err.cant.read.file", // UNUSED |
|
202 "compiler.err.illegal.self.ref", // UNUSED |
|
203 "compiler.err.io.exception", // UNUSED |
|
204 "compiler.err.limit.pool.in.class", // UNUSED |
|
205 "compiler.err.name.reserved.for.internal.use", // UNUSED |
|
206 "compiler.err.no.match.entry", // UNUSED |
|
207 "compiler.err.not.within.bounds.explain", // UNUSED |
|
208 "compiler.err.signature.doesnt.match.intf", // UNUSED |
|
209 "compiler.err.signature.doesnt.match.supertype", // UNUSED |
|
210 "compiler.err.type.var.more.than.once", // UNUSED |
|
211 "compiler.err.type.var.more.than.once.in.result", // UNUSED |
|
212 "compiler.misc.ccf.found.later.version", // UNUSED |
|
213 "compiler.misc.non.denotable.type", // UNUSED |
|
214 "compiler.misc.unnamed.package", // should be required, CR 6964147 |
|
215 "compiler.misc.verbose.retro", // UNUSED |
|
216 "compiler.misc.verbose.retro.with", // UNUSED |
|
217 "compiler.misc.verbose.retro.with.list", // UNUSED |
|
218 "compiler.warn.proc.type.already.exists", // TODO in JavacFiler |
|
219 "javac.err.invalid.arg", // UNUSED ?? |
|
220 "javac.opt.arg.class", // UNUSED ?? |
|
221 "javac.opt.arg.pathname", // UNUSED ?? |
|
222 "javac.opt.moreinfo", // option commented out |
|
223 "javac.opt.nogj", // UNUSED |
|
224 "javac.opt.printflat", // option commented out |
|
225 "javac.opt.printsearch", // option commented out |
|
226 "javac.opt.prompt", // option commented out |
|
227 "javac.opt.retrofit", // UNUSED |
|
228 "javac.opt.s", // option commented out |
|
229 "javac.opt.scramble", // option commented out |
|
230 "javac.opt.scrambleall" // option commented out |
|
231 )); |
|
232 |
|
233 /** |
|
234 * For all strings in the code that look like they might be fragments of |
|
235 * a resource key, verify that a key exists. |
|
236 */ |
|
237 void findMissingKeys(Set<String> codeStrings, Set<String> resourceKeys) { |
|
238 for (String cs: codeStrings) { |
|
239 if (cs.matches("[A-Za-z][^.]*\\..*")) { |
|
240 // ignore filenames (i.e. in SourceFile attribute |
|
241 if (cs.matches(".*\\.java")) |
|
242 continue; |
|
243 // ignore package and class names |
|
244 if (cs.matches("(com|java|javax|sun)\\.[A-Za-z.]+")) |
|
245 continue; |
|
246 // explicit known exceptions |
|
247 if (noResourceRequired.contains(cs)) |
|
248 continue; |
|
249 // look for matching resource |
|
250 if (hasMatch(resourceKeys, cs)) |
|
251 continue; |
|
252 error("no match for \"" + cs + "\""); |
|
253 } |
|
254 } |
|
255 } |
|
256 // where |
|
257 private Set<String> noResourceRequired = new HashSet<String>(Arrays.asList( |
|
258 // system properties |
|
259 "application.home", // in Paths.java |
|
260 "env.class.path", |
|
261 "line.separator", |
|
262 "user.dir", |
|
263 // file names |
|
264 "ct.sym", |
|
265 "rt.jar", |
|
266 "tools.jar", |
|
267 // -XD option names |
|
268 "process.packages", |
|
269 "ignore.symbol.file", |
|
270 // prefix/embedded strings |
|
271 "compiler.", |
|
272 "compiler.misc.", |
|
273 "count.", |
|
274 "illegal.", |
|
275 "javac.", |
|
276 "verbose." |
|
277 )); |
|
278 |
|
279 /** |
|
280 * Look for a resource that ends in this string fragment. |
|
281 */ |
|
282 boolean hasMatch(Set<String> resourceKeys, String s) { |
|
283 for (String rk: resourceKeys) { |
|
284 if (rk.endsWith(s)) |
|
285 return true; |
|
286 } |
|
287 return false; |
|
288 } |
|
289 |
|
290 /** |
|
291 * Get the set of strings from (most of) the javac classfiles. |
|
292 */ |
|
293 Set<String> getCodeStrings() throws IOException { |
|
294 Set<String> results = new TreeSet<String>(); |
|
295 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); |
|
296 JavaFileManager fm = c.getStandardFileManager(null, null, null); |
|
297 String[] pkgs = { |
|
298 "javax.annotation.processing", |
|
299 "javax.lang.model", |
|
300 "javax.tools", |
|
301 "com.sun.source", |
|
302 "com.sun.tools.javac" |
|
303 }; |
|
304 for (String pkg: pkgs) { |
|
305 for (JavaFileObject fo: fm.list(StandardLocation.PLATFORM_CLASS_PATH, |
|
306 pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) { |
|
307 String name = fo.getName(); |
|
308 // ignore resource files, and files which are not really part of javac |
|
309 if (name.contains("resources") |
|
310 || name.contains("Launcher.class") |
|
311 || name.contains("CreateSymbols.class")) |
|
312 continue; |
|
313 scan(fo, results); |
|
314 } |
|
315 } |
|
316 return results; |
|
317 } |
|
318 |
|
319 /** |
|
320 * Get the set of strings from a class file. |
|
321 * Only strings that look like they might be a resource key are returned. |
|
322 */ |
|
323 void scan(JavaFileObject fo, Set<String> results) throws IOException { |
|
324 InputStream in = fo.openInputStream(); |
|
325 try { |
|
326 ClassFile cf = ClassFile.read(in); |
|
327 for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) { |
|
328 if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) { |
|
329 String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value; |
|
330 if (v.matches("[A-Za-z0-9-_.]+")) |
|
331 results.add(v); |
|
332 } |
|
333 } |
|
334 } catch (ConstantPoolException ignore) { |
|
335 } finally { |
|
336 in.close(); |
|
337 } |
|
338 } |
|
339 |
|
340 /** |
|
341 * Get the set of keys from the javac resource bundles. |
|
342 */ |
|
343 Set<String> getResourceKeys() { |
|
344 Set<String> results = new TreeSet<String>(); |
|
345 for (String name : new String[]{"javac", "compiler"}) { |
|
346 ResourceBundle b = |
|
347 ResourceBundle.getBundle("com.sun.tools.javac.resources." + name); |
|
348 results.addAll(b.keySet()); |
|
349 } |
|
350 return results; |
|
351 } |
|
352 |
|
353 /** |
|
354 * Report an error. |
|
355 */ |
|
356 void error(String msg) { |
|
357 System.err.println("Error: " + msg); |
|
358 errors++; |
|
359 } |
|
360 |
|
361 int errors; |
|
362 } |