From c2fd1e255a9fce04500c6c05746bbf6ad77a3586 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 9 Mar 2012 15:06:06 +0100 Subject: [PATCH] DATACMNS-134 - Changed instantation infrastructure to be able to instantiate member types. In case a member class is not static the compiler will either create a constructor with a parameter of the enclosing type or alter the existing ones to add a first parameter of that type. Thus when instantiating the class we need to detect that case and hand in the parent object as parameter value. Updated PreferredConstructor and Parameter abstractions to allow detecting this case as well as the ParameterValueProvider abstraction to capture the parent object. --- .../data/mapping/PreferredConstructor.java | 31 +++++++- ...ersistentEntityParameterValueProvider.java | 14 +++- .../model/PreferredConstructorDiscoverer.java | 2 +- .../ReflectionEntityInstantiatorUnitTest.java | 31 ++++++++ ...eferredConstructorDiscovererUnitTests.java | 26 ++++++- ...EntityParameterValueProviderUnitTests.java | 73 +++++++++++++++++++ 6 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProviderUnitTests.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index 55dee69da..82515bea5 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -36,7 +36,7 @@ import org.springframework.util.StringUtils; public class PreferredConstructor> { private final Constructor constructor; - private final List> parameters; + private final List> parameters; /** * Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s. @@ -44,7 +44,7 @@ public class PreferredConstructor> { * @param constructor * @param parameters */ - public PreferredConstructor(Constructor constructor, Parameter... parameters) { + public PreferredConstructor(Constructor constructor, Parameter... parameters) { Assert.notNull(constructor); Assert.notNull(parameters); @@ -68,7 +68,7 @@ public class PreferredConstructor> { * * @return */ - public Iterable> getParameters() { + public Iterable> getParameters() { return parameters; } @@ -121,6 +121,25 @@ public class PreferredConstructor> { return false; } + /** + * Returns whether the given {@link Parameter} is one referring to an enclosing class. That is in case the class this + * {@link PreferredConstructor} belongs to is a member class actually. If that's the case the compiler creates a first + * constructor argument of the enclosing class type. + * + * @param parameter must not be {@literal null}. + * @return + */ + public boolean isEnclosingClassParameter(Parameter parameter) { + + Assert.notNull(parameter); + + if (parameters.isEmpty()) { + return false; + } + + return parameters.get(0).equals(parameter) && parameter.isEnclosingClassParameter(); + } + /** * Value object to represent constructor parameters. * @@ -220,5 +239,11 @@ public class PreferredConstructor> { P referencedProperty = entity == null ? null : entity.getPersistentProperty(name); return property == null ? false : property.equals(referencedProperty); } + + private boolean isEnclosingClassParameter() { + + Class owningType = entity.getType(); + return owningType.isMemberClass() && type.getType().equals(owningType.getEnclosingClass()); + } } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java index a42895e34..bc138064f 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java @@ -17,6 +17,7 @@ package org.springframework.data.mapping.model; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.util.Assert; @@ -33,6 +34,7 @@ public class PersistentEntityParameterValueProvider

entity; private final PropertyValueProvider

provider; + private final Object parent; private SpELExpressionEvaluator spELEvaluator; @@ -42,14 +44,17 @@ public class PersistentEntityParameterValueProvider

entity, PropertyValueProvider

provider) { + public PersistentEntityParameterValueProvider(PersistentEntity entity, PropertyValueProvider

provider, + Object parent) { Assert.notNull(entity); Assert.notNull(provider); this.entity = entity; this.provider = provider; + this.parent = parent; } /** @@ -66,12 +71,19 @@ public class PersistentEntityParameterValueProvider

T getParameterValue(Parameter parameter) { if (spELEvaluator != null && parameter.hasSpelExpression()) { return spELEvaluator.evaluate(parameter.getSpelExpression()); } + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + if (constructor.isEnclosingClassParameter(parameter)) { + return (T) parent; + } + P property = entity.getPersistentProperty(parameter.getName()); return provider.getPropertyValue(property); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java index d8e258aef..4c242d03d 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -107,7 +107,7 @@ public class PreferredConstructorDiscoverer> } String[] parameterNames = nameDiscoverer.getParameterNames(constructor); - Parameter[] parameters = new Parameter[parameterTypes.size()]; + Parameter[] parameters = new Parameter[parameterTypes.size()]; Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); for (int i = 0; i < parameterTypes.size(); i++) { diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java b/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java index 9e99ce612..d728cee19 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java @@ -15,6 +15,7 @@ */ package org.springframework.data.convert; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.data.convert.ReflectionEntityInstantiator.*; @@ -23,12 +24,16 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.convert.ReflectionEntityInstantiatorUnitTest.Outer.Inner; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.test.util.ReflectionTestUtils; /** * Unit tests for {@link ReflectionEntityInstantiator}. @@ -78,10 +83,36 @@ public class ReflectionEntityInstantiatorUnitTest

entity = new BasicPersistentEntity(ClassTypeInformation.from(Inner.class)); + PreferredConstructor constructor = entity.getPersistenceConstructor(); + Parameter parameter = constructor.getParameters().iterator().next(); + + Object outer = new Outer(); + + when(provider.getParameterValue(parameter)).thenReturn(outer); + Inner instance = INSTANCE.createInstance(entity, provider); + + assertThat(instance, is(notNullValue())); + assertThat(ReflectionTestUtils.getField(instance, "this$1"), is(outer)); + } + static class Foo { Foo(String foo) { } } + + static class Outer { + + class Inner { + + } + } } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java index 11b4626a6..4ad4a8559 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java @@ -23,7 +23,10 @@ import java.util.Iterator; import org.junit.Test; import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.PreferredConstructorDiscovererUnitTests.Outer.Inner; +import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; +import org.springframework.data.util.ClassTypeInformation; /** * Unit tests for {@link PreferredConstructorDiscoverer}. @@ -76,13 +79,27 @@ public class PreferredConstructorDiscovererUnitTests

> parameters = constructor.getParameters().iterator(); + Iterator> parameters = constructor.getParameters().iterator(); Parameter parameter = parameters.next(); assertThat(parameter.getType().getType(), typeCompatibleWith(Long.class)); assertThat(parameters.hasNext(), is(false)); } + /** + * @see DATACMNS-134 + */ + @Test + public void discoversInnerClassConstructorCorrectly() { + + PersistentEntity entity = new BasicPersistentEntity(ClassTypeInformation.from(Inner.class)); + PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer(entity); + PreferredConstructor constructor = discoverer.getConstructor(); + + Parameter parameter = constructor.getParameters().iterator().next(); + assertThat(constructor.isEnclosingClassParameter(parameter), is(true)); + } + static class EntityWithoutConstructor { } @@ -123,4 +140,11 @@ public class PreferredConstructorDiscovererUnitTests

> { + + @Mock + PropertyValueProvider

propertyValueProvider; + + /** + * @see DATACMNS-134 + */ + @Test + public void usesParentObjectAsImplicitFirstConstructorArgument() { + + Object outer = new Outer(); + + PersistentEntity entity = new BasicPersistentEntity(ClassTypeInformation.from(Inner.class)); + PreferredConstructor constructor = entity.getPersistenceConstructor(); + Iterator> iterator = constructor.getParameters().iterator(); + + ParameterValueProvider

provider = new PersistentEntityParameterValueProvider

(entity, propertyValueProvider, + outer); + assertThat(provider.getParameterValue(iterator.next()), is(outer)); + assertThat(provider.getParameterValue(iterator.next()), is(nullValue())); + assertThat(iterator.hasNext(), is(false)); + } + + static class Outer { + + class Inner { + + Inner(Object myObject) { + + } + } + } +}