Skip to content

Commit 33d5b01

Browse files
committed
Reduce code duplication in ContextLoaderUtils
Prior to this commit, the following two methods in ContextLoaderUtils contained almost identical loops for traversing the test class hierarchy: - resolveContextLoaderClass(Class<?>, String) - resolveContextConfigurationAttributes(Class<?>) With this commit, resolveContextLoaderClass() no longer traverses the class hierarchy. Instead, it now works directly with the resolved list of ContextConfigurationAttributes, thereby removing code duplication. Issue: SPR-9918
1 parent 397d20b commit 33d5b01

File tree

2 files changed

+90
-103
lines changed

2 files changed

+90
-103
lines changed

spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java

Lines changed: 85 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24-
import java.util.Collections;
2524
import java.util.HashSet;
2625
import java.util.List;
2726
import java.util.Set;
@@ -64,58 +63,70 @@ private ContextLoaderUtils() {
6463
}
6564

6665
/**
67-
* Resolve the {@link ContextLoader} {@link Class class} to use for the
68-
* supplied {@link Class testClass} and then instantiate and return that
69-
* {@code ContextLoader}.
66+
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
67+
* supplied list of {@link ContextConfigurationAttributes} and then
68+
* instantiate and return that {@code ContextLoader}.
7069
*
7170
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
72-
* <code>null</code> or <em>empty</em>, the <em>standard</em>
73-
* default context loader class name {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
74-
* will be used. For details on the class resolution process, see
75-
* {@link #resolveContextLoaderClass()}.
71+
* {@code null} or <em>empty</em>, depending on the absence or presence
72+
* of @{@link WebAppConfiguration} either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
73+
* or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
74+
* default context loader class name. For details on the class resolution
75+
* process, see {@link #resolveContextLoaderClass()}.
7676
*
7777
* @param testClass the test class for which the {@code ContextLoader}
78-
* should be resolved (must not be <code>null</code>)
78+
* should be resolved; must not be {@code null}
79+
* @param configAttributesList the list of configuration attributes to process;
80+
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
81+
* (i.e., as if we were traversing up the class hierarchy)
7982
* @param defaultContextLoaderClassName the name of the default
80-
* {@code ContextLoader} class to use (may be <code>null</code>)
83+
* {@code ContextLoader} class to use; may be {@code null} or <em>empty</em>
8184
* @return the resolved {@code ContextLoader} for the supplied
82-
* <code>testClass</code> (never <code>null</code>)
85+
* <code>testClass</code> (never {@code null})
8386
* @see #resolveContextLoaderClass()
8487
*/
85-
static ContextLoader resolveContextLoader(Class<?> testClass, String defaultContextLoaderClassName) {
86-
Assert.notNull(testClass, "Test class must not be null");
88+
static ContextLoader resolveContextLoader(Class<?> testClass,
89+
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
90+
Assert.notNull(testClass, "Class must not be null");
91+
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
8792

8893
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
8994
defaultContextLoaderClassName = testClass.isAnnotationPresent(WebAppConfiguration.class) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
9095
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
9196
}
9297

93-
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass,
98+
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList,
9499
defaultContextLoaderClassName);
95100

96101
return instantiateClass(contextLoaderClass, ContextLoader.class);
97102
}
98103

99104
/**
100-
* Resolve the {@link ContextLoader} {@link Class} to use for the supplied
101-
* {@link Class testClass}.
105+
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
106+
* supplied list of {@link ContextConfigurationAttributes}.
107+
*
108+
* <p>Beginning with the first level in the context configuration attributes
109+
* hierarchy:
102110
*
103111
* <ol>
104-
* <li>If the {@link ContextConfiguration#loader() loader} attribute of
105-
* {@link ContextConfiguration &#064;ContextConfiguration} is configured
106-
* with an explicit class, that class will be returned.</li>
107-
* <li>If a <code>loader</code> class is not specified, the class hierarchy
108-
* will be traversed to find a parent class annotated with
109-
* {@code @ContextConfiguration}; go to step #1.</li>
110-
* <li>If no explicit <code>loader</code> class is found after traversing
111-
* the class hierarchy, an attempt will be made to load and return the class
112+
* <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
113+
* contextLoaderClass} property of {@link ContextConfigurationAttributes} is
114+
* configured with an explicit class, that class will be returned.</li>
115+
* <li>If an explicit {@code ContextLoader} class is not specified at the
116+
* current level in the hierarchy, traverse to the next level in the hierarchy
117+
* and return to step #1.</li>
118+
* <li>If no explicit {@code ContextLoader} class is found after traversing
119+
* the hierarchy, an attempt will be made to load and return the class
112120
* with the supplied <code>defaultContextLoaderClassName</code>.</li>
113121
* </ol>
114122
*
115123
* @param testClass the class for which to resolve the {@code ContextLoader}
116-
* class; must not be <code>null</code>
124+
* class; must not be {@code null}; only used for logging purposes
125+
* @param configAttributesList the list of configuration attributes to process;
126+
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
127+
* (i.e., as if we were traversing up the class hierarchy)
117128
* @param defaultContextLoaderClassName the name of the default
118-
* {@code ContextLoader} class to use; must not be <code>null</code> or empty
129+
* {@code ContextLoader} class to use; must not be {@code null} or empty
119130
* @return the {@code ContextLoader} class to use for the supplied test class
120131
* @throws IllegalArgumentException if {@code @ContextConfiguration} is not
121132
* <em>present</em> on the supplied test class
@@ -124,46 +135,37 @@ static ContextLoader resolveContextLoader(Class<?> testClass, String defaultCont
124135
*/
125136
@SuppressWarnings("unchecked")
126137
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass,
127-
String defaultContextLoaderClassName) {
138+
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
128139
Assert.notNull(testClass, "Class must not be null");
140+
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
129141
Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty");
130142

131-
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
132-
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
133-
Assert.notNull(declaringClass, String.format(
134-
"Could not find an 'annotation declaring class' for annotation type [%s] and test class [%s]",
135-
annotationType, testClass));
136-
137-
while (declaringClass != null) {
138-
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
139-
143+
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
140144
if (logger.isTraceEnabled()) {
141-
logger.trace(String.format(
142-
"Processing ContextLoader for @ContextConfiguration [%s] and declaring class [%s]",
143-
contextConfiguration, declaringClass));
145+
logger.trace(String.format("Processing ContextLoader for context configuration attributes %s",
146+
configAttributes));
144147
}
145148

146-
Class<? extends ContextLoader> contextLoaderClass = contextConfiguration.loader();
149+
Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
147150
if (!ContextLoader.class.equals(contextLoaderClass)) {
148151
if (logger.isDebugEnabled()) {
149152
logger.debug(String.format(
150-
"Found explicit ContextLoader class [%s] for @ContextConfiguration [%s] and declaring class [%s]",
151-
contextLoaderClass, contextConfiguration, declaringClass));
153+
"Found explicit ContextLoader class [%s] for context configuration attributes %s",
154+
contextLoaderClass.getName(), configAttributes));
152155
}
153156
return contextLoaderClass;
154157
}
155-
156-
declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
157158
}
158159

159160
try {
160161
if (logger.isTraceEnabled()) {
161162
logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]",
162-
defaultContextLoaderClassName, testClass));
163+
defaultContextLoaderClassName, testClass.getName()));
163164
}
164165
return (Class<? extends ContextLoader>) ContextLoaderUtils.class.getClassLoader().loadClass(
165166
defaultContextLoaderClassName);
166-
} catch (ClassNotFoundException ex) {
167+
}
168+
catch (ClassNotFoundException ex) {
167169
throw new IllegalStateException("Could not load default ContextLoader class ["
168170
+ defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' "
169171
+ "attribute or make the default loader class available.");
@@ -181,30 +183,29 @@ static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testCla
181183
* consideration. If these flags need to be honored, that must be handled
182184
* manually when traversing the list returned by this method.
183185
*
184-
* @param clazz the class for which to resolve the configuration attributes (must
185-
* not be <code>null</code>)
186-
* @return the list of configuration attributes for the specified class
187-
* (never <code>null</code>)
188-
* @throws IllegalArgumentException if the supplied class is <code>null</code> or
186+
* @param testClass the class for which to resolve the configuration attributes (must
187+
* not be {@code null})
188+
* @return the list of configuration attributes for the specified class, ordered <em>bottom-up</em>
189+
* (i.e., as if we were traversing up the class hierarchy); never {@code null}
190+
* @throws IllegalArgumentException if the supplied class is {@code null} or
189191
* if {@code @ContextConfiguration} is not <em>present</em> on the supplied class
190192
*/
191-
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> clazz) {
192-
Assert.notNull(clazz, "Class must not be null");
193+
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
194+
Assert.notNull(testClass, "Class must not be null");
193195

194196
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
195197

196198
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
197-
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, clazz);
199+
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
198200
Assert.notNull(declaringClass, String.format(
199-
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
200-
clazz));
201+
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
202+
annotationType.getName(), testClass.getName()));
201203

202204
while (declaringClass != null) {
203205
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
204-
205206
if (logger.isTraceEnabled()) {
206207
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
207-
contextConfiguration, declaringClass));
208+
contextConfiguration, declaringClass.getName()));
208209
}
209210

210211
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
@@ -213,28 +214,14 @@ static List<ContextConfigurationAttributes> resolveContextConfigurationAttribute
213214
logger.trace("Resolved context configuration attributes: " + attributes);
214215
}
215216

216-
attributesList.add(0, attributes);
217+
attributesList.add(attributes);
217218

218219
declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
219220
}
220221

221222
return attributesList;
222223
}
223224

224-
/**
225-
* Create a copy of the supplied list of {@code ContextConfigurationAttributes}
226-
* in reverse order.
227-
*
228-
* @since 3.2
229-
*/
230-
private static List<ContextConfigurationAttributes> reverseContextConfigurationAttributes(
231-
List<ContextConfigurationAttributes> configAttributesList) {
232-
List<ContextConfigurationAttributes> configAttributesListReversed = new ArrayList<ContextConfigurationAttributes>(
233-
configAttributesList);
234-
Collections.reverse(configAttributesListReversed);
235-
return configAttributesListReversed;
236-
}
237-
238225
/**
239226
* Resolve the list of merged {@code ApplicationContextInitializer} classes
240227
* for the supplied list of {@code ContextConfigurationAttributes}.
@@ -247,22 +234,21 @@ private static List<ContextConfigurationAttributes> reverseContextConfigurationA
247234
* at the given level will be merged with those defined in higher levels
248235
* of the class hierarchy.
249236
*
250-
* @param configAttributesList the list of configuration attributes to process
251-
* (must not be <code>null</code>)
252-
* @return the list of merged context initializer classes, including those
253-
* from superclasses if appropriate (never <code>null</code>)
237+
* @param configAttributesList the list of configuration attributes to process;
238+
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
239+
* (i.e., as if we were traversing up the class hierarchy)
240+
* @return the set of merged context initializer classes, including those
241+
* from superclasses if appropriate (never {@code null})
254242
* @since 3.2
255243
*/
256244
static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses(
257245
List<ContextConfigurationAttributes> configAttributesList) {
258-
Assert.notNull(configAttributesList, "configAttributesList must not be null");
246+
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
259247

260248
final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
261249
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
262250

263-
// Traverse config attributes in reverse order (i.e., as if we were traversing up
264-
// the class hierarchy).
265-
for (ContextConfigurationAttributes configAttributes : reverseContextConfigurationAttributes(configAttributesList)) {
251+
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
266252
if (logger.isTraceEnabled()) {
267253
logger.trace(String.format("Processing context initializers for context configuration attributes %s",
268254
configAttributes));
@@ -287,23 +273,23 @@ static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableA
287273
* set to <code>true</code>, profiles defined in the test class will be
288274
* merged with those defined in superclasses.
289275
*
290-
* @param clazz the class for which to resolve the active profiles (must
291-
* not be <code>null</code>)
276+
* @param testClass the class for which to resolve the active profiles (must
277+
* not be {@code null})
292278
* @return the set of active profiles for the specified class, including
293-
* active profiles from superclasses if appropriate (never <code>null</code>)
279+
* active profiles from superclasses if appropriate (never {@code null})
294280
* @see org.springframework.test.context.ActiveProfiles
295281
* @see org.springframework.context.annotation.Profile
296282
*/
297-
static String[] resolveActiveProfiles(Class<?> clazz) {
298-
Assert.notNull(clazz, "Class must not be null");
283+
static String[] resolveActiveProfiles(Class<?> testClass) {
284+
Assert.notNull(testClass, "Class must not be null");
299285

300286
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
301-
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, clazz);
287+
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
302288

303289
if (declaringClass == null && logger.isDebugEnabled()) {
304290
logger.debug(String.format(
305291
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
306-
annotationType, clazz));
292+
annotationType.getName(), testClass.getName()));
307293
}
308294

309295
final Set<String> activeProfiles = new HashSet<String>();
@@ -313,7 +299,7 @@ static String[] resolveActiveProfiles(Class<?> clazz) {
313299

314300
if (logger.isTraceEnabled()) {
315301
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
316-
declaringClass));
302+
declaringClass.getName()));
317303
}
318304

319305
String[] profiles = annotation.profiles();
@@ -322,11 +308,12 @@ static String[] resolveActiveProfiles(Class<?> clazz) {
322308
if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) {
323309
String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
324310
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
325-
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass,
311+
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
326312
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
327313
logger.error(msg);
328314
throw new IllegalStateException(msg);
329-
} else if (!ObjectUtils.isEmpty(valueProfiles)) {
315+
}
316+
else if (!ObjectUtils.isEmpty(valueProfiles)) {
330317
profiles = valueProfiles;
331318
}
332319

@@ -349,9 +336,9 @@ static String[] resolveActiveProfiles(Class<?> clazz) {
349336
* <code>defaultContextLoaderClassName</code>.
350337
*
351338
* @param testClass the test class for which the {@code MergedContextConfiguration}
352-
* should be built (must not be <code>null</code>)
339+
* should be built (must not be {@code null})
353340
* @param defaultContextLoaderClassName the name of the default
354-
* {@code ContextLoader} class to use (may be <code>null</code>)
341+
* {@code ContextLoader} class to use (may be {@code null})
355342
* @return the merged context configuration
356343
* @see #resolveContextLoader()
357344
* @see #resolveContextConfigurationAttributes()
@@ -363,14 +350,13 @@ static String[] resolveActiveProfiles(Class<?> clazz) {
363350
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
364351
String defaultContextLoaderClassName) {
365352

366-
final ContextLoader contextLoader = resolveContextLoader(testClass, defaultContextLoaderClassName);
367353
final List<ContextConfigurationAttributes> configAttributesList = resolveContextConfigurationAttributes(testClass);
354+
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
355+
defaultContextLoaderClassName);
368356
final List<String> locationsList = new ArrayList<String>();
369357
final List<Class<?>> classesList = new ArrayList<Class<?>>();
370358

371-
// Traverse config attributes in reverse order (i.e., as if we were traversing up
372-
// the class hierarchy).
373-
for (ContextConfigurationAttributes configAttributes : reverseContextConfigurationAttributes(configAttributesList)) {
359+
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
374360
if (logger.isTraceEnabled()) {
375361
logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
376362
configAttributes));
@@ -381,7 +367,8 @@ static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testC
381367
smartContextLoader.processContextConfiguration(configAttributes);
382368
locationsList.addAll(0, Arrays.asList(configAttributes.getLocations()));
383369
classesList.addAll(0, Arrays.asList(configAttributes.getClasses()));
384-
} else {
370+
}
371+
else {
385372
String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
386373
configAttributes.getLocations());
387374
locationsList.addAll(0, Arrays.asList(processedLocations));

0 commit comments

Comments
 (0)