0
# String and Format Annotations
1
2
String and format annotations validate string formats, regular expressions, and format strings to prevent runtime errors from malformed strings, invalid regex patterns, and mismatched format arguments.
3
4
## Capabilities
5
6
### Regular Expression Annotations
7
8
Annotations for validating regular expression patterns.
9
10
```java { .api }
11
/**
12
* Indicates that the annotated String is a valid regular expression
13
*/
14
@Documented
15
@Retention(RetentionPolicy.RUNTIME)
16
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
17
@SubtypeOf(UnknownRegex.class)
18
public @interface Regex {}
19
20
/**
21
* Indicates a partial regular expression (part of a larger pattern)
22
*/
23
@Documented
24
@Retention(RetentionPolicy.RUNTIME)
25
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
26
@SubtypeOf(Regex.class)
27
public @interface PartialRegex {}
28
29
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
30
@SubtypeOf({})
31
@DefaultQualifierInHierarchy
32
public @interface UnknownRegex {}
33
34
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
35
@SubtypeOf(Regex.class)
36
public @interface RegexBottom {}
37
38
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
39
@PolymorphicQualifier(UnknownRegex.class)
40
public @interface PolyRegex {}
41
```
42
43
**Usage Examples:**
44
45
```java
46
import org.checkerframework.checker.regex.qual.*;
47
import java.util.regex.Pattern;
48
49
public class RegexExample {
50
// Method requires a valid regex pattern
51
public Pattern compile(@Regex String pattern) {
52
return Pattern.compile(pattern); // Safe - pattern is validated
53
}
54
55
// Method that validates and returns regex
56
public @Regex String createEmailPattern() {
57
return "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
58
}
59
60
// Polymorphic method preserves regex properties
61
public @PolyRegex String process(@PolyRegex String input) {
62
return input.trim();
63
}
64
65
public void example() {
66
@Regex String emailRegex = createEmailPattern();
67
Pattern emailPattern = compile(emailRegex); // Safe
68
69
String userInput = getUserInput(); // Unknown if valid regex
70
// compile(userInput); // Would cause type error
71
72
if (isValidRegex(userInput)) {
73
@Regex String validPattern = (@Regex String) userInput;
74
Pattern pattern = compile(validPattern); // Safe after validation
75
}
76
}
77
78
private boolean isValidRegex(String pattern) {
79
try {
80
Pattern.compile(pattern);
81
return true;
82
} catch (Exception e) {
83
return false;
84
}
85
}
86
}
87
```
88
89
### Format String Annotations
90
91
Annotations for validating printf-style format strings and their arguments.
92
93
```java { .api }
94
/**
95
* Indicates a valid format string with specified conversion categories
96
*/
97
@Documented
98
@Retention(RetentionPolicy.RUNTIME)
99
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
100
@SubtypeOf(UnknownFormat.class)
101
public @interface Format {
102
ConversionCategory[] value();
103
}
104
105
/**
106
* Indicates an invalid format string
107
*/
108
@Documented
109
@Retention(RetentionPolicy.RUNTIME)
110
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
111
@SubtypeOf(UnknownFormat.class)
112
public @interface InvalidFormat {}
113
114
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
115
@SubtypeOf({})
116
@DefaultQualifierInHierarchy
117
public @interface UnknownFormat {}
118
119
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
120
@SubtypeOf(Format.class)
121
public @interface FormatBottom {}
122
123
/**
124
* Method that returns a format string
125
*/
126
@Target(ElementType.METHOD)
127
@Retention(RetentionPolicy.RUNTIME)
128
public @interface ReturnsFormat {}
129
130
/**
131
* Method that uses format strings (like printf)
132
*/
133
@Target(ElementType.METHOD)
134
@Retention(RetentionPolicy.RUNTIME)
135
public @interface FormatMethod {}
136
137
/**
138
* Categories of printf conversion specifiers
139
*/
140
public enum ConversionCategory {
141
UNUSED, // %% - literal %
142
GENERAL, // %s, %b, %h - any type
143
CHAR, // %c - char, Character, byte, Byte, short, Short, int, Integer
144
INT, // %d, %o, %x - integral types
145
FLOAT, // %e, %f, %g - floating point types
146
TIME, // %t - date/time types
147
CHAR_AND_INT, // %c or %d
148
INT_AND_TIME // %d or %t
149
}
150
```
151
152
**Usage Examples:**
153
154
```java
155
import org.checkerframework.checker.formatter.qual.*;
156
157
public class FormatExample {
158
// Method that requires a format string with specific argument types
159
@FormatMethod
160
public void logMessage(@Format({ConversionCategory.GENERAL, ConversionCategory.INT}) String format,
161
Object message, int count) {
162
System.out.printf(format, message, count); // Safe - format matches arguments
163
}
164
165
@ReturnsFormat({ConversionCategory.GENERAL, ConversionCategory.FLOAT})
166
public String createFormatString() {
167
return "User: %s, Score: %.2f";
168
}
169
170
public void example() {
171
// Valid format strings
172
@Format({ConversionCategory.GENERAL}) String nameFormat = "Hello, %s!";
173
@Format({ConversionCategory.INT, ConversionCategory.FLOAT}) String scoreFormat = "Score: %d/%.1f";
174
175
// Safe usage
176
System.out.printf(nameFormat, "Alice");
177
System.out.printf(scoreFormat, 85, 100.0);
178
179
// Using method-created format
180
@Format({ConversionCategory.GENERAL, ConversionCategory.FLOAT}) String userFormat = createFormatString();
181
System.out.printf(userFormat, "Bob", 92.5);
182
183
// Log with specific format requirements
184
logMessage("Processing %s: %d items", "data.txt", 150);
185
}
186
187
// Method that validates and converts unknown format strings
188
public @Format({ConversionCategory.GENERAL}) String validateSimpleFormat(String input) {
189
if (input.matches("^[^%]*%s[^%]*$")) {
190
return (@Format({ConversionCategory.GENERAL}) String) input;
191
}
192
throw new IllegalArgumentException("Invalid format string");
193
}
194
}
195
```
196
197
### Signature String Annotations
198
199
Annotations for Java signature strings used in reflection and bytecode operations.
200
201
```java { .api }
202
/**
203
* Java binary name (e.g., "java.lang.Object", "java.lang.Object$Inner")
204
*/
205
@Documented
206
@Retention(RetentionPolicy.RUNTIME)
207
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
208
@SubtypeOf(SignatureUnknown.class)
209
public @interface BinaryName {}
210
211
/**
212
* Java canonical name (e.g., "java.lang.Object", "java.lang.Object.Inner")
213
*/
214
@Documented
215
@Retention(RetentionPolicy.RUNTIME)
216
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
217
@SubtypeOf(SignatureUnknown.class)
218
public @interface CanonicalName {}
219
220
/**
221
* JVM field descriptor (e.g., "I", "Ljava/lang/String;")
222
*/
223
@Documented
224
@Retention(RetentionPolicy.RUNTIME)
225
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
226
@SubtypeOf(SignatureUnknown.class)
227
public @interface FieldDescriptor {}
228
229
/**
230
* JVM method descriptor (e.g., "(I)V", "(Ljava/lang/String;)I")
231
*/
232
@Documented
233
@Retention(RetentionPolicy.RUNTIME)
234
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
235
@SubtypeOf(SignatureUnknown.class)
236
public @interface MethodDescriptor {}
237
238
/**
239
* Java identifier (valid Java name)
240
*/
241
@Documented
242
@Retention(RetentionPolicy.RUNTIME)
243
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
244
@SubtypeOf(SignatureUnknown.class)
245
public @interface Identifier {}
246
247
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
248
@DefaultQualifierInHierarchy
249
public @interface SignatureUnknown {}
250
251
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
252
@SubtypeOf(BinaryName.class)
253
public @interface SignatureBottom {}
254
255
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
256
@PolymorphicQualifier(SignatureUnknown.class)
257
public @interface PolySignature {}
258
```
259
260
**Usage Examples:**
261
262
```java
263
import org.checkerframework.checker.signature.qual.*;
264
import java.lang.reflect.Field;
265
import java.lang.reflect.Method;
266
267
public class SignatureExample {
268
// Method that requires a valid Java binary name
269
public Class<?> loadClass(@BinaryName String className) throws ClassNotFoundException {
270
return Class.forName(className); // Safe - className is valid binary name
271
}
272
273
// Method that requires a field descriptor
274
public void analyzeField(@FieldDescriptor String descriptor) {
275
// Parse JVM field descriptor safely
276
char typeChar = descriptor.charAt(0);
277
switch (typeChar) {
278
case 'I': System.out.println("int field"); break;
279
case 'L': System.out.println("object field"); break;
280
// ... other cases
281
}
282
}
283
284
// Convert canonical name to binary name
285
public @BinaryName String canonicalToBinary(@CanonicalName String canonical) {
286
return canonical.replace('.', '$'); // Convert inner class separator
287
}
288
289
public void reflectionExample() throws Exception {
290
// Valid binary names
291
@BinaryName String stringClass = "java.lang.String";
292
@BinaryName String innerClass = "java.util.Map$Entry";
293
294
Class<?> clazz1 = loadClass(stringClass); // Safe
295
Class<?> clazz2 = loadClass(innerClass); // Safe
296
297
// Field descriptors
298
@FieldDescriptor String intDescriptor = "I";
299
@FieldDescriptor String stringDescriptor = "Ljava/lang/String;";
300
301
analyzeField(intDescriptor); // Safe
302
analyzeField(stringDescriptor); // Safe
303
304
// Method descriptors for reflection
305
@MethodDescriptor String voidMethod = "()V";
306
@MethodDescriptor String stringToInt = "(Ljava/lang/String;)I";
307
308
// Use in reflection calls (conceptual)
309
// Method method = getMethodByDescriptor(clazz1, stringToInt);
310
}
311
312
// Validate Java identifier
313
public boolean isValidIdentifier(@Identifier String name) {
314
return Character.isJavaIdentifierStart(name.charAt(0)) &&
315
name.chars().skip(1).allMatch(Character::isJavaIdentifierPart);
316
}
317
}
318
```
319
320
### Internationalization Format Annotations
321
322
Annotations for internationalized format strings and message keys.
323
324
```java { .api }
325
/**
326
* Valid internationalized format string
327
*/
328
@Documented
329
@Retention(RetentionPolicy.RUNTIME)
330
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
331
@SubtypeOf(I18nUnknownFormat.class)
332
public @interface I18nFormat {
333
I18nConversionCategory[] value();
334
}
335
336
/**
337
* Invalid internationalized format string
338
*/
339
@Documented
340
@Retention(RetentionPolicy.RUNTIME)
341
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
342
@SubtypeOf(I18nUnknownFormat.class)
343
public @interface I18nInvalidFormat {}
344
345
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
346
@DefaultQualifierInHierarchy
347
public @interface I18nUnknownFormat {}
348
349
/**
350
* I18n conversion categories (similar to printf categories)
351
*/
352
public enum I18nConversionCategory {
353
UNUSED,
354
GENERAL,
355
DATE,
356
NUMBER
357
}
358
359
/**
360
* Valid localizable key for resource bundles
361
*/
362
@Documented
363
@Retention(RetentionPolicy.RUNTIME)
364
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
365
@SubtypeOf(UnknownLocalizableKey.class)
366
public @interface LocalizableKey {}
367
368
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
369
@DefaultQualifierInHierarchy
370
public @interface UnknownLocalizableKey {}
371
372
/**
373
* String that has been localized
374
*/
375
@Documented
376
@Retention(RetentionPolicy.RUNTIME)
377
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
378
@SubtypeOf(UnknownLocalized.class)
379
public @interface Localized {}
380
381
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
382
@DefaultQualifierInHierarchy
383
public @interface UnknownLocalized {}
384
```
385
386
**Usage Examples:**
387
388
```java
389
import org.checkerframework.checker.i18n.qual.*;
390
import org.checkerframework.checker.i18nformatter.qual.*;
391
import java.util.ResourceBundle;
392
import java.text.MessageFormat;
393
394
public class I18nExample {
395
private ResourceBundle bundle = ResourceBundle.getBundle("messages");
396
397
// Method that requires a valid localizable key
398
public @Localized String getMessage(@LocalizableKey String key) {
399
return bundle.getString(key); // Safe - key is valid
400
}
401
402
// Method that formats i18n messages
403
public @Localized String formatMessage(@I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String pattern,
404
Object name, Number count) {
405
return MessageFormat.format(pattern, name, count); // Safe - format matches arguments
406
}
407
408
public void example() {
409
// Valid localizable keys (would be validated against resource bundle)
410
@LocalizableKey String welcomeKey = "welcome.message";
411
@LocalizableKey String errorKey = "error.file_not_found";
412
413
// Get localized strings
414
@Localized String welcome = getMessage(welcomeKey);
415
@Localized String error = getMessage(errorKey);
416
417
// Valid i18n format strings
418
@I18nFormat({I18nConversionCategory.GENERAL}) String nameFormat = "Hello, {0}!";
419
@I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER})
420
String countFormat = "Processing {0}: {1} items";
421
422
// Safe formatting
423
@Localized String greeting = formatMessage(nameFormat, "Alice");
424
@Localized String status = formatMessage(countFormat, "data.txt", 150);
425
426
System.out.println(greeting);
427
System.out.println(status);
428
}
429
}
430
```
431
432
### Property Key Annotations
433
434
Annotations for validating property keys in configuration files.
435
436
```java { .api }
437
/**
438
* Valid property key for configuration files
439
*/
440
@Documented
441
@Retention(RetentionPolicy.RUNTIME)
442
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
443
@SubtypeOf(UnknownPropertyKey.class)
444
public @interface PropertyKey {}
445
446
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
447
@DefaultQualifierInHierarchy
448
public @interface UnknownPropertyKey {}
449
450
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
451
@SubtypeOf(PropertyKey.class)
452
public @interface PropertyKeyBottom {}
453
```
454
455
**Usage Examples:**
456
457
```java
458
import org.checkerframework.checker.propkey.qual.*;
459
import java.util.Properties;
460
461
public class PropertyExample {
462
private Properties config = new Properties();
463
464
// Method that requires a valid property key
465
public String getProperty(@PropertyKey String key) {
466
return config.getProperty(key); // Safe - key is validated
467
}
468
469
public void configure() {
470
// Valid property keys (would be validated against properties file)
471
@PropertyKey String dbUrlKey = "database.url";
472
@PropertyKey String dbUserKey = "database.username";
473
@PropertyKey String dbPassKey = "database.password";
474
475
// Safe property access
476
String dbUrl = getProperty(dbUrlKey);
477
String dbUser = getProperty(dbUserKey);
478
String dbPass = getProperty(dbPassKey);
479
480
// Use configuration values
481
connectToDatabase(dbUrl, dbUser, dbPass);
482
}
483
484
private void connectToDatabase(String url, String user, String password) {
485
// Database connection logic
486
}
487
}
488
```