001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context;
018
019import java.util.Arrays;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023
024import org.springframework.context.ApplicationContextInitializer;
025import org.springframework.core.annotation.AnnotationAttributes;
026import org.springframework.core.style.ToStringCreator;
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029import org.springframework.util.ObjectUtils;
030import org.springframework.util.StringUtils;
031
032/**
033 * {@code ContextConfigurationAttributes} encapsulates the context configuration
034 * attributes declared via {@link ContextConfiguration @ContextConfiguration}.
035 *
036 * @author Sam Brannen
037 * @author Phillip Webb
038 * @since 3.1
039 * @see ContextConfiguration
040 * @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
041 * @see MergedContextConfiguration
042 */
043public class ContextConfigurationAttributes {
044
045        private static final String[] EMPTY_LOCATIONS = new String[0];
046
047        private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0];
048
049
050        private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class);
051
052        private final Class<?> declaringClass;
053
054        private Class<?>[] classes = new Class<?>[0];
055
056        private String[] locations = new String[0];
057
058        private final boolean inheritLocations;
059
060        private final Class<? extends ApplicationContextInitializer<?>>[] initializers;
061
062        private final boolean inheritInitializers;
063
064        @Nullable
065        private final String name;
066
067        private final Class<? extends ContextLoader> contextLoaderClass;
068
069
070        /**
071         * Construct a new {@link ContextConfigurationAttributes} instance with default values.
072         * @param declaringClass the test class that declared {@code @ContextConfiguration},
073         * either explicitly or implicitly
074         * @since 4.3
075         */
076        @SuppressWarnings("unchecked")
077        public ContextConfigurationAttributes(Class<?> declaringClass) {
078                this(declaringClass, EMPTY_LOCATIONS, EMPTY_CLASSES, false, (Class[]) EMPTY_CLASSES, true, ContextLoader.class);
079        }
080
081        /**
082         * Construct a new {@link ContextConfigurationAttributes} instance for the
083         * supplied {@link ContextConfiguration @ContextConfiguration} annotation and
084         * the {@linkplain Class test class} that declared it.
085         * @param declaringClass the test class that declared {@code @ContextConfiguration}
086         * @param contextConfiguration the annotation from which to retrieve the attributes
087         */
088        public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
089                this(declaringClass, contextConfiguration.locations(), contextConfiguration.classes(),
090                                contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
091                                contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
092        }
093
094        /**
095         * Construct a new {@link ContextConfigurationAttributes} instance for the
096         * supplied {@link AnnotationAttributes} (parsed from a
097         * {@link ContextConfiguration @ContextConfiguration} annotation) and
098         * the {@linkplain Class test class} that declared them.
099         * @param declaringClass the test class that declared {@code @ContextConfiguration}
100         * @param annAttrs the annotation attributes from which to retrieve the attributes
101         */
102        @SuppressWarnings("unchecked")
103        public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
104                this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getClassArray("classes"),
105                                annAttrs.getBoolean("inheritLocations"),
106                                (Class<? extends ApplicationContextInitializer<?>>[]) annAttrs.getClassArray("initializers"),
107                                annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), annAttrs.getClass("loader"));
108        }
109
110        /**
111         * Construct a new {@link ContextConfigurationAttributes} instance for the
112         * {@linkplain Class test class} that declared the
113         * {@link ContextConfiguration @ContextConfiguration} annotation and its
114         * corresponding attributes.
115         * @param declaringClass the test class that declared {@code @ContextConfiguration}
116         * @param locations the resource locations declared via {@code @ContextConfiguration}
117         * @param classes the annotated classes declared via {@code @ContextConfiguration}
118         * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
119         * @param initializers the context initializers declared via {@code @ContextConfiguration}
120         * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
121         * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
122         * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
123         * {@code null}
124         */
125        public ContextConfigurationAttributes(
126                        Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations,
127                        Class<? extends ApplicationContextInitializer<?>>[] initializers,
128                        boolean inheritInitializers, Class<? extends ContextLoader> contextLoaderClass) {
129
130                this(declaringClass, locations, classes, inheritLocations, initializers, inheritInitializers, null,
131                                contextLoaderClass);
132        }
133
134        /**
135         * Construct a new {@link ContextConfigurationAttributes} instance for the
136         * {@linkplain Class test class} that declared the
137         * {@link ContextConfiguration @ContextConfiguration} annotation and its
138         * corresponding attributes.
139         * @param declaringClass the test class that declared {@code @ContextConfiguration}
140         * @param locations the resource locations declared via {@code @ContextConfiguration}
141         * @param classes the annotated classes declared via {@code @ContextConfiguration}
142         * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
143         * @param initializers the context initializers declared via {@code @ContextConfiguration}
144         * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
145         * @param name the name of level in the context hierarchy, or {@code null} if not applicable
146         * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
147         * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
148         * {@code null}
149         */
150        public ContextConfigurationAttributes(
151                        Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations,
152                        Class<? extends ApplicationContextInitializer<?>>[] initializers,
153                        boolean inheritInitializers, @Nullable String name, Class<? extends ContextLoader> contextLoaderClass) {
154
155                Assert.notNull(declaringClass, "'declaringClass' must not be null");
156                Assert.notNull(contextLoaderClass, "'contextLoaderClass' must not be null");
157
158                if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes) && logger.isDebugEnabled()) {
159                        logger.debug(String.format(
160                                        "Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s " +
161                                        "and 'classes' %s attributes. Most SmartContextLoader implementations support " +
162                                        "only one declaration of resources per @ContextConfiguration annotation.",
163                                        declaringClass.getName(), ObjectUtils.nullSafeToString(locations),
164                                        ObjectUtils.nullSafeToString(classes)));
165                }
166
167                this.declaringClass = declaringClass;
168                this.locations = locations;
169                this.classes = classes;
170                this.inheritLocations = inheritLocations;
171                this.initializers = initializers;
172                this.inheritInitializers = inheritInitializers;
173                this.name = (StringUtils.hasText(name) ? name : null);
174                this.contextLoaderClass = contextLoaderClass;
175        }
176
177
178        /**
179         * Get the {@linkplain Class class} that declared the
180         * {@link ContextConfiguration @ContextConfiguration} annotation, either explicitly
181         * or implicitly.
182         * @return the declaring class (never {@code null})
183         */
184        public Class<?> getDeclaringClass() {
185                return this.declaringClass;
186        }
187
188        /**
189         * Set the <em>processed</em> annotated classes, effectively overriding the
190         * original value declared via {@link ContextConfiguration @ContextConfiguration}.
191         * @see #getClasses()
192         */
193        public void setClasses(Class<?>... classes) {
194                this.classes = classes;
195        }
196
197        /**
198         * Get the annotated classes that were declared via
199         * {@link ContextConfiguration @ContextConfiguration}.
200         * <p>Note: this is a mutable property. The returned value may therefore
201         * represent a <em>processed</em> value that does not match the original value
202         * declared via {@link ContextConfiguration @ContextConfiguration}.
203         * @return the annotated classes (potentially {<em>empty</em>)
204         * @see ContextConfiguration#classes
205         * @see #setClasses(Class[])
206         */
207        public Class<?>[] getClasses() {
208                return this.classes;
209        }
210
211        /**
212         * Determine if this {@code ContextConfigurationAttributes} instance has
213         * class-based resources.
214         * @return {@code true} if the {@link #getClasses() classes} array is not empty
215         * @see #hasResources()
216         * @see #hasLocations()
217         */
218        public boolean hasClasses() {
219                return (getClasses().length > 0);
220        }
221
222        /**
223         * Set the <em>processed</em> resource locations, effectively overriding the
224         * original value declared via {@link ContextConfiguration @ContextConfiguration}.
225         * @see #getLocations()
226         */
227        public void setLocations(String... locations) {
228                this.locations = locations;
229        }
230
231        /**
232         * Get the resource locations that were declared via
233         * {@link ContextConfiguration @ContextConfiguration}.
234         * <p>Note: this is a mutable property. The returned value may therefore
235         * represent a <em>processed</em> value that does not match the original value
236         * declared via {@link ContextConfiguration @ContextConfiguration}.
237         * @return the resource locations (potentially <em>empty</em>)
238         * @see ContextConfiguration#value
239         * @see ContextConfiguration#locations
240         * @see #setLocations
241         */
242        public String[] getLocations() {
243                return this.locations;
244        }
245
246        /**
247         * Determine if this {@code ContextConfigurationAttributes} instance has
248         * path-based resource locations.
249         * @return {@code true} if the {@link #getLocations() locations} array is not empty
250         * @see #hasResources()
251         * @see #hasClasses()
252         */
253        public boolean hasLocations() {
254                return (getLocations().length > 0);
255        }
256
257        /**
258         * Determine if this {@code ContextConfigurationAttributes} instance has
259         * either path-based resource locations or class-based resources.
260         * @return {@code true} if either the {@link #getLocations() locations}
261         * or the {@link #getClasses() classes} array is not empty
262         * @see #hasLocations()
263         * @see #hasClasses()
264         */
265        public boolean hasResources() {
266                return (hasLocations() || hasClasses());
267        }
268
269        /**
270         * Get the {@code inheritLocations} flag that was declared via
271         * {@link ContextConfiguration @ContextConfiguration}.
272         * @return the {@code inheritLocations} flag
273         * @see ContextConfiguration#inheritLocations
274         */
275        public boolean isInheritLocations() {
276                return this.inheritLocations;
277        }
278
279        /**
280         * Get the {@code ApplicationContextInitializer} classes that were declared via
281         * {@link ContextConfiguration @ContextConfiguration}.
282         * @return the {@code ApplicationContextInitializer} classes
283         * @since 3.2
284         */
285        public Class<? extends ApplicationContextInitializer<?>>[] getInitializers() {
286                return this.initializers;
287        }
288
289        /**
290         * Get the {@code inheritInitializers} flag that was declared via
291         * {@link ContextConfiguration @ContextConfiguration}.
292         * @return the {@code inheritInitializers} flag
293         * @since 3.2
294         */
295        public boolean isInheritInitializers() {
296                return this.inheritInitializers;
297        }
298
299        /**
300         * Get the name of the context hierarchy level that was declared via
301         * {@link ContextConfiguration @ContextConfiguration}.
302         * @return the name of the context hierarchy level or {@code null} if not applicable
303         * @see ContextConfiguration#name()
304         * @since 3.2.2
305         */
306        @Nullable
307        public String getName() {
308                return this.name;
309        }
310
311        /**
312         * Get the {@code ContextLoader} class that was declared via
313         * {@link ContextConfiguration @ContextConfiguration}.
314         * @return the {@code ContextLoader} class
315         * @see ContextConfiguration#loader
316         */
317        public Class<? extends ContextLoader> getContextLoaderClass() {
318                return this.contextLoaderClass;
319        }
320
321
322        /**
323         * Determine if the supplied object is equal to this
324         * {@code ContextConfigurationAttributes} instance by comparing both object's
325         * {@linkplain #getDeclaringClass() declaring class},
326         * {@linkplain #getLocations() locations},
327         * {@linkplain #getClasses() annotated classes},
328         * {@linkplain #isInheritLocations() inheritLocations flag},
329         * {@linkplain #getInitializers() context initializer classes},
330         * {@linkplain #isInheritInitializers() inheritInitializers flag}, and the
331         * {@link #getContextLoaderClass() ContextLoader class}.
332         */
333        @Override
334        public boolean equals(@Nullable Object other) {
335                if (this == other) {
336                        return true;
337                }
338                if (!(other instanceof ContextConfigurationAttributes)) {
339                        return false;
340                }
341                ContextConfigurationAttributes otherAttr = (ContextConfigurationAttributes) other;
342                return (ObjectUtils.nullSafeEquals(this.declaringClass, otherAttr.declaringClass) &&
343                                Arrays.equals(this.classes, otherAttr.classes)) &&
344                                Arrays.equals(this.locations, otherAttr.locations) &&
345                                this.inheritLocations == otherAttr.inheritLocations &&
346                                Arrays.equals(this.initializers, otherAttr.initializers) &&
347                                this.inheritInitializers == otherAttr.inheritInitializers &&
348                                ObjectUtils.nullSafeEquals(this.name, otherAttr.name) &&
349                                ObjectUtils.nullSafeEquals(this.contextLoaderClass, otherAttr.contextLoaderClass);
350        }
351
352        /**
353         * Generate a unique hash code for all properties of this
354         * {@code ContextConfigurationAttributes} instance excluding the
355         * {@linkplain #getName() name}.
356         */
357        @Override
358        public int hashCode() {
359                int result = this.declaringClass.hashCode();
360                result = 31 * result + Arrays.hashCode(this.classes);
361                result = 31 * result + Arrays.hashCode(this.locations);
362                result = 31 * result + Arrays.hashCode(this.initializers);
363                return result;
364        }
365
366        /**
367         * Provide a String representation of the context configuration attributes
368         * and declaring class.
369         */
370        @Override
371        public String toString() {
372                return new ToStringCreator(this)
373                                .append("declaringClass", this.declaringClass.getName())
374                                .append("classes", ObjectUtils.nullSafeToString(this.classes))
375                                .append("locations", ObjectUtils.nullSafeToString(this.locations))
376                                .append("inheritLocations", this.inheritLocations)
377                                .append("initializers", ObjectUtils.nullSafeToString(this.initializers))
378                                .append("inheritInitializers", this.inheritInitializers)
379                                .append("name", this.name)
380                                .append("contextLoaderClass", this.contextLoaderClass.getName())
381                                .toString();
382        }
383
384}