• 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

93.41
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ClassTypeImpl.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.types;
6

7
import static net.sourceforge.pmd.util.CollectionUtil.emptyList;
8
import static net.sourceforge.pmd.util.CollectionUtil.map;
9

10
import java.lang.reflect.Modifier;
11
import java.util.List;
12
import java.util.Objects;
13
import java.util.function.Predicate;
14
import java.util.stream.Stream;
15

16
import org.checkerframework.checker.nullness.qual.NonNull;
17
import org.checkerframework.checker.nullness.qual.Nullable;
18
import org.pcollections.HashTreePSet;
19
import org.pcollections.PSet;
20

21
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
22
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
23
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
24
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
25
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
26
import net.sourceforge.pmd.lang.java.symbols.table.internal.SuperTypesEnumerator;
27
import net.sourceforge.pmd.lang.java.types.JVariableSig.FieldSig;
28
import net.sourceforge.pmd.util.CollectionUtil;
29

30
class ClassTypeImpl implements JClassType {
31
    private final @Nullable JClassType enclosingType;
32

33
    private final JClassSymbol symbol;
34
    private final TypeSystem ts;
35
    private final List<JTypeMirror> typeArgs;
36
    private final PSet<SymAnnot> typeAnnotations;
37
    private final TypeGenericity genericity;
38

39
    private JClassType superClass;
40
    private List<JClassType> interfaces;
41

42
    private Substitution subst;
43

44
    // Cache the hash. Instances are super often put in sets or used as
45
    // map keys and hash computation is expensive since we recurse in the
46
    // type arguments. This was confirmed through profiling
47
    private int hash = 0;
1✔
48

49
    /**
50
     * @param symbol   Erased type
51
     * @param typeArgs Type arguments of this parameterization. If empty
52
     *                 and isDecl is false, this will represent a raw type.
53
     *                 If empty and isDecl is true, this will represent a
54
     *                 generic type declaration.
55
     * @param isRaw    Choose a bias towards generic type declaration or raw
56
     *                 type. If the [rawType] param has no type parameters,
57
     *                 then this parameter makes *no difference*.
58
     *
59
     * @throws IllegalArgumentException if the typeArgs don't have the same length
60
     *                                  as the type's type parameters
61
     * @throws IllegalArgumentException if any type argument is of a primitive type.
62
     */
63
    ClassTypeImpl(TypeSystem ts, JClassSymbol symbol, List<JTypeMirror> typeArgs, boolean isRaw, PSet<SymAnnot> typeAnnotations) {
64
        this(ts, null, symbol, typeArgs, typeAnnotations, isRaw);
1✔
65
    }
1✔
66

67
    private ClassTypeImpl(TypeSystem ts, JClassType enclosing, JClassSymbol symbol, List<JTypeMirror> typeArgs, PSet<SymAnnot> typeAnnotations, boolean isRaw) {
1✔
68
        this.typeAnnotations = typeAnnotations;
1✔
69
        validateParams(enclosing, symbol, typeArgs);
1✔
70

71
        this.ts = ts;
1✔
72
        this.symbol = symbol;
1✔
73
        this.typeArgs = typeArgs;
1✔
74

75
        this.enclosingType = enclosing != null
1✔
76
                             ? enclosing
1✔
77
                             : makeEnclosingOf(symbol);
1✔
78

79
        this.genericity = computeGenericity(isRaw);
1✔
80

81
    }
1✔
82

83
    // Special ctor for boxed primitives and other specials that are built
84
    // during initialization of TypeSystem. This cannot call JClassSymbol#isGeneric,
85
    // because that would trigger parsing of the class file while the type system is not ready
86
    protected ClassTypeImpl(TypeSystem ts, JClassSymbol symbol, PSet<SymAnnot> typeAnnotations) {
1✔
87
        this.typeAnnotations = typeAnnotations;
1✔
88
        this.ts = ts;
1✔
89
        this.symbol = symbol;
1✔
90
        this.typeArgs = emptyList();
1✔
91
        this.enclosingType = null;
1✔
92
        this.genericity = TypeGenericity.NON_GENERIC;
1✔
93
    }
1✔
94

95
    private @NonNull TypeGenericity computeGenericity(boolean isRaw) {
96
        boolean isGeneric = symbol.isGeneric();
1✔
97
        if (enclosingType != null && enclosingType.isRaw()) {
1✔
98
            return TypeGenericity.RAW;
1✔
99
        } else if (typeArgs.isEmpty()) {
1✔
100
            if (isGeneric && isRaw) {
1✔
101
                return TypeGenericity.RAW;
1✔
102
            } else if (isGeneric) {
1✔
103
                return TypeGenericity.GENERIC_TYPEDECL;
1✔
104
            } else {
105
                return TypeGenericity.NON_GENERIC;
1✔
106
            }
107
        } else {
108
            return TypeGenericity.GENERIC_PARAMETERIZED;
1✔
109
        }
110
    }
111

112
    private JClassType makeEnclosingOf(JClassSymbol sym) {
113
        if (Modifier.isStatic(sym.getModifiers())) {
1✔
114
            return null;
1✔
115
        }
116

117
        JClassSymbol enclosing = sym.getEnclosingClass();
1✔
118
        if (enclosing == null) {
1✔
119
            return null;
1✔
120
        }
121
        // this means, that all enclosing types of a raw type are erased.
122
        if (this.hasErasedSuperTypes()) {
1✔
123
            return ts.erasedType(enclosing);
1✔
124
        }
125
        return (JClassType) ts.typeOf(enclosing, false);
1✔
126
    }
127

128
    @Override
129
    public TypeSystem getTypeSystem() {
130
        return ts;
1✔
131
    }
132

133
    @Override
134
    public PSet<SymAnnot> getTypeAnnotations() {
135
        return typeAnnotations;
1✔
136
    }
137

138
    @Override
139
    public JClassType withAnnotations(PSet<SymAnnot> newTypeAnnots) {
140
        if (newTypeAnnots.isEmpty() && this.typeAnnotations.isEmpty()) {
1✔
141
            return this;
1✔
142
        }
143
        return new ClassTypeImpl(ts, enclosingType, symbol, typeArgs, newTypeAnnots, isRaw());
1✔
144
    }
145

146
    @Override
147
    public List<JTypeVar> getFormalTypeParams() {
148
        return symbol.getTypeParameters();
1✔
149
    }
150

151
    @Override
152
    public Substitution getTypeParamSubst() {
153
        if (subst == null) {
1✔
154
            Substitution enclSubst = getEnclosingType() == null
1✔
155
                                     ? Substitution.EMPTY
1✔
156
                                     : getEnclosingType().getTypeParamSubst();
1✔
157
            subst = enclSubst.andThen(localSubst());
1✔
158
        }
159
        return subst;
1✔
160
    }
161

162
    private Substitution localSubst() {
163
        if (hasErasedSuperTypes()) {
1✔
164
            return Substitution.erasing(getFormalTypeParams());
1✔
165
        } else if (isGenericTypeDeclaration() || !isGeneric()) {
1✔
166
            return Substitution.EMPTY;
1✔
167
        } else {
168
            return Substitution.mapping(getFormalTypeParams(), getTypeArgs());
1✔
169
        }
170
    }
171

172
    /**
173
     * Given a type appearing in a member, and the given substitution
174
     */
175
    static JTypeMirror eraseToRaw(JTypeMirror t, Substitution typeSubst) {
176
        if (TypeOps.mentionsAny(t, typeSubst.getMap().keySet())) {
1✔
177
            return t.getErasure();
1✔
178
        } else {
179
            // This type does not depend on any of the type variables to erase.
180
            return t;
1✔
181
        }
182
    }
183

184
    /**
185
     * Given a type appearing in a member of the given owner, erase
186
     * the member type if the owner is raw. The type needs not be erased
187
     * if it is generic but does not mention any of the type variables
188
     * to erase.
189
     */
190
    static JTypeMirror maybeEraseMemberType(JClassType owner, JTypeMirror t) {
191
        if (owner.isRaw() && TypeOps.mentionsAny(t, owner.getTypeParamSubst().getMap().keySet())) {
×
192
            return t.getErasure();
×
193
        } else {
194
            // This type does not depend on any of the type variables to erase.
195
            return t;
×
196
        }
197
    }
198

199
    static List<JTypeMirror> maybeEraseMemberType(JClassType owner, List<JTypeMirror> ts) {
200
        if (owner.isRaw()) {
×
201
            return map(ts, t -> maybeEraseMemberType(owner, t));
×
202
        } else {
203
            // This type does not depend on any of the type variables to erase.
204
            return ts;
×
205
        }
206
    }
207

208
    @Override
209
    public final JClassType selectInner(JClassSymbol symbol, List<? extends JTypeMirror> targs, PSet<SymAnnot> typeAnnotations) {
210
        return new ClassTypeImpl(ts,
1✔
211
                                 this,
212
                                 symbol,
213
                                 CollectionUtil.defensiveUnmodifiableCopy(targs),
1✔
214
                                 typeAnnotations,
215
                                 isRaw());
1✔
216
    }
217

218
    @Override
219
    public final boolean isRaw() {
220
        return genericity == TypeGenericity.RAW;
1✔
221
    }
222

223
    @Override
224
    public final boolean isGenericTypeDeclaration() {
225
        return genericity == TypeGenericity.GENERIC_TYPEDECL;
1✔
226
    }
227

228
    @Override
229
    public final boolean isParameterizedType() {
230
        return genericity == TypeGenericity.GENERIC_PARAMETERIZED;
1✔
231
    }
232

233
    @Override
234
    public final boolean isGeneric() {
235
        return genericity != TypeGenericity.NON_GENERIC;
1✔
236
    }
237

238
    @Override
239
    public JClassType getGenericTypeDeclaration() {
240
        if (isGenericTypeDeclaration() || !isGeneric()) {
1✔
241
            return this;
1✔
242
        }
243
        return new ClassTypeImpl(ts, symbol, emptyList(), false, typeAnnotations);
1✔
244
    }
245

246
    @Override
247
    @SuppressWarnings({ "unchecked", "rawtypes" })
248
    public List<JTypeMirror> getTypeArgs() {
249
        return isGenericTypeDeclaration() ? (List) getFormalTypeParams() : typeArgs;
1✔
250
    }
251

252
    @Override
253
    public @Nullable JClassType getEnclosingType() {
254
        return enclosingType;
1✔
255
    }
256

257
    @Override
258
    public boolean hasErasedSuperTypes() {
259
        return isRaw();
1✔
260
    }
261

262
    @Override
263
    public JClassType getErasure() {
264
        if ((!isGeneric() || isRaw()) && enclosingType == null) {
1✔
265
            return this;
1✔
266
        }
267

268
        return new ErasedClassType(ts, symbol, typeAnnotations);
1✔
269
    }
270

271
    @Override
272
    public JClassType withTypeArguments(List<? extends JTypeMirror> typeArgs) {
273
        if (enclosingType != null) {
1✔
274
            return enclosingType.selectInner(this.symbol, typeArgs, this.typeAnnotations);
1✔
275
        }
276

277
        int expected = symbol.getTypeParameterCount();
1✔
278
        if (expected == 0 && typeArgs.isEmpty() && this.typeArgs.isEmpty()) {
1✔
279
            return this; // non-generic
1✔
280
        }
281
        return new ClassTypeImpl(ts, symbol, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), true, typeAnnotations);
1✔
282
    }
283

284
    @Override
285
    public @Nullable JClassType getSuperClass() {
286
        if (superClass == null && !isTop()) {
1✔
287
            if (hasErasedSuperTypes()) {
1✔
288
                superClass = ts.erasedType(symbol.getSuperclass());
1✔
289
            } else {
290
                superClass = symbol.getSuperclassType(getTypeParamSubst());
1✔
291
            }
292
        }
293
        return superClass;
1✔
294
    }
295

296
    @Override
297
    public List<JClassType> getSuperInterfaces() {
298
        if (interfaces == null) {
1✔
299
            if (hasErasedSuperTypes()) {
1✔
300
                interfaces = map(symbol.getSuperInterfaces(), ts::erasedType);
1✔
301
            } else {
302
                interfaces = symbol.getSuperInterfaceTypes(getTypeParamSubst());
1✔
303
            }
304
        }
305
        return interfaces;
1✔
306
    }
307

308
    @Override
309
    public List<FieldSig> getDeclaredFields() {
310
        return CollectionUtil.map(symbol.getDeclaredFields(), it -> JVariableSig.forField(this, it));
1✔
311
    }
312

313
    @Override
314
    public List<JClassType> getDeclaredClasses() {
315
        return CollectionUtil.map(symbol.getDeclaredClasses(), this::getDeclaredClass);
1✔
316
    }
317

318
    private JClassType getDeclaredClass(JClassSymbol inner) {
319
        if (Modifier.isStatic(inner.getModifiers())) {
1✔
320
            return new ClassTypeImpl(ts, null, inner, emptyList(), typeAnnotations, isRaw());
1✔
321
        } else {
322
            return selectInner(inner, emptyList());
1✔
323
        }
324
    }
325

326
    @Override
327
    public @Nullable FieldSig getDeclaredField(String simpleName) {
328
        @Nullable JFieldSymbol declaredField = symbol.getDeclaredField(simpleName);
1✔
329
        if (declaredField != null) {
1✔
330
            return JVariableSig.forField(this, declaredField);
1✔
331
        }
332
        return null;
1✔
333
    }
334

335
    @Override
336
    public @Nullable JClassType getDeclaredClass(String simpleName) {
337
        JClassSymbol declaredClass = symbol.getDeclaredClass(simpleName);
1✔
338
        if (declaredClass != null) {
1✔
339
            if (Modifier.isStatic(declaredClass.getModifiers())) {
1✔
340
                return new ClassTypeImpl(ts, null, declaredClass, emptyList(), HashTreePSet.empty(), isRaw());
1✔
341
            } else {
342
                return selectInner(declaredClass, emptyList());
1✔
343
            }
344
        }
345
        return null;
1✔
346
    }
347

348
    @Override
349
    public List<JMethodSig> getConstructors() {
350
        return map(
1✔
351
            symbol.getConstructors(),
1✔
352
            it -> new ClassMethodSigImpl(this, it)
1✔
353
        );
354
    }
355

356
    @Override
357
    public Stream<JMethodSig> streamMethods(Predicate<? super JMethodSymbol> prefilter) {
358
        return SuperTypesEnumerator.ALL_SUPERTYPES_INCLUDING_SELF.stream(this)
1✔
359
                                                                 .flatMap(sup -> sup.streamDeclaredMethods(prefilter));
1✔
360
    }
361

362
    @Override
363
    public Stream<JMethodSig> streamDeclaredMethods(Predicate<? super JMethodSymbol> prefilter) {
364
        return getSymbol().getDeclaredMethods().stream()
1✔
365
                          .filter(prefilter)
1✔
366
                          .map(m -> new ClassMethodSigImpl(this, m));
1✔
367
    }
368

369
    @Override
370
    public @Nullable JMethodSig getDeclaredMethod(JExecutableSymbol sym) {
371
        if (sym.getEnclosingClass().equals(getSymbol())) {
1✔
372
            return new ClassMethodSigImpl(this, sym);
1✔
373
        }
374
        return null;
×
375
    }
376

377

378
    @Override
379
    public final @NonNull JClassSymbol getSymbol() {
380
        return symbol;
1✔
381
    }
382

383
    @Override
384
    public final boolean isTop() {
385
        return this.getSymbol().equals(ts.OBJECT.getSymbol()); // NOPMD CompareObjectsWithEquals
1✔
386
    }
387

388
    @Override
389
    public boolean equals(Object o) {
390
        if (this == o) {
1✔
391
            return true;
1✔
392
        }
393

394
        if (!(o instanceof JClassType)) {
1✔
395
            return false;
1✔
396
        }
397

398
        JClassType that = (JClassType) o;
1✔
399
        return TypeOps.isSameType(this, that);
1✔
400
    }
401

402
    @Override
403
    public int hashCode() {
404
        if (hash == 0) { // hash collision is harmless
1✔
405
            hash = Objects.hash(typeArgs, symbol);
1✔
406
        }
407
        return hash;
1✔
408
    }
409

410
    @Override
411
    public String toString() {
412
        return TypePrettyPrint.prettyPrint(this);
1✔
413
    }
414

415
    private static void validateParams(JClassType enclosing, JClassSymbol symbol, List<JTypeMirror> typeArgs) {
416
        Objects.requireNonNull(symbol, "Symbol shouldn't be null");
1✔
417

418
        checkUserEnclosingTypeIsOk(enclosing, symbol);
1✔
419

420
        if (!typeArgsAreOk(symbol, typeArgs)) {
1✔
421
            throw invalidTypeArgs(symbol, typeArgs);
1✔
422
        }
423

424
        for (JTypeMirror arg : typeArgs) {
1✔
425
            checkTypeArg(symbol, typeArgs, arg);
1✔
426
        }
1✔
427
    }
1✔
428

429
    protected static @NonNull IllegalArgumentException invalidTypeArgs(JClassSymbol symbol, List<? extends JTypeMirror> typeArgs) {
430
        return new IllegalArgumentException("Cannot parameterize " + symbol + " with " + typeArgs
1✔
431
                                                + ", expecting  " + symbol.getTypeParameterCount()
1✔
432
                                                + " type arguments");
433
    }
434

435
    private static void checkTypeArg(JClassSymbol symbol, List<JTypeMirror> typeArgs, JTypeMirror arg) {
436
        if (arg == null) {
1✔
437
            throw new IllegalArgumentException("Null type argument for " + symbol + " in " + typeArgs);
×
438
        } else if (arg.isPrimitive()) {
1✔
439
            throw new IllegalArgumentException("Primitive type argument for " + symbol + " in " + typeArgs);
×
440
        }
441
    }
1✔
442

443
    private static boolean typeArgsAreOk(JClassSymbol symbol, List<? extends JTypeMirror> typeArgs) {
444
        return typeArgs.isEmpty() // always ok (raw/ decl/ non-generic)
1✔
445
            || symbol.isUnresolved()
1✔
446
            || symbol.getTypeParameterCount() == typeArgs.size();
1✔
447
    }
448

449
    private static void checkUserEnclosingTypeIsOk(@Nullable JClassType enclosing, JClassSymbol symbol) {
450
        // TODO all enclosing types should be raw, or all should be parameterized
451

452
        if (symbol.isUnresolved() || enclosing != null && enclosing.getSymbol().isUnresolved()) {
1✔
453
            return;
1✔
454
        }
455

456
        if (enclosing != null) {
1✔
457
            if (Modifier.isStatic(symbol.getModifiers())) {
1✔
458
                throw new IllegalArgumentException("Cannot select *static* type " + symbol + " inside " + enclosing);
×
459
            } else if (!enclosing.getSymbol().equals(symbol.getEnclosingClass())) {
1✔
460
                throw new IllegalArgumentException("Cannot select type " + symbol + " inside " + enclosing);
×
461
            }
462
        }
463
    }
1✔
464

465
    private enum TypeGenericity {
1✔
466
        RAW,
1✔
467
        GENERIC_TYPEDECL,
1✔
468
        GENERIC_PARAMETERIZED,
1✔
469
        NON_GENERIC
1✔
470
    }
471
}
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