From d859540f92aa3e1e150d5dfe2b87174bbbcc0cc6 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 21 May 2015 19:57:28 +0200 Subject: [PATCH] DATACMNS-703 - PropertyReferenceException now exposes potential matches. We now use Spring's PropertyMatches to build up the exception message and include a list of potentially meant properties. Removed unused config file for tests along the way. Original pull request: #125. --- .../mapping/PropertyReferenceException.java | 41 ++++++++++++++++--- .../data/mapping/PropertyPathUnitTests.java | 22 +++++++++- .../data/repository/config/populators.xml | 18 -------- 3 files changed, 56 insertions(+), 25 deletions(-) delete mode 100644 src/test/resources/org/springframework/data/repository/config/populators.xml diff --git a/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java b/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java index dc074f624..a8c68b57a 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java +++ b/src/main/java/org/springframework/data/mapping/PropertyReferenceException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -15,10 +15,14 @@ */ package org.springframework.data.mapping; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.springframework.beans.PropertyMatches; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Exception being thrown when creating {@link PropertyPath} instances. @@ -29,10 +33,12 @@ public class PropertyReferenceException extends RuntimeException { private static final long serialVersionUID = -5254424051438976570L; private static final String ERROR_TEMPLATE = "No property %s found for type %s!"; + private static final String HINTS_TEMPLATE = " Did you mean %s?"; private final String propertyName; private final TypeInformation type; private final List alreadyResolvedPath; + private final List propertyMatches; /** * Creates a new {@link PropertyReferenceException}. @@ -41,7 +47,8 @@ public class PropertyReferenceException extends RuntimeException { * @param type the type the property could not be found on. * @param alreadyResolvedPah the previously calculated {@link PropertyPath}s. */ - public PropertyReferenceException(String propertyName, TypeInformation type, List alreadyResolvedPah) { + public PropertyReferenceException(String propertyName, TypeInformation type, + List alreadyResolvedPah) { Assert.hasText(propertyName); Assert.notNull(type); @@ -49,6 +56,7 @@ public class PropertyReferenceException extends RuntimeException { this.propertyName = propertyName; this.type = type; this.alreadyResolvedPath = alreadyResolvedPah; + this.propertyMatches = detectPotentialMatches(propertyName, type.getType()); } /** @@ -76,11 +84,18 @@ public class PropertyReferenceException extends RuntimeException { @Override public String getMessage() { - StringBuilder builder = new StringBuilder(String.format(ERROR_TEMPLATE, propertyName, type.getType() - .getSimpleName())); + StringBuilder builder = new StringBuilder( + String.format(ERROR_TEMPLATE, propertyName, type.getType().getSimpleName())); + + if (!propertyMatches.isEmpty()) { + String matches = StringUtils.collectionToDelimitedString(propertyMatches, ",", "'", "'"); + builder.append(String.format(HINTS_TEMPLATE, matches)); + } if (!alreadyResolvedPath.isEmpty()) { - builder.append(" Traversed path: ").append(alreadyResolvedPath.get(0).toString()).append("."); + builder.append(" Traversed path: "); + builder.append(alreadyResolvedPath.get(0).toString()); + builder.append("."); } return builder.toString(); @@ -105,4 +120,20 @@ public class PropertyReferenceException extends RuntimeException { public boolean hasDeeperResolutionDepthThan(PropertyReferenceException exception) { return this.alreadyResolvedPath.size() > exception.alreadyResolvedPath.size(); } + + /** + * Detects all potential matches for the given property name and type. + * + * @param propertyName must not be {@literal null} or empty. + * @param type must not be {@literal null}. + * @return + */ + private static List detectPotentialMatches(String propertyName, Class type) { + + List result = new ArrayList(); + result.addAll(Arrays.asList(PropertyMatches.forField(propertyName, type).getPossibleMatches())); + result.addAll(Arrays.asList(PropertyMatches.forProperty(propertyName, type).getPossibleMatches())); + + return result; + } } diff --git a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java index 328b04a75..7ede4f994 100644 --- a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2015 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. @@ -353,6 +353,9 @@ public class PropertyPathUnitTests { from("foo", (TypeInformation) null); } + /** + * @see DATACMNS-546 + */ @Test public void returnsCompletePathIfResolutionFailedCompletely() { @@ -362,8 +365,11 @@ public class PropertyPathUnitTests { from("somethingDifferent", Foo.class); } + /** + * @see DATACMNS-546 + */ @Test - public void foobar() { + public void includesResolvedPathInExceptionMessage() { exception.expect(PropertyReferenceException.class); exception.expectMessage("fooName"); @@ -373,6 +379,18 @@ public class PropertyPathUnitTests { from("userFooName", Bar.class); } + /** + * @see DATACMNS-703 + */ + @Test + public void includesPropertyHintsOnTypos() { + + exception.expect(PropertyReferenceException.class); + exception.expectMessage("userName"); + + from("userAme", Foo.class); + } + private class Foo { String userName; diff --git a/src/test/resources/org/springframework/data/repository/config/populators.xml b/src/test/resources/org/springframework/data/repository/config/populators.xml deleted file mode 100644 index fba2c6662..000000000 --- a/src/test/resources/org/springframework/data/repository/config/populators.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - -