001/* 002 * Copyright 2012-2018 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 * http://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.boot.context.annotation; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.LinkedHashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Set; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import org.springframework.context.ApplicationContext; 032import org.springframework.context.annotation.Configuration; 033import org.springframework.context.annotation.ImportSelector; 034import org.springframework.core.OrderComparator; 035import org.springframework.core.Ordered; 036import org.springframework.test.context.junit4.SpringRunner; 037import org.springframework.util.Assert; 038import org.springframework.util.ClassUtils; 039 040/** 041 * A set of {@link Configuration @Configuration} classes that can be registered in 042 * {@link ApplicationContext}. Classes can be returned from one or more 043 * {@link Configurations} instances by using {@link #getClasses(Configurations[])}. The 044 * resulting array follows the ordering rules usually applied by the 045 * {@link ApplicationContext} and/or custom {@link ImportSelector} implementations. 046 * <p> 047 * This class is primarily intended for use with tests that need to specify configuration 048 * classes but can't use {@link SpringRunner}. 049 * <p> 050 * Implementations of this class should be annotated with {@code @Order} or implement 051 * {@link Ordered}. 052 * 053 * @author Phillip Webb 054 * @since 2.0.0 055 * @see UserConfigurations 056 */ 057public abstract class Configurations { 058 059 private static final Comparator<Object> COMPARATOR = OrderComparator.INSTANCE 060 .thenComparing((other) -> other.getClass().getName()); 061 062 private final Set<Class<?>> classes; 063 064 protected Configurations(Collection<Class<?>> classes) { 065 Assert.notNull(classes, "Classes must not be null"); 066 Collection<Class<?>> sorted = sort(classes); 067 this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted)); 068 } 069 070 /** 071 * Sort configuration classes into the order that they should be applied. 072 * @param classes the classes to sort 073 * @return a sorted set of classes 074 */ 075 protected Collection<Class<?>> sort(Collection<Class<?>> classes) { 076 return classes; 077 } 078 079 protected final Set<Class<?>> getClasses() { 080 return this.classes; 081 } 082 083 /** 084 * Merge configurations from another source of the same type. 085 * @param other the other {@link Configurations} (must be of the same type as this 086 * instance) 087 * @return a new configurations instance (must be of the same type as this instance) 088 */ 089 protected Configurations merge(Configurations other) { 090 Set<Class<?>> mergedClasses = new LinkedHashSet<>(getClasses()); 091 mergedClasses.addAll(other.getClasses()); 092 return merge(mergedClasses); 093 } 094 095 /** 096 * Merge configurations. 097 * @param mergedClasses the merged classes 098 * @return a new configurations instance (must be of the same type as this instance) 099 */ 100 protected abstract Configurations merge(Set<Class<?>> mergedClasses); 101 102 /** 103 * Return the classes from all the specified configurations in the order that they 104 * would be registered. 105 * @param configurations the source configuration 106 * @return configuration classes in registration order 107 */ 108 public static Class<?>[] getClasses(Configurations... configurations) { 109 return getClasses(Arrays.asList(configurations)); 110 } 111 112 /** 113 * Return the classes from all the specified configurations in the order that they 114 * would be registered. 115 * @param configurations the source configuration 116 * @return configuration classes in registration order 117 */ 118 public static Class<?>[] getClasses(Collection<Configurations> configurations) { 119 List<Configurations> ordered = new ArrayList<>(configurations); 120 ordered.sort(COMPARATOR); 121 List<Configurations> collated = collate(ordered); 122 LinkedHashSet<Class<?>> classes = collated.stream() 123 .flatMap(Configurations::streamClasses) 124 .collect(Collectors.toCollection(LinkedHashSet::new)); 125 return ClassUtils.toClassArray(classes); 126 } 127 128 private static Stream<Class<?>> streamClasses(Configurations configurations) { 129 return configurations.getClasses().stream(); 130 } 131 132 private static List<Configurations> collate( 133 List<Configurations> orderedConfigurations) { 134 LinkedList<Configurations> collated = new LinkedList<>(); 135 for (Configurations item : orderedConfigurations) { 136 if (collated.isEmpty() || collated.getLast().getClass() != item.getClass()) { 137 collated.add(item); 138 } 139 else { 140 collated.set(collated.size() - 1, collated.getLast().merge(item)); 141 } 142 } 143 return collated; 144 } 145 146}