diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java index f2c9ecb1651..3f0556935e6 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java @@ -89,9 +89,18 @@ public interface ClassMetadata { String getSuperClassName(); /** - * Return the name of all interfaces that the underlying class + * Return the names of all interfaces that the underlying class * implements, or an empty array if there are none. */ String[] getInterfaceNames(); + /** + * Return the names of all classes declared as members of the class represented by + * this ClassMetadata object. This includes public, protected, default (package) + * access, and private classes and interfaces declared by the class, but excludes + * inherited classes and interfaces. An empty array is returned if no member classes + * or interfaces exist. + */ + String[] getMemberClassNames(); + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java index b7ae58be73a..d40aea8354a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java @@ -17,6 +17,7 @@ package org.springframework.core.type; import java.lang.reflect.Modifier; +import java.util.LinkedHashSet; import org.springframework.util.Assert; @@ -102,4 +103,12 @@ public class StandardClassMetadata implements ClassMetadata { return ifcNames; } + public String[] getMemberClassNames() { + LinkedHashSet memberClassNames = new LinkedHashSet(); + for (Class nestedClass : this.introspectedClass.getDeclaredClasses()) { + memberClassNames.add(nestedClass.getName()); + } + return memberClassNames.toArray(new String[memberClassNames.size()]); + } + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java index 07581904273..4e21320fbd0 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -16,6 +16,9 @@ package org.springframework.core.type.classreading; +import java.util.LinkedHashSet; +import java.util.Set; + import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.Attribute; import org.springframework.asm.ClassVisitor; @@ -54,7 +57,9 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { private String superClassName; private String[] interfaces; - + + private Set memberClassNames = new LinkedHashSet(); + public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { this.className = ClassUtils.convertResourcePathToClassName(name); @@ -75,9 +80,16 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { } public void visitInnerClass(String name, String outerName, String innerName, int access) { - if (outerName != null && this.className.equals(ClassUtils.convertResourcePathToClassName(name))) { - this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerName); - this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0); + String fqName = ClassUtils.convertResourcePathToClassName(name); + String fqOuterName = ClassUtils.convertResourcePathToClassName(outerName); + if (outerName != null) { + if (this.className.equals(fqName)) { + this.enclosingClassName = fqOuterName; + this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0); + } + else if (this.className.equals(fqOuterName)) { + this.memberClassNames.add(fqName); + } } } @@ -153,4 +165,8 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { return this.interfaces; } + public String[] getMemberClassNames() { + return this.memberClassNames.toArray(new String[this.memberClassNames.size()]); + } + } diff --git a/org.springframework.core/src/test/java/org/springframework/core/type/AbstractClassMetadataMemberClassTests.java b/org.springframework.core/src/test/java/org/springframework/core/type/AbstractClassMetadataMemberClassTests.java new file mode 100644 index 00000000000..64802bbecf3 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/type/AbstractClassMetadataMemberClassTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2011 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.core.type; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +/** + * Abstract base class for testing implementations of + * {@link ClassMetadata#getMemberClassNames()}. + * + * @author Chris Beams + * @since 3.1 + */ +public abstract class AbstractClassMetadataMemberClassTests { + + public abstract ClassMetadata getClassMetadataFor(Class clazz); + + @Test + public void withNoMemberClasses() { + ClassMetadata metadata = getClassMetadataFor(L0_a.class); + String[] nestedClasses = metadata.getMemberClassNames(); + assertThat(nestedClasses, equalTo(new String[]{})); + } + + public static class L0_a { + } + + + @Test + public void withPublicMemberClasses() { + ClassMetadata metadata = getClassMetadataFor(L0_b.class); + String[] nestedClasses = metadata.getMemberClassNames(); + assertThat(nestedClasses, equalTo(new String[]{L0_b.L1.class.getName()})); + } + + public static class L0_b { + public static class L1 { } + } + + + @Test + public void withNonPublicMemberClasses() { + ClassMetadata metadata = getClassMetadataFor(L0_c.class); + String[] nestedClasses = metadata.getMemberClassNames(); + assertThat(nestedClasses, equalTo(new String[]{L0_c.L1.class.getName()})); + } + + public static class L0_c { + private static class L1 { } + } + + + @Test + public void againstMemberClass() { + ClassMetadata metadata = getClassMetadataFor(L0_b.L1.class); + String[] nestedClasses = metadata.getMemberClassNames(); + assertThat(nestedClasses, equalTo(new String[]{})); + } +} diff --git a/org.springframework.core/src/test/java/org/springframework/core/type/StandardClassMetadataMemberClassTests.java b/org.springframework.core/src/test/java/org/springframework/core/type/StandardClassMetadataMemberClassTests.java new file mode 100644 index 00000000000..716c03c4879 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/type/StandardClassMetadataMemberClassTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2011 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.core.type; + +/** + * @author Chris Beams + * @since 3.1 + * @see AbstractClassMetadataMemberClassTests + */ +public class StandardClassMetadataMemberClassTests + extends AbstractClassMetadataMemberClassTests { + + @Override + public ClassMetadata getClassMetadataFor(Class clazz) { + return new StandardClassMetadata(clazz); + } + +} diff --git a/org.springframework.core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java b/org.springframework.core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java new file mode 100644 index 00000000000..cc4ca072bd4 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2011 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.core.type.classreading; + +import java.io.IOException; + +import org.springframework.core.type.AbstractClassMetadataMemberClassTests; +import org.springframework.core.type.ClassMetadata; + +/** + * @author Chris Beams + * @since 3.1 + * @see AbstractClassMetadataMemberClassTests + */ +public class ClassMetadataReadingVisitorMemberClassTests + extends AbstractClassMetadataMemberClassTests { + + @Override + public ClassMetadata getClassMetadataFor(Class clazz) { + try { + MetadataReader reader = + new SimpleMetadataReaderFactory().getMetadataReader(clazz.getName()); + return reader.getAnnotationMetadata(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + +}