/**
  * Recursively collect all declared {@code @Import} values. Unlike most meta-annotations it is
  * valid to have several {@code @Import}s declared with different values; the usual process of
  * returning values from the first meta-annotation on a class is not sufficient.
  *
  * <p>For example, it is common for a {@code @Configuration} class to declare direct
  * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} annotation.
  *
  * @param sourceClass the class to search
  * @param imports the imports collected so far
  * @param visited used to track visited classes to prevent infinite recursion
  * @throws IOException if there is any problem reading metadata from the named class
  */
 private void collectImports(
     SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
     throws IOException {
   if (visited.add(sourceClass)) {
     for (SourceClass annotation : sourceClass.getAnnotations()) {
       String annName = annotation.getMetadata().getClassName();
       if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
         collectImports(annotation, imports, visited);
       }
     }
     imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
   }
 }
 /**
  * Register member (nested) classes that happen to be configuration classes themselves.
  *
  * @param sourceClass the source class to process
  * @throws IOException if there is any problem reading metadata from a member class
  */
 private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass)
     throws IOException {
   for (SourceClass memberClass : sourceClass.getMemberClasses()) {
     if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())
         && !memberClass
             .getMetadata()
             .getClassName()
             .equals(configClass.getMetadata().getClassName())) {
       if (this.importStack.contains(configClass)) {
         this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
       } else {
         this.importStack.push(configClass);
         try {
           processConfigurationClass(memberClass.asConfigClass(configClass));
         } finally {
           this.importStack.pop();
         }
       }
     }
   }
 }
  private void processImports(
      ConfigurationClass configClass,
      SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates,
      boolean checkForCircularImports)
      throws IOException {

    if (importCandidates.isEmpty()) {
      return;
    }

    if (checkForCircularImports && this.importStack.contains(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
      this.importStack.push(configClass);
      try {
        for (SourceClass candidate : importCandidates) {
          if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            ImportSelector selector =
                BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
            invokeAwareMethods(selector);
            if (this.deferredImportSelectors != null
                && selector instanceof DeferredImportSelector) {
              this.deferredImportSelectors.add(
                  new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
            } else {
              String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
              processImports(configClass, currentSourceClass, importSourceClasses, false);
            }
          } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // Candidate class is an ImportBeanDefinitionRegistrar ->
            // delegate to it to register additional bean definitions
            Class<?> candidateClass = candidate.loadClass();
            ImportBeanDefinitionRegistrar registrar =
                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
            invokeAwareMethods(registrar);
            configClass.addImportBeanDefinitionRegistrar(
                registrar, currentSourceClass.getMetadata());
          } else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            this.importStack.registerImport(
                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass));
          }
        }
      } catch (BeanDefinitionStoreException ex) {
        throw ex;
      } catch (Exception ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class ["
                + configClass.getMetadata().getClassName()
                + "]",
            ex);
      } finally {
        this.importStack.pop();
      }
    }
  }
  /**
   * Apply processing and build a complete {@link ConfigurationClass} by reading the annotations,
   * members and methods from the source class. This method can be called multiple times as relevant
   * sources are discovered.
   *
   * @param configClass the configuration class being build
   * @param sourceClass a source class
   * @return the superclass, or {@code null} if none found or previously processed
   */
  protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    // Recursively process any member (nested) classes first
    processMemberClasses(configClass, sourceClass);

    // Process any @PropertySource annotations
    for (AnnotationAttributes propertySource :
        AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(),
            PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
        processPropertySource(propertySource);
      } else {
        logger.warn(
            "Ignoring @PropertySource annotation on ["
                + sourceClass.getMetadata().getClassName()
                + "]. Reason: Environment must implement ConfigurableEnvironment");
      }
    }

    // Process any @ComponentScan annotations
    Set<AnnotationAttributes> componentScans =
        AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty()
        && !this.conditionEvaluator.shouldSkip(
            sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
            this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively
        // if necessary
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(
              holder.getBeanDefinition(), this.metadataReaderFactory)) {
            parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
          }
        }
      }
    }

    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // Process any @ImportResource annotations
    if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
      AnnotationAttributes importResource =
          AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
      String[] resources =
          importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
        String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
        configClass.addImportedResource(resolvedResource, readerClass);
      }
    }

    // Process individual @Bean methods
    Set<MethodMetadata> beanMethods =
        sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
    for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // Process default methods on interfaces
    for (SourceClass ifc : sourceClass.getInterfaces()) {
      beanMethods = ifc.getMetadata().getAnnotatedMethods(Bean.class.getName());
      for (MethodMetadata methodMetadata : beanMethods) {
        if (!methodMetadata.isAbstract()) {
          // A default method or other concrete method on a Java 8+ interface...
          configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }
      }
    }

    // Process superclass, if any
    if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
        this.knownSuperclasses.put(superclass, configClass);
        // Superclass found, return its annotation metadata and recurse
        return sourceClass.getSuperClass();
      }
    }

    // No superclass -> processing is complete
    return null;
  }