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

pmd / pmd / #3722

pending completion
#3722

push

github actions

adangel
Suppress MissingOverride for Chars::isEmpty (#4291)

67270 of 127658 relevant lines covered (52.7%)

0.53 hits per line

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

74.23
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/SymbolicValue.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.symbols;
6

7
import java.lang.annotation.Annotation;
8
import java.lang.annotation.RetentionPolicy;
9
import java.lang.reflect.Array;
10
import java.lang.reflect.InvocationTargetException;
11
import java.util.ArrayList;
12
import java.util.Collections;
13
import java.util.List;
14
import java.util.Objects;
15

16
import org.apache.commons.lang3.AnnotationUtils;
17
import org.apache.commons.lang3.ArrayUtils;
18
import org.apache.commons.lang3.ClassUtils;
19
import org.apache.commons.lang3.EnumUtils;
20
import org.apache.commons.lang3.NotImplementedException;
21
import org.apache.commons.lang3.reflect.MethodUtils;
22
import org.checkerframework.checker.nullness.qual.NonNull;
23
import org.checkerframework.checker.nullness.qual.Nullable;
24
import org.pcollections.PSet;
25

26
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ClassNamesUtil;
27
import net.sourceforge.pmd.lang.java.types.TypeSystem;
28
import net.sourceforge.pmd.util.OptionalBool;
29

30
/**
31
 * Structure to represent constant values of annotations symbolically.
32
 * Annotations may contain:
33
 * <ul>
34
 * <li>Primitive or string values: {@link SymValue}
35
 * <li>Enum constants: {@link SymEnum}
36
 * <li>Class instances: {@link SymClass}
37
 * <li>Other annotations: {@link SymAnnot}
38
 * <li>Arrays of the above, of dimension 1: {@link SymArray}
39
 * </ul>
40
 *
41
 * <p>Any other values, including the null reference, are unsupported and
42
 * cannot be represented by this API.
43
 *
44
 * <p>Currently the public API allows comparing the values to an actual
45
 * java value that you compiled against ({@link #valueEquals(Object)}).
46
 * This may be improved later to allow comparing values without needing
47
 * them in the compile classpath.
48
 *
49
 * <p>This is a sealed interface and should not be implemented by clients.
50
 *
51
 * <p>Note: the point of this api is to enable comparisons between values,
52
 * not deep introspection into values. This is why there are very few getter
53
 * methods, except in {@link SymAnnot}, which is the API point used by
54
 * {@link AnnotableSymbol}.
55
 */
56
public interface SymbolicValue {
57

58

59
    /**
60
     * Returns true if this symbolic value represents the same value as
61
     * the given object. If the parameter is null, returns false.
62
     */
63
    boolean valueEquals(Object o);
64

65
    /**
66
     * Returns true if this value is equal to the other one. The parameter
67
     * must be a {@link SymbolicValue} of the same type. Use {@link #valueEquals(Object)}
68
     * to compare to a java object.
69
     */
70
    @Override
71
    boolean equals(Object o);
72

73
    /**
74
     * Returns a symbolic value for the given java object
75
     * Returns an annotation element for the given java value. Returns
76
     * null if the value cannot be an annotation element or cannot be
77
     * constructed.
78
     */
79
    static @Nullable SymbolicValue of(TypeSystem ts, Object value) {
80
        Objects.requireNonNull(ts);
1✔
81
        if (value == null) {
1✔
82
            return null;
1✔
83
        }
84

85
        if (SymValue.isOkValue(value)) {
1✔
86
            return new SymValue(value);
1✔
87
        }
88

89
        if (value instanceof Enum<?>) {
1✔
90
            return SymEnum.fromEnum(ts, (Enum<?>) value);
1✔
91
        }
92

93
        if (value instanceof Annotation) {
1✔
94
            return AnnotWrapper.wrap(ts, (Annotation) value);
1✔
95
        }
96

97
        if (value instanceof Class<?>) {
1✔
98
            return SymClass.ofBinaryName(ts, ((Class<?>) value).getName());
1✔
99
        }
100

101
        if (value.getClass().isArray()) {
1✔
102
            if (!SymArray.isOkComponentType(value.getClass().getComponentType())) {
1✔
103
                return null;
×
104
            }
105
            return SymArray.forArray(ts, value);
1✔
106
        }
107

108
        return null;
×
109
    }
110

111
    /**
112
     * Symbolic representation of an annotation.
113
     */
114
    interface SymAnnot extends SymbolicValue {
115

116
        /**
117
         * Returns the value of the attribute, which may fall back to
118
         * the default value of the annotation element. Returns null if
119
         * the attribute does not exist, is unresolved, or has no default.
120
         * TODO do we need separate sentinels for that?
121
         */
122
        @Nullable SymbolicValue getAttribute(String attrName);
123

124
        /**
125
         * Return the symbol for the declaring class of the annotation.
126
         */
127
        @NonNull JClassSymbol getAnnotationSymbol();
128

129
        /**
130
         * Return the simple names of all attributes, including those
131
         * defined in the annotation type but not explicitly set in this annotation.
132
         * Note that if the annotation is reflected from a class file,
133
         * we can't know which annotations used their default value, so it
134
         * returns a set of all attribute names.
135
         */
136
        default PSet<String> getAttributeNames() {
137
            return getAnnotationSymbol().getAnnotationAttributeNames();
1✔
138
        }
139

140
        /** Return the binary name of the annotation type. */
141
        default String getBinaryName() {
142
            return getAnnotationSymbol().getBinaryName();
1✔
143
        }
144

145
        /** Return the simple name of the annotation type. */
146
        default String getSimpleName() {
147
            return getAnnotationSymbol().getSimpleName();
×
148
        }
149

150
        @Override
151
        default boolean valueEquals(Object o) {
152
            if (!(o instanceof Annotation)) {
1✔
153
                return false;
×
154
            }
155
            Annotation annot = (Annotation) o;
1✔
156
            if (!this.isOfType(annot.annotationType())) {
1✔
157
                return false;
×
158
            }
159

160
            for (String attrName : getAttributeNames()) { // todo this is not symmetric...
1✔
161

162
                Object attr = null;
1✔
163
                try {
164
                    attr = MethodUtils.invokeExactMethod(annot, attrName);
1✔
165
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
×
166
                }
1✔
167
                if (attr == null || !AnnotationUtils.isValidAnnotationMemberType(attr.getClass())) {
1✔
168
                    continue;
×
169
                }
170

171
                SymbolicValue myAttr = getAttribute(attrName);
1✔
172
                if (myAttr == null || !myAttr.valueEquals(attr)) {
1✔
173
                    return false;
×
174
                }
175
            }
1✔
176
            return true;
1✔
177
        }
178

179
        /**
180
         * The retention policy. Note that naturally, members accessed
181
         * from class files cannot reflect annotations with {@link RetentionPolicy#SOURCE}.
182
         */
183
        default RetentionPolicy getRetention() {
184
            return getAnnotationSymbol().getAnnotationRetention();
×
185
        }
186

187
        /**
188
         * Return true if this annotation's binary name matches the given
189
         * binary name.
190
         */
191
        default boolean isOfType(String binaryName) {
192
            return getBinaryName().equals(binaryName);
1✔
193
        }
194

195
        /**
196
         * Whether the annotation has the given type. Note that only
197
         * the name of the class is taken into account, because its
198
         * {@code Class} instance may be missing from the type system classpath.
199
         */
200
        default boolean isOfType(Class<? extends Annotation> klass) {
201
            return isOfType(klass.getName());
1✔
202
        }
203

204
        /**
205
         * Returns YES if the annotation has the attribute set to the
206
         * given value. Returns NO if it is set to another value.
207
         * Returns UNKNOWN if the attribute does not exist or is
208
         * unresolved.
209
         */
210
        default OptionalBool attributeMatches(String attrName, Object attrValue) {
211
            SymbolicValue attr = getAttribute(attrName);
1✔
212
            if (attr == null) {
1✔
213
                return OptionalBool.UNKNOWN;
1✔
214
            }
215

216
            return OptionalBool.definitely(SymbolicValueHelper.equalsModuloWrapper(attr, attrValue));
1✔
217
        }
218

219
        /**
220
         * Returns YES if the annotation has the attribute set to the
221
         * given value, or to an array containing the given value. Returns
222
         * NO if that's not the case. Returns UNKNOWN if the attribute
223
         * does not exist or is unresolved.
224
         */
225
        default OptionalBool attributeContains(String attrName, Object attrValue) {
226
            SymbolicValue attr = getAttribute(attrName);
1✔
227
            if (attr == null) {
1✔
228
                return OptionalBool.UNKNOWN;
×
229
            }
230
            if (attr instanceof SymArray) {
1✔
231
                // todo what if the value is an array itself
232
                return OptionalBool.definitely(((SymArray) attr).containsValue(attrValue));
1✔
233
            }
234

235
            return OptionalBool.definitely(SymbolicValueHelper.equalsModuloWrapper(attr, attrValue));
1✔
236
        }
237

238
    }
239

240
    /**
241
     * An array of values.
242
     */
243
    final class SymArray implements SymbolicValue {
1✔
244

245
        // exactly one of those is non-null
246
        private final @Nullable List<SymbolicValue> elements;
247
        private final @Nullable Object primArray; // for primitive arrays we keep this around
248
        private final int length;
249

250
        private SymArray(@Nullable List<SymbolicValue> elements, @Nullable Object primArray, int length) {
1✔
251
            this.elements = elements;
1✔
252
            this.primArray = primArray;
1✔
253
            this.length = length;
1✔
254
            assert elements == null ^ primArray == null : "Either elements or array must be mentioned";
1✔
255
            assert primArray == null || primArray.getClass().isArray();
1✔
256
        }
1✔
257

258
        /**
259
         * Returns a SymArray for a list of symbolic values.
260
         *
261
         * @param values The elements
262
         *
263
         * @throws NullPointerException if the parameter is null
264
         */
265
        public static SymArray forElements(List<SymbolicValue> values) {
266
            return new SymArray(Collections.unmodifiableList(new ArrayList<>(values)), null, values.size());
1✔
267
        }
268

269
        /**
270
         * Returns a SymArray for the parameter.
271
         *
272
         * @throws NullPointerException     if the parameter is null
273
         * @throws IllegalArgumentException If the parameter is not an array,
274
         *                                  or has an unsupported component type
275
         */
276
        // package-private, people should use SymbolicValue#of
277
        static SymArray forArray(TypeSystem ts, @NonNull Object array) {
278
            if (!array.getClass().isArray()) {
1✔
279
                throw new IllegalArgumentException("Needs an array, got " + array);
×
280
            }
281

282
            if (array.getClass().getComponentType().isPrimitive()) {
1✔
283
                int len = Array.getLength(array);
1✔
284
                return new SymArray(null, array, len);
1✔
285
            } else {
286
                Object[] arr = (Object[]) array;
1✔
287
                if (!isOkComponentType(arr.getClass().getComponentType())) {
1✔
288
                    throw new IllegalArgumentException(
×
289
                        "Unsupported component type" + arr.getClass().getComponentType());
×
290
                }
291

292
                List<SymbolicValue> lst = new ArrayList<>(arr.length);
1✔
293
                for (Object o : arr) {
1✔
294
                    SymbolicValue elt = SymbolicValue.of(ts, o);
1✔
295
                    if (elt == null) {
1✔
296
                        throw new IllegalArgumentException("Unsupported array element" + o);
×
297
                    }
298
                    lst.add(elt);
1✔
299
                }
300
                return new SymArray(lst, null, arr.length);
1✔
301
            }
302
        }
303

304
        static boolean isOkComponentType(Class<?> compType) {
305
            return compType.isPrimitive()
1✔
306
                || compType == String.class
307
                || compType == Class.class
308
                || compType.isEnum()
1✔
309
                || compType.isAnnotation();
1✔
310
        }
311

312
        public int length() {
313
            return length;
×
314
        }
315

316

317
        /**
318
         * Return true if this array contains the given object. If the
319
         * object is a {@link SymbolicValue}, it uses {@link #equals(Object)},
320
         * otherwise it uses {@link #valueEquals(Object)} to compare elements.
321
         */
322
        public boolean containsValue(Object value) {
323
            if (primArray != null) {
1✔
324
                // todo I don't know how to code that without switching on the type
325
                throw new NotImplementedException("not implemented: containsValue with a primitive array");
×
326
            } else if (elements != null) {
1✔
327
                return elements.stream().anyMatch(it -> SymbolicValueHelper.equalsModuloWrapper(it, value));
1✔
328
            }
329
            return false;
×
330
        }
331

332
        @Override
333
        public boolean valueEquals(Object o) {
334
            if (!o.getClass().isArray() || !isOkComponentType(o.getClass().getComponentType())) {
1✔
335
                return false;
1✔
336
            }
337
            if (primArray != null) {
1✔
338
                return Objects.deepEquals(primArray, o);
1✔
339
            } else if (!(o instanceof Object[])) {
1✔
340
                return false;
×
341
            }
342
            assert elements != null;
1✔
343

344
            Object[] arr = (Object[]) o;
1✔
345
            if (arr.length != length) {
1✔
346
                return false;
1✔
347
            }
348
            for (int i = 0; i < elements.size(); i++) {
1✔
349
                if (!elements.get(i).valueEquals(arr[i])) {
1✔
350
                    return false;
×
351
                }
352
            }
353
            return true;
1✔
354

355
        }
356

357
        @Override
358
        public boolean equals(Object o) {
359
            if (this == o) {
1✔
360
                return true;
×
361
            }
362
            if (o == null || getClass() != o.getClass()) {
1✔
363
                return false;
×
364
            }
365
            SymArray array = (SymArray) o;
1✔
366
            if (elements != null) {
1✔
367
                return Objects.equals(elements, array.elements);
1✔
368
            } else {
369
                return Objects.deepEquals(primArray, array.primArray);
1✔
370
            }
371
        }
372

373
        @Override
374
        public int hashCode() {
375
            if (elements != null) {
×
376
                return elements.hashCode();
×
377
            } else {
378
                assert primArray != null;
×
379
                return primArray.hashCode();
×
380
            }
381
        }
382

383
        @Override
384
        public String toString() {
385
            if (elements != null) {
×
386
                return "[list " + elements + ']';
×
387
            } else {
388
                return "[array " + ArrayUtils.toString(primArray) + ']';
×
389
            }
390
        }
391
    }
392

393

394
    /**
395
     * Symbolic representation of an enum constant.
396
     */
397
    final class SymEnum implements SymbolicValue {
398

399
        private final String enumBinaryName;
400
        private final String enumName;
401

402
        private SymEnum(String enumBinaryName, String enumConstName) {
1✔
403
            this.enumBinaryName = Objects.requireNonNull(enumBinaryName);
1✔
404
            this.enumName = Objects.requireNonNull(enumConstName);
1✔
405
        }
1✔
406

407
        /**
408
         * If this enum constant is declared in the given enum class,
409
         * returns its value. Otherwise returns null.
410
         *
411
         * @param enumClass Class of an enum
412
         * @param <E>       Return type
413
         */
414
        public <E extends Enum<E>> @Nullable E toEnum(Class<E> enumClass) {
415
            return enumClass.getName().equals(enumBinaryName) ? EnumUtils.getEnum(enumClass, enumName)
1✔
416
                                                              : null;
×
417
        }
418

419
        /**
420
         * Returns the symbolic value for the given enum constant.
421
         *
422
         * @param ts    Type system
423
         * @param value An enum constant
424
         *
425
         * @throws NullPointerException if the parameter is null
426
         */
427
        public static SymbolicValue fromEnum(TypeSystem ts, Enum<?> value) {
428
            return fromBinaryName(ts, value.getDeclaringClass().getName(), value.name());
1✔
429
        }
430

431
        /**
432
         * @param ts             Type system
433
         * @param enumBinaryName A binary name, eg {@code com.MyEnum}
434
         * @param enumConstName  Simple name of the enum constant
435
         *
436
         * @throws NullPointerException if any parameter is null
437
         */
438
        public static SymEnum fromBinaryName(TypeSystem ts, String enumBinaryName, String enumConstName) {
439
            return new SymEnum(enumBinaryName, enumConstName);
1✔
440
        }
441

442
        /**
443
         * @param ts                 Type system
444
         * @param enumTypeDescriptor The type descriptor, eg {@code Lcom/MyEnum;}
445
         * @param enumConstName      Simple name of the enum constant
446
         */
447
        public static SymEnum fromTypeDescriptor(TypeSystem ts, String enumTypeDescriptor, String enumConstName) {
448
            String enumBinaryName = ClassNamesUtil.classDescriptorToBinaryName(enumTypeDescriptor);
1✔
449
            return fromBinaryName(ts, enumBinaryName, enumConstName);
1✔
450
        }
451

452
        @Override
453
        public boolean valueEquals(Object o) {
454
            if (!(o instanceof Enum)) {
1✔
455
                return false;
×
456
            }
457
            Enum<?> value = (Enum<?>) o;
1✔
458
            return this.enumName.equals(value.name())
1✔
459
                && enumBinaryName.equals(value.getDeclaringClass().getName());
1✔
460
        }
461

462
        @Override
463
        public boolean equals(Object o) {
464
            if (this == o) {
1✔
465
                return true;
×
466
            }
467
            if (o == null || getClass() != o.getClass()) {
1✔
468
                return false;
×
469
            }
470
            SymEnum that = (SymEnum) o;
1✔
471
            return Objects.equals(enumBinaryName, that.enumBinaryName)
1✔
472
                && Objects.equals(enumName, that.enumName);
1✔
473
        }
474

475
        @Override
476
        public int hashCode() {
477
            return Objects.hash(enumBinaryName, enumName);
×
478
        }
479

480
        @Override
481
        public String toString() {
482
            return enumBinaryName + "#" + enumName;
×
483
        }
484
    }
485

486
    /**
487
     * Represents a primitive or string value.
488
     */
489
    final class SymValue implements SymbolicValue {
1✔
490

491
        private final Object value;
492

493
        private SymValue(Object value) { // note that the value is always boxed
1✔
494
            assert value != null && isOkValue(value) : "Invalid value " + value;
1✔
495
            this.value = value;
1✔
496
        }
1✔
497

498
        private static boolean isOkValue(@NonNull Object value) {
499
            return ClassUtils.isPrimitiveWrapper(value.getClass())
1✔
500
                || value instanceof String;
501
        }
502

503
        @Override
504
        public boolean valueEquals(Object o) {
505
            return Objects.equals(value, o);
1✔
506
        }
507

508
        @Override
509
        public boolean equals(Object o) {
510
            if (this == o) {
1✔
511
                return true;
×
512
            }
513
            if (o == null || getClass() != o.getClass()) {
1✔
514
                return false;
×
515
            }
516
            SymValue symValue = (SymValue) o;
1✔
517
            return valueEquals(symValue.value);
1✔
518
        }
519

520
        @Override
521
        public int hashCode() {
522
            return value.hashCode();
×
523
        }
524

525
        @Override
526
        public String toString() {
527
            return value.toString();
×
528
        }
529
    }
530

531

532
    /**
533
     * Represents a class constant.
534
     */
535
    final class SymClass implements SymbolicValue {
536

537
        private final String binaryName;
538

539
        private SymClass(String binaryName) {
1✔
540
            this.binaryName = binaryName;
1✔
541
        }
1✔
542

543

544
        public static SymClass ofBinaryName(TypeSystem ts, String binaryName) {
545
            return new SymClass(binaryName);
1✔
546
        }
547

548
        @Override
549
        public boolean valueEquals(Object o) {
550
            return o instanceof Class<?> && ((Class<?>) o).getName().equals(binaryName);
×
551
        }
552

553
        @Override
554
        public boolean equals(Object o) {
555
            if (this == o) {
1✔
556
                return true;
×
557
            }
558
            if (o == null || getClass() != o.getClass()) {
1✔
559
                return false;
×
560
            }
561
            SymClass symClass = (SymClass) o;
1✔
562
            return Objects.equals(binaryName, symClass.binaryName);
1✔
563
        }
564

565
        @Override
566
        public int hashCode() {
567
            return binaryName.hashCode();
×
568
        }
569
    }
570
}
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