diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/CandidateComponentsIndexer.java b/spring-context-indexer/src/main/java/org/springframework/context/index/CandidateComponentsIndexer.java index 0061c7de24..1480b8474b 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/CandidateComponentsIndexer.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/CandidateComponentsIndexer.java @@ -19,6 +19,7 @@ package org.springframework.context.index; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -29,7 +30,9 @@ import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; /** @@ -42,6 +45,10 @@ import javax.lang.model.element.TypeElement; */ public class CandidateComponentsIndexer implements Processor { + private static final Set TYPE_KINDS = + Collections.unmodifiableSet(EnumSet.of(ElementKind.CLASS, + ElementKind.INTERFACE)); + private MetadataStore metadataStore; private MetadataCollector metadataCollector; @@ -102,6 +109,11 @@ public class CandidateComponentsIndexer implements Processor { } private void processElement(Element element) { + addMetadataFor(element); + staticTypesIn(element.getEnclosedElements()).forEach(this::processElement); + } + + private void addMetadataFor(Element element) { Set stereotypes = new LinkedHashSet<>(); this.stereotypesProviders.forEach(p -> stereotypes.addAll(p.getStereotypes(element))); if (!stereotypes.isEmpty()) { @@ -121,4 +133,14 @@ public class CandidateComponentsIndexer implements Processor { } } + private static List staticTypesIn(Iterable elements) { + List list = new ArrayList<>(); + for (Element e : elements) { + if (TYPE_KINDS.contains(e.getKind()) + && e.getModifiers().contains(Modifier.STATIC)) + list.add(TypeElement.class.cast(e)); + } + return list; + } + } diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/CandidateComponentsIndexerTests.java b/spring-context-indexer/src/test/java/org/springframework/context/index/CandidateComponentsIndexerTests.java index 8cd75d5f25..cac34ee00b 100644 --- a/spring-context-indexer/src/test/java/org/springframework/context/index/CandidateComponentsIndexerTests.java +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/CandidateComponentsIndexerTests.java @@ -38,6 +38,7 @@ import org.springframework.context.index.sample.SampleComponent; import org.springframework.context.index.sample.SampleController; import org.springframework.context.index.sample.SampleMetaController; import org.springframework.context.index.sample.SampleMetaIndexedController; +import org.springframework.context.index.sample.SampleNonStaticEmbedded; import org.springframework.context.index.sample.SampleNone; import org.springframework.context.index.sample.SampleRepository; import org.springframework.context.index.sample.SampleService; @@ -45,6 +46,7 @@ import org.springframework.context.index.sample.cdi.SampleManagedBean; import org.springframework.context.index.sample.cdi.SampleNamed; import org.springframework.context.index.sample.jpa.SampleConverter; import org.springframework.context.index.sample.jpa.SampleEmbeddable; +import org.springframework.context.index.sample.SampleEmbedded; import org.springframework.context.index.sample.jpa.SampleEntity; import org.springframework.context.index.sample.jpa.SampleMappedSuperClass; import org.springframework.context.index.sample.type.Repo; @@ -55,6 +57,7 @@ import org.springframework.context.index.sample.type.SmartRepo; import org.springframework.context.index.sample.type.SpecializedRepo; import org.springframework.context.index.test.TestCompiler; import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -198,6 +201,26 @@ public class CandidateComponentsIndexerTests { testSingleComponent(Repo.class, Repo.class); } + @Test + public void embeddedCandidatesAreDetected() + throws IOException, ClassNotFoundException { + // Validate nested type structure + String nestedType = "org.springframework.context.index.sample.SampleEmbedded.Another$AnotherPublicCandidate"; + Class type = ClassUtils.forName(nestedType, getClass().getClassLoader()); + assertThat(type, sameInstance(SampleEmbedded.Another.AnotherPublicCandidate.class)); + + CandidateComponentsMetadata metadata = compile(SampleEmbedded.class); + assertThat(metadata, hasComponent( + SampleEmbedded.PublicCandidate.class, Component.class)); + assertThat(metadata, hasComponent(nestedType, Component.class.getName())); + assertThat(metadata.getItems(), hasSize(2)); + } + + @Test + public void embeddedNonStaticCandidateAreIgnored() throws IOException { + CandidateComponentsMetadata metadata = compile(SampleNonStaticEmbedded.class); + assertThat(metadata.getItems(), hasSize(0)); + } private void testComponent(Class... classes) throws IOException { CandidateComponentsMetadata metadata = compile(classes); diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleEmbedded.java b/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleEmbedded.java new file mode 100644 index 0000000000..d7bd7cb5e4 --- /dev/null +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleEmbedded.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2017 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.index.sample; + +import org.springframework.stereotype.Component; + +/** + * Test candidate for an embedded {@link Component}. + * + * @author Stephane Nicoll + */ +public class SampleEmbedded { + + @Component + public static class PublicCandidate { + + } + + public static class Another { + + @Component + public static class AnotherPublicCandidate { + + } + } + +} diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleNonStaticEmbedded.java b/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleNonStaticEmbedded.java new file mode 100644 index 0000000000..5f74293c83 --- /dev/null +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/sample/SampleNonStaticEmbedded.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2017 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.index.sample; + +import org.springframework.stereotype.Component; + +/** + * Candidate with a inner class that isn't static (and should therefore not be added). + * + * @author Stephane Nicoll + */ +public class SampleNonStaticEmbedded { + + @Component + public class InvalidCandidate { + + } + +}