• 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

93.81
today-context/src/main/java/infra/context/annotation/ConfigurationClass.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.util.HashSet;
23
import java.util.LinkedHashMap;
24
import java.util.LinkedHashSet;
25

26
import infra.beans.factory.BeanRegistrar;
27
import infra.beans.factory.parsing.Location;
28
import infra.beans.factory.parsing.Problem;
29
import infra.beans.factory.parsing.ProblemReporter;
30
import infra.beans.factory.support.BeanDefinitionReader;
31
import infra.core.io.DescriptiveResource;
32
import infra.core.io.Resource;
33
import infra.core.type.AnnotationMetadata;
34
import infra.core.type.MethodMetadata;
35
import infra.core.type.classreading.MetadataReader;
36
import infra.lang.Assert;
37
import infra.stereotype.Component;
38
import infra.util.ClassUtils;
39

40
/**
41
 * Represents a user-defined {@link Configuration @Configuration} class.
42
 * <p>Includes a set of {@link Component} methods, including all such methods
43
 * defined in the ancestry of the class, in a 'flattened-out' manner.
44
 *
45
 * @author Chris Beams
46
 * @author Juergen Hoeller
47
 * @author Phillip Webb
48
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
49
 * @see ComponentMethod
50
 * @see ConfigurationClassParser
51
 * @since 4.0
52
 */
53
final class ConfigurationClass {
54

55
  public final AnnotationMetadata metadata;
56

57
  public final Resource resource;
58

59
  @Nullable
60
  public String beanName;
61

62
  /**
63
   * whether this configuration class has been registered through a scan.
64
   */
65
  public boolean scanned = false;
15✔
66

67
  /**
68
   * Return the configuration classes that imported this class,
69
   * or an empty Set if this configuration was not imported.
70
   *
71
   * @see #isImported()
72
   */
73
  public final LinkedHashSet<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
30✔
74

75
  public final LinkedHashSet<ComponentMethod> componentMethods = new LinkedHashSet<>();
25✔
76

77
  public final LinkedHashMap<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>();
25✔
78

79
  public final LinkedHashMap<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
25✔
80

81
  public final HashSet<String> skippedComponentMethods = new HashSet<>();
25✔
82

83
  public final LinkedHashMap<String, BeanRegistrar> beanRegistrars = new LinkedHashMap<>();
25✔
84

85
  /**
86
   * Create a new {@link ConfigurationClass} with the given name.
87
   *
88
   * @param metadataReader reader used to parse the underlying {@link Class}
89
   * @param beanName must not be {@code null}
90
   * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
91
   */
92
  ConfigurationClass(MetadataReader metadataReader, @Nullable String beanName) {
2✔
93
    this.metadata = metadataReader.getAnnotationMetadata();
4✔
94
    this.resource = metadataReader.getResource();
4✔
95
    this.beanName = beanName;
3✔
96
  }
1✔
97

98
  /**
99
   * Create a new {@link ConfigurationClass} representing a class that was imported
100
   * using the {@link Import} annotation or automatically processed as a nested
101
   * configuration class (if importedBy is not {@code null}).
102
   *
103
   * @param metadataReader reader used to parse the underlying {@link Class}
104
   * @param importedBy the configuration class importing this one or {@code null}
105
   */
106
  ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
2✔
107
    this.metadata = metadataReader.getAnnotationMetadata();
4✔
108
    this.resource = metadataReader.getResource();
4✔
109
    this.importedBy.add(importedBy);
5✔
110
  }
1✔
111

112
  /**
113
   * Create a new {@link ConfigurationClass} with the given name.
114
   *
115
   * @param clazz the underlying {@link Class} to represent
116
   * @param beanName name of the {@code @Configuration} class bean
117
   * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
118
   */
119
  ConfigurationClass(Class<?> clazz, @Nullable String beanName) {
2✔
120
    this.metadata = AnnotationMetadata.introspect(clazz);
4✔
121
    this.resource = new DescriptiveResource(clazz.getName());
7✔
122
    this.beanName = beanName;
3✔
123
  }
1✔
124

125
  /**
126
   * Create a new {@link ConfigurationClass} representing a class that was imported
127
   * using the {@link Import} annotation or automatically processed as a nested
128
   * configuration class (if imported is {@code true}).
129
   *
130
   * @param clazz the underlying {@link Class} to represent
131
   * @param importedBy the configuration class importing this one (or {@code null})
132
   */
133
  ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
2✔
134
    this.metadata = AnnotationMetadata.introspect(clazz);
4✔
135
    this.resource = new DescriptiveResource(clazz.getName());
7✔
136
    this.importedBy.add(importedBy);
5✔
137
  }
1✔
138

139
  /**
140
   * Create a new {@link ConfigurationClass} with the given name.
141
   *
142
   * @param metadata the metadata for the underlying class to represent
143
   * @param beanName name of the {@code @Configuration} class bean
144
   * @param scanned whether the underlying class has been registered through a scan
145
   */
146
  ConfigurationClass(AnnotationMetadata metadata, String beanName, boolean scanned) {
2✔
147
    Assert.notNull(beanName, "Bean name is required");
3✔
148
    this.metadata = metadata;
3✔
149
    this.resource = new DescriptiveResource(metadata.getClassName());
7✔
150
    this.beanName = beanName;
3✔
151
    this.scanned = scanned;
3✔
152
  }
1✔
153

154
  String getSimpleName() {
155
    return ClassUtils.getShortName(metadata.getClassName());
5✔
156
  }
157

158
  void setBeanName(@Nullable String beanName) {
159
    this.beanName = beanName;
3✔
160
  }
1✔
161

162
  /**
163
   * Return whether this configuration class was registered via @{@link Import} or
164
   * automatically registered due to being nested within another configuration class.
165
   *
166
   * @see #importedBy
167
   */
168
  public boolean isImported() {
169
    return !this.importedBy.isEmpty();
8✔
170
  }
171

172
  boolean hasNonStaticComponentMethods() {
173
    for (var beanMethod : this.componentMethods) {
11✔
174
      if (!beanMethod.metadata.isStatic()) {
4✔
175
        return true;
2✔
176
      }
177
    }
1✔
178
    return false;
2✔
179
  }
180

181
  /**
182
   * Merge the imported-by declarations from the given configuration class into this one.
183
   */
184
  void mergeImportedBy(ConfigurationClass otherConfigClass) {
185
    this.importedBy.addAll(otherConfigClass.importedBy);
6✔
186
  }
1✔
187

188
  void addMethod(ComponentMethod method) {
189
    this.componentMethods.add(method);
5✔
190
  }
1✔
191

192
  void addBeanRegistrar(String sourceClassName, BeanRegistrar beanRegistrar) {
193
    this.beanRegistrars.put(sourceClassName, beanRegistrar);
6✔
194
  }
1✔
195

196
  void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
197
    this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
6✔
198
  }
1✔
199

200
  void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
201
    this.importedResources.put(importedResource, readerClass);
6✔
202
  }
1✔
203

204
  void validate(ProblemReporter problemReporter) {
205
    // A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
206
    var annotation = metadata.getAnnotation(Configuration.class);
5✔
207
    if (metadata.isFinal() && annotation.isPresent() && hasNonStaticComponentMethods()
12✔
208
            && annotation.getBoolean("proxyBeanMethods")) {
2✔
209
      problemReporter.error(new FinalConfigurationProblem());
×
210
    }
211

212
    for (ComponentMethod componentMethod : componentMethods) {
11✔
213
      componentMethod.validate(problemReporter);
3✔
214
    }
1✔
215

216
    // A configuration class may not contain overloaded bean methods unless it declares enforceUniqueMethods=false
217
    if (annotation.isPresent() && annotation.getBoolean("enforceUniqueMethods")) {
7✔
218
      LinkedHashMap<String, MethodMetadata> beanMethodsByName = new LinkedHashMap<>();
4✔
219
      for (ComponentMethod beanMethod : componentMethods) {
11✔
220
        MethodMetadata current = beanMethod.metadata;
3✔
221
        MethodMetadata existing = beanMethodsByName.put(current.getMethodName(), current);
7✔
222
        if (existing != null && existing.getDeclaringClassName().equals(current.getDeclaringClassName())) {
8!
223
          problemReporter.error(new BeanMethodOverloadingProblem(existing.getMethodName()));
×
224
        }
225
      }
1✔
226
    }
227
  }
1✔
228

229
  @Override
230
  public boolean equals(@Nullable Object other) {
231
    return (this == other || (other instanceof ConfigurationClass &&
9✔
232
            metadata.getClassName().equals(((ConfigurationClass) other).metadata.getClassName())));
10✔
233
  }
234

235
  @Override
236
  public int hashCode() {
237
    return metadata.getClassName().hashCode();
5✔
238
  }
239

240
  @Override
241
  public String toString() {
242
    return "ConfigurationClass: beanName '%s', %s".formatted(this.beanName, this.resource);
15✔
243
  }
244

245
  /**
246
   * Configuration classes must be non-final to accommodate CGLIB subclassing.
247
   */
248
  private class FinalConfigurationProblem extends Problem {
249

250
    FinalConfigurationProblem() {
3✔
251
      super(String.format("@Configuration class '%s' may not be final, when proxyBeanMethods is enabled. Remove the final modifier to continue.",
16✔
252
              getSimpleName()), new Location(resource, metadata));
2✔
253
    }
1✔
254
  }
255

256
  /**
257
   * Configuration classes are not allowed to contain overloaded bean methods
258
   * by default
259
   */
260
  private class BeanMethodOverloadingProblem extends Problem {
261

262
    BeanMethodOverloadingProblem(String methodName) {
×
263
      super(String.format("@Configuration class '%s' contains overloaded @Bean methods with name '%s'. Use " +
×
264
                      "unique method names for separate bean definitions (with individual conditions etc) " +
265
                      "or switch '@Configuration.enforceUniqueMethods' to 'false'.",
266
              getSimpleName(), methodName), new Location(resource, metadata));
×
267
    }
×
268
  }
269

270
}
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