2121
2222import java .util .ArrayList ;
2323import java .util .Arrays ;
24- import java .util .Collections ;
2524import java .util .HashSet ;
2625import java .util .List ;
2726import 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 @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