diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Property.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Property.java index 4f4e4222d..8af6a6866 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Property.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Property.java @@ -15,8 +15,9 @@ */ package org.springframework.data.repository.query.parser; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,8 +33,9 @@ import org.springframework.util.StringUtils; * @author Oliver Gierke */ public class Property { - - private static String ERROR_TEMPLATE = "No property %s found for type %s"; + + private static final Pattern SPLITTER = Pattern.compile("(?:_?(_*?[^_]+))"); + private static final String ERROR_TEMPLATE = "No property %s found for type %s"; private final String name; private final TypeInformation type; @@ -228,8 +230,15 @@ public class Property { } private static Property from(String source, TypeInformation type) { + + List iteratorSource = new ArrayList(); + Matcher matcher = SPLITTER.matcher("_" + source); + + while (matcher.find()) { + iteratorSource.add(matcher.group(1)); + } - Iterator parts = Arrays.asList(source.split("_")).iterator(); + Iterator parts = iteratorSource.iterator(); Property result = null; Property current = null; diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 8c5dc3dfd..aa95a2f61 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -17,9 +17,11 @@ package org.springframework.data.util; import static org.springframework.util.ObjectUtils.*; +import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -28,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.BeanUtils; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -159,15 +162,51 @@ class TypeDiscoverer implements TypeInformation { return info == null ? null : info.getProperty(fieldname.substring(separatorIndex + 1)); } + /** + * Returns the {@link TypeInformation} for the given atomic field. Will inspect fields first and return the type of a + * field if available. Otherwise it will fall back to a {@link PropertyDescriptor}. + * + * @see #getGenericType(PropertyDescriptor) + * @param fieldname + * @return + */ private TypeInformation getPropertyInformation(String fieldname) { + + Class type = getType(); + Field field = ReflectionUtils.findField(type, fieldname); - Field field = ReflectionUtils.findField(getType(), fieldname); + if (field != null) { + return createInfo(field.getGenericType()); + } + + PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, fieldname); - if (field == null) { + return descriptor == null ? null : createInfo(getGenericType(descriptor)); + } + + /** + * Returns the generic type for the given {@link PropertyDescriptor}. Will inspect its read method + * followed by the first parameter of the write method. + * + * @param descriptor must not be {@literal null} + * @return + */ + private static Type getGenericType(PropertyDescriptor descriptor) { + + Method method = descriptor.getReadMethod(); + + if (method != null) { + return method.getGenericReturnType(); + } + + method = descriptor.getWriteMethod(); + + if (method == null) { return null; } - - return createInfo(field.getGenericType()); + + Type[] parameterTypes = method.getGenericParameterTypes(); + return parameterTypes.length == 0 ? null : parameterTypes[0]; } /* diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/PropertyUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/PropertyUnitTests.java index a426919db..69781b082 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/PropertyUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/PropertyUnitTests.java @@ -71,7 +71,7 @@ public class PropertyUnitTests { @Test - public void prfersExplicitPaths() throws Exception { + public void prefersExplicitPaths() throws Exception { Property reference = Property.from("user_name", Sample.class); assertThat(reference.getName(), is("user")); @@ -130,9 +130,24 @@ public class PropertyUnitTests { Property from = Property.from("barUserName", Sample.class); } + /** + * @see DATACMNS-45 + */ + @Test + public void handlesEmptyUnderscoresCorrectly() { + + Property property = Property.from("_foo", Sample2.class); + assertThat(property.getName(), is("_foo")); + assertThat(property.getType(), is(typeCompatibleWith(Foo.class))); + + property = Property.from("_foo__email", Sample2.class); + assertThat(property.toDotPath(), is("_foo._email")); + } + private class Foo { String userName; + String _email; } private class Bar { @@ -159,5 +174,6 @@ public class PropertyUnitTests { private String userNameWhatever; private FooBar user; + private Foo _foo; } } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java index b700a832b..ba4cb6daf 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java @@ -15,7 +15,7 @@ */ package org.springframework.data.util; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.util.Calendar; @@ -134,6 +134,20 @@ public class ClassTypeInformationUnitTests { assertFalse(first.getProperty("wrapped").equals(second.getProperty("wrapped"))); } + @Test + public void handlesPropertyFieldMismatchCorrectly() { + + TypeInformation from = ClassTypeInformation.from(PropertyGetter.class); + + TypeInformation property = from.getProperty("_name"); + assertThat(property, is(notNullValue())); + assertThat(property.getType(), is(typeCompatibleWith(String.class))); + + property = from.getProperty("name"); + assertThat(property, is(notNullValue())); + assertThat(property.getType(), is(typeCompatibleWith(byte[].class))); + } + static class StringMapContainer extends MapContainer { } @@ -199,4 +213,12 @@ public class ClassTypeInformationUnitTests { static class AnotherConcreteWrapper extends GenericWrapper { } + + static class PropertyGetter { + private String _name; + + public byte[] getName() { + return _name.getBytes(); + } + } }