• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 353

16 Jan 2026 01:01PM UTC coverage: 78.957% (-0.02%) from 78.976%
353

push

github

adangel
[core] Fix #6184: More consistent enum properties (#6233)

18529 of 24342 branches covered (76.12%)

Branch coverage included in aggregate %.

146 of 164 new or added lines in 20 files covered. (89.02%)

6 existing lines in 5 files now uncovered.

40349 of 50228 relevant lines covered (80.33%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.07
/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyFactory.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.properties;
6

7
import static java.util.Arrays.asList;
8
import static net.sourceforge.pmd.properties.internal.PropertyParsingUtil.enumerationParser;
9

10
import java.util.Arrays;
11
import java.util.Collections;
12
import java.util.List;
13
import java.util.Map;
14
import java.util.Objects;
15
import java.util.function.Function;
16
import java.util.stream.Collectors;
17

18
import org.apache.commons.lang3.EnumUtils;
19
import org.checkerframework.checker.nullness.qual.NonNull;
20

21
import net.sourceforge.pmd.properties.PropertyBuilder.GenericCollectionPropertyBuilder;
22
import net.sourceforge.pmd.properties.PropertyBuilder.GenericPropertyBuilder;
23
import net.sourceforge.pmd.properties.PropertyBuilder.RegexPropertyBuilder;
24
import net.sourceforge.pmd.properties.internal.PropertyParsingUtil;
25
import net.sourceforge.pmd.util.CollectionUtil;
26
import net.sourceforge.pmd.util.StringUtil;
27

28
//@formatter:off
29
/**
30
 * Provides factory methods for common property types.
31
 * Note: from 7.0.0 on, this will be the only way to
32
 * build property descriptors. Concrete property classes
33
 * and their constructors/ builders will be gradually
34
 * deprecated before 7.0.0.
35
 *
36
 * <h2>Usage</h2>
37
 *
38
 * Properties are a way to make your rule configurable by
39
 * letting a user fill in some config value in their
40
 * ruleset XML.
41
 *
42
 * As a rule developer, to declare a property on your rule, you
43
 * must:
44
 * <ul>
45
 *     <li>Build a {@link PropertyDescriptor} using one of
46
 *     the factory methods of this class, and providing the
47
 *     {@linkplain PropertyBuilder builder} with the required
48
 *     info.
49
 *     <li>Define the property on your rule using {@link PropertySource#definePropertyDescriptor(PropertyDescriptor)}.
50
 *     This should be done in your rule's constructor.
51
 * </ul>
52
 *
53
 * You can then retrieve the value configured by the user in your
54
 * rule using {@link PropertySource#getProperty(PropertyDescriptor)}.
55
 *
56
 * <h2>Example</h2>
57
 *
58
 * <pre>
59
 * class MyRule {
60
 *   // The property descriptor may be static, it can be shared across threads.
61
 *   private static final {@link PropertyDescriptor}&lt;Integer&gt; myIntProperty
62
 *     = PropertyFactory.{@linkplain #intProperty(String) intProperty}("myIntProperty")
63
 *                      .{@linkplain PropertyBuilder#desc(String) desc}("This is my property")
64
 *                      .{@linkplain PropertyBuilder#defaultValue(Object) defaultValue}(3)
65
 *                      .{@linkplain PropertyBuilder#require(PropertyConstraint) require}(inRange(0, 100))   // constraints are checked before the rule is run
66
 *                      .{@linkplain PropertyBuilder#build() build}();
67
 *
68
 *   // ..
69
 *
70
 *   public MyRule() {
71
 *     {@linkplain PropertySource#definePropertyDescriptor(PropertyDescriptor) definePropertyDescriptor}(myIntProperty);
72
 *   }
73
 *
74
 *     // ... somewhere in the rule
75
 *
76
 *     int myPropertyValue = {@linkplain PropertySource#getProperty(PropertyDescriptor) getProperty(myIntProperty)};
77
 *     // use it.
78
 *
79
 * }
80
 * </pre>
81
 *
82
 *
83
 * @author Clément Fournier
84
 * @since 6.10.0
85
 */
86
//@formatter:on
87
public final class PropertyFactory {
88

89

90
    /**
91
     * Default delimiter for all properties. Note that in PMD 6 this was
92
     * the pipe character {@code |}, while now it is {@value}. In PMD 6,
93
     * numeric properties had a different delimiter, whereas in PMD 7, all
94
     * properties have the same delimiter.
95
     */
96
    public static final char DEFAULT_DELIMITER = ',';
97

98

99
    private PropertyFactory() {
100

101
    }
102

103

104
    /**
105
     * Returns a builder for an integer property. The property descriptor
106
     * will by default accept any value conforming to the format specified
107
     * by {@link Integer#parseInt(String)}, e.g. {@code 1234} or {@code -123}.
108
     *
109
     * <p>Note that that parser only supports decimal representations.
110
     *
111
     * <p>Acceptable values may be further refined by {@linkplain PropertyBuilder#require(PropertyConstraint) adding constraints}.
112
     * The class {@link NumericConstraints} provides some useful ready-made constraints
113
     * for that purpose.
114
     *
115
     * @param name Name of the property to build
116
     *
117
     * @return A new builder
118
     *
119
     * @see NumericConstraints
120
     */
121
    public static GenericPropertyBuilder<Integer> intProperty(String name) {
122
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.INTEGER);
1✔
123
    }
124

125

126
    /**
127
     * Returns a builder for a property having as value a list of integers. The
128
     * format of the individual items is the same as for {@linkplain #intProperty(String) intProperty}.
129
     *
130
     * @param name Name of the property to build
131
     *
132
     * @return A new builder
133
     */
134
    public static GenericCollectionPropertyBuilder<Integer, List<Integer>> intListProperty(String name) {
135
        return intProperty(name).toList();
1✔
136
    }
137

138

139
    /**
140
     * Returns a builder for a long integer property. The property descriptor
141
     * will by default accept any value conforming to the format specified
142
     * by {@link Long#parseLong(String)}, e.g. {@code 1234455678854}.
143
     *
144
     * <p>Note that that parser only supports decimal representations, and that neither
145
     * the character L nor l is permitted to appear at the end of the string as a type
146
     * indicator, as would be permitted in Java source.
147
     *
148
     * <p>Acceptable values may be further refined by {@linkplain PropertyBuilder#require(PropertyConstraint) adding constraints}.
149
     * The class {@link NumericConstraints} provides some useful ready-made constraints
150
     * for that purpose.
151
     *
152
     * @param name Name of the property to build
153
     *
154
     * @return A new builder
155
     *
156
     * @see NumericConstraints
157
     */
158
    public static GenericPropertyBuilder<Long> longIntProperty(String name) {
159
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.LONG);
×
160
    }
161

162

163
    /**
164
     * Returns a builder for a property having as value a list of long integers. The
165
     * format of the individual items is the same as for {@linkplain #longIntProperty(String)} longIntProperty}.
166
     *
167
     * @param name Name of the property to build
168
     *
169
     * @return A new builder
170
     */
171
    public static GenericCollectionPropertyBuilder<Long, List<Long>> longIntListProperty(String name) {
172
        return longIntProperty(name).toList();
×
173
    }
174

175

176
    /**
177
     * Returns a builder for a double property. The property descriptor
178
     * will by default accept any value conforming to the format specified
179
     * by {@link Double#valueOf(String)}, e.g. {@code 0}, {@code .93}, or {@code 1e-1}.
180
     * Acceptable values may be further refined by {@linkplain PropertyBuilder#require(PropertyConstraint) adding constraints}.
181
     * The class {@link NumericConstraints} provides some useful ready-made constraints
182
     * for that purpose.
183
     *
184
     * @param name Name of the property to build
185
     *
186
     * @return A new builder
187
     *
188
     * @see NumericConstraints
189
     */
190
    public static GenericPropertyBuilder<Double> doubleProperty(String name) {
191
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.DOUBLE);
1✔
192
    }
193

194

195
    /**
196
     * Returns a builder for a property having as value a list of decimal numbers. The
197
     * format of the individual items is the same as for {@linkplain #doubleProperty(String) doubleProperty}.
198
     *
199
     * @param name Name of the property to build
200
     *
201
     * @return A new builder
202
     */
203
    public static GenericCollectionPropertyBuilder<Double, List<Double>> doubleListProperty(String name) {
204
        return doubleProperty(name).toList();
1✔
205
    }
206

207

208
    /**
209
     * Returns a builder for a regex property. The value type of such
210
     * a property is {@link java.util.regex.Pattern}. For this use case, this type of
211
     * property should be preferred over {@linkplain #stringProperty(String) stringProperty}
212
     * as pattern compilation, including syntax errors, are handled transparently to
213
     * the rule.
214
     *
215
     * @param name Name of the property to build
216
     *
217
     * @return A new builder
218
     */
219
    public static RegexPropertyBuilder regexProperty(String name) {
220
        return new RegexPropertyBuilder(name);
1✔
221
    }
222

223

224
    /**
225
     * Returns a builder for a string property. The property descriptor
226
     * will accept any string, and performs no expansion of escape
227
     * sequences (e.g. {@code \n} in the XML will be represented as the
228
     * character sequence '\' 'n' and not the line-feed character '\n').
229
     * The value of a string attribute is trimmed.
230
     * This behaviour could be changed with PMD 7.0.0.
231
     *
232
     * @param name Name of the property to build
233
     *
234
     * @return A new builder
235
     */
236
    public static GenericPropertyBuilder<String> stringProperty(String name) {
237
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.STRING);
1✔
238
    }
239

240
    /**
241
     * Returns a builder for a property having as value a list of strings. The
242
     * format of the individual items is the same as for {@linkplain #stringProperty(String) stringProperty}.
243
     *
244
     * @param name Name of the property to build
245
     *
246
     * @return A new builder
247
     */
248
    public static GenericCollectionPropertyBuilder<String, List<String>> stringListProperty(String name) {
249
        return stringProperty(name).toList();
1✔
250
    }
251

252

253
    /**
254
     * Returns a builder for a character property. The property descriptor
255
     * will accept any single character string. No unescaping is performed
256
     * other than what the XML parser does itself. That means that Java
257
     * escape sequences are not expanded: e.g. "\n", will be represented as the
258
     * character sequence '\' 'n', so it's not a valid value for this type
259
     * of property. On the other hand, XML character references are expanded,
260
     * like {@literal &amp;} ('&amp;') or {@literal &lt;} ('&lt;').
261
     *
262
     * @param name Name of the property to build
263
     *
264
     * @return A new builder
265
     */
266
    public static GenericPropertyBuilder<Character> charProperty(String name) {
267
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.CHARACTER);
1✔
268
    }
269

270

271
    /**
272
     * Returns a builder for a property having as value a list of characters. The
273
     * format of the individual items is the same as for {@linkplain #charProperty(String) charProperty}.
274
     *
275
     * @param name Name of the property to build
276
     *
277
     * @return A new builder
278
     */
279
    public static GenericCollectionPropertyBuilder<Character, List<Character>> charListProperty(String name) {
280
        return charProperty(name).toList();
×
281
    }
282

283

284
    /**
285
     * Returns a builder for a boolean property. The boolean is parsed from
286
     * the XML using {@link Boolean#valueOf(String)}, i.e. the only truthy
287
     * value is the string "true", and all other string values are falsy.
288
     *
289
     * @param name Name of the property to build
290
     *
291
     * @return A new builder
292
     */
293
    public static GenericPropertyBuilder<Boolean> booleanProperty(String name) {
294
        return new GenericPropertyBuilder<>(name, PropertyParsingUtil.BOOLEAN);
1✔
295
    }
296

297
    /**
298
     * Returns a builder for an enumerated property. Such a property can be
299
     * defined for any type {@code <T>}, provided the possible values can be
300
     * indexed by strings. This is enforced by passing a {@code Map<String, T>}
301
     * at construction time, which maps labels to values. If {@link Map#get(Object)}
302
     * returns null for a given label, then the value is rejected. Null values
303
     * are hence prohibited.
304
     *
305
     * @param name        Name of the property to build
306
     * @param nameToValue Map of labels to values. The null key is ignored.
307
     * @param <T>         Value type of the property
308
     *
309
     * @throws IllegalArgumentException If the map contains a null value or key
310
     *
311
     * @return A new builder
312
     *
313
     * @deprecated Since 7.21.0. Use {@link #conventionalEnumProperty(String, Class)} instead.
314
     */
315
    @Deprecated
316
    public static <T> GenericPropertyBuilder<T> enumProperty(String name, Map<String, T> nameToValue) {
317
        PropertySerializer<T> parser = enumerationParser(
1✔
318
            nameToValue,
319
            Collections.emptyMap(),
1✔
UNCOV
320
            t -> Objects.requireNonNull(CollectionUtil.getKeyOfValue(nameToValue, t))
×
321
        );
322
        return new GenericPropertyBuilder<>(name, parser);
1✔
323
    }
324

325
    /**
326
     * Returns a builder for an enumerated property for the given enum
327
     * class, using the {@link Object#toString() toString} of its enum
328
     * constants as labels.
329
     *
330
     * @param name      Property name
331
     * @param enumClass Enum class
332
     * @param <T>       Type of the enum class
333
     *
334
     * @return A new builder
335
     * @deprecated Since 7.21.0. Use {@link #conventionalEnumProperty(String, Class)} instead.
336
     */
337
    @Deprecated
338
    public static <T extends Enum<T>> GenericPropertyBuilder<T> enumProperty(String name, Class<T> enumClass) {
339
        return enumProperty(name, enumClass, Object::toString);
×
340
    }
341

342
    /**
343
     * Returns a builder for an enumerated property for the given enum
344
     * class, using the given function to label its constants.
345
     *
346
     * @param name       Property name
347
     * @param enumClass  Enum class
348
     * @param labelMaker Function that labels each constant
349
     * @param <T>        Type of the enum class
350
     *
351
     * @return A new builder
352
     *
353
     * @throws NullPointerException     If any of the arguments is null
354
     * @throws IllegalArgumentException If the label maker returns null on some constant
355
     * @throws IllegalStateException    If the label maker maps two constants to the same label
356
     *
357
     * @deprecated Since 7.21.0. Use {@link #conventionalEnumProperty(String, Class)} instead.
358
     */
359
    @Deprecated
360
    public static <T extends Enum<T>> GenericPropertyBuilder<T> enumProperty(String name,
361
                                                                             Class<T> enumClass,
362
                                                                             Function<? super T, @NonNull String> labelMaker) {
363
        // don't use a merge function, so that it throws if multiple
364
        // values have the same key
365
        Map<String, T> labelsToValues = Arrays.stream(enumClass.getEnumConstants())
×
366
                                              .collect(Collectors.toMap(labelMaker, t -> t));
×
367

NEW
368
        return new GenericPropertyBuilder<>(name, enumerationParser(labelsToValues, Collections.emptyMap(), labelMaker));
×
369
    }
370

371
    /**
372
     * Returns a builder for an enumerated property for the given enum
373
     * class. It is using the conventional mapping of its enum constants
374
     * to labels. Enum constants are expected to be in "SCREAMING_SNAKE_CASE",
375
     * the labels are in "camelCase".
376
     *
377
     * @param name      Property name
378
     * @param enumClass Enum class
379
     * @param <T>       Type of the enum class
380
     *
381
     * @return A new builder
382
     * @since 7.21.0
383
     */
384
    public static <T extends Enum<T>> GenericPropertyBuilder<T> conventionalEnumProperty(String name, Class<T> enumClass) {
385
        return enumPropertyTransitional(name, enumClass, Collections.emptyMap());
1✔
386
    }
387

388
    /**
389
     * Returns a builder for an enumerated property for the given enum
390
     * class. It is using the conventional mapping of its enum constants
391
     * to labels. Enum constants are expected to be in "SCREAMING_SNAKE_CASE",
392
     * the labels are in "camelCase".
393
     *
394
     * <p>It uses additionally the given mapping to support deprecated (old) values in addition to the
395
     * default mapping.</p>
396
     *
397
     * <p>This builder should only be used for maintaining backwards compatibility.</p>
398
     *
399
     * @param name              Property name
400
     * @param enumClass         Enum class
401
     * @param deprecatedMapping Map with still allowed, but deprecated values
402
     * @param <T>               Type of the enum class
403
     *
404
     * @return a new builder
405
     * @since 7.21.0
406
     */
407
    public static <T extends Enum<T>> GenericPropertyBuilder<T> enumPropertyTransitional(String name,
408
                                                                             Class<T> enumClass,
409
                                                                             Map<String, T> deprecatedMapping) {
410
        // default mapping using the default naming convention of enum properties: camel case
411
        Function<T, String> toString = v -> StringUtil.CaseConvention.SCREAMING_SNAKE_CASE.convertTo(StringUtil.CaseConvention.CAMEL_CASE, v.name());
1✔
412
        Map<String, T> labelsToValues = EnumUtils.getEnumMap(enumClass, toString);
1✔
413

414
        return new GenericPropertyBuilder<>(name, enumerationParser(labelsToValues, deprecatedMapping, toString));
1✔
415
    }
416

417
    /**
418
     * Returns a builder for a property having as value a list of the given enum class {@code <T>}. The
419
     * format of the individual items is the same as for {@linkplain #conventionalEnumProperty(String, Class)}.
420
     *
421
     * @param name        Name of the property to build
422
     * @param enumClass   Enum class
423
     * @param <T>         Value type of the property
424
     *
425
     * @return A new builder
426
     * @since 7.21.0
427
     */
428
    public static <T extends Enum<T>> GenericCollectionPropertyBuilder<T, List<T>> conventionalEnumListProperty(String name, Class<T> enumClass) {
429
        return conventionalEnumProperty(name, enumClass).toList();
1✔
430
    }
431

432
    /**
433
     * Returns a builder for a property having as value a list of the given enum class {@code <T>}. The
434
     * format of the individual items is the same as for {@linkplain #conventionalEnumProperty(String, Class)}.
435
     *
436
     * <p>It uses additionally the given mapping to support deprecated (old) values in addition to the
437
     * default mapping.</p>
438
     *
439
     * <p>This build should only be used for maintaining backwards compatibility.</p>
440
     *
441
     * @param name        Name of the property to build
442
     * @param enumClass   Enum class
443
     * @param deprecatedMapping Map with still allowed, but deprecated values
444
     * @param <T>         Value type of the property
445
     *
446
     * @return A new builder
447
     */
448
    public static <T extends Enum<T>> GenericCollectionPropertyBuilder<T, List<T>> enumListPropertyTransitional(String name,
449
                                                                                                                Class<T> enumClass, Map<String, T> deprecatedMapping) {
NEW
450
        return enumPropertyTransitional(name, enumClass, deprecatedMapping).toList();
×
451
    }
452

453

454
    /**
455
     * Returns a builder for a property having as value a list of {@code <T>}. The
456
     * format of the individual items is the same as for {@linkplain #enumProperty(String, Map)}.
457
     *
458
     * @param name        Name of the property to build
459
     * @param nameToValue Map of labels to values. The null key is ignored.
460
     * @param <T>         Value type of the property
461
     *
462
     * @return A new builder
463
     *
464
     * @deprecated Since 7.21.0. Use {@link #conventionalEnumListProperty(String, Class)} instead.
465
     */
466
    @Deprecated
467
    public static <T> GenericCollectionPropertyBuilder<T, List<T>> enumListProperty(String name, Map<String, T> nameToValue) {
468
        return enumProperty(name, nameToValue).toList();
1✔
469
    }
470

471

472
    /**
473
     * Returns a builder for a property having as value a list of {@code <T>}. The
474
     * format of the individual items is the same as for {@linkplain #enumProperty(String, Map)}.
475
     *
476
     * @param name       Name of the property to build
477
     * @param enumClass  Class of the values
478
     * @param labelMaker Function that associates enum constants to their label
479
     * @param <T>        Value type of the property
480
     *
481
     * @return A new builder
482
     *
483
     * @deprecated Since 7.21.0. Use {@link #conventionalEnumListProperty(String, Class)} instead.
484
     */
485
    @Deprecated
486
    public static <T extends Enum<T>> GenericCollectionPropertyBuilder<T, List<T>> enumListProperty(String name, Class<T> enumClass, Function<? super T, String> labelMaker) {
487
        Map<String, T> enumMap = CollectionUtil.associateBy(asList(enumClass.getEnumConstants()), labelMaker);
×
488
        return enumListProperty(name, enumMap);
×
489
    }
490

491

492
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc