Browse Source

DATACMNS-1206 - Add API to read methods in declaration order.

We now provide MethodsMetadataReader to read method metadata from a class file. MethodMetadata is read for all user-declared methods except for constructors (which are technically methods, too).

MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory();
MethodsMetadataReader metadataReader = factory.getMetadataReader("com.acme.Foo");
MethodsMetadata metadata = metadataReader.getMethodsMetadata();

This new API is now used by DefaultProjectionInformation to make sure the order of input properties is based on the declaration order in the projection interfaces. Previously that order could not be guaranteed to be stable.

Original pull request: #263.
pull/269/head
Mark Paluch 8 years ago
parent
commit
9e013d3c14
  1. 94
      src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java
  2. 51
      src/main/java/org/springframework/data/type/MethodsMetadata.java
  3. 33
      src/main/java/org/springframework/data/type/MethodsMetadataReader.java
  4. 99
      src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java
  5. 76
      src/main/java/org/springframework/data/type/classreading/MethodsMetadataReaderFactory.java
  6. 107
      src/main/java/org/springframework/data/type/classreading/MethodsMetadataReadingVisitor.java
  7. 6
      src/main/java/org/springframework/data/type/classreading/package-info.java
  8. 5
      src/main/java/org/springframework/data/type/package-info.java
  9. 36
      src/test/java/org/springframework/data/projection/DefaultProjectionInformationUnitTests.java
  10. 70
      src/test/java/org/springframework/data/type/MethodsMetadataReaderFactoryUnitTests.java
  11. 117
      src/test/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReaderUnitTests.java

94
src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java

@ -16,14 +16,26 @@ @@ -16,14 +16,26 @@
package org.springframework.data.projection;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.springframework.beans.BeanUtils;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.data.type.MethodsMetadata;
import org.springframework.data.type.MethodsMetadataReader;
import org.springframework.data.type.classreading.MethodsMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Default implementation of {@link ProjectionInformation}. Exposes all properties of the type as required input
@ -31,6 +43,7 @@ import org.springframework.util.Assert; @@ -31,6 +43,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.12
*/
class DefaultProjectionInformation implements ProjectionInformation {
@ -43,7 +56,7 @@ class DefaultProjectionInformation implements ProjectionInformation { @@ -43,7 +56,7 @@ class DefaultProjectionInformation implements ProjectionInformation {
*
* @param type must not be {@literal null}.
*/
public DefaultProjectionInformation(Class<?> type) {
DefaultProjectionInformation(Class<?> type) {
Assert.notNull(type, "Projection type must not be null!");
@ -102,17 +115,86 @@ class DefaultProjectionInformation implements ProjectionInformation { @@ -102,17 +115,86 @@ class DefaultProjectionInformation implements ProjectionInformation {
private static List<PropertyDescriptor> collectDescriptors(Class<?> type) {
List<PropertyDescriptor> result = new ArrayList<>();
result.addAll(Arrays.stream(BeanUtils.getPropertyDescriptors(type))//
.filter(it -> !hasDefaultGetter(it))//
.collect(Collectors.toList()));
for (Class<?> interfaze : type.getInterfaces()) {
result.addAll(collectDescriptors(interfaze));
Optional<MethodsMetadata> metadata = getMetadata(type);
Stream<PropertyDescriptor> stream = Arrays.stream(BeanUtils.getPropertyDescriptors(type))//
.filter(it -> !hasDefaultGetter(it));
Stream<PropertyDescriptor> streamToUse = metadata.map(DefaultProjectionInformation::getMethodOrder)
.filter(it -> !it.isEmpty()) //
.map(it -> stream.filter(descriptor -> it.containsKey(descriptor.getReadMethod().getName()))
.sorted(Comparator.comparingInt(left -> it.get(left.getReadMethod().getName())))) //
.orElse(stream);
result.addAll(streamToUse.collect(Collectors.toList()));
if (metadata.isPresent()) {
Stream<String> interfaceNames = metadata.map(ClassMetadata::getInterfaceNames) //
.map(Arrays::stream) //
.orElse(Stream.empty());
result.addAll(interfaceNames.map(it -> loadClass(it, type.getClassLoader())) //
.map(DefaultProjectionInformation::collectDescriptors) //
.flatMap(List::stream) //
.collect(Collectors.toList()));
} else {
for (Class<?> interfaze : type.getInterfaces()) {
result.addAll(collectDescriptors(interfaze));
}
}
return result.stream().distinct().collect(Collectors.toList());
}
private static Class<?> loadClass(String className, ClassLoader classLoader) {
try {
return ClassUtils.forName(className, classLoader);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(String.format("Cannot load class %s", className));
}
}
/**
* Returns a {@link Map} containing method name to its positional index according to {@link MethodsMetadata}.
*
* @param metadata
* @return
*/
private static Map<String, Integer> getMethodOrder(MethodsMetadata metadata) {
List<String> methods = metadata.getMethods() //
.stream() //
.map(MethodMetadata::getMethodName) //
.distinct() //
.collect(Collectors.toList());
return IntStream.range(0, methods.size()) //
.boxed() //
.collect(Collectors.toMap(methods::get, i -> i));
}
/**
* Attempts to obtain {@link MethodsMetadata} from {@link Class}. Returns {@link Optional} containing
* {@link MethodsMetadata} if metadata was read successfully, {@link Optional#empty()} otherwise.
*
* @param type must not be {@literal null}.
* @return the optional {@link MethodsMetadata}.
*/
private static Optional<MethodsMetadata> getMetadata(Class<?> type) {
try {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(type.getClassLoader());
MethodsMetadataReader metadataReader = factory.getMetadataReader(ClassUtils.getQualifiedName(type));
return Optional.of(metadataReader.getMethodsMetadata());
} catch (IOException e) {
return Optional.empty();
}
}
/**
* Returns whether the given {@link PropertyDescriptor} has a getter that is a Java 8 default method.
*

51
src/main/java/org/springframework/data/type/MethodsMetadata.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* Copyright 2018 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.data.type;
import java.util.Set;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
/**
* Interface that defines abstract metadata of a specific class, in a form that does not require that class to be loaded
* yet.
*
* @author Mark Paluch
* @since 2.1
* @see MethodMetadata
* @see ClassMetadata
* @see MethodsMetadataReader#getMethodsMetadata()
*/
public interface MethodsMetadata extends ClassMetadata {
/**
* Return all methods.
*
* @return the methods declared in the class ordered as found in the class file. Order does not necessarily reflect
* the declaration order in the source file.
*/
Set<MethodMetadata> getMethods();
/**
* Return all methods matching method {@code name}.
*
* @param name name of the method, must not be {@literal null} or empty.
* @return the methods matching method {@code name } declared in the class ordered as found in the class file. Order
* does not necessarily reflect the declaration order in the source file.
*/
Set<MethodMetadata> getMethods(String name);
}

33
src/main/java/org/springframework/data/type/MethodsMetadataReader.java

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
/*
* Copyright 2018 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.data.type;
import org.springframework.core.type.classreading.MetadataReader;
/**
* Extension to {@link MetadataReader} for accessing class metadata and method metadata as read by an ASM
* {@link org.springframework.asm.ClassReader}.
*
* @author Mark Paluch
* @since 2.1
*/
public interface MethodsMetadataReader extends MetadataReader {
/**
* @return the metadata for methods in the class file.
*/
MethodsMetadata getMethodsMetadata();
}

99
src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
/*
* Copyright 2018 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.data.type.classreading;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.asm.ClassReader;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.data.type.MethodsMetadata;
import org.springframework.data.type.MethodsMetadataReader;
import org.springframework.lang.Nullable;
/**
* {@link MethodsMetadataReader} implementation based on an ASM {@link org.springframework.asm.ClassReader}.
*
* @author Mark Paluch
* @since 2.1
*/
class DefaultMethodsMetadataReader implements MethodsMetadataReader {
private final Resource resource;
private final ClassMetadata classMetadata;
private final AnnotationMetadata annotationMetadata;
private final MethodsMetadata methodsMetadata;
DefaultMethodsMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
this.resource = resource;
ClassReader classReader;
try (InputStream is = new BufferedInputStream(getResource().getInputStream())) {
classReader = new ClassReader(is);
} catch (IllegalArgumentException ex) {
throw new NestedIOException("ASM ClassReader failed to parse class file - "
+ "probably due to a new Java class file version that isn't supported yet: " + getResource(), ex);
}
MethodsMetadataReadingVisitor visitor = new MethodsMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);
classMetadata = visitor;
annotationMetadata = visitor;
methodsMetadata = visitor;
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.MetadataReader#getResource()
*/
@Override
public Resource getResource() {
return resource;
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.MetadataReader#getClassMetadata()
*/
@Override
public ClassMetadata getClassMetadata() {
return classMetadata;
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata()
*/
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
/* (non-Javadoc)
* @see org.springframework.data.util.ClassMetadataReader#getMethodsMetadata()
*/
@Override
public MethodsMetadata getMethodsMetadata() {
return methodsMetadata;
}
}

76
src/main/java/org/springframework/data/type/classreading/MethodsMetadataReaderFactory.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* Copyright 2018 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.data.type.classreading;
import java.io.IOException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.data.type.MethodsMetadata;
import org.springframework.data.type.MethodsMetadataReader;
import org.springframework.lang.Nullable;
/**
* Extension of {@link SimpleMetadataReaderFactory} that reads {@link MethodsMetadata}, creating a new ASM
* {@link MethodsMetadataReader} for every request.
*
* @author Mark Paluch
* @since 2.1
*/
public class MethodsMetadataReaderFactory extends SimpleMetadataReaderFactory {
/**
* Create a new {@link MethodsMetadataReaderFactory} for the default class loader.
*/
public MethodsMetadataReaderFactory() {}
/**
* Create a new {@link MethodsMetadataReaderFactory} for the given {@link ResourceLoader}.
*
* @param resourceLoader the Spring {@link ResourceLoader} to use (also determines the {@link ClassLoader} to use).
*/
public MethodsMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* Create a new {@link MethodsMetadataReaderFactory} for the given {@link ClassLoader}.
*
* @param classLoader the class loader to use.
*/
public MethodsMetadataReaderFactory(@Nullable ClassLoader classLoader) {
super(classLoader);
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.SimpleMetadataReaderFactory#getMetadataReader(java.lang.String)
*/
@Override
public MethodsMetadataReader getMetadataReader(String className) throws IOException {
return (MethodsMetadataReader) super.getMetadataReader(className);
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.SimpleMetadataReaderFactory#getMetadataReader(org.springframework.core.io.Resource)
*/
@Override
public MethodsMetadataReader getMetadataReader(Resource resource) throws IOException {
return new DefaultMethodsMetadataReader(resource, getResourceLoader().getClassLoader());
}
}

107
src/main/java/org/springframework/data/type/classreading/MethodsMetadataReadingVisitor.java

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
/*
* Copyright 2018 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.data.type.classreading;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor;
import org.springframework.core.type.classreading.MethodMetadataReadingVisitor;
import org.springframework.data.type.MethodsMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* ASM class visitor which looks for the class name and implemented types as well as for the methods defined in the
* class, exposing them through the {@link MethodsMetadata} interface.
*
* @author Mark Paluch
* @since 2.1
* @see ClassMetadata
* @see MethodMetadata
* @see MethodMetadataReadingVisitor
*/
class MethodsMetadataReadingVisitor extends AnnotationMetadataReadingVisitor implements MethodsMetadata {
/**
* Construct a new {@link MethodsMetadataReadingVisitor} given {@link ClassLoader}.
*
* @param classLoader may be {@literal null}.
*/
MethodsMetadataReadingVisitor(@Nullable ClassLoader classLoader) {
super(classLoader);
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// Skip bridge methods - we're only interested in original user methods.
// On JDK 8, we'd otherwise run into double detection of the same method...
if ((access & Opcodes.ACC_BRIDGE) != 0) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
// Skip constructors
if (name.equals("<init>")) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
MethodMetadataReadingVisitor visitor = new MethodMetadataReadingVisitor(name, access, getClassName(),
Type.getReturnType(desc).getClassName(), this.classLoader, this.methodMetadataSet);
this.methodMetadataSet.add(visitor);
return visitor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.MethodsMetadata#getMethods()
*/
@Override
public Set<MethodMetadata> getMethods() {
return Collections.unmodifiableSet(methodMetadataSet);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.MethodsMetadata#getMethods(String)
*/
@Override
public Set<MethodMetadata> getMethods(String name) {
Assert.hasText(name, "Method name must not be null or empty");
Set<MethodMetadata> result = new LinkedHashSet<>(4);
for (MethodMetadata metadata : methodMetadataSet) {
if (metadata.getMethodName().equals(name)) {
result.add(metadata);
}
}
return Collections.unmodifiableSet(result);
}
}

6
src/main/java/org/springframework/data/type/classreading/package-info.java

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
/**
* Support classes for reading annotation and class-level metadata.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.type.classreading;

5
src/main/java/org/springframework/data/type/package-info.java

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
/**
* Core support package for type introspection.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.type;

36
src/test/java/org/springframework/data/projection/DefaultProjectionInformationUnitTests.java

@ -28,6 +28,7 @@ import org.junit.Test; @@ -28,6 +28,7 @@ import org.junit.Test;
* Unit tests for {@link DefaultProjectionInformation}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class DefaultProjectionInformationUnitTests {
@ -47,6 +48,23 @@ public class DefaultProjectionInformationUnitTests { @@ -47,6 +48,23 @@ public class DefaultProjectionInformationUnitTests {
assertThat(toNames(information.getInputProperties())).containsExactly("age", "firstname", "lastname");
}
@Test // DATACMNS-1206
public void discoversInputPropertiesInOrder() {
ProjectionInformation information = new DefaultProjectionInformation(SameMethodNamesInAlternateOrder.class);
assertThat(toNames(information.getInputProperties())).containsExactly("firstname", "lastname");
}
@Test // DATACMNS-1206
public void discoversAllInputPropertiesInOrder() {
assertThat(toNames(new DefaultProjectionInformation(CompositeProjection.class).getInputProperties()))
.containsExactly("firstname", "lastname", "age");
assertThat(toNames(new DefaultProjectionInformation(ReorderedCompositeProjection.class).getInputProperties()))
.containsExactly("age", "firstname", "lastname");
}
@Test // DATACMNS-967
public void doesNotConsiderDefaultMethodInputProperties() throws Exception {
@ -76,6 +94,24 @@ public class DefaultProjectionInformationUnitTests { @@ -76,6 +94,24 @@ public class DefaultProjectionInformationUnitTests {
int getAge();
}
interface SameMethodNamesInAlternateOrder {
String getFirstname();
String getLastname();
String getFirstname(String foo);
}
interface CompositeProjection extends CustomerProjection, AgeProjection {}
interface ReorderedCompositeProjection extends AgeProjection, CustomerProjection {}
interface AgeProjection {
int getAge();
}
interface WithDefaultMethod {
String getFirstname();

70
src/test/java/org/springframework/data/type/MethodsMetadataReaderFactoryUnitTests.java

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/*
* Copyright 2018 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.data.type;
import static org.assertj.core.api.Assertions.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import org.junit.Test;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.type.classreading.MethodsMetadataReaderFactory;
/**
* Unit tests for {@link MethodsMetadataReaderFactory}.
*
* @author Mark Paluch
*/
public class MethodsMetadataReaderFactoryUnitTests {
@Test // DATACMNS-1206
public void shouldReadFromDefaultClassLoader() throws IOException {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory();
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName());
assertThat(reader).isNotNull();
}
@Test // DATACMNS-1206
public void shouldReadFromClassLoader() throws IOException {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(getClass().getClassLoader());
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName());
assertThat(reader).isNotNull();
}
@Test // DATACMNS-1206
public void shouldNotFindClass() {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(new URLClassLoader(new URL[0], null));
assertThatThrownBy(() -> factory.getMetadataReader(getClass().getName())).isInstanceOf(FileNotFoundException.class);
}
@Test // DATACMNS-1206
public void shouldReadFromResourceLoader() throws IOException {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(new DefaultResourceLoader());
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName());
assertThat(reader).isNotNull();
}
}

117
src/test/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReaderUnitTests.java

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
/*
* Copyright 2018 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.data.type.classreading;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
import java.util.Iterator;
import org.junit.Test;
import org.springframework.core.type.MethodMetadata;
import org.springframework.data.type.MethodsMetadata;
import org.springframework.data.type.MethodsMetadataReader;
/**
* Unit tests for {@link DefaultMethodsMetadataReader}.
*
* @author Mark Paluch
*/
public class DefaultMethodsMetadataReaderUnitTests {
@Test // DATACMNS-1206
public void shouldReadClassMethods() throws IOException {
MethodsMetadata metadata = getMethodsMetadata(Foo.class);
assertThat(metadata.getMethods()).hasSize(3);
Iterator<MethodMetadata> iterator = metadata.getMethods().iterator();
assertThat(iterator.next().getMethodName()).isEqualTo("one");
assertThat(iterator.next().getMethodName()).isEqualTo("two");
assertThat(iterator.next().getMethodName()).isEqualTo("three");
}
@Test // DATACMNS-1206
public void shouldReadInterfaceMethods() throws IOException {
MethodsMetadata metadata = getMethodsMetadata(Baz.class);
assertThat(metadata.getMethods()).hasSize(3);
Iterator<MethodMetadata> iterator = metadata.getMethods().iterator();
assertThat(iterator.next().getMethodName()).isEqualTo("one");
assertThat(iterator.next().getMethodName()).isEqualTo("two");
assertThat(iterator.next().getMethodName()).isEqualTo("three");
}
@Test // DATACMNS-1206
public void shouldMetadata() throws IOException {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory();
MethodsMetadataReader metadataReader = factory.getMetadataReader(getClass().getName());
assertThat(metadataReader.getClassMetadata()).isNotNull();
assertThat(metadataReader.getAnnotationMetadata()).isNotNull();
}
@Test // DATACMNS-1206
public void shouldReturnMethodMetadataByName() throws IOException {
MethodsMetadata metadata = getMethodsMetadata(Foo.class);
assertThat(metadata.getMethods()).hasSize(3);
assertThat(metadata.getMethods("one")).extracting(MethodMetadata::getMethodName).contains("one");
assertThat(metadata.getMethods("foo")).isEmpty();
}
private static MethodsMetadata getMethodsMetadata(Class<?> classToIntrospect) throws IOException {
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory();
MethodsMetadataReader metadataReader = factory.getMetadataReader(classToIntrospect.getName());
return metadataReader.getMethodsMetadata();
}
// Create a scenario with a cyclic dependency to mix up methods reported by class.getDeclaredMethods()
// That's not exactly deterministic because it depends on when the compiler sees the classes.
abstract class Foo {
abstract void one(Foo b);
abstract void two(Bar b);
abstract void three(Foo b);
}
interface Baz {
void one(Foo b);
void two(Bar b);
void three(Baz b);
}
abstract class Bar {
abstract void dependOnFoo(Foo f);
abstract void dependOnBaz(Baz f);
}
}
Loading…
Cancel
Save