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.
This commit is contained in:
Mark Paluch
2017-11-29 17:25:06 +01:00
parent 4a2df6299b
commit 9e013d3c14
11 changed files with 688 additions and 6 deletions

View File

@@ -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 {
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 {
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();

View File

@@ -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();
}
}

View File

@@ -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);
}
}