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

pmd / pmd / 4509

21 Mar 2025 11:23AM UTC coverage: 77.757% (-0.004%) from 77.761%
4509

push

github

adangel
[doc] Use full class name for deprecation in release notes

17505 of 23464 branches covered (74.6%)

Branch coverage included in aggregate %.

38318 of 48328 relevant lines covered (79.29%)

0.8 hits per line

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

81.35
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.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.internal.asm;
6

7
import java.io.IOException;
8
import java.io.InputStream;
9
import java.util.ArrayList;
10
import java.util.Collections;
11
import java.util.List;
12
import java.util.Objects;
13
import java.util.regex.Pattern;
14

15
import org.checkerframework.checker.nullness.qual.NonNull;
16
import org.checkerframework.checker.nullness.qual.Nullable;
17
import org.objectweb.asm.ClassReader;
18
import org.objectweb.asm.Opcodes;
19
import org.pcollections.HashTreePSet;
20
import org.pcollections.PSet;
21

22
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
23
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
24
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
25
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
26
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
27
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
28
import net.sourceforge.pmd.lang.java.symbols.JRecordComponentSymbol;
29
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
30
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterOwnerSymbol;
31
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue;
32
import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot;
33
import net.sourceforge.pmd.lang.java.symbols.internal.SymbolEquality;
34
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.CtorStub;
35
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ExecutableStub.MethodStub;
36
import net.sourceforge.pmd.lang.java.symbols.internal.asm.GenericSigBase.LazyClassSignature;
37
import net.sourceforge.pmd.lang.java.symbols.internal.asm.ParseLock.CheckedParseLock;
38
import net.sourceforge.pmd.lang.java.types.JClassType;
39
import net.sourceforge.pmd.lang.java.types.JTypeVar;
40
import net.sourceforge.pmd.lang.java.types.LexicalScope;
41
import net.sourceforge.pmd.lang.java.types.Substitution;
42
import net.sourceforge.pmd.lang.java.types.TypeSystem;
43
import net.sourceforge.pmd.util.CollectionUtil;
44

45

46
final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
47

48
    static final int UNKNOWN_ARITY = 0;
49

50
    private final AsmSymbolResolver resolver;
51

52
    private final Names names;
53

54
    // all the following are lazy and depend on the parse lock
55

56
    private int accessFlags;
57

58
    private EnclosingInfo enclosingInfo;
59
    private LazyClassSignature signature;
60
    private LexicalScope scope;
61

62
    private List<JFieldSymbol> fields = new ArrayList<>();
1✔
63
    private List<JClassSymbol> memberClasses = new ArrayList<>();
1✔
64
    private List<JMethodSymbol> methods = new ArrayList<>();
1✔
65
    private List<JConstructorSymbol> ctors = new ArrayList<>();
1✔
66
    private List<JRecordComponentSymbol> recordComponents = null;
1✔
67
    private List<JFieldSymbol> enumConstants = null;
1✔
68
    private List<JClassSymbol> permittedSubclasses = null;
1✔
69

70
    private PSet<SymAnnot> annotations = HashTreePSet.empty();
1✔
71

72
    private PSet<String> annotAttributes;
73

74
    private final ParseLock parseLock;
75

76
    /** Note that '.' is forbidden because in internal names they're replaced by slashes '/'. */
77
    private static final Pattern INTERNAL_NAME_FORBIDDEN_CHARS = Pattern.compile("[;<>\\[.]");
1✔
78

79
    private static boolean isValidInternalName(String internalName) {
80
        return !internalName.isEmpty() && !INTERNAL_NAME_FORBIDDEN_CHARS.matcher(internalName).find();
1!
81
    }
82

83
    ClassStub(AsmSymbolResolver resolver, String internalName, @NonNull Loader loader, int observedArity) {
1✔
84
        assert isValidInternalName(internalName) : internalName;
1!
85

86
        this.resolver = resolver;
1✔
87
        this.names = new Names(internalName);
1✔
88

89
        this.parseLock = new CheckedParseLock("ClassStub:" + internalName) {
1✔
90
            @Override
91
            protected boolean doParse() throws IOException {
92
                try (InputStream instream = loader.getInputStream()) {
1✔
93
                    if (instream != null) {
1✔
94
                        ClassReader classReader = new ClassReader(instream);
1✔
95
                        ClassStubBuilder builder = new ClassStubBuilder(ClassStub.this, resolver);
1✔
96
                        classReader.accept(builder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
1✔
97
                        return true;
1✔
98
                    } else {
99
                        return false;
1✔
100
                    }
101
                } catch (IOException e) {
1!
102
                    // add a bit more info to the exception
103
                    throw new IOException("While loading class from " + loader, e);
×
104
                }
105
            }
106

107
            @Override
108
            protected void finishParse(boolean failed) {
109
                if (enclosingInfo == null) {
1✔
110
                    // this may be normal
111
                    enclosingInfo = EnclosingInfo.NO_ENCLOSING;
1✔
112
                }
113
                if (signature == null) {
1✔
114
                    assert failed : "No signature, but the parse hasn't failed? investigate " + names.internalName;
1!
115
                    signature = LazyClassSignature.defaultWhenUnresolved(ClassStub.this, observedArity);
1✔
116
                }
117
                methods = Collections.unmodifiableList(methods);
1✔
118
                ctors = Collections.unmodifiableList(ctors);
1✔
119
                fields = Collections.unmodifiableList(fields);
1✔
120
                memberClasses = Collections.unmodifiableList(memberClasses);
1✔
121
                enumConstants = CollectionUtil.makeUnmodifiableAndNonNull(enumConstants);
1✔
122
                recordComponents = CollectionUtil.makeUnmodifiableAndNonNull(recordComponents);
1✔
123
                if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
1✔
124
                    permittedSubclasses = Collections.emptyList();
1✔
125
                }
126
                permittedSubclasses = CollectionUtil.makeUnmodifiableAndNonNull(permittedSubclasses);
1✔
127

128
                if (enclosingInfo.getEnclosingClass() == null && names.simpleName == null) {
1✔
129
                    // Top-level classes don't get their simple-name populated during parsing.
130
                    // If the class simple name contains dollars, we can only know after parsing
131
                    // whether they are top-level or not.
132
                    names.finishOuterClass();
1✔
133
                }
134
            }
1✔
135

136
            @Override
137
            protected boolean postCondition() {
138
                return signature != null && enclosingInfo != null && names.simpleName != null;
1!
139
            }
140
        };
141
    }
1✔
142

143
    @Override
144
    public AsmSymbolResolver getResolver() {
145
        return resolver;
1✔
146
    }
147

148
    // <editor-fold  defaultstate="collapsed" desc="Setters used during loading">
149

150
    void setHeader(@Nullable String signature,
151
                   @Nullable String superName,
152
                   String[] interfaces) {
153
        this.signature = new LazyClassSignature(this, signature, superName, interfaces);
1✔
154
    }
1✔
155

156
    /**
157
     * Called if this is an inner class (their simple name cannot be
158
     * derived from splitting the internal/binary name on dollars, as
159
     * the simple name may itself contain dollars).
160
     */
161
    void setSimpleName(String simpleName) {
162
        this.names.simpleName = simpleName;
1✔
163
    }
1✔
164

165
    void setModifiers(int accessFlags, boolean fromClassInfo) {
166
        /*
167
            A different set of modifiers is contained in the ClassInfo
168
            structure and the InnerClasses structure. See
169
            https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.1-200-E.1
170
            https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1
171

172
            Here is the diff (+ lines (resp. - lines) are only available
173
            in InnerClasses (resp. ClassInfo), the rest are available in both)
174

175
            ACC_PUBLIC      0x0001  Declared public; may be accessed from outside its package.
176
         +  ACC_PRIVATE     0x0002  Marked private in source.
177
         +  ACC_PROTECTED   0x0004  Marked protected in source.
178
         +  ACC_STATIC      0x0008  Marked or implicitly static in source.
179
            ACC_FINAL       0x0010  Declared final; no subclasses allowed.
180
         -  ACC_SUPER       0x0020  Treat superclass methods specially when invoked by the invokespecial instruction.
181
            ACC_INTERFACE   0x0200  Is an interface, not a class.
182
            ACC_ABSTRACT    0x0400  Declared abstract; must not be instantiated.
183
            ACC_SYNTHETIC   0x1000  Declared synthetic; not present in the source code.
184
            ACC_ANNOTATION  0x2000  Declared as an annotation type.
185
            ACC_ENUM        0x4000  Declared as an enum type.
186
         -  ACC_MODULE      0x8000  Is a module, not a class or interface.
187

188
            If this stub is a nested class, then we don't have all its
189
            modifiers just with the ClassInfo, the actual source-declared
190
            visibility (if not public) is only in the InnerClasses, as
191
            well as its ACC_STATIC.
192

193
            Also ACC_SUPER conflicts with ACC_SYNCHRONIZED, which
194
            Modifier.toString would reflect.
195

196
            Since the differences are disjoint we can just OR the two
197
            sets of flags.
198
         */
199
        final int visibilityMask = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE;
1✔
200
        int myAccess = this.accessFlags;
1✔
201
        if (fromClassInfo) {
1✔
202
            // we don't care about ACC_SUPER and it conflicts
203
            // with ACC_SYNCHRONIZED
204
            accessFlags = accessFlags & ~Opcodes.ACC_SUPER;
1✔
205
        } else if ((myAccess & Opcodes.ACC_PUBLIC) != 0
1✔
206
            && (accessFlags & visibilityMask) != Opcodes.ACC_PUBLIC) {
207
            // ClassInfo mentions ACC_PUBLIC even if the real
208
            // visibility is protected or private
209
            // We remove the public to avoid a "public protected" or "public private" combination
210
            myAccess = myAccess & ~Opcodes.ACC_PUBLIC;
1✔
211
        }
212
        this.accessFlags = myAccess | accessFlags;
1✔
213

214
        // setModifiers is called multiple times: once from ClassFile structure (fromClassInfo==true)
215
        // and additionally from InnerClasses attribute (fromClassInfo==false)
216
        // The enum constants and record components should only be initialized once
217
        // to avoid losing the constants.
218
        if (fromClassInfo) {
1✔
219
            if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
1✔
220
                this.enumConstants = new ArrayList<>();
1✔
221
            }
222
            if ((accessFlags & Opcodes.ACC_RECORD) != 0) {
1✔
223
                this.recordComponents = new ArrayList<>();
1✔
224
            }
225
        }
226
    }
1✔
227

228
    void setEnclosingInfo(ClassStub outer, boolean localOrAnon, @Nullable String methodName, @Nullable String methodDescriptor) {
229
        if (enclosingInfo == null) {
1✔
230
            if (outer == null) {
1!
231
                assert methodName == null && methodDescriptor == null
×
232
                    : "Enclosing method requires enclosing class";
233
                this.enclosingInfo = EnclosingInfo.NO_ENCLOSING;
×
234
            } else {
235
                this.enclosingInfo = new EnclosingInfo(outer, localOrAnon, methodName, methodDescriptor);
1✔
236
            }
237
        }
238
    }
1✔
239

240
    void addField(FieldStub fieldStub) {
241
        fields.add(fieldStub);
1✔
242

243
        if (fieldStub.isEnumConstant() && enumConstants != null) {
1!
244
            enumConstants.add(fieldStub);
1✔
245
        }
246
    }
1✔
247

248
    void addMemberClass(ClassStub classStub) {
249
        classStub.setEnclosingInfo(this, false, null, null);
1✔
250
        memberClasses.add(classStub);
1✔
251
    }
1✔
252

253
    void addMethod(MethodStub methodStub) {
254
        methods.add(methodStub);
1✔
255
    }
1✔
256

257
    void addCtor(CtorStub methodStub) {
258
        ctors.add(methodStub);
1✔
259
    }
1✔
260

261
    void addRecordComponent(RecordComponentStub recordComponentStub) {
262
        if (recordComponents == null) {
1!
263
            recordComponents = new ArrayList<>();
×
264
        }
265
        recordComponents.add(recordComponentStub);
1✔
266
    }
1✔
267

268
    void addPermittedSubclass(ClassStub permittedSubclass) {
269
        if (this.permittedSubclasses == null) {
1✔
270
            this.permittedSubclasses = new ArrayList<>(2);
1✔
271
        }
272
        this.permittedSubclasses.add(permittedSubclass);
1✔
273
    }
1✔
274

275
    @Override
276
    public void addAnnotation(SymAnnot annot) {
277
        annotations = annotations.plus(annot);
1✔
278
    }
1✔
279

280

281
    // </editor-fold>
282

283

284
    @Override
285
    public @Nullable JClassSymbol getSuperclass() {
286
        parseLock.ensureParsed();
1✔
287
        return signature.getRawSuper();
1✔
288
    }
289

290
    @Override
291
    public List<JClassSymbol> getSuperInterfaces() {
292
        parseLock.ensureParsed();
1✔
293
        return signature.getRawItfs();
1✔
294
    }
295

296
    @Override
297
    public @Nullable JClassType getSuperclassType(Substitution substitution) {
298
        parseLock.ensureParsed();
1✔
299
        return signature.getSuperType(substitution);
1✔
300
    }
301

302
    @Override
303
    public List<JClassType> getSuperInterfaceTypes(Substitution substitution) {
304
        parseLock.ensureParsed();
1✔
305
        return signature.getSuperItfs(substitution);
1✔
306
    }
307

308
    @Override
309
    public List<JTypeVar> getTypeParameters() {
310
        parseLock.ensureParsed();
1✔
311
        return signature.getTypeParams();
1✔
312
    }
313

314
    @Override
315
    public int getTypeParameterCount() {
316
        parseLock.ensureParsed();
1✔
317
        return signature.getTypeParameterCount();
1✔
318
    }
319

320
    @Override
321
    public LexicalScope getLexicalScope() {
322
        if (scope == null) {
1✔
323
            scope = JClassSymbol.super.getLexicalScope();
1✔
324
        }
325
        return scope;
1✔
326
    }
327

328
    @Override
329
    public List<JFieldSymbol> getDeclaredFields() {
330
        parseLock.ensureParsed();
1✔
331
        return fields;
1✔
332
    }
333

334
    @Override
335
    public List<JMethodSymbol> getDeclaredMethods() {
336
        parseLock.ensureParsed();
1✔
337
        return methods;
1✔
338
    }
339

340
    @Override
341
    public List<JConstructorSymbol> getConstructors() {
342
        parseLock.ensureParsed();
1✔
343
        return ctors;
1✔
344
    }
345

346
    @Override
347
    public List<JClassSymbol> getDeclaredClasses() {
348
        parseLock.ensureParsed();
1✔
349
        return memberClasses;
1✔
350
    }
351

352
    @Override
353
    public PSet<SymAnnot> getDeclaredAnnotations() {
354
        parseLock.ensureParsed();
1✔
355
        return annotations;
1✔
356
    }
357

358
    @Override
359
    public PSet<String> getAnnotationAttributeNames() {
360
        if (annotAttributes == null) {
1✔
361
            parseLock.ensureParsed();
1✔
362
            annotAttributes = isAnnotation()
1!
363
                              ? getDeclaredMethods().stream().filter(JMethodSymbol::isAnnotationAttribute)
1✔
364
                                                    .map(JElementSymbol::getSimpleName)
1✔
365
                                                    .collect(CollectionUtil.toPersistentSet())
1✔
366
                              : HashTreePSet.empty();
1✔
367
        }
368
        return annotAttributes;
1✔
369
    }
370

371
    @Override
372
    public @Nullable SymbolicValue getDefaultAnnotationAttributeValue(String attrName) {
373
        parseLock.ensureParsed();
1✔
374
        if (!getAnnotationAttributeNames().contains(attrName)) {
1✔
375
            // this is a shortcut, because the default impl checks each method
376
            return null;
1✔
377
        }
378
        return JClassSymbol.super.getDefaultAnnotationAttributeValue(attrName);
1✔
379
    }
380

381
    @Override
382
    public @Nullable JClassSymbol getEnclosingClass() {
383
        parseLock.ensureParsed();
1✔
384
        return enclosingInfo.getEnclosingClass();
1✔
385
    }
386

387
    @Override
388
    public @Nullable JExecutableSymbol getEnclosingMethod() {
389
        parseLock.ensureParsed();
1✔
390
        return enclosingInfo.getEnclosingMethod();
1✔
391
    }
392

393
    @Override
394
    public @NonNull List<JFieldSymbol> getEnumConstants() {
395
        parseLock.ensureParsed();
1✔
396
        return enumConstants;
1✔
397
    }
398

399

400
    @Override
401
    public @NonNull List<JRecordComponentSymbol> getRecordComponents() {
402
        parseLock.ensureParsed();
1✔
403
        return recordComponents;
1✔
404
    }
405

406

407
    @Override
408
    public List<JClassSymbol> getPermittedSubtypes() {
409
        parseLock.ensureParsed();
1✔
410
        return permittedSubclasses;
1✔
411
    }
412

413
    @Override
414
    public JTypeParameterOwnerSymbol getEnclosingTypeParameterOwner() {
415
        parseLock.ensureParsed();
1✔
416
        return enclosingInfo.getEnclosing();
1✔
417
    }
418

419
    @Override
420
    public String toString() {
421
        // do not use SymbolToString as it triggers the class parsing,
422
        // making tests undebuggable
423
        return getInternalName();
1✔
424
    }
425

426
    @Override
427
    public int hashCode() {
428
        return SymbolEquality.CLASS.hash(this);
1✔
429
    }
430

431
    @Override
432
    public boolean equals(Object obj) {
433
        return SymbolEquality.CLASS.equals(this, obj);
1✔
434
    }
435

436
    // <editor-fold  defaultstate="collapsed" desc="Names">
437

438
    public String getInternalName() {
439
        return getNames().internalName;
1✔
440
    }
441

442
    private Names getNames() {
443
        return names;
1✔
444
    }
445

446
    @Override
447
    public @NonNull String getBinaryName() {
448
        return getNames().binaryName;
1✔
449
    }
450

451
    /**
452
     * Simpler check than computing the canonical name.
453
     */
454
    boolean hasCanonicalName() {
455
        return getCanonicalName() != null;
1!
456
    }
457

458
    @Override
459
    public @Nullable String getCanonicalName() {
460
        @Nullable String canoName = names.canonicalName;
1✔
461
        if (canoName == null) {
1✔
462
            canoName = computeCanonicalName();
1✔
463
            names.canonicalName = canoName;
1✔
464
        }
465

466
        if (Names.NO_CANONAME.equals(canoName)) {
1!
467
            return null;
×
468
        }
469
        return canoName;
1✔
470
    }
471

472
    private @NonNull String computeCanonicalName() {
473
        parseLock.ensureParsed();
1✔
474
        if (names.canonicalName != null) {
1✔
475
            return names.canonicalName;
1✔
476
        } else if (enclosingInfo.isLocalOrAnon()) {
1!
477
            return Names.NO_CANONAME;
×
478
        }
479
        assert names.simpleName != null && !names.simpleName.isEmpty() : "Anon class should not take this branch";
1!
480

481
        JClassSymbol enclosing = enclosingInfo.getEnclosingClass();
1✔
482
        if (enclosing == null) {
1!
483
            return names.binaryName;
×
484
        }
485
        String outerName = enclosing.getCanonicalName();
1✔
486
        if (outerName == null) {
1!
487
            return Names.NO_CANONAME;
×
488
        }
489
        return outerName + '.' + names.simpleName;
1✔
490
    }
491

492
    @Override
493
    public @NonNull String getPackageName() {
494
        return getNames().packageName;
1✔
495
    }
496

497
    @Override
498
    public @NonNull String getSimpleName() {
499
        String mySimpleName = names.simpleName;
1✔
500
        if (mySimpleName == null) {
1!
501
            parseLock.ensureParsed();
×
502
            return Objects.requireNonNull(names.simpleName, "Null simple name after parsing " + getInternalName());
×
503
        }
504
        return mySimpleName;
1✔
505
    }
506

507
    @Override
508
    public TypeSystem getTypeSystem() {
509
        return getResolver().getTypeSystem();
1✔
510
    }
511

512
    // </editor-fold>
513

514
    // <editor-fold  defaultstate="collapsed" desc="Modifier info">
515

516

517
    @Override
518
    public boolean isUnresolved() {
519
        return parseLock.isFailed();
1✔
520
    }
521

522
    @Override
523
    public boolean isArray() {
524
        return false;
1✔
525
    }
526

527
    @Override
528
    public boolean isPrimitive() {
529
        return false;
×
530
    }
531

532
    @Override
533
    public @Nullable JTypeDeclSymbol getArrayComponent() {
534
        return null;
×
535
    }
536

537
    @Override
538
    public int getModifiers() {
539
        parseLock.ensureParsed();
1✔
540
        return accessFlags;
1✔
541
    }
542

543
    @Override
544
    public boolean isAbstract() {
545
        return (getModifiers() & Opcodes.ACC_ABSTRACT) != 0;
1✔
546
    }
547

548
    @Override
549
    public boolean isEnum() {
550
        return (getModifiers() & Opcodes.ACC_ENUM) != 0;
1✔
551
    }
552

553
    @Override
554
    public boolean isAnnotation() {
555
        return (getModifiers() & Opcodes.ACC_ANNOTATION) != 0;
1✔
556
    }
557

558
    @Override
559
    public boolean isInterface() {
560
        return (getModifiers() & Opcodes.ACC_INTERFACE) != 0;
1✔
561
    }
562

563
    @Override
564
    public boolean isClass() {
565
        return (getModifiers() & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION)) == 0;
1✔
566
    }
567

568
    @Override
569
    public boolean isRecord() {
570
        JClassSymbol sup = getSuperclass();
1✔
571
        return sup != null && "java.lang.Record".equals(sup.getBinaryName());
1!
572
    }
573

574
    @Override
575
    public boolean isLocalClass() {
576
        parseLock.ensureParsed();
1✔
577
        return enclosingInfo.isLocalOrAnon() && !isAnonymousClass();
1!
578
    }
579

580
    @Override
581
    public boolean isAnonymousClass() {
582
        return getSimpleName().isEmpty();
1✔
583
    }
584

585
    boolean isFailed() {
586
        return this.parseLock.isFailed();
1✔
587
    }
588

589
    boolean isNotParsed() {
590
        return this.parseLock.isNotParsed();
1✔
591
    }
592

593

594
    // </editor-fold>
595

596

597
    static class Names {
1✔
598
        /**
599
         * Placeholder to represent that the class has no canonical name.
600
         * This is not a valid canonical names so cannot class with an
601
         * actual canoname.
602
         */
603
        private static final String NO_CANONAME = "--NO-CANONICAL-NAME";
604

605
        final String binaryName;
606
        final String internalName;
607
        final String packageName;
608
        /** If null, the class requires parsing to find out the actual canonical name. */
609
        @Nullable String canonicalName;
610
        /** If null, the class requires parsing to find out the actual simple name. */
611
        @Nullable String simpleName;
612

613
        Names(String internalName) {
1✔
614
            assert isValidInternalName(internalName) : internalName;
1!
615
            int packageEnd = internalName.lastIndexOf('/');
1✔
616

617
            this.internalName = internalName;
1✔
618
            this.binaryName = internalName.replace('/', '.');
1✔
619
            if (packageEnd == -1) {
1✔
620
                this.packageName = "";
1✔
621
            } else {
622
                this.packageName = binaryName.substring(0, packageEnd);
1✔
623
            }
624

625
            if (binaryName.indexOf('$', packageEnd + 1) >= 0) {
1✔
626
                // Contains a dollar in class name (after package)
627
                // Requires parsing to find out the actual simple name,
628
                // this might be an inner class, or simply a class with
629
                // a dollar in its name.
630

631
                // ASSUMPTION: all JVM languages use the $ convention
632
                // to separate inner classes. Java compilers do so but
633
                // not necessarily true of all compilers/languages.
634
                this.canonicalName = null;
1✔
635
                this.simpleName = null;
1✔
636
            } else {
637
                // fast path
638
                this.canonicalName = binaryName;
1✔
639
                this.simpleName = binaryName.substring(packageEnd + 1);
1✔
640
            }
641
        }
1✔
642

643
        public void finishOuterClass() {
644
            int packageEnd = internalName.lastIndexOf('/');
1✔
645
            this.simpleName = binaryName.substring(packageEnd + 1); // if -1, start from 0
1✔
646
            this.canonicalName = binaryName;
1✔
647
        }
1✔
648
    }
649

650
    static class EnclosingInfo {
651

652
        static final EnclosingInfo NO_ENCLOSING = new EnclosingInfo(null, false, null, null);
1✔
653

654
        private final @Nullable JClassSymbol stub;
655
        private final @Nullable String methodName;
656
        private final @Nullable String methodDescriptor;
657
        private final boolean isLocalOrAnon;
658

659
        EnclosingInfo(@Nullable JClassSymbol stub, boolean isLocalOrAnon, @Nullable String methodName, @Nullable String methodDescriptor) {
1✔
660
            this.stub = stub;
1✔
661
            this.isLocalOrAnon = isLocalOrAnon;
1✔
662
            this.methodName = methodName;
1✔
663
            this.methodDescriptor = methodDescriptor;
1✔
664
        }
1✔
665

666
        boolean isLocalOrAnon() {
667
            return isLocalOrAnon;
1✔
668
        }
669

670
        public @Nullable JClassSymbol getEnclosingClass() {
671
            return stub;
1✔
672
        }
673

674
        public @Nullable MethodStub getEnclosingMethod() {
675
            if (stub instanceof ClassStub && methodName != null) {
1✔
676
                ClassStub stub1 = (ClassStub) stub;
1✔
677
                stub1.parseLock.ensureParsed();
1✔
678
                for (JMethodSymbol m : stub1.methods) {
1!
679
                    MethodStub ms = (MethodStub) m;
1✔
680
                    if (ms.matches(methodName, methodDescriptor)) {
1!
681
                        return ms;
1✔
682
                    }
683
                }
×
684
            }
685
            return null;
1✔
686
        }
687

688

689
        JTypeParameterOwnerSymbol getEnclosing() {
690
            if (methodName != null) {
1!
691
                return getEnclosingMethod();
×
692
            } else {
693
                return getEnclosingClass();
1✔
694
            }
695
        }
696

697
        @Override
698
        public boolean equals(Object o) {
699
            if (this == o) {
×
700
                return true;
×
701
            }
702
            if (o == null || getClass() != o.getClass()) {
×
703
                return false;
×
704
            }
705
            EnclosingInfo that = (EnclosingInfo) o;
×
706
            return Objects.equals(stub, that.stub)
×
707
                && isLocalOrAnon == that.isLocalOrAnon
708
                && Objects.equals(methodName, that.methodName)
×
709
                && Objects.equals(methodDescriptor, that.methodDescriptor);
×
710
        }
711

712
        @Override
713
        public int hashCode() {
714
            return Objects.hash(stub, isLocalOrAnon, methodName, methodDescriptor);
×
715
        }
716
    }
717
}
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