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

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

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

92.56
today-context/src/main/java/infra/context/annotation/ConfigurationClassParser.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.annotation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.io.IOException;
23
import java.lang.annotation.Annotation;
24
import java.util.ArrayList;
25
import java.util.Collection;
26
import java.util.Collections;
27
import java.util.Comparator;
28
import java.util.Deque;
29
import java.util.HashMap;
30
import java.util.Iterator;
31
import java.util.LinkedHashMap;
32
import java.util.LinkedHashSet;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Set;
36
import java.util.function.Predicate;
37

38
import infra.beans.factory.BeanDefinitionStoreException;
39
import infra.beans.factory.BeanRegistrar;
40
import infra.beans.factory.annotation.AnnotatedBeanDefinition;
41
import infra.beans.factory.config.BeanDefinition;
42
import infra.beans.factory.config.BeanDefinitionHolder;
43
import infra.beans.factory.parsing.Location;
44
import infra.beans.factory.parsing.Problem;
45
import infra.beans.factory.parsing.ProblemReporter;
46
import infra.beans.factory.support.AbstractBeanDefinition;
47
import infra.beans.factory.support.BeanDefinitionReader;
48
import infra.beans.factory.support.BeanNameGenerator;
49
import infra.context.ApplicationContextException;
50
import infra.context.BootstrapContext;
51
import infra.context.ConfigurableApplicationContext;
52
import infra.context.annotation.ConfigurationCondition.ConfigurationPhase;
53
import infra.context.annotation.DeferredImportSelector.Group;
54
import infra.core.OrderComparator;
55
import infra.core.Ordered;
56
import infra.core.annotation.AnnotationAwareOrderComparator;
57
import infra.core.annotation.AnnotationUtils;
58
import infra.core.annotation.MergedAnnotation;
59
import infra.core.annotation.MergedAnnotations;
60
import infra.core.env.ConfigurableEnvironment;
61
import infra.core.env.Environment;
62
import infra.core.io.PropertySourceDescriptor;
63
import infra.core.type.AnnotationMetadata;
64
import infra.core.type.MethodMetadata;
65
import infra.core.type.StandardAnnotationMetadata;
66
import infra.core.type.classreading.MetadataReader;
67
import infra.core.type.filter.AbstractTypeHierarchyTraversingFilter;
68
import infra.core.type.filter.AssignableTypeFilter;
69
import infra.core.type.filter.TypeFilter;
70
import infra.lang.Assert;
71
import infra.logging.Logger;
72
import infra.logging.LoggerFactory;
73
import infra.stereotype.Component;
74
import infra.util.ClassUtils;
75
import infra.util.LinkedMultiValueMap;
76
import infra.util.MultiValueMap;
77
import infra.util.StringUtils;
78

79
/**
80
 * Parses a {@link Configuration} class definition, populating a collection of
81
 * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
82
 * any number of ConfigurationClass objects because one Configuration class may import
83
 * another using the {@link Import} annotation).
84
 *
85
 * <p>This class helps separate the concern of parsing the structure of a Configuration
86
 * class from the concern of registering BeanDefinition objects based on the content of
87
 * that model (with the exception of {@code @ComponentScan} annotations which need to be
88
 * registered immediately).
89
 *
90
 * <p>This ASM-based implementation avoids reflection and eager class loading in order to
91
 * interoperate effectively with lazy class loading in a Framework ApplicationContext.
92
 *
93
 * @author Chris Beams
94
 * @author Juergen Hoeller
95
 * @author Phillip Webb
96
 * @author Sam Brannen
97
 * @author Stephane Nicoll
98
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
99
 * @see ConfigurationClassBeanDefinitionReader
100
 * @since 4.0
101
 */
102
class ConfigurationClassParser {
103

104
  private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = className ->
2✔
105
          className.startsWith("java.lang.annotation.")
7✔
106
                  || className.startsWith("infra.lang.");
5!
107

108
  private static final Predicate<Condition> REGISTER_BEAN_CONDITION_FILTER = condition ->
2✔
109
          condition instanceof ConfigurationCondition configCondition
5✔
110
                  && ConfigurationPhase.REGISTER_BEAN.equals(configCondition.getConfigurationPhase());
9!
111

112
  private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
3✔
113
          (o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.importSelector, o2.importSelector);
7✔
114

115
  private final Logger logger = LoggerFactory.getLogger(getClass());
5✔
116

117
  private final LinkedHashMap<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();
5✔
118

119
  private final MultiValueMap<String, ConfigurationClass> knownSuperclasses = new LinkedMultiValueMap<>();
5✔
120

121
  private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();
6✔
122

123
  private final SourceClass objectSourceClass = new SourceClass(Object.class);
7✔
124

125
  private final BootstrapContext bootstrapContext;
126

127
  @Nullable
128
  private final PropertySourceRegistry propertySourceRegistry;
129

130
  public final ImportRegistry importRegistry = new ImportStack();
6✔
131

132
  /**
133
   * Create a new {@link ConfigurationClassParser} instance that will be used
134
   * to populate the set of configuration classes.
135
   */
136
  public ConfigurationClassParser(BootstrapContext bootstrapContext) {
2✔
137
    this.bootstrapContext = bootstrapContext;
3✔
138
    this.propertySourceRegistry = bootstrapContext.getEnvironment() instanceof ConfigurableEnvironment ce ?
10!
139
            new PropertySourceRegistry(ce, bootstrapContext) : null;
7✔
140
  }
1✔
141

142
  public void parse(Set<BeanDefinitionHolder> configCandidates) {
143
    for (BeanDefinitionHolder holder : configCandidates) {
10✔
144
      BeanDefinition bd = holder.getBeanDefinition();
3✔
145
      try {
146
        ConfigurationClass configClass;
147
        if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
6✔
148
          configClass = parse(annotatedBeanDef, holder.getBeanName());
7✔
149
        }
150
        else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
9!
151
          configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
8✔
152
        }
153
        else {
154
          configClass = parse(bd.getBeanClassName(), holder.getBeanName());
7✔
155
        }
156

157
        // Downgrade to lite (no enhancement) in case of no instance-level @Component methods.
158
        if (!configClass.hasNonStaticComponentMethods()
6✔
159
                && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
2✔
160
                bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
1✔
161
          bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
4✔
162
                  ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
163
        }
164
      }
165
      catch (BeanDefinitionStoreException ex) {
1✔
166
        throw ex;
2✔
167
      }
168
      catch (Throwable ex) {
1✔
169
        throw new BeanDefinitionStoreException(
8✔
170
                "Failed to parse configuration class [%s]".formatted(bd.getBeanClassName()), ex);
6✔
171
      }
1✔
172
    }
1✔
173

174
    deferredImportSelectorHandler.process();
3✔
175
  }
1✔
176

177
  private ConfigurationClass parse(AnnotatedBeanDefinition beanDef, String beanName) {
178
    ConfigurationClass configClass = new ConfigurationClass(
3✔
179
            beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition));
6✔
180
    processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
4✔
181
    return configClass;
2✔
182
  }
183

184
  private ConfigurationClass parse(Class<?> clazz, String beanName) {
185
    ConfigurationClass configClass = new ConfigurationClass(clazz, beanName);
6✔
186
    processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
4✔
187
    return configClass;
2✔
188
  }
189

190
  final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
191
    Assert.notNull(className, "No bean class name for configuration class bean definition");
3✔
192
    MetadataReader reader = bootstrapContext.getMetadataReader(className);
5✔
193
    ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
6✔
194
    processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
4✔
195
    return configClass;
2✔
196
  }
197

198
  /**
199
   * Validate each {@link ConfigurationClass} object.
200
   *
201
   * @see ConfigurationClass#validate
202
   */
203
  public void validate() {
204
    ProblemReporter problemReporter = bootstrapContext.getProblemReporter();
4✔
205
    for (ConfigurationClass configClass : configurationClasses.keySet()) {
12✔
206
      configClass.validate(problemReporter);
3✔
207
    }
1✔
208
  }
1✔
209

210
  public Set<ConfigurationClass> getConfigurationClasses() {
211
    return configurationClasses.keySet();
4✔
212
  }
213

214
  List<PropertySourceDescriptor> getPropertySourceDescriptors() {
215
    return propertySourceRegistry != null ? propertySourceRegistry.getDescriptors()
8!
216
            : Collections.emptyList();
×
217
  }
218

219
  protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) {
220
    if (bootstrapContext.passCondition(configClass.metadata, ConfigurationPhase.PARSE_CONFIGURATION)) {
7✔
221
      ConfigurationClass existingClass = configurationClasses.get(configClass);
6✔
222
      if (existingClass != null) {
2✔
223
        if (configClass.isImported()) {
3✔
224
          if (existingClass.isImported()) {
3✔
225
            existingClass.mergeImportedBy(configClass);
3✔
226
          }
227
          // Otherwise ignore new imported config class; existing non-imported class overrides it.
228
          return;
1✔
229
        }
230
        else if (configClass.scanned) {
3✔
231
          String beanName = configClass.beanName;
3✔
232
          if (StringUtils.isNotEmpty(beanName) && bootstrapContext.containsBeanDefinition(beanName)) {
8!
233
            bootstrapContext.removeBeanDefinition(beanName);
4✔
234
          }
235
          // An implicitly scanned bean definition should not override an explicit import.
236
          return;
1✔
237
        }
238
        else {
239
          // Explicit bean definition found, probably replacing an import.
240
          // Let's remove the old one and go with the new one.
241
          configurationClasses.remove(configClass);
5✔
242
          removeKnownSuperclass(configClass.metadata.getClassName(), false);
6✔
243
        }
244
      }
245

246
      // Recursively process the configuration class and its superclass hierarchy.
247
      SourceClass sourceClass = null;
2✔
248
      try {
249
        sourceClass = asSourceClass(configClass, filter);
5✔
250
        do {
251
          sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
6✔
252
        }
253
        while (sourceClass != null);
2✔
254
      }
255
      catch (IOException ex) {
1✔
256
        throw new BeanDefinitionStoreException(
9✔
257
                "I/O failure while processing configuration class [%s]".formatted(sourceClass), ex);
4✔
258
      }
1✔
259

260
      configurationClasses.put(configClass, configClass);
6✔
261
    }
262
  }
1✔
263

264
  /**
265
   * Apply processing and build a complete {@link ConfigurationClass} by reading the
266
   * annotations, members and methods from the source class. This method can be called
267
   * multiple times as relevant sources are discovered.
268
   *
269
   * @param configClass the configuration class being build
270
   * @param sourceClass a source class
271
   * @return the superclass, or {@code null} if none found or previously processed
272
   */
273
  @Nullable
274
  protected final SourceClass doProcessConfigurationClass(
275
          ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
276

277
    if (configClass.metadata.isAnnotated(Component.class)) {
5✔
278
      // Recursively process any member (nested) classes first
279
      processMemberClasses(configClass, sourceClass, filter);
5✔
280
    }
281

282
    // Process any @PropertySource annotations
283
    Environment environment = bootstrapContext.getEnvironment();
4✔
284
    MergedAnnotations annotations = sourceClass.metadata.getAnnotations();
4✔
285
    for (var propertySource : sourceClass.metadata.getMergedRepeatableAnnotation(
15✔
286
            PropertySource.class, PropertySources.class, true)) {
287
      if (this.propertySourceRegistry != null) {
3!
288
        this.propertySourceRegistry.processPropertySource(propertySource);
5✔
289
      }
290
      else {
291
        logger.info("Ignoring @PropertySource annotation on [{}]. Reason: Environment must implement ConfigurableEnvironment",
×
292
                sourceClass.metadata.getClassName());
×
293
      }
294
    }
1✔
295

296
    // Process any @ComponentScan annotations
297

298
    var componentScans = sourceClass.metadata.getMergedRepeatableAnnotation(ComponentScan.class, ComponentScans.class,
8✔
299
            false, MergedAnnotation::isDirectlyPresent);
300

301
    // Fall back to searching for @ComponentScan meta-annotations (which indirectly
302
    // includes locally declared composed annotations).
303
    if (componentScans.isEmpty()) {
3✔
304
      componentScans = sourceClass.metadata.getMergedRepeatableAnnotation(
8✔
305
              ComponentScan.class, ComponentScans.class, false, MergedAnnotation::isMetaPresent);
306
    }
307

308
    if (!componentScans.isEmpty()) {
3✔
309
      List<Condition> registerBeanConditions = collectRegisterBeanConditions(configClass);
4✔
310
      if (!registerBeanConditions.isEmpty()) {
3✔
311
        throw new ApplicationContextException(
9✔
312
                "Component scan for configuration class [%s] could not be used with conditions in REGISTER_BEAN phase: %s"
313
                        .formatted(configClass.metadata.getClassName(), registerBeanConditions));
9✔
314
      }
315
      for (MergedAnnotation<ComponentScan> componentScan : componentScans) {
10✔
316
        // The config class is annotated with @ComponentScan -> perform the scan immediately
317
        Set<BeanDefinitionHolder> scannedBeanDefinitions = parseComponentScan(componentScan, sourceClass.metadata.getClassName());
7✔
318
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
319
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
10✔
320
          BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
4✔
321
          if (bdCand == null) {
2✔
322
            bdCand = holder.getBeanDefinition();
3✔
323
          }
324
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, bootstrapContext)) {
5✔
325
            parse(bdCand.getBeanClassName(), holder.getBeanName());
7✔
326
          }
327
        }
1✔
328
      }
1✔
329
    }
330

331
    // Process any @Import annotations
332
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
9✔
333

334
    // Process any @ImportResource annotations
335
    MergedAnnotation<ImportResource> importResource = annotations.get(ImportResource.class);
4✔
336
    if (importResource.isPresent()) {
3✔
337
      String[] resources = importResource.getStringArray("locations");
4✔
338
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
4✔
339
      for (String resource : resources) {
16✔
340
        String resolvedResource = environment.resolveRequiredPlaceholders(resource);
4✔
341
        configClass.addImportedResource(resolvedResource, readerClass);
4✔
342
      }
343
    }
344

345
    // Process individual @Component methods
346
    Set<MethodMetadata> beanMethods = retrieveComponentMethodMetadata(sourceClass);
4✔
347
    for (MethodMetadata methodMetadata : beanMethods) {
10✔
348
      configClass.addMethod(new ComponentMethod(methodMetadata, configClass));
7✔
349
    }
1✔
350

351
    // Process default methods on interfaces
352
    processInterfaces(configClass, sourceClass);
4✔
353

354
    // Process superclass, if any
355
    if (sourceClass.metadata.hasSuperClass()) {
4✔
356
      String superclass = sourceClass.metadata.getSuperClassName();
4✔
357
      if (superclass != null && !superclass.startsWith("java")) {
6!
358
        boolean superclassKnown = this.knownSuperclasses.containsKey(superclass);
5✔
359
        this.knownSuperclasses.add(superclass, configClass);
5✔
360
        if (!superclassKnown) {
2✔
361
          // Superclass found, return its annotation metadata and recurse
362
          return sourceClass.getSuperClass();
3✔
363
        }
364
      }
365
    }
366

367
    // No superclass -> processing is complete
368
    return null;
2✔
369
  }
370

371
  /**
372
   * Register member (nested) classes that happen to be configuration classes themselves.
373
   */
374
  private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
375
          throws IOException //
376
  {
377
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
3✔
378
    if (!memberClasses.isEmpty()) {
3✔
379
      ArrayList<SourceClass> candidates = new ArrayList<>(memberClasses.size());
6✔
380
      for (SourceClass memberClass : memberClasses) {
10✔
381
        if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.metadata)
6✔
382
                && !memberClass.metadata.getClassName().equals(configClass.metadata.getClassName())) {
6✔
383
          candidates.add(memberClass);
4✔
384
        }
385
      }
1✔
386
      OrderComparator.sort(candidates);
2✔
387
      for (SourceClass candidate : candidates) {
10✔
388
        if (importRegistry.contains(configClass)) {
5!
389
          bootstrapContext.reportError(new CircularImportProblem(configClass, importRegistry));
×
390
        }
391
        else {
392
          importRegistry.push(configClass);
4✔
393
          try {
394
            processConfigurationClass(candidate.asConfigClass(configClass), filter);
6✔
395
          }
396
          finally {
397
            importRegistry.pop();
4✔
398
          }
399
        }
400
      }
1✔
401
    }
402
  }
1✔
403

404
  /**
405
   * Register default methods on interfaces implemented by the configuration class.
406
   */
407
  private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
408
    for (SourceClass ifc : sourceClass.getInterfaces()) {
11✔
409
      Set<MethodMetadata> beanMethods = retrieveComponentMethodMetadata(ifc);
4✔
410
      for (MethodMetadata methodMetadata : beanMethods) {
10✔
411
        if (!methodMetadata.isAbstract()) {
3!
412
          // A default method or other concrete method on a Java 8+ interface...
413
          configClass.addMethod(new ComponentMethod(methodMetadata, configClass));
7✔
414
        }
415
      }
1✔
416
      processInterfaces(configClass, ifc);
4✔
417
    }
1✔
418
  }
1✔
419

420
  /**
421
   * Retrieve the metadata for all <code>@Component</code> methods.
422
   */
423
  private Set<MethodMetadata> retrieveComponentMethodMetadata(SourceClass sourceClass) {
424
    AnnotationMetadata original = sourceClass.metadata;
3✔
425
    Set<MethodMetadata> componentMethods = original.getAnnotatedMethods(Component.class.getName());
5✔
426
    if (componentMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
7✔
427
      // Try reading the class file via ASM for deterministic declaration order...
428
      // Unfortunately, the JVM's standard reflection returns methods in arbitrary
429
      // order, even between different runs of the same application on the same JVM.
430
      try {
431
        AnnotationMetadata asm = bootstrapContext.getAnnotationMetadata(original.getClassName());
6✔
432
        Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Component.class.getName());
5✔
433
        if (asmMethods.size() >= componentMethods.size()) {
5✔
434
          LinkedHashSet<MethodMetadata> candidateMethods = new LinkedHashSet<>(componentMethods);
5✔
435
          LinkedHashSet<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
6✔
436
          for (MethodMetadata asmMethod : asmMethods) {
10✔
437
            for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext(); ) {
6!
438
              MethodMetadata beanMethod = it.next();
4✔
439
              if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
6✔
440
                selectedMethods.add(beanMethod);
4✔
441
                it.remove();
2✔
442
                break;
1✔
443
              }
444
            }
1✔
445
          }
1✔
446
          if (selectedMethods.size() == componentMethods.size()) {
5!
447
            // All reflection-detected methods found in ASM method set -> proceed
448
            componentMethods = selectedMethods;
2✔
449
          }
450
        }
451
      }
452
      catch (Exception ex) {
×
453
        logger.debug("Failed to read class file via ASM for determining @Component method order", ex);
×
454
        // No worries, let's continue with the reflection metadata we started with...
455
      }
1✔
456
    }
457
    return componentMethods;
2✔
458
  }
459

460
  /**
461
   * Remove known superclasses for the given removed class, potentially replacing
462
   * the superclass exposure on a different config class with the same superclass.
463
   */
464
  private void removeKnownSuperclass(String removedClass, boolean replace) {
465
    String replacedSuperclass = null;
2✔
466
    ConfigurationClass replacingClass = null;
2✔
467

468
    var it = this.knownSuperclasses.entrySet().iterator();
5✔
469
    while (it.hasNext()) {
3✔
470
      Map.Entry<String, List<ConfigurationClass>> entry = it.next();
4✔
471
      if (entry.getValue().removeIf(configClass -> configClass.metadata.getClassName().equals(removedClass))) {
13✔
472
        if (entry.getValue().isEmpty()) {
5✔
473
          it.remove();
3✔
474
        }
475
        else if (replace && replacingClass == null) {
4!
476
          replacedSuperclass = entry.getKey();
4✔
477
          replacingClass = entry.getValue().get(0);
7✔
478
        }
479
      }
480
    }
1✔
481

482
    if (replacingClass != null) {
2✔
483
      try {
484
        SourceClass sourceClass = asSourceClass(replacingClass, DEFAULT_EXCLUSION_FILTER).getSuperClass();
6✔
485
        while (!sourceClass.metadata.getClassName().equals(replacedSuperclass)
6!
486
                && sourceClass.metadata.getSuperClassName() != null) {
×
487
          sourceClass = sourceClass.getSuperClass();
×
488
        }
489
        do {
490
          sourceClass = doProcessConfigurationClass(replacingClass, sourceClass, DEFAULT_EXCLUSION_FILTER);
6✔
491
        }
492
        while (sourceClass != null);
2✔
493
      }
494
      catch (IOException ex) {
×
495
        throw new BeanDefinitionStoreException(
×
496
                "I/O failure while removing configuration class [%s]".formatted(removedClass), ex);
×
497
      }
1✔
498
    }
499
  }
1✔
500

501
  /**
502
   * Returns {@code @Import} class, considering all meta-annotations.
503
   */
504
  private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
505
    Set<SourceClass> imports = new LinkedHashSet<>();
4✔
506
    Set<SourceClass> visited = new LinkedHashSet<>();
4✔
507
    collectImports(sourceClass, imports, visited);
5✔
508
    return imports;
2✔
509
  }
510

511
  /**
512
   * Recursively collect all declared {@code @Import} values. Unlike most
513
   * meta-annotations it is valid to have several {@code @Import}s declared with
514
   * different values; the usual process of returning values from the first
515
   * meta-annotation on a class is not sufficient.
516
   * <p>For example, it is common for a {@code @Configuration} class to declare direct
517
   * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
518
   * annotation.
519
   * <p>As of 5.0, {@code @Import} annotations declared on interfaces
520
   * implemented by the configuration class are also considered. This allows imports to
521
   * be triggered indirectly via marker interfaces or shared base interfaces.
522
   *
523
   * @param sourceClass the class to search
524
   * @param imports the imports collected so far
525
   * @param visited used to track visited classes and interfaces to prevent infinite
526
   * recursion
527
   * @throws IOException if there is any problem reading metadata from the named class
528
   */
529
  private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException {
530
    if (visited.add(sourceClass)) {
4✔
531
      for (SourceClass ifc : sourceClass.getInterfaces()) {
11✔
532
        collectImports(ifc, imports, visited);
5✔
533
      }
1✔
534
      for (SourceClass annotation : sourceClass.getAnnotations()) {
11✔
535
        String annName = annotation.metadata.getClassName();
4✔
536
        if (!annName.equals(Import.class.getName())) {
5✔
537
          collectImports(annotation, imports, visited);
5✔
538
        }
539
      }
1✔
540
      imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
8✔
541
    }
542
  }
1✔
543

544
  private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
545
          Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
546
    if (importCandidates.isEmpty()) {
3✔
547
      return;
1✔
548
    }
549

550
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
6✔
551
      bootstrapContext.reportError(new CircularImportProblem(configClass, importRegistry));
×
552
    }
553
    else {
554
      importRegistry.push(configClass);
4✔
555
      try {
556
        for (SourceClass candidate : importCandidates) {
10✔
557
          if (candidate.isAssignable(ImportSelector.class)) {
4✔
558
            // Candidate class is an ImportSelector -> delegate to it to determine imports
559
            Class<?> candidateClass = candidate.loadClass();
3✔
560
            var selector = bootstrapContext.instantiate(candidateClass, ImportSelector.class);
7✔
561
            Predicate<String> selectorFilter = selector.getExclusionFilter();
3✔
562
            if (selectorFilter != null) {
2✔
563
              exclusionFilter = exclusionFilter.or(selectorFilter);
4✔
564
            }
565
            if (selector instanceof DeferredImportSelector deferredSelector) {
6✔
566
              deferredImportSelectorHandler.handle(configClass, deferredSelector);
6✔
567
            }
568
            else {
569
              String[] importClassNames = selector.selectImports(currentSourceClass.metadata);
5✔
570
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
5✔
571
              processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
7✔
572
            }
573
          }
1✔
574
          else if (candidate.isAssignable(BeanRegistrar.class)) {
4✔
575
            Class<?> candidateClass = candidate.loadClass();
3✔
576
            BeanRegistrar registrar = bootstrapContext.instantiate(candidateClass, BeanRegistrar.class);
7✔
577

578
            AnnotationMetadata metadata = currentSourceClass.metadata;
3✔
579
            if (registrar instanceof ImportAware importAware) {
6✔
580
              importAware.setImportMetadata(metadata);
3✔
581
            }
582

583
            configClass.addBeanRegistrar(metadata.getClassName(), registrar);
5✔
584
          }
1✔
585
          else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
4✔
586
            // Candidate class is an ImportBeanDefinitionRegistrar ->
587
            // delegate to it to register additional bean definitions
588
            Class<?> candidateClass = candidate.loadClass();
3✔
589
            var registrar = bootstrapContext.instantiate(candidateClass, ImportBeanDefinitionRegistrar.class);
7✔
590
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.metadata);
5✔
591
          }
1✔
592
          else {
593
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
594
            // process it as an @Configuration class
595
            importRegistry.registerImport(
7✔
596
                    currentSourceClass.metadata, candidate.metadata.getClassName());
1✔
597
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
6✔
598
          }
599
        }
1✔
600
      }
601
      catch (BeanDefinitionStoreException ex) {
1✔
602
        throw ex;
2✔
603
      }
604
      catch (Throwable ex) {
1✔
605
        throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [%s]: %s"
9✔
606
                .formatted(configClass.metadata.getClassName(), ex.getMessage()), ex);
11✔
607
      }
608
      finally {
609
        importRegistry.pop();
4✔
610
      }
611
    }
612
  }
1✔
613

614
  private boolean isChainedImportOnStack(ConfigurationClass configClass) {
615
    if (importRegistry.contains(configClass)) {
5✔
616
      String configClassName = configClass.metadata.getClassName();
4✔
617
      AnnotationMetadata importingClass = importRegistry.getImportingClassFor(configClassName);
5✔
618
      while (importingClass != null) {
2✔
619
        if (configClassName.equals(importingClass.getClassName())) {
5✔
620
          return true;
2✔
621
        }
622
        importingClass = importRegistry.getImportingClassFor(importingClass.getClassName());
7✔
623
      }
624
    }
625
    return false;
2✔
626
  }
627

628
  /**
629
   * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
630
   */
631
  private SourceClass asSourceClass(ConfigurationClass configurationClass, Predicate<String> filter) throws IOException {
632
    AnnotationMetadata metadata = configurationClass.metadata;
3✔
633
    if (metadata instanceof StandardAnnotationMetadata) {
3✔
634
      return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass(), filter);
7✔
635
    }
636
    return asSourceClass(metadata.getClassName(), filter);
6✔
637
  }
638

639
  /**
640
   * Factory method to obtain a {@link SourceClass} from a {@link Class}.
641
   */
642
  SourceClass asSourceClass(@Nullable Class<?> classType, Predicate<String> filter) throws IOException {
643
    if (classType == null || filter.test(classType.getName())) {
7!
644
      return objectSourceClass;
3✔
645
    }
646
    try {
647
      // Sanity test that we can reflectively read annotations,
648
      // including Class attributes; if not -> fall back to ASM
649
      for (Annotation ann : classType.getDeclaredAnnotations()) {
17✔
650
        AnnotationUtils.validateAnnotation(ann);
2✔
651
      }
652
      return new SourceClass(classType);
6✔
653
    }
654
    catch (Throwable ex) {
1✔
655
      // Enforce ASM via class name resolution
656
      return asSourceClass(classType.getName(), filter);
6✔
657
    }
658
  }
659

660
  /**
661
   * Factory method to obtain a {@link SourceClass} collection from class names.
662
   */
663
  private Collection<SourceClass> asSourceClasses(String[] classNames, Predicate<String> filter) throws IOException {
664
    ArrayList<SourceClass> annotatedClasses = new ArrayList<>(classNames.length);
6✔
665
    for (String className : classNames) {
16✔
666
      SourceClass sourceClass = asSourceClass(className, filter);
5✔
667
      if (this.objectSourceClass != sourceClass) {
4✔
668
        annotatedClasses.add(sourceClass);
4✔
669
      }
670
    }
671
    return annotatedClasses;
2✔
672
  }
673

674
  /**
675
   * Factory method to obtain a {@link SourceClass} from a class name.
676
   */
677
  SourceClass asSourceClass(@Nullable String className, Predicate<String> filter) throws IOException {
678
    if (className == null || filter.test(className)) {
6!
679
      return objectSourceClass;
3✔
680
    }
681
    if (className.startsWith("java")) {
4!
682
      // Never use ASM for core java types
683
      try {
684
        return new SourceClass(ClassUtils.forName(className, bootstrapContext.getClassLoader()));
×
685
      }
686
      catch (ClassNotFoundException ex) {
×
687
        throw new IOException("Failed to load class [%s]".formatted(className), ex);
×
688
      }
689
    }
690
    return new SourceClass(bootstrapContext.getMetadataReader(className));
9✔
691
  }
692

693
  private List<Condition> collectRegisterBeanConditions(ConfigurationClass configurationClass) {
694
    AnnotationMetadata metadata = configurationClass.metadata;
3✔
695
    ConditionEvaluator conditionEvaluator = bootstrapContext.getConditionEvaluator();
4✔
696
    ArrayList<Condition> allConditions = new ArrayList<>(conditionEvaluator.collectConditions(metadata));
7✔
697
    ConfigurationClass enclosingConfigurationClass = getEnclosingConfigurationClass(configurationClass);
4✔
698
    if (enclosingConfigurationClass != null) {
2✔
699
      allConditions.addAll(conditionEvaluator.collectConditions(enclosingConfigurationClass.metadata));
7✔
700
    }
701
    return allConditions.stream().filter(REGISTER_BEAN_CONDITION_FILTER).toList();
6✔
702
  }
703

704
  @Nullable
705
  private ConfigurationClass getEnclosingConfigurationClass(ConfigurationClass configurationClass) {
706
    String enclosingClassName = configurationClass.metadata.getEnclosingClassName();
4✔
707
    if (enclosingClassName != null) {
2✔
708
      return configurationClass.importedBy.stream()
6✔
709
              .filter(candidate -> enclosingClassName.equals(candidate.metadata.getClassName()))
7✔
710
              .findFirst()
2✔
711
              .orElse(null);
2✔
712
    }
713
    return null;
2✔
714
  }
715

716
  /**
717
   * Parse for the @{@link ComponentScan} annotation.
718
   *
719
   * @see ClassPathBeanDefinitionScanner#scan(String...)
720
   * @since 5.0
721
   */
722
  Set<BeanDefinitionHolder> parseComponentScan(MergedAnnotation<ComponentScan> componentScan, String declaringClass) {
723
    var scanner = new ClassPathBeanDefinitionScanner(
4✔
724
            bootstrapContext.getRegistry(), componentScan.getBoolean("useDefaultFilters"),
6✔
725
            bootstrapContext.getEnvironment(), bootstrapContext.getResourceLoader());
6✔
726

727
    var generatorClass = componentScan.<BeanNameGenerator>getClass("nameGenerator");
4✔
728
    scanner.setBeanNameGenerator(BeanNameGenerator.class == generatorClass
5✔
729
            ? bootstrapContext.getBeanNameGenerator()
4✔
730
            : bootstrapContext.instantiate(generatorClass));
5✔
731

732
    var scopedProxyMode = componentScan.getEnum("scopedProxy", ScopedProxyMode.class);
6✔
733
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
3✔
734
      scanner.setScopedProxyMode(scopedProxyMode);
4✔
735
    }
736
    else {
737
      var resolverClass = componentScan.<ScopeMetadataResolver>getClass("scopeResolver");
4✔
738
      if (resolverClass != ScopeMetadataResolver.class) {
3✔
739
        scanner.setScopeMetadataResolver(bootstrapContext.instantiate(resolverClass));
7✔
740
      }
741
    }
742

743
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));
5✔
744

745
    for (var includeFilter : componentScan.getAnnotationArray("includeFilters", ComponentScan.Filter.class)) {
19✔
746
      List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilter, bootstrapContext);
5✔
747
      for (TypeFilter typeFilter : typeFilters) {
10✔
748
        scanner.addIncludeFilter(typeFilter);
3✔
749
      }
1✔
750
    }
751
    for (var excludeFilter : componentScan.getAnnotationArray("excludeFilters", ComponentScan.Filter.class)) {
19✔
752
      List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilter, bootstrapContext);
5✔
753
      for (TypeFilter typeFilter : typeFilters) {
10✔
754
        scanner.addExcludeFilter(typeFilter);
3✔
755
      }
1✔
756
    }
757

758
    boolean lazyInit = componentScan.getBoolean("lazyInit");
4✔
759
    if (lazyInit) {
2✔
760
      scanner.getBeanDefinitionDefaults().setLazyInit(true);
4✔
761
    }
762

763
    LinkedHashSet<String> basePackages = new LinkedHashSet<>();
4✔
764
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
4✔
765
    for (String pkg : basePackagesArray) {
16✔
766
      String[] tokenized = StringUtils.tokenizeToStringArray(
5✔
767
              bootstrapContext.evaluateExpression(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
2✔
768
      Collections.addAll(basePackages, tokenized);
4✔
769
    }
770
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
18✔
771
      basePackages.add(ClassUtils.getPackageName(clazz));
5✔
772
    }
773
    if (basePackages.isEmpty()) {
3✔
774
      basePackages.add(ClassUtils.getPackageName(declaringClass));
5✔
775
    }
776

777
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
20✔
778

779
      @Override
780
      protected boolean matchClassName(String className) {
781
        return declaringClass.equals(className);
5✔
782
      }
783
    });
784

785
    scanner.setMetadataReaderFactory(bootstrapContext.getMetadataReaderFactory());
5✔
786
    return scanner.collectHolders(StringUtils.toStringArray(basePackages));
5✔
787
  }
788

789
  private final class DeferredImportSelectorHandler {
5✔
790

791
    @Nullable
6✔
792
    private ArrayList<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
793

794
    /**
795
     * Handle the specified {@link DeferredImportSelector}. If deferred import
796
     * selectors are being collected, this registers this instance to the list. If
797
     * they are being processed, the {@link DeferredImportSelector} is also processed
798
     * immediately according to its {@link DeferredImportSelector.Group}.
799
     *
800
     * @param configClass the source configuration class
801
     * @param importSelector the selector to handle
802
     */
803
    public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
804
      DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
6✔
805
      if (deferredImportSelectors == null) {
3✔
806
        DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
6✔
807
        handler.register(holder);
3✔
808
        handler.processGroupImports();
2✔
809
      }
1✔
810
      else {
811
        deferredImportSelectors.add(holder);
5✔
812
      }
813
    }
1✔
814

815
    public void process() {
816
      List<DeferredImportSelectorHolder> deferredImports = deferredImportSelectors;
3✔
817
      this.deferredImportSelectors = null;
3✔
818
      try {
819
        if (deferredImports != null) {
2!
820
          var handler = new DeferredImportSelectorGroupingHandler();
6✔
821
          deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
3✔
822

823
          for (DeferredImportSelectorHolder deferredImport : deferredImports) {
10✔
824
            handler.register(deferredImport);
3✔
825
          }
1✔
826

827
          handler.processGroupImports();
2✔
828
        }
829
      }
830
      finally {
831
        this.deferredImportSelectors = new ArrayList<>();
5✔
832
      }
833
    }
1✔
834
  }
835

836
  private final class DeferredImportSelectorGroupingHandler {
5✔
837

838
    private final HashMap<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
5✔
839

840
    private final LinkedHashMap<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
6✔
841

842
    public void register(DeferredImportSelectorHolder deferredImport) {
843
      var group = deferredImport.importSelector.getImportGroup();
4✔
844
      var grouping = groupings.computeIfAbsent(
5✔
845
              group != null ? group : deferredImport,
8✔
846
              key -> new DeferredImportSelectorGrouping(createGroup(group))
7✔
847
      );
848
      grouping.add(deferredImport);
3✔
849
      configurationClasses.put(deferredImport.configurationClass.metadata,
9✔
850
              deferredImport.configurationClass);
851
    }
1✔
852

853
    @SuppressWarnings("NullAway")
854
    public void processGroupImports() {
855
      for (DeferredImportSelectorGrouping grouping : groupings.values()) {
12✔
856
        Predicate<String> exclusionFilter = grouping.getCandidateFilter();
3✔
857
        for (Group.Entry entry : grouping.getImports()) {
11✔
858
          ConfigurationClass configurationClass = configurationClasses.get(entry.metadata());
7✔
859
          try {
860
            processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
12✔
861
                    Collections.singleton(asSourceClass(entry.importClassName(), exclusionFilter)),
6✔
862
                    exclusionFilter, false);
863
          }
864
          catch (BeanDefinitionStoreException ex) {
×
865
            throw ex;
×
866
          }
867
          catch (Throwable ex) {
×
868
            throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [%s]"
×
869
                    .formatted(configurationClass.metadata.getClassName()), ex);
×
870
          }
1✔
871
        }
1✔
872
      }
1✔
873
    }
1✔
874

875
    private Group createGroup(@Nullable Class<? extends Group> type) {
876
      Class<? extends Group> effectiveType = type != null ? type : DefaultDeferredImportSelectorGroup.class;
6✔
877
      return bootstrapContext.instantiate(effectiveType, Group.class);
8✔
878
    }
879
  }
880

881
  private static final class DeferredImportSelectorHolder {
882

883
    public final ConfigurationClass configurationClass;
884

885
    public final DeferredImportSelector importSelector;
886

887
    public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) {
2✔
888
      this.configurationClass = configClass;
3✔
889
      this.importSelector = selector;
3✔
890
    }
1✔
891
  }
892

893
  private static final class DeferredImportSelectorGrouping {
894

895
    private final DeferredImportSelector.Group group;
896

897
    private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
5✔
898

899
    DeferredImportSelectorGrouping(Group group) {
2✔
900
      this.group = group;
3✔
901
    }
1✔
902

903
    public void add(DeferredImportSelectorHolder deferredImport) {
904
      this.deferredImports.add(deferredImport);
5✔
905
    }
1✔
906

907
    /**
908
     * Return the imports defined by the group.
909
     *
910
     * @return each import with its associated configuration class
911
     */
912
    public Iterable<Group.Entry> getImports() {
913
      for (DeferredImportSelectorHolder deferredImport : deferredImports) {
11✔
914
        group.process(deferredImport.configurationClass.metadata, deferredImport.importSelector);
8✔
915
      }
1✔
916
      return group.selectImports();
4✔
917
    }
918

919
    public Predicate<String> getCandidateFilter() {
920
      Predicate<String> mergedFilter = DEFAULT_EXCLUSION_FILTER;
2✔
921
      for (DeferredImportSelectorHolder deferredImport : deferredImports) {
11✔
922
        Predicate<String> selectorFilter = deferredImport.importSelector.getExclusionFilter();
4✔
923
        if (selectorFilter != null) {
2✔
924
          mergedFilter = mergedFilter.or(selectorFilter);
4✔
925
        }
926
      }
1✔
927
      return mergedFilter;
2✔
928
    }
929
  }
930

931
  private static final class DefaultDeferredImportSelectorGroup implements Group {
2✔
932

933
    private final ArrayList<Entry> imports = new ArrayList<>();
6✔
934

935
    @Override
936
    public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
937
      for (String importClassName : selector.selectImports(metadata)) {
18✔
938
        imports.add(new Entry(metadata, importClassName));
9✔
939
      }
940
    }
1✔
941

942
    @Override
943
    public Iterable<Entry> selectImports() {
944
      return imports;
3✔
945
    }
946
  }
947

948
  /**
949
   * Simple wrapper that allows annotated source classes to be dealt with
950
   * in a uniform manner, regardless of how they are loaded.
951
   */
952
  private final class SourceClass implements Ordered {
953

954
    public final Object source;  // Class or MetadataReader
955

956
    public final AnnotationMetadata metadata;
957

958
    public SourceClass(Object source) {
5✔
959
      this.source = source;
3✔
960
      if (source instanceof Class) {
3✔
961
        this.metadata = AnnotationMetadata.introspect((Class<?>) source);
6✔
962
      }
963
      else {
964
        this.metadata = ((MetadataReader) source).getAnnotationMetadata();
5✔
965
      }
966
    }
1✔
967

968
    @Override
969
    public int getOrder() {
970
      Integer order = ConfigurationClassUtils.getOrder(metadata);
4✔
971
      return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
7✔
972
    }
973

974
    public Class<?> loadClass() throws ClassNotFoundException {
975
      if (source instanceof Class) {
4✔
976
        return (Class<?>) source;
4✔
977
      }
978
      String className = ((MetadataReader) source).getClassMetadata().getClassName();
6✔
979
      return ClassUtils.forName(className, bootstrapContext.getClassLoader());
7✔
980
    }
981

982
    public boolean isAssignable(Class<?> clazz) throws IOException {
983
      if (source instanceof Class) {
4✔
984
        return clazz.isAssignableFrom((Class<?>) source);
6✔
985
      }
986
      return new AssignableTypeFilter(clazz)
11✔
987
              .match((MetadataReader) source, bootstrapContext.getMetadataReaderFactory());
2✔
988
    }
989

990
    public ConfigurationClass asConfigClass(ConfigurationClass importedBy) {
991
      if (source instanceof Class) {
4✔
992
        return new ConfigurationClass((Class<?>) source, importedBy);
8✔
993
      }
994
      return new ConfigurationClass((MetadataReader) source, importedBy);
8✔
995
    }
996

997
    public Collection<SourceClass> getMemberClasses() throws IOException {
998
      Object sourceToProcess = this.source;
3✔
999
      if (sourceToProcess instanceof Class<?> sourceClass) {
6✔
1000
        try {
1001
          Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
3✔
1002
          ArrayList<SourceClass> members = new ArrayList<>(declaredClasses.length);
6✔
1003
          for (Class<?> declaredClass : declaredClasses) {
16✔
1004
            members.add(asSourceClass(declaredClass, DEFAULT_EXCLUSION_FILTER));
8✔
1005
          }
1006
          return members;
2✔
1007
        }
1008
        catch (NoClassDefFoundError err) {
×
1009
          // getDeclaredClasses() failed because of non-resolvable dependencies
1010
          // -> fall back to ASM below
1011
          sourceToProcess = bootstrapContext.getMetadataReader(sourceClass.getName());
×
1012
        }
1013
      }
1014

1015
      // ASM-based resolution - safe for non-resolvable classes as well
1016
      MetadataReader sourceReader = (MetadataReader) sourceToProcess;
3✔
1017
      String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames();
4✔
1018
      ArrayList<SourceClass> members = new ArrayList<>(memberClassNames.length);
6✔
1019
      for (String memberClassName : memberClassNames) {
16✔
1020
        try {
1021
          members.add(asSourceClass(memberClassName, DEFAULT_EXCLUSION_FILTER));
8✔
1022
        }
1023
        catch (IOException ex) {
×
1024
          // Let's skip it if it's not resolvable - we're just looking for candidates
1025
          logger.debug("Failed to resolve member class [{}] - not considering it as a configuration class candidate",
×
1026
                  memberClassName);
1027
        }
1✔
1028
      }
1029
      return members;
2✔
1030
    }
1031

1032
    public SourceClass getSuperClass() throws IOException {
1033
      if (source instanceof Class) {
4✔
1034
        return asSourceClass(((Class<?>) source).getSuperclass(), DEFAULT_EXCLUSION_FILTER);
9✔
1035
      }
1036
      return asSourceClass(
7✔
1037
              ((MetadataReader) source).getClassMetadata().getSuperClassName(), DEFAULT_EXCLUSION_FILTER);
3✔
1038
    }
1039

1040
    public LinkedHashSet<SourceClass> getInterfaces() throws IOException {
1041
      LinkedHashSet<SourceClass> result = new LinkedHashSet<>();
4✔
1042
      if (source instanceof Class<?> sourceClass) {
9✔
1043
        for (Class<?> ifcClass : sourceClass.getInterfaces()) {
18✔
1044
          result.add(asSourceClass(ifcClass, DEFAULT_EXCLUSION_FILTER));
8✔
1045
        }
1046
      }
1047
      else {
1048
        for (String className : metadata.getInterfaceNames()) {
18✔
1049
          result.add(asSourceClass(className, DEFAULT_EXCLUSION_FILTER));
8✔
1050
        }
1051
      }
1052
      return result;
2✔
1053
    }
1054

1055
    public Set<SourceClass> getAnnotations() {
1056
      LinkedHashSet<SourceClass> result = new LinkedHashSet<>();
4✔
1057
      if (source instanceof Class<?> sourceClass) {
9✔
1058
        for (Annotation ann : sourceClass.getDeclaredAnnotations()) {
18✔
1059
          Class<?> annType = ann.annotationType();
3✔
1060
          if (!annType.getName().startsWith("java")) {
5✔
1061
            try {
1062
              result.add(asSourceClass(annType, DEFAULT_EXCLUSION_FILTER));
8✔
1063
            }
1064
            catch (Throwable ex) {
×
1065
              // An annotation not present on the classpath is being ignored
1066
              // by the JVM's class loading -> ignore here as well.
1067
            }
1✔
1068
          }
1069
        }
1070
      }
1071
      else {
1072
        for (String className : metadata.getAnnotationTypes()) {
12✔
1073
          if (!className.startsWith("java")) {
4!
1074
            try {
1075
              result.add(getRelated(className));
6✔
1076
            }
1077
            catch (Throwable ex) {
×
1078
              // An annotation not present on the classpath is being ignored
1079
              // by the JVM's class loading -> ignore here as well.
1080
            }
1✔
1081
          }
1082
        }
1✔
1083
      }
1084
      return result;
2✔
1085
    }
1086

1087
    public Collection<SourceClass> getAnnotationAttributes(String annType, String attribute) throws IOException {
1088
      MergedAnnotation<Annotation> annotation = metadata.getAnnotation(annType);
5✔
1089
      if (annotation.isPresent()) {
3✔
1090
        String[] classNames = annotation.getValue(attribute, String[].class);
6✔
1091
        if (classNames != null) {
2!
1092
          LinkedHashSet<SourceClass> result = new LinkedHashSet<>();
4✔
1093
          for (String className : classNames) {
16✔
1094
            result.add(getRelated(className));
6✔
1095
          }
1096
          return result;
2✔
1097
        }
1098
      }
1099
      return Collections.emptySet();
2✔
1100
    }
1101

1102
    private SourceClass getRelated(String className) throws IOException {
1103
      if (source instanceof Class) {
4✔
1104
        try {
1105
          Class<?> clazz = ClassUtils.forName(className, ((Class<?>) source).getClassLoader());
7✔
1106
          return asSourceClass(clazz, DEFAULT_EXCLUSION_FILTER);
6✔
1107
        }
1108
        catch (ClassNotFoundException ex) {
×
1109
          // Ignore -> fall back to ASM next, except for core java types.
1110
          if (className.startsWith("java")) {
×
1111
            throw new IOException("Failed to load class [%s]".formatted(className), ex);
×
1112
          }
1113
          return new SourceClass(bootstrapContext.getMetadataReader(className));
×
1114
        }
1115
      }
1116
      return asSourceClass(className, DEFAULT_EXCLUSION_FILTER);
6✔
1117
    }
1118

1119
    @Override
1120
    public boolean equals(@Nullable Object other) {
1121
      return this == other
9!
1122
              || (other instanceof SourceClass && metadata.getClassName().equals(((SourceClass) other).metadata.getClassName()));
9!
1123
    }
1124

1125
    @Override
1126
    public int hashCode() {
1127
      return metadata.getClassName().hashCode();
5✔
1128
    }
1129

1130
    @Override
1131
    public String toString() {
1132
      return metadata.getClassName();
4✔
1133
    }
1134
  }
1135

1136
  /**
1137
   * {@link Problem} registered upon detection of a circular {@link Import}.
1138
   */
1139
  private static final class CircularImportProblem extends Problem {
1140

1141
    public CircularImportProblem(ConfigurationClass attemptedImport, Deque<ConfigurationClass> importStack) {
1142
      super(String.format("A circular @Import has been detected: " +
12✔
1143
                              "Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
1144
                              "already present in the current import stack %s", importStack.element().getSimpleName(),
7✔
1145
                      attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack),
11✔
1146
              new Location(importStack.element().resource, attemptedImport.metadata));
6✔
1147
    }
1✔
1148
  }
1149

1150
  final class ImportStack extends ImportRegistry {
6✔
1151

1152
    @Override
1153
    public void removeImportingClass(String importingClass) {
1154
      super.removeImportingClass(importingClass);
3✔
1155
      removeKnownSuperclass(importingClass, true);
5✔
1156
    }
1✔
1157

1158
  }
1159

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

© 2026 Coveralls, Inc