ConfigurationClassParser.java 28.3 KB
Newer Older
1
/*
2
 * Copyright 2002-2013 the original author or authors.
3 4 5 6 7 8 9 10 11 12 13 14 15
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
C
Chris Beams 已提交
16

17
package org.springframework.context.annotation;
18

19
import java.io.IOException;
20
import java.util.ArrayList;
J
Juergen Hoeller 已提交
21
import java.util.Collection;
22 23
import java.util.Collections;
import java.util.Comparator;
C
Chris Beams 已提交
24
import java.util.HashMap;
25
import java.util.Iterator;
26
import java.util.LinkedHashSet;
27 28
import java.util.LinkedList;
import java.util.List;
29
import java.util.Map;
30
import java.util.Set;
31
import java.util.Stack;
32

C
Chris Beams 已提交
33
import org.springframework.beans.BeanUtils;
34 35
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
36
import org.springframework.beans.factory.BeanDefinitionStoreException;
37 38
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
39
import org.springframework.beans.factory.config.BeanDefinition;
40
import org.springframework.beans.factory.config.BeanDefinitionHolder;
41
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
42 43
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
44
import org.springframework.beans.factory.parsing.ProblemReporter;
45
import org.springframework.beans.factory.support.AbstractBeanDefinition;
46
import org.springframework.beans.factory.support.BeanDefinitionReader;
47
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
48
import org.springframework.beans.factory.support.BeanNameGenerator;
49
import org.springframework.context.EnvironmentAware;
50
import org.springframework.context.ResourceLoaderAware;
J
Juergen Hoeller 已提交
51
import org.springframework.core.NestedIOException;
52
import org.springframework.core.annotation.AnnotationAttributes;
53
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
54
import org.springframework.core.env.CompositePropertySource;
55
import org.springframework.core.env.Environment;
C
Chris Beams 已提交
56
import org.springframework.core.env.PropertySource;
57
import org.springframework.core.io.ResourceLoader;
58
import org.springframework.core.io.support.ResourcePropertySource;
59 60 61 62 63
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
C
Chris Beams 已提交
64
import org.springframework.core.type.filter.AssignableTypeFilter;
65
import org.springframework.util.StringUtils;
66

67 68
import static org.springframework.context.annotation.MetadataUtils.*;

69
/**
70
 * Parses a {@link Configuration} class definition, populating a collection of
71 72 73
 * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
 * any number of ConfigurationClass objects because one Configuration class may import
 * another using the {@link Import} annotation).
74
 *
75
 * <p>This class helps separate the concern of parsing the structure of a Configuration
76
 * class from the concern of registering BeanDefinition objects based on the
77 78
 * content of that model (with the exception of {@code @ComponentScan} annotations which
 * need to be registered immediately).
79
 *
80 81 82
 * <p>This ASM-based implementation avoids reflection and eager class loading in order to
 * interoperate effectively with lazy class loading in a Spring ApplicationContext.
 *
83
 * @author Chris Beams
84
 * @author Juergen Hoeller
85
 * @author Phillip Webb
86 87
 * @since 3.0
 * @see ConfigurationClassBeanDefinitionReader
C
Chris Beams 已提交
88
 */
89
class ConfigurationClassParser {
90

91 92
	private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
			new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
93 94 95 96 97 98
				@Override
				public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder o2) {
					return AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());
				}
			};

99

100
	private final MetadataReaderFactory metadataReaderFactory;
101

102
	private final ProblemReporter problemReporter;
103

104 105
	private final Environment environment;

106 107
	private final ResourceLoader resourceLoader;

108 109
	private final BeanDefinitionRegistry registry;

110 111
	private final ComponentScanAnnotationParser componentScanParser;

112 113 114 115 116 117 118 119 120
	private final Set<ConfigurationClass> configurationClasses = new LinkedHashSet<ConfigurationClass>();

	private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();

	private final Stack<PropertySource<?>> propertySources = new Stack<PropertySource<?>>();

	private final ImportStack importStack = new ImportStack();

	private final List<DeferredImportSelectorHolder> deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
121

122

123
	/**
124
	 * Create a new {@link ConfigurationClassParser} instance that will be used
125
	 * to populate the set of configuration classes.
126
	 */
127
	public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
128 129
			ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
			BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
130

131
		this.metadataReaderFactory = metadataReaderFactory;
132
		this.problemReporter = problemReporter;
133
		this.environment = environment;
134
		this.resourceLoader = resourceLoader;
135
		this.registry = registry;
136
		this.componentScanParser = new ComponentScanAnnotationParser(
137
				resourceLoader, environment, componentScanBeanNameGenerator, registry);
138 139
	}

140

141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
			}
		}
		processDeferredImportSelectors();
	}
158

159
	/**
160 161 162 163
	 * Parse the specified {@link Configuration @Configuration} class.
	 * @param className the name of the class to parse
	 * @param beanName may be null, but if populated represents the bean id
	 * (assumes that this configuration class was configured via XML)
164
	 */
165
	public void parse(String className, String beanName) throws IOException {
166 167 168 169 170 171
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName));
	}

	/**
	 * Parse the specified {@link Configuration @Configuration} class.
172
	 * @param clazz the Class to parse
173
	 * @param beanName must not be null (as of Spring 3.1.1)
174
	 */
175
	public void parse(Class<?> clazz, String beanName) throws IOException {
176 177 178
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}

179

180
	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
181

182
		if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {
183 184
			// Explicit bean definition found, probably replacing an import.
			// Let's remove the old one and go with the new one.
185
			this.configurationClasses.remove(configClass);
186 187 188 189 190
			for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
				if (configClass.equals(it.next())) {
					it.remove();
				}
			}
191
		}
192

193
		// Recursively process the configuration class and its superclass hierarchy.
194
		SourceClass sourceClass = asSourceClass(configClass);
195
		do {
196
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
197
		}
198
		while (sourceClass != null);
199

200
		this.configurationClasses.add(configClass);
201
	}
202

203
	/**
204 205 206 207 208 209
	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
	 * annotations, members and methods from the source class. This method can be called
	 * multiple times as relevant sources are discovered.
	 * @param configClass the configuration class being build
	 * @param sourceClass a source class
	 * @return the superclass, {@code null} if none found or previously processed
210
	 */
211 212
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
213 214

		// recursively process any member (nested) classes first
215
		processMemberClasses(configClass, sourceClass);
216 217

		// process any @PropertySource annotations
218
		AnnotationAttributes propertySource = attributesFor(sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class);
219
		if (propertySource != null) {
J
Juergen Hoeller 已提交
220
			processPropertySource(propertySource);
C
Chris Beams 已提交
221 222
		}

223
		// process any @ComponentScan annotations
224
		AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class);
225
		if (componentScan != null) {
226
			// the config class is annotated with @ComponentScan -> perform the scan immediately
227 228 229
			if (!ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip(
					this.registry, this.environment)) {
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
230
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
231 232 233 234 235 236

				// check the set of scanned definitions for any further config classes and parse recursively if necessary
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
						this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
					}
237 238 239 240
				}
			}
		}

241
		// process any @Import annotations
242
		processImports(configClass, getImports(sourceClass), true);
243

244
		// process any @ImportResource annotations
245 246
		if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
			AnnotationAttributes importResource = attributesFor(sourceClass.getMetadata(), ImportResource.class);
247
			String[] resources = importResource.getStringArray("value");
248
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
249 250
			for (String resource : resources) {
				configClass.addImportedResource(resource, readerClass);
251 252
			}
		}
253 254

		// process individual @Bean methods
255
		Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
256 257
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
258 259
		}

260
		// process superclass, if any
261 262
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
263 264
			if (!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
265
				// superclass found, return its annotation metadata and recurse
266 267
				try {
					return sourceClass.getSuperClass();
268
				}
269 270
				catch (ClassNotFoundException ex) {
					throw new IllegalStateException(ex);
271 272 273 274 275 276 277
				}
			}
		}

		// no superclass, processing is complete
		return null;
	}
278

J
Juergen Hoeller 已提交
279 280
	/**
	 * Register member (nested) classes that happen to be configuration classes themselves.
281
	 * @param sourceClass the source class to process
J
Juergen Hoeller 已提交
282 283
	 * @throws IOException if there is any problem reading metadata from a member class
	 */
284 285 286 287
	private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
		for (SourceClass memberClass : sourceClass.getMemberClasses()) {
			if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())) {
				processConfigurationClass(memberClass.asConfigClass(configClass));
J
Juergen Hoeller 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
			}
		}
	}

	/**
	 * Process the given <code>@PropertySource</code> annotation metadata.
	 * @param propertySource metadata for the <code>@PropertySource</code> annotation found
	 * @throws IOException if loading a property source failed
	 */
	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
		String name = propertySource.getString("name");
		String[] locations = propertySource.getStringArray("value");
		int nLocations = locations.length;
		if (nLocations == 0) {
			throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
		}
		for (int i = 0; i < nLocations; i++) {
			locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
		}
		ClassLoader classLoader = this.resourceLoader.getClassLoader();
		if (!StringUtils.hasText(name)) {
			for (String location : locations) {
				this.propertySources.push(new ResourcePropertySource(location, classLoader));
			}
		}
		else {
			if (nLocations == 1) {
				this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
			}
			else {
				CompositePropertySource ps = new CompositePropertySource(name);
				for (String location : locations) {
					ps.addPropertySource(new ResourcePropertySource(location, classLoader));
				}
				this.propertySources.push(ps);
			}
		}
	}

327 328 329 330 331 332 333 334 335 336
	/**
	 * Returns {@code @Import} class, considering all meta-annotations.
	 */
	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
		Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}

337
	/**
338 339 340 341
	 * Recursively collect all declared {@code @Import} values. Unlike most
	 * meta-annotations it is valid to have several {@code @Import}s declared with
	 * different values, the usual process or returning values from the first
	 * meta-annotation on a class is not sufficient.
342 343
	 * <p>
	 * For example, it is common for a {@code @Configuration} class to declare direct
344 345
	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
	 * annotation.
346 347
	 *
	 * @param sourceClass the class to search
J
Juergen Hoeller 已提交
348 349
	 * @param imports the imports collected so far
	 * @param visited used to track visited classes to prevent infinite recursion
350
	 * @throws IOException if there is any problem reading metadata from the named class
351
	 */
352 353 354 355 356 357 358
	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports,
			Set<SourceClass> visited) throws IOException {
		try {
			if (visited.add(sourceClass)) {
				for (SourceClass annotation : sourceClass.getAnnotations()) {
					if(!annotation.getMetadata().getClassName().startsWith("java") && !annotation.isAssignable(Import.class)) {
						collectImports(annotation, imports, visited);
J
Juergen Hoeller 已提交
359
					}
360
				}
361
				imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
362 363
			}
		}
364 365 366
		catch (ClassNotFoundException ex) {
			throw new NestedIOException("Unable to collect imports", ex);
		}
367 368
	}

369 370 371 372 373 374
	private void processDeferredImportSelectors() {
		Collections.sort(this.deferredImportSelectors, DEFERRED_IMPORT_COMPARATOR);
		for (DeferredImportSelectorHolder deferredImport : this.deferredImportSelectors) {
			try {
				ConfigurationClass configClass = deferredImport.getConfigurationClass();
				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
375
				processImports(configClass, asSourceClasses(imports), false);
376
			}
377
			catch (Exception ex) {
378 379 380
				throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
			}
		}
381
		this.deferredImportSelectors.clear();
382 383
	}

384 385 386 387 388 389
	private void processImports(ConfigurationClass configClass,
			Collection<SourceClass> sourceClasses, boolean checkForCircularImports)
			throws IOException {
		if(sourceClasses.isEmpty()) {
			return;
		}
C
Chris Beams 已提交
390
		if (checkForCircularImports && this.importStack.contains(configClass)) {
391
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
392 393 394
		}
		else {
			this.importStack.push(configClass);
C
Chris Beams 已提交
395
			AnnotationMetadata importingClassMetadata = configClass.getMetadata();
J
Juergen Hoeller 已提交
396
			try {
397 398
				for (SourceClass candidate : sourceClasses) {
					if (candidate.isAssignable(ImportSelector.class)) {
J
Juergen Hoeller 已提交
399
						// the candidate class is an ImportSelector -> delegate to it to determine imports
400
						Class<?> candidateClass = candidate.loadClass();
J
Juergen Hoeller 已提交
401
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
P
Phillip Webb 已提交
402
						invokeAwareMethods(selector);
403
						if(selector instanceof DeferredImportSelector) {
404 405 406
							this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
						}
						else {
407 408 409
							String[] importClassNames = selector.selectImports(importingClassMetadata);
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, importSourceClasses, false);
410
						}
J
Juergen Hoeller 已提交
411
					}
412
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
J
Juergen Hoeller 已提交
413
						// the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
414
						Class<?> candidateClass = candidate.loadClass();
J
Juergen Hoeller 已提交
415
						ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
416
						invokeAwareMethods(registrar);
417
						configClass.addImportBeanDefinitionRegistrar(registrar);
J
Juergen Hoeller 已提交
418
					}
J
Juergen Hoeller 已提交
419 420
					else {
						// candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class
421
						this.importStack.registerImport(importingClassMetadata.getClassName(),
422 423
								candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass));
C
Chris Beams 已提交
424 425
					}
				}
426
			}
J
Juergen Hoeller 已提交
427 428 429 430 431 432 433 434 435
			catch (ClassNotFoundException ex) {
				throw new NestedIOException("Failed to load import candidate class", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

436 437
	/**
	 * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
P
Phillip Webb 已提交
438
	 * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
439
	 */
P
Phillip Webb 已提交
440 441
	private void invokeAwareMethods(Object importStrategyBean) {
		if (importStrategyBean instanceof Aware) {
442 443 444
			if (importStrategyBean instanceof EnvironmentAware) {
				((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
			}
P
Phillip Webb 已提交
445 446
			if (importStrategyBean instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
447
			}
P
Phillip Webb 已提交
448
			if (importStrategyBean instanceof BeanClassLoaderAware) {
449 450 451
				ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
						((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
						this.resourceLoader.getClassLoader());
P
Phillip Webb 已提交
452
				((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader);
453
			}
P
Phillip Webb 已提交
454 455
			if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
				((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry);
456 457 458 459 460
			}
		}
	}


461
	/**
462
	 * Validate each {@link ConfigurationClass} object.
463
	 * @see ConfigurationClass#validate
464
	 */
465
	public void validate() {
466
		for (ConfigurationClass configClass : this.configurationClasses) {
467 468 469 470
			configClass.validate(this.problemReporter);
		}
	}

471 472
	public Set<ConfigurationClass> getConfigurationClasses() {
		return this.configurationClasses;
C
Chris Beams 已提交
473
	}
474

C
Chris Beams 已提交
475 476 477 478
	public Stack<PropertySource<?>> getPropertySources() {
		return this.propertySources;
	}

479
	ImportRegistry getImportRegistry() {
C
Chris Beams 已提交
480 481 482
		return this.importStack;
	}

483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
	/**
	 * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
	 */
	public SourceClass asSourceClass(ConfigurationClass configurationClass)
			throws IOException {
		try {
			AnnotationMetadata metadata = configurationClass.getMetadata();
			if (metadata instanceof StandardAnnotationMetadata) {
				return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
			}
			return asSourceClass(configurationClass.getMetadata().getClassName());
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(ex);
		}
	}

	/**
	 * Factory method to obtain a {@link SourceClass} from a {@link Class}.
	 */
	public SourceClass asSourceClass(Class<?> classType)
			throws ClassNotFoundException, IOException {
		try {
			// Sanity test that we can read annotations, if not fall back to ASM
			classType.getAnnotations();
		}
		catch (Throwable ex) {
			return asSourceClass(classType.getName());
		}
		return new SourceClass(classType);
	}

	/**
	 * Factory method to obtain {@link SourceClass}s from class names.
	 */
	public Collection<SourceClass> asSourceClasses(String[] classNamess)
			throws ClassNotFoundException, IOException {
		List<SourceClass> annotatedClasses = new ArrayList<SourceClass>();
		for (String className : classNamess) {
			annotatedClasses.add(asSourceClass(className));
		}
		return annotatedClasses;
	}

	/**
	 * Factory method to obtain a {@link SourceClass} from a class name.
	 */
	public SourceClass asSourceClass(String className)
			throws ClassNotFoundException, IOException {
		if (className.startsWith("java")) {
			// Never use ASM for core java types
			return new SourceClass(this.resourceLoader.getClassLoader().loadClass(
					className));
		}
		return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
	}

540

C
Chris Beams 已提交
541
	interface ImportRegistry {
J
Juergen Hoeller 已提交
542

C
Chris Beams 已提交
543
		String getImportingClassFor(String importedClass);
544

C
Chris Beams 已提交
545 546
	}

547 548

	@SuppressWarnings("serial")
C
Chris Beams 已提交
549 550
	private static class ImportStack extends Stack<ConfigurationClass> implements ImportRegistry {

J
Juergen Hoeller 已提交
551
		private final Map<String, String> imports = new HashMap<String, String>();
C
Chris Beams 已提交
552

J
Juergen Hoeller 已提交
553 554
		public void registerImport(String importingClass, String importedClass) {
			this.imports.put(importedClass, importingClass);
C
Chris Beams 已提交
555 556
		}

557
		@Override
J
Juergen Hoeller 已提交
558 559
		public String getImportingClassFor(String importedClass) {
			return this.imports.get(importedClass);
C
Chris Beams 已提交
560
		}
561 562 563 564 565 566 567 568 569 570

		/**
		 * Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
		 * exists within this stack that has the same name as <var>elem</var>. Elem must be of
		 * type ConfigurationClass.
		 */
		@Override
		public boolean contains(Object elem) {
			ConfigurationClass configClass = (ConfigurationClass) elem;
			Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
571
				@Override
572 573 574 575 576 577 578 579 580
				public int compare(ConfigurationClass first, ConfigurationClass second) {
					return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1;
				}
			};
			return (Collections.binarySearch(this, configClass, comparator) != -1);
		}

		/**
		 * Given a stack containing (in order)
C
Chris Beams 已提交
581
		 * <ul>
582 583 584
		 * <li>com.acme.Foo</li>
		 * <li>com.acme.Bar</li>
		 * <li>com.acme.Baz</li>
C
Chris Beams 已提交
585 586
		 * </ul>
		 * return "ImportStack: [Foo->Bar->Baz]".
587 588 589
		 */
		@Override
		public String toString() {
C
Chris Beams 已提交
590
			StringBuilder builder = new StringBuilder("ImportStack: [");
591 592 593 594 595 596 597
			Iterator<ConfigurationClass> iterator = iterator();
			while (iterator.hasNext()) {
				builder.append(iterator.next().getSimpleName());
				if (iterator.hasNext()) {
					builder.append("->");
				}
			}
C
Chris Beams 已提交
598
			return builder.append(']').toString();
599 600 601 602
		}
	}


603 604
	private static class DeferredImportSelectorHolder {

605
		private final ConfigurationClass configurationClass;
606

607
		private final DeferredImportSelector importSelector;
608 609 610 611 612 613 614

		public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) {
			this.configurationClass = configurationClass;
			this.importSelector = importSelector;
		}

		public ConfigurationClass getConfigurationClass() {
615
			return this.configurationClass;
616 617 618
		}

		public DeferredImportSelector getImportSelector() {
619
			return this.importSelector;
620 621
		}
	}
622 623


624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
	/**
	 * Simple wrapper that allows annotated source classes to be dealt with in a uniform
	 * manor, regardless of how they are loaded.
	 */
	private class SourceClass {

		private final Object source; // Class or MetaDataReader

		private final AnnotationMetadata metadata;


		private SourceClass(Object source) {
			this.source = source;
			if (source instanceof Class<?>) {
				this.metadata = new StandardAnnotationMetadata((Class<?>) source, true);
			}
			else {
				this.metadata = ((MetadataReader) source).getAnnotationMetadata();
			}
		}


		public Class<?> loadClass() throws ClassNotFoundException {
			if(source instanceof Class<?>) {
				return (Class<?>) source;
			}
			String className = ((MetadataReader) source).getClassMetadata().getClassName();
			return resourceLoader.getClassLoader().loadClass(className);
		}

		public boolean isAssignable(Class<?> clazz) throws IOException {
			if (source instanceof Class) {
				return clazz.isAssignableFrom((Class) source);
			}
			return new AssignableTypeFilter(clazz).match((MetadataReader) source,
					metadataReaderFactory);
		}

		public ConfigurationClass asConfigClass(ConfigurationClass importedBy)
				throws IOException {
			if (this.source instanceof Class<?>) {
				return new ConfigurationClass((Class<?>) this.source, importedBy);
			}
			return new ConfigurationClass((MetadataReader) source, importedBy);
		}

		public Collection<SourceClass> getMemberClasses() throws IOException {
			List<SourceClass> members = new ArrayList<SourceClass>();
			if (source instanceof Class<?>) {
				Class<?> sourceClass = (Class<?>) source;
				for (Class<?> declaredClass : sourceClass.getDeclaredClasses()) {
					try {
						members.add(asSourceClass(declaredClass));
					}
					catch (ClassNotFoundException e) {
					}
				}
			}
			else {
				MetadataReader sourceReader = (MetadataReader) source;
				for (String memberClassName : sourceReader.getClassMetadata().getMemberClassNames()) {
					try {
						members.add(asSourceClass(memberClassName));
					}
					catch (ClassNotFoundException e) {
					}
				}
			}
			return members;
		}

		public SourceClass getSuperClass() throws ClassNotFoundException, IOException {
			if (source instanceof Class<?>) {
				return asSourceClass(((Class<?>) source).getSuperclass());
			}
			return asSourceClass(((MetadataReader) source).getClassMetadata().getSuperClassName());
		}

		public Set<SourceClass> getAnnotations() throws ClassNotFoundException, IOException {
			Set<SourceClass> annotations = new LinkedHashSet<SourceClass>();
			for(String annotation : getMetadata().getAnnotationTypes()) {
				annotations.add(getRelated(annotation));
			}
			return annotations;
		}

		public Collection<SourceClass> getAnnotationAttributes(String annotationType,
				String attribute) throws ClassNotFoundException, IOException {
			Map<String, Object> annotationAttributes = getMetadata().getAnnotationAttributes(
					annotationType, true);
			if (annotationAttributes == null
					|| !annotationAttributes.containsKey(attribute)) {
				return Collections.emptySet();
			}
			String[] classNames = (String[]) annotationAttributes.get(attribute);
			Set<SourceClass> rtn = new LinkedHashSet<SourceClass>();
			for (String className : classNames) {
				rtn.add(getRelated(className));
			}
			return rtn;
		}

		private SourceClass getRelated(String className) throws IOException,
				ClassNotFoundException {
			if (source instanceof Class<?>) {
				try {
					Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
					return asSourceClass(clazz);
				}
				catch (ClassNotFoundException ex) {
				}
			}
			return asSourceClass(className);
		}

		public AnnotationMetadata getMetadata() {
			return this.metadata;
		}

		@Override
		public int hashCode() {
			return toString().hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (obj instanceof SourceClass) {
				return toString().equals(obj.toString());
			}
			return false;
		}

		@Override
		public String toString() {
			return getMetadata().getClassName();
		}
	}


769 770 771 772 773 774 775 776 777 778 779 780 781 782
	/**
	 * {@link Problem} registered upon detection of a circular {@link Import}.
	 */
	private static class CircularImportProblem extends Problem {

		public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack, AnnotationMetadata metadata) {
			super(String.format("A circular @Import has been detected: " +
					"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
					"already present in the current import stack [%s]", importStack.peek().getSimpleName(),
					attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack),
					new Location(importStack.peek().getResource(), metadata));
		}
	}

783
}