/*
* Copyright 2002-2013 the original author or authors.
*
* 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.
*/
package org.springframework.context.annotation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.NestedIOException;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
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;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.StringUtils;
import static org.springframework.context.annotation.MetadataUtils.*;
/**
* Parses a {@link Configuration} class definition, populating a collection of
* {@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).
*
*
This class helps separate the concern of parsing the structure of a Configuration
* class from the concern of registering BeanDefinition objects based on the
* content of that model (with the exception of {@code @ComponentScan} annotations which
* need to be registered immediately).
*
*
This ASM-based implementation avoids reflection and eager class loading in order to
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.0
* @see ConfigurationClassBeanDefinitionReader
*/
class ConfigurationClassParser {
private static final Comparator DEFERRED_IMPORT_COMPARATOR =
new Comparator() {
@Override
public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder o2) {
return AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());
}
};
private final MetadataReaderFactory metadataReaderFactory;
private final ProblemReporter problemReporter;
private final Environment environment;
private final ResourceLoader resourceLoader;
private final BeanDefinitionRegistry registry;
private final ComponentScanAnnotationParser componentScanParser;
private final Set configurationClasses = new LinkedHashSet();
private final Map knownSuperclasses = new HashMap();
private final Stack> propertySources = new Stack>();
private final ImportStack importStack = new ImportStack();
private final List deferredImportSelectors = new LinkedList();
/**
* Create a new {@link ConfigurationClassParser} instance that will be used
* to populate the set of configuration classes.
*/
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
this.metadataReaderFactory = metadataReaderFactory;
this.problemReporter = problemReporter;
this.environment = environment;
this.resourceLoader = resourceLoader;
this.registry = registry;
this.componentScanParser = new ComponentScanAnnotationParser(
resourceLoader, environment, componentScanBeanNameGenerator, registry);
}
public void parse(Set 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();
}
/**
* 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)
*/
public void parse(String className, String beanName) throws IOException {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
/**
* Parse the specified {@link Configuration @Configuration} class.
* @param clazz the Class to parse
* @param beanName must not be null (as of Spring 3.1.1)
*/
public void parse(Class> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
for (Iterator it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
if (configClass.equals(it.next())) {
it.remove();
}
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.add(configClass);
}
/**
* 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
*/
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
// process any @PropertySource annotations
AnnotationAttributes propertySource = attributesFor(sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class);
if (propertySource != null) {
processPropertySource(propertySource);
}
// process any @ComponentScan annotations
AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class);
if (componentScan != null) {
// the config class is annotated with @ComponentScan -> perform the scan immediately
if (!ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip(
this.registry, this.environment)) {
Set scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 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());
}
}
}
}
// process any @Import annotations
processImports(configClass, getImports(sourceClass), true);
// process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource = attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("value");
Class extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
configClass.addImportedResource(resource, readerClass);
}
}
// process individual @Bean methods
Set beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// superclass found, return its annotation metadata and recurse
try {
return sourceClass.getSuperClass();
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
}
// no superclass, processing is complete
return null;
}
/**
* Register member (nested) classes that happen to be configuration classes themselves.
* @param sourceClass the source class to process
* @throws IOException if there is any problem reading metadata from a member class
*/
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
for (SourceClass memberClass : sourceClass.getMemberClasses()) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())) {
processConfigurationClass(memberClass.asConfigClass(configClass));
}
}
}
/**
* Process the given @PropertySource
annotation metadata.
* @param propertySource metadata for the @PropertySource
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);
}
}
}
/**
* Returns {@code @Import} class, considering all meta-annotations.
*/
private Set getImports(SourceClass sourceClass) throws IOException {
Set imports = new LinkedHashSet();
Set visited = new LinkedHashSet();
collectImports(sourceClass, imports, visited);
return imports;
}
/**
* 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.
*
* For example, it is common for a {@code @Configuration} class to declare direct
* {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
* annotation.
*
* @param sourceClass the class to search
* @param imports the imports collected so far
* @param visited used to track visited classes to prevent infinite recursion
* @throws IOException if there is any problem reading metadata from the named class
*/
private void collectImports(SourceClass sourceClass, Set imports,
Set 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);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Unable to collect imports", ex);
}
}
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());
processImports(configClass, asSourceClasses(imports), false);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
}
}
this.deferredImportSelectors.clear();
}
private void processImports(ConfigurationClass configClass,
Collection sourceClasses, boolean checkForCircularImports)
throws IOException {
if(sourceClasses.isEmpty()) {
return;
}
if (checkForCircularImports && this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
}
else {
this.importStack.push(configClass);
AnnotationMetadata importingClassMetadata = configClass.getMetadata();
try {
for (SourceClass candidate : sourceClasses) {
if (candidate.isAssignable(ImportSelector.class)) {
// the candidate class is an ImportSelector -> delegate to it to determine imports
Class> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
invokeAwareMethods(selector);
if(selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(importingClassMetadata);
Collection importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
Class> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
invokeAwareMethods(registrar);
configClass.addImportBeanDefinitionRegistrar(registrar);
}
else {
// candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class
this.importStack.registerImport(importingClassMetadata.getClassName(),
candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Failed to load import candidate class", ex);
}
finally {
this.importStack.pop();
}
}
}
/**
* Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
* {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
*/
private void invokeAwareMethods(Object importStrategyBean) {
if (importStrategyBean instanceof Aware) {
if (importStrategyBean instanceof EnvironmentAware) {
((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
}
if (importStrategyBean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
}
if (importStrategyBean instanceof BeanClassLoaderAware) {
ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
this.resourceLoader.getClassLoader());
((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader);
}
if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry);
}
}
}
/**
* Validate each {@link ConfigurationClass} object.
* @see ConfigurationClass#validate
*/
public void validate() {
for (ConfigurationClass configClass : this.configurationClasses) {
configClass.validate(this.problemReporter);
}
}
public Set getConfigurationClasses() {
return this.configurationClasses;
}
public Stack> getPropertySources() {
return this.propertySources;
}
ImportRegistry getImportRegistry() {
return this.importStack;
}
/**
* 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 asSourceClasses(String[] classNamess)
throws ClassNotFoundException, IOException {
List annotatedClasses = new ArrayList();
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));
}
interface ImportRegistry {
String getImportingClassFor(String importedClass);
}
@SuppressWarnings("serial")
private static class ImportStack extends Stack implements ImportRegistry {
private final Map imports = new HashMap();
public void registerImport(String importingClass, String importedClass) {
this.imports.put(importedClass, importingClass);
}
@Override
public String getImportingClassFor(String importedClass) {
return this.imports.get(importedClass);
}
/**
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
* exists within this stack that has the same name as elem. Elem must be of
* type ConfigurationClass.
*/
@Override
public boolean contains(Object elem) {
ConfigurationClass configClass = (ConfigurationClass) elem;
Comparator comparator = new Comparator() {
@Override
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)
*
* - com.acme.Foo
* - com.acme.Bar
* - com.acme.Baz
*
* return "ImportStack: [Foo->Bar->Baz]".
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder("ImportStack: [");
Iterator iterator = iterator();
while (iterator.hasNext()) {
builder.append(iterator.next().getSimpleName());
if (iterator.hasNext()) {
builder.append("->");
}
}
return builder.append(']').toString();
}
}
private static class DeferredImportSelectorHolder {
private final ConfigurationClass configurationClass;
private final DeferredImportSelector importSelector;
public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) {
this.configurationClass = configurationClass;
this.importSelector = importSelector;
}
public ConfigurationClass getConfigurationClass() {
return this.configurationClass;
}
public DeferredImportSelector getImportSelector() {
return this.importSelector;
}
}
/**
* 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 getMemberClasses() throws IOException {
List members = new ArrayList();
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 getAnnotations() throws ClassNotFoundException, IOException {
Set annotations = new LinkedHashSet();
for(String annotation : getMetadata().getAnnotationTypes()) {
annotations.add(getRelated(annotation));
}
return annotations;
}
public Collection getAnnotationAttributes(String annotationType,
String attribute) throws ClassNotFoundException, IOException {
Map annotationAttributes = getMetadata().getAnnotationAttributes(
annotationType, true);
if (annotationAttributes == null
|| !annotationAttributes.containsKey(attribute)) {
return Collections.emptySet();
}
String[] classNames = (String[]) annotationAttributes.get(attribute);
Set rtn = new LinkedHashSet();
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();
}
}
/**
* {@link Problem} registered upon detection of a circular {@link Import}.
*/
private static class CircularImportProblem extends Problem {
public CircularImportProblem(ConfigurationClass attemptedImport, Stack 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));
}
}
}