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:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user