32
32
33
33
import java .io .*;
34
34
import java .util .*;
35
+ import java .util .regex .*;
35
36
import javax .tools .*;
36
37
import com .sun .tools .classfile .*;
37
38
import com .sun .tools .javac .code .Lint .LintCategory ;
@@ -47,6 +48,8 @@ public class CheckResourceKeys {
47
48
* look for keys in resource bundles that are no longer required
48
49
* -findmissingkeys
49
50
* look for keys in resource bundles that are missing
51
+ * -checkformats
52
+ * validate MessageFormat patterns in resource bundles
50
53
*
51
54
* @throws Exception if invoked by jtreg and errors occur
52
55
*/
@@ -71,16 +74,19 @@ static boolean is_jtreg() {
71
74
boolean run (String ... args ) throws Exception {
72
75
boolean findDeadKeys = false ;
73
76
boolean findMissingKeys = false ;
77
+ boolean checkFormats = false ;
74
78
75
79
if (args .length == 0 ) {
76
80
if (is_jtreg ()) {
77
81
findDeadKeys = true ;
78
82
findMissingKeys = true ;
83
+ checkFormats = true ;
79
84
} else {
80
85
System .err .println ("Usage: java CheckResourceKeys <options>" );
81
86
System .err .println ("where options include" );
82
87
System .err .println (" -finddeadkeys find keys in resource bundles which are no longer required" );
83
88
System .err .println (" -findmissingkeys find keys in resource bundles that are required but missing" );
89
+ System .err .println (" -checkformats validate MessageFormat patterns in resource bundles" );
84
90
return true ;
85
91
}
86
92
} else {
@@ -89,6 +95,8 @@ boolean run(String... args) throws Exception {
89
95
findDeadKeys = true ;
90
96
else if (arg .equalsIgnoreCase ("-findmissingkeys" ))
91
97
findMissingKeys = true ;
98
+ else if (arg .equalsIgnoreCase ("-checkformats" ))
99
+ checkFormats = true ;
92
100
else
93
101
error ("bad option: " + arg );
94
102
}
@@ -106,6 +114,9 @@ else if (arg.equalsIgnoreCase("-findmissingkeys"))
106
114
if (findMissingKeys )
107
115
findMissingKeys (codeStrings , resourceKeys );
108
116
117
+ if (checkFormats )
118
+ checkFormats (getMessageFormatBundles ());
119
+
109
120
return (errors == 0 );
110
121
}
111
122
@@ -312,6 +323,109 @@ void findMissingKeys(Set<String> codeStrings, Set<String> resourceKeys) {
312
323
"locn."
313
324
));
314
325
326
+ void checkFormats (List <ResourceBundle > messageFormatBundles ) {
327
+ for (ResourceBundle bundle : messageFormatBundles ) {
328
+ for (String key : bundle .keySet ()) {
329
+ final String pattern = bundle .getString (key );
330
+ try {
331
+ validateMessageFormatPattern (pattern );
332
+ } catch (IllegalArgumentException e ) {
333
+ error ("Invalid MessageFormat pattern for resource \" "
334
+ + key + "\" : " + e .getMessage ());
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Do some basic validation of a {@link java.text.MessageFormat} format string.
342
+ *
343
+ * <p>
344
+ * This checks for balanced braces and unnecessary quoting.
345
+ * Code cut, pasted, & simplified from {@link java.text.MessageFormat#applyPattern}.
346
+ *
347
+ * @throws IllegalArgumentException if {@code pattern} is invalid
348
+ * @throws IllegalArgumentException if {@code pattern} is null
349
+ */
350
+ public static void validateMessageFormatPattern (String pattern ) {
351
+
352
+ // Check for null
353
+ if (pattern == null )
354
+ throw new IllegalArgumentException ("null pattern" );
355
+
356
+ // Replicate the quirky lexical analysis of MessageFormat's parsing algorithm
357
+ final int SEG_RAW = 0 ;
358
+ final int SEG_INDEX = 1 ;
359
+ final int SEG_TYPE = 2 ;
360
+ final int SEG_MODIFIER = 3 ;
361
+ int part = SEG_RAW ;
362
+ int braceStack = 0 ;
363
+ int quotedStartPos = -1 ;
364
+ for (int i = 0 ; i < pattern .length (); i ++) {
365
+ final char ch = pattern .charAt (i );
366
+ if (part == SEG_RAW ) {
367
+ if (ch == '\'' ) {
368
+ if (i + 1 < pattern .length () && pattern .charAt (i + 1 ) == '\'' )
369
+ i ++;
370
+ else if (quotedStartPos == -1 )
371
+ quotedStartPos = i ;
372
+ else {
373
+ validateMessageFormatQuoted (pattern .substring (quotedStartPos + 1 , i ));
374
+ quotedStartPos = -1 ;
375
+ }
376
+ } else if (ch == '{' && quotedStartPos == -1 )
377
+ part = SEG_INDEX ;
378
+ continue ;
379
+ }
380
+ if (quotedStartPos != -1 ) {
381
+ if (ch == '\'' ) {
382
+ validateMessageFormatQuoted (pattern .substring (quotedStartPos + 1 , i ));
383
+ quotedStartPos = -1 ;
384
+ }
385
+ continue ;
386
+ }
387
+ switch (ch ) {
388
+ case ',' :
389
+ if (part < SEG_MODIFIER )
390
+ part ++;
391
+ break ;
392
+ case '{' :
393
+ braceStack ++;
394
+ break ;
395
+ case '}' :
396
+ if (braceStack == 0 )
397
+ part = SEG_RAW ;
398
+ else
399
+ braceStack --;
400
+ break ;
401
+ case '\'' :
402
+ quotedStartPos = i ;
403
+ break ;
404
+ default :
405
+ break ;
406
+ }
407
+ }
408
+ if (part != SEG_RAW )
409
+ throw new IllegalArgumentException ("unmatched braces" );
410
+ if (quotedStartPos != -1 )
411
+ throw new IllegalArgumentException ("unmatched quote starting at offset " + quotedStartPos );
412
+ }
413
+
414
+ /**
415
+ * Validate the content of a quoted substring in a {@link java.text.MessageFormat} pattern.
416
+ *
417
+ * <p>
418
+ * We expect this content to contain at least one special character. Otherwise,
419
+ * it was probably meant to be something in single quotes but somebody forgot
420
+ * to escape the single quotes by doulbing them; and even if intentional,
421
+ * it's still bogus because the single quotes are just going to get discarded
422
+ * and so they were unnecessary in the first place.
423
+ */
424
+ static void validateMessageFormatQuoted (String quoted ) {
425
+ if (quoted .matches ("[^'{},]+" ))
426
+ throw new IllegalArgumentException ("unescaped single quotes around \" " + quoted + "\" " );
427
+ }
428
+
315
429
/**
316
430
* Look for a resource that ends in this string fragment.
317
431
*/
@@ -405,6 +519,20 @@ Set<String> getResourceKeys() {
405
519
return results ;
406
520
}
407
521
522
+ /**
523
+ * Get resource bundles containing MessageFormat strings.
524
+ */
525
+ List <ResourceBundle > getMessageFormatBundles () {
526
+ Module jdk_compiler = ModuleLayer .boot ().findModule ("jdk.compiler" ).get ();
527
+ List <ResourceBundle > results = new ArrayList <>();
528
+ for (String name : new String []{"compiler" , "launcher" }) {
529
+ ResourceBundle b =
530
+ ResourceBundle .getBundle ("com.sun.tools.javac.resources." + name , jdk_compiler );
531
+ results .add (b );
532
+ }
533
+ return results ;
534
+ }
535
+
408
536
/**
409
537
* Report an error.
410
538
*/
0 commit comments