0
# Properties System
1
2
The Properties System provides a type-safe, configurable framework for managing rule and component properties. It includes property descriptors, validation, serialization, factory methods, and source management for flexible configuration.
3
4
## Capabilities
5
6
### Property Source Interface
7
8
Base interface for objects that support configurable properties with type-safe access and management.
9
10
```java { .api }
11
/**
12
* Base interface for objects that can have configurable properties.
13
* Provides type-safe property access and management capabilities.
14
*/
15
public interface PropertySource {
16
17
/**
18
* Get property value by descriptor
19
* @param propertyDescriptor PropertyDescriptor defining the property
20
* @return Current value of the property
21
*/
22
<T> T getProperty(PropertyDescriptor<T> propertyDescriptor);
23
24
/**
25
* Set property value by descriptor
26
* @param propertyDescriptor PropertyDescriptor defining the property
27
* @param value New value for the property
28
* @throws IllegalArgumentException if value fails validation
29
*/
30
<T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value);
31
32
/**
33
* Check if property descriptor is supported
34
* @param descriptor PropertyDescriptor to check
35
* @return true if this source supports the property
36
*/
37
boolean hasDescriptor(PropertyDescriptor<?> descriptor);
38
39
/**
40
* Get all supported property descriptors
41
* @return Unmodifiable list of all PropertyDescriptor instances
42
*/
43
List<PropertyDescriptor<?>> getPropertyDescriptors();
44
45
/**
46
* Get property source for chained access
47
* @return This PropertySource instance for method chaining
48
*/
49
PropertySource getPropertySource();
50
51
/**
52
* Get all properties as map
53
* @return Map of PropertyDescriptor to current value for all properties
54
*/
55
Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor();
56
}
57
```
58
59
**Usage Examples:**
60
61
```java
62
import net.sourceforge.pmd.properties.*;
63
import net.sourceforge.pmd.lang.rule.Rule;
64
65
// Working with rule properties
66
public class PropertyExamples {
67
68
public void configureRule(Rule rule) {
69
// Get available properties
70
List<PropertyDescriptor<?>> properties = rule.getPropertyDescriptors();
71
System.out.printf("Rule %s has %d properties:%n",
72
rule.getName(), properties.size());
73
74
for (PropertyDescriptor<?> prop : properties) {
75
System.out.printf(" %s (%s): %s%n",
76
prop.name(),
77
prop.type().getSimpleName(),
78
prop.description());
79
}
80
81
// Check if rule supports specific property
82
PropertyDescriptor<Integer> thresholdProp = PropertyFactory
83
.intProperty("threshold").build();
84
85
if (rule.hasDescriptor(thresholdProp)) {
86
// Get current value
87
Integer currentThreshold = rule.getProperty(thresholdProp);
88
System.out.printf("Current threshold: %d%n", currentThreshold);
89
90
// Set new value
91
rule.setProperty(thresholdProp, 15);
92
}
93
94
// Get all property values
95
Map<PropertyDescriptor<?>, Object> allProps =
96
rule.getPropertiesByPropertyDescriptor();
97
98
allProps.forEach((desc, value) ->
99
System.out.printf("%s = %s%n", desc.name(), value));
100
}
101
102
public void bulkPropertyConfiguration(Rule rule) {
103
// Configure multiple properties at once
104
Map<PropertyDescriptor<?>, Object> newValues = new HashMap<>();
105
106
// Find and configure threshold property
107
rule.getPropertyDescriptors().stream()
108
.filter(prop -> prop.name().equals("threshold"))
109
.findFirst()
110
.ifPresent(prop -> {
111
if (prop.type() == Integer.class) {
112
@SuppressWarnings("unchecked")
113
PropertyDescriptor<Integer> intProp =
114
(PropertyDescriptor<Integer>) prop;
115
rule.setProperty(intProp, 20);
116
}
117
});
118
}
119
}
120
```
121
122
### Property Descriptor Interface
123
124
Descriptor interface defining property metadata, validation, and serialization capabilities.
125
126
```java { .api }
127
/**
128
* Describes a configurable property with type, constraints, and validation.
129
* Provides metadata and serialization capabilities for type-safe properties.
130
*/
131
public interface PropertyDescriptor<T> {
132
133
/**
134
* Get property name (unique identifier)
135
* @return Name of the property
136
*/
137
String name();
138
139
/**
140
* Get property description for documentation
141
* @return Human-readable description of property purpose
142
*/
143
String description();
144
145
/**
146
* Get property value type
147
* @return Class representing the property type
148
*/
149
Class<T> type();
150
151
/**
152
* Get default value for property
153
* @return Default value when property is not explicitly set
154
*/
155
T defaultValue();
156
157
/**
158
* Check if property is required (must be explicitly set)
159
* @return true if property must have a non-default value
160
*/
161
boolean isRequired();
162
163
/**
164
* Serialize property value to string representation
165
* @param value Property value to serialize
166
* @return String representation of the value
167
*/
168
String asDelimitedString(T value);
169
170
/**
171
* Parse property value from string representation
172
* @param propertyString String representation to parse
173
* @return Parsed property value
174
* @throws IllegalArgumentException if string cannot be parsed
175
*/
176
T valueFrom(String propertyString);
177
178
/**
179
* Validate property value and return error message
180
* @param value Value to validate
181
* @return Error message if validation fails, null if valid
182
*/
183
String errorFor(T value);
184
}
185
```
186
187
**Usage Examples:**
188
189
```java
190
import net.sourceforge.pmd.properties.*;
191
192
// Working with property descriptors
193
public class PropertyDescriptorExamples {
194
195
// Define custom property descriptors
196
private static final PropertyDescriptor<Integer> MAX_COMPLEXITY =
197
PropertyFactory.intProperty("maxComplexity")
198
.desc("Maximum allowed cyclomatic complexity")
199
.defaultValue(10)
200
.range(1, 100)
201
.build();
202
203
private static final PropertyDescriptor<String> NAMING_PATTERN =
204
PropertyFactory.stringProperty("namingPattern")
205
.desc("Regular expression for valid names")
206
.defaultValue("[a-zA-Z][a-zA-Z0-9]*")
207
.build();
208
209
private static final PropertyDescriptor<List<String>> IGNORED_ANNOTATIONS =
210
PropertyFactory.stringListProperty("ignoredAnnotations")
211
.desc("Annotations to ignore during analysis")
212
.defaultValue(Arrays.asList("SuppressWarnings", "Generated"))
213
.build();
214
215
public void demonstratePropertyUsage() {
216
// Working with property metadata
217
System.out.printf("Property: %s%n", MAX_COMPLEXITY.name());
218
System.out.printf("Type: %s%n", MAX_COMPLEXITY.type().getSimpleName());
219
System.out.printf("Description: %s%n", MAX_COMPLEXITY.description());
220
System.out.printf("Default: %s%n", MAX_COMPLEXITY.defaultValue());
221
System.out.printf("Required: %s%n", MAX_COMPLEXITY.isRequired());
222
223
// Serialization and deserialization
224
Integer value = 15;
225
String serialized = MAX_COMPLEXITY.asDelimitedString(value);
226
System.out.printf("Serialized: %s%n", serialized);
227
228
Integer deserialized = MAX_COMPLEXITY.valueFrom(serialized);
229
System.out.printf("Deserialized: %s%n", deserialized);
230
231
// Validation
232
String error = MAX_COMPLEXITY.errorFor(150); // Out of range
233
if (error != null) {
234
System.out.printf("Validation error: %s%n", error);
235
}
236
237
// Working with complex types
238
List<String> annotations = Arrays.asList("Override", "Deprecated");
239
String listSerialized = IGNORED_ANNOTATIONS.asDelimitedString(annotations);
240
System.out.printf("List serialized: %s%n", listSerialized);
241
242
List<String> listDeserialized = IGNORED_ANNOTATIONS.valueFrom(listSerialized);
243
System.out.printf("List deserialized: %s%n", listDeserialized);
244
}
245
246
public void validatePropertyValues() {
247
// Test various validation scenarios
248
testValidation(MAX_COMPLEXITY, 5, true); // Valid
249
testValidation(MAX_COMPLEXITY, 0, false); // Too low
250
testValidation(MAX_COMPLEXITY, 150, false); // Too high
251
testValidation(MAX_COMPLEXITY, null, false); // Null
252
253
testValidation(NAMING_PATTERN, "validName", true); // Valid
254
testValidation(NAMING_PATTERN, "123invalid", false); // Invalid pattern
255
testValidation(NAMING_PATTERN, "", false); // Empty
256
}
257
258
private <T> void testValidation(PropertyDescriptor<T> prop, T value, boolean expectValid) {
259
String error = prop.errorFor(value);
260
boolean isValid = (error == null);
261
262
System.out.printf("Property %s, value %s: %s%n",
263
prop.name(),
264
value,
265
isValid ? "VALID" : "INVALID (" + error + ")");
266
267
assert isValid == expectValid :
268
"Validation result mismatch for " + prop.name() + " = " + value;
269
}
270
}
271
```
272
273
### Property Factory
274
275
Factory class providing builder methods for creating typed property descriptors with validation and constraints.
276
277
```java { .api }
278
/**
279
* Factory for creating typed property descriptors with builders.
280
* Provides convenient methods for common property types with validation.
281
*/
282
public final class PropertyFactory {
283
284
/**
285
* Create string property builder
286
* @param name Property name
287
* @return StringPropertyBuilder for configuring string properties
288
*/
289
static StringPropertyBuilder stringProperty(String name);
290
291
/**
292
* Create boolean property builder
293
* @param name Property name
294
* @return BooleanPropertyBuilder for configuring boolean properties
295
*/
296
static BooleanPropertyBuilder booleanProperty(String name);
297
298
/**
299
* Create integer property builder
300
* @param name Property name
301
* @return IntegerPropertyBuilder for configuring integer properties
302
*/
303
static IntegerPropertyBuilder intProperty(String name);
304
305
/**
306
* Create long property builder
307
* @param name Property name
308
* @return LongPropertyBuilder for configuring long properties
309
*/
310
static LongPropertyBuilder longProperty(String name);
311
312
/**
313
* Create double property builder
314
* @param name Property name
315
* @return DoublePropertyBuilder for configuring double properties
316
*/
317
static DoublePropertyBuilder doubleProperty(String name);
318
319
/**
320
* Create character property builder
321
* @param name Property name
322
* @return CharacterPropertyBuilder for configuring character properties
323
*/
324
static CharacterPropertyBuilder charProperty(String name);
325
326
/**
327
* Create regex pattern property builder
328
* @param name Property name
329
* @return RegexPropertyBuilder for configuring Pattern properties
330
*/
331
static RegexPropertyBuilder regexProperty(String name);
332
333
/**
334
* Create enum property builder
335
* @param name Property name
336
* @param enumType Enum class for valid values
337
* @return EnumPropertyBuilder for configuring enum properties
338
*/
339
static <E extends Enum<E>> EnumPropertyBuilder<E> enumProperty(String name, Class<E> enumType);
340
341
/**
342
* Create file property builder
343
* @param name Property name
344
* @return FilePropertyBuilder for configuring File properties
345
*/
346
static FilePropertyBuilder fileProperty(String name);
347
348
/**
349
* Create class type property builder
350
* @param name Property name
351
* @return ClassPropertyBuilder for configuring Class<?> properties
352
*/
353
static ClassPropertyBuilder typeProperty(String name);
354
}
355
```
356
357
**Usage Examples:**
358
359
```java
360
import net.sourceforge.pmd.properties.*;
361
import java.io.File;
362
import java.util.Arrays;
363
import java.util.regex.Pattern;
364
365
// Creating property descriptors with PropertyFactory
366
public class PropertyFactoryExamples {
367
368
public void createBasicProperties() {
369
// String properties with validation
370
PropertyDescriptor<String> nameProperty = PropertyFactory
371
.stringProperty("ruleName")
372
.desc("Name of the rule")
373
.defaultValue("CustomRule")
374
.build();
375
376
// Integer properties with range validation
377
PropertyDescriptor<Integer> thresholdProperty = PropertyFactory
378
.intProperty("threshold")
379
.desc("Threshold value for rule")
380
.defaultValue(10)
381
.range(1, 100)
382
.build();
383
384
// Boolean properties
385
PropertyDescriptor<Boolean> enabledProperty = PropertyFactory
386
.booleanProperty("enabled")
387
.desc("Whether rule is enabled")
388
.defaultValue(true)
389
.build();
390
391
// Double properties with precision constraints
392
PropertyDescriptor<Double> ratioProperty = PropertyFactory
393
.doubleProperty("ratio")
394
.desc("Ratio threshold")
395
.defaultValue(0.5)
396
.range(0.0, 1.0)
397
.build();
398
}
399
400
public void createAdvancedProperties() {
401
// Enum properties
402
enum Priority { LOW, MEDIUM, HIGH }
403
PropertyDescriptor<Priority> priorityProperty = PropertyFactory
404
.enumProperty("priority", Priority.class)
405
.desc("Rule priority level")
406
.defaultValue(Priority.MEDIUM)
407
.build();
408
409
// Regular expression properties
410
PropertyDescriptor<Pattern> patternProperty = PropertyFactory
411
.regexProperty("namePattern")
412
.desc("Pattern for valid names")
413
.defaultValue(Pattern.compile("[a-zA-Z][a-zA-Z0-9]*"))
414
.build();
415
416
// File properties
417
PropertyDescriptor<File> configFileProperty = PropertyFactory
418
.fileProperty("configFile")
419
.desc("Configuration file path")
420
.defaultValue(new File("config.properties"))
421
.build();
422
423
// Class type properties
424
PropertyDescriptor<Class<?>> handlerClassProperty = PropertyFactory
425
.typeProperty("handlerClass")
426
.desc("Handler class for processing")
427
.defaultValue(Object.class)
428
.build();
429
}
430
431
public void createCollectionProperties() {
432
// String list properties
433
PropertyDescriptor<List<String>> exclusionsProperty = PropertyFactory
434
.stringListProperty("exclusions")
435
.desc("List of patterns to exclude")
436
.defaultValue(Arrays.asList("test", "generated"))
437
.delim(',') // Custom delimiter
438
.build();
439
440
// Integer list properties
441
PropertyDescriptor<List<Integer>> thresholdsProperty = PropertyFactory
442
.intListProperty("thresholds")
443
.desc("List of threshold values")
444
.defaultValue(Arrays.asList(5, 10, 15))
445
.range(1, 50) // Range applies to each element
446
.build();
447
}
448
449
public void createConstrainedProperties() {
450
// String with length constraints
451
PropertyDescriptor<String> abbreviationProperty = PropertyFactory
452
.stringProperty("abbreviation")
453
.desc("Short abbreviation")
454
.defaultValue("ABC")
455
.range(2, 5) // Length range
456
.build();
457
458
// String with choice constraints
459
PropertyDescriptor<String> modeProperty = PropertyFactory
460
.stringProperty("mode")
461
.desc("Operation mode")
462
.defaultValue("strict")
463
.validChoices("strict", "lenient", "custom")
464
.build();
465
466
// Character with constraints
467
PropertyDescriptor<Character> separatorProperty = PropertyFactory
468
.charProperty("separator")
469
.desc("Field separator character")
470
.defaultValue(',')
471
.validChoices(',', ';', '|', '\t')
472
.build();
473
}
474
475
public void demonstratePropertyBuilder() {
476
// Complex property with multiple constraints
477
PropertyDescriptor<Integer> complexProperty = PropertyFactory
478
.intProperty("complexThreshold")
479
.desc("Complex threshold with multiple constraints")
480
.defaultValue(15)
481
.range(5, 50)
482
.require() // Make property required
483
.build();
484
485
// Multi-valued property with custom validation
486
PropertyDescriptor<List<String>> packagePrefixes = PropertyFactory
487
.stringListProperty("packagePrefixes")
488
.desc("Valid package prefixes")
489
.defaultValue(Arrays.asList("com.example", "org.project"))
490
.delim(';')
491
.emptyAllowed(false) // Don't allow empty list
492
.build();
493
}
494
495
public void usePropertiesInRule() {
496
// Example of defining properties in a custom rule
497
class CustomRule extends AbstractRule {
498
private static final PropertyDescriptor<Integer> COMPLEXITY_THRESHOLD =
499
PropertyFactory.intProperty("complexityThreshold")
500
.desc("Maximum allowed complexity")
501
.defaultValue(10)
502
.range(1, 50)
503
.build();
504
505
private static final PropertyDescriptor<List<String>> IGNORED_METHODS =
506
PropertyFactory.stringListProperty("ignoredMethods")
507
.desc("Method names to ignore")
508
.defaultValue(Arrays.asList("toString", "hashCode", "equals"))
509
.build();
510
511
public CustomRule() {
512
definePropertyDescriptor(COMPLEXITY_THRESHOLD);
513
definePropertyDescriptor(IGNORED_METHODS);
514
}
515
516
@Override
517
public void apply(List<? extends Node> nodes, RuleContext ctx) {
518
int threshold = getProperty(COMPLEXITY_THRESHOLD);
519
List<String> ignored = getProperty(IGNORED_METHODS);
520
521
// Use properties in rule logic
522
for (Node node : nodes) {
523
// Rule implementation using configured values
524
}
525
}
526
}
527
}
528
}
529
```
530
531
### Property Builders
532
533
Builder interfaces for fluent construction of property descriptors with type-specific constraints and validation.
534
535
```java { .api }
536
/**
537
* Builder for string properties with length and choice constraints
538
*/
539
interface StringPropertyBuilder {
540
StringPropertyBuilder desc(String description);
541
StringPropertyBuilder defaultValue(String defaultValue);
542
StringPropertyBuilder require();
543
StringPropertyBuilder range(int minLength, int maxLength);
544
StringPropertyBuilder validChoices(String... choices);
545
PropertyDescriptor<String> build();
546
}
547
548
/**
549
* Builder for integer properties with range constraints
550
*/
551
interface IntegerPropertyBuilder {
552
IntegerPropertyBuilder desc(String description);
553
IntegerPropertyBuilder defaultValue(Integer defaultValue);
554
IntegerPropertyBuilder require();
555
IntegerPropertyBuilder range(int min, int max);
556
PropertyDescriptor<Integer> build();
557
}
558
559
/**
560
* Builder for boolean properties
561
*/
562
interface BooleanPropertyBuilder {
563
BooleanPropertyBuilder desc(String description);
564
BooleanPropertyBuilder defaultValue(Boolean defaultValue);
565
BooleanPropertyBuilder require();
566
PropertyDescriptor<Boolean> build();
567
}
568
569
/**
570
* Builder for double properties with range constraints
571
*/
572
interface DoublePropertyBuilder {
573
DoublePropertyBuilder desc(String description);
574
DoublePropertyBuilder defaultValue(Double defaultValue);
575
DoublePropertyBuilder require();
576
DoublePropertyBuilder range(double min, double max);
577
PropertyDescriptor<Double> build();
578
}
579
580
/**
581
* Builder for enum properties with type safety
582
*/
583
interface EnumPropertyBuilder<E extends Enum<E>> {
584
EnumPropertyBuilder<E> desc(String description);
585
EnumPropertyBuilder<E> defaultValue(E defaultValue);
586
EnumPropertyBuilder<E> require();
587
PropertyDescriptor<E> build();
588
}
589
590
/**
591
* Builder for regular expression pattern properties
592
*/
593
interface RegexPropertyBuilder {
594
RegexPropertyBuilder desc(String description);
595
RegexPropertyBuilder defaultValue(Pattern defaultValue);
596
RegexPropertyBuilder require();
597
PropertyDescriptor<Pattern> build();
598
}
599
600
/**
601
* Builder for file properties
602
*/
603
interface FilePropertyBuilder {
604
FilePropertyBuilder desc(String description);
605
FilePropertyBuilder defaultValue(File defaultValue);
606
FilePropertyBuilder require();
607
PropertyDescriptor<File> build();
608
}
609
610
/**
611
* Builder for list properties with element constraints
612
*/
613
interface ListPropertyBuilder<T> {
614
ListPropertyBuilder<T> desc(String description);
615
ListPropertyBuilder<T> defaultValue(List<T> defaultValue);
616
ListPropertyBuilder<T> require();
617
ListPropertyBuilder<T> delim(char delimiter);
618
ListPropertyBuilder<T> emptyAllowed(boolean allowed);
619
PropertyDescriptor<List<T>> build();
620
}
621
```
622
623
## Types
624
625
```java { .api }
626
/**
627
* Exception thrown when property operations fail
628
*/
629
class PropertyException extends RuntimeException {
630
PropertyException(String message);
631
PropertyException(String message, Throwable cause);
632
}
633
634
/**
635
* Property constraint for validation
636
*/
637
interface PropertyConstraint<T> {
638
639
/**
640
* Validate value against constraint
641
* @param value Value to validate
642
* @return Error message if invalid, null if valid
643
*/
644
String validate(T value);
645
646
/**
647
* Get constraint description
648
* @return Human-readable constraint description
649
*/
650
String getConstraintDescription();
651
}
652
653
/**
654
* Property serializer for custom types
655
*/
656
interface PropertySerializer<T> {
657
658
/**
659
* Serialize value to string
660
* @param value Value to serialize
661
* @return String representation
662
*/
663
String toString(T value);
664
665
/**
666
* Deserialize value from string
667
* @param str String representation
668
* @return Deserialized value
669
*/
670
T fromString(String str);
671
}
672
673
/**
674
* Property bundle for grouped configuration
675
*/
676
interface PropertyBundle extends PropertySource {
677
678
/**
679
* Get bundle name
680
* @return Name of the property bundle
681
*/
682
String getName();
683
684
/**
685
* Get bundle description
686
* @return Description of bundle purpose
687
*/
688
String getDescription();
689
690
/**
691
* Copy bundle with modifications
692
* @return New PropertyBundle copy
693
*/
694
PropertyBundle copy();
695
696
/**
697
* Merge with another bundle
698
* @param other PropertyBundle to merge
699
* @return New PropertyBundle with merged properties
700
*/
701
PropertyBundle merge(PropertyBundle other);
702
}
703
704
/**
705
* Property change listener for configuration updates
706
*/
707
interface PropertyChangeListener {
708
709
/**
710
* Handle property value change
711
* @param source PropertySource that changed
712
* @param property PropertyDescriptor that changed
713
* @param oldValue Previous value
714
* @param newValue New value
715
*/
716
<T> void propertyChanged(PropertySource source, PropertyDescriptor<T> property,
717
T oldValue, T newValue);
718
}
719
720
/**
721
* Manager for property configurations
722
*/
723
interface PropertyManager {
724
725
/**
726
* Register property change listener
727
* @param listener PropertyChangeListener to add
728
*/
729
void addPropertyChangeListener(PropertyChangeListener listener);
730
731
/**
732
* Remove property change listener
733
* @param listener PropertyChangeListener to remove
734
*/
735
void removePropertyChangeListener(PropertyChangeListener listener);
736
737
/**
738
* Load properties from configuration source
739
* @param source Configuration source (file, URL, etc.)
740
* @param target PropertySource to configure
741
*/
742
void loadProperties(String source, PropertySource target);
743
744
/**
745
* Save properties to configuration source
746
* @param target Configuration target (file, stream, etc.)
747
* @param source PropertySource to save
748
*/
749
void saveProperties(String target, PropertySource source);
750
}
751
```