DATACMNS-363 - Improved support for unicode characters for repository query methods.
In order to find a keyword like "And" followed by a property name we now probe also for non Basic Latin characters in addition to just an uppercase letter. This is needed to support property-names with characters that don't have an upper or lowercase representation. This allows us to support finder methods like: findBy이름And생일OrderBy생일Asc(…). Original pull request: #47.
This commit is contained in:
committed by
Oliver Gierke
parent
93b2d39cf1
commit
43b7a54294
@@ -34,11 +34,23 @@ import org.springframework.util.StringUtils;
|
||||
* each query execution.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class PartTree implements Iterable<OrPart> {
|
||||
|
||||
private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get|count)(\\p{Lu}.*?)??By");
|
||||
private static final String KEYWORD_TEMPLATE = "(%s)(?=\\p{Lu})";
|
||||
|
||||
/*
|
||||
* We look for a pattern of: keyword followed by
|
||||
*
|
||||
* an upper-case letter that has a lower-case variant \p{Lu}
|
||||
* OR
|
||||
* any other letter NOT in the BASIC_LATIN Uni-code Block \\P{InBASIC_LATIN} (like Chinese, Korean, Japanese, etc.).
|
||||
*
|
||||
* @see http://www.regular-expressions.info/unicode.html
|
||||
* @see http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html#ubc
|
||||
*/
|
||||
private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))";
|
||||
|
||||
/**
|
||||
* The subject, for example "findDistinctUserByNameOrderByAge" would have the subject "DistinctUser".
|
||||
|
||||
@@ -319,6 +319,94 @@ public class PartTreeUnitTests {
|
||||
assertTrue(tree.getSort().getOrderFor("år").isAscending());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-363
|
||||
*/
|
||||
@Test
|
||||
public void parsesSpecialCharactersOnlyCorrectly_Korean() {
|
||||
|
||||
PartTree tree = new PartTree("findBy이름And생일OrderBy생일Asc", DomainObjectWithSpecialChars.class);
|
||||
|
||||
assertPart(tree, new Part[] { new Part("이름", DomainObjectWithSpecialChars.class),
|
||||
new Part("생일", DomainObjectWithSpecialChars.class) });
|
||||
assertTrue(tree.getSort().getOrderFor("생일").isAscending());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-363
|
||||
*/
|
||||
@Test
|
||||
public void parsesSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_Korean() {
|
||||
|
||||
PartTree tree = new PartTree("findBy이름AndOrderIdOrderBy생일Asc", DomainObjectWithSpecialChars.class);
|
||||
|
||||
assertPart(tree, new Part[] { new Part("이름", DomainObjectWithSpecialChars.class),
|
||||
new Part("order.id", DomainObjectWithSpecialChars.class) });
|
||||
assertTrue(tree.getSort().getOrderFor("생일").isAscending());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-363
|
||||
*/
|
||||
@Test
|
||||
public void parsesNestedSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_Korean() {
|
||||
|
||||
PartTree tree = new PartTree( //
|
||||
"findBy" + "이름" //
|
||||
+ "And" + "OrderId" //
|
||||
+ "And" + "Nested_이름" // we use _ here to mark the beginning of a new property reference "이름"
|
||||
+ "Or" + "NestedOrderId" //
|
||||
+ "OrderBy" + "생일" + "Asc", DomainObjectWithSpecialChars.class);
|
||||
|
||||
Iterator<OrPart> parts = tree.iterator();
|
||||
assertPartsIn(parts.next(), new Part[] { //
|
||||
new Part("이름", DomainObjectWithSpecialChars.class), //
|
||||
new Part("order.id", DomainObjectWithSpecialChars.class), //
|
||||
new Part("nested.이름", DomainObjectWithSpecialChars.class) //
|
||||
});
|
||||
assertPartsIn(parts.next(), new Part[] { //
|
||||
new Part("nested.order.id", DomainObjectWithSpecialChars.class) //
|
||||
});
|
||||
|
||||
assertTrue(tree.getSort().getOrderFor("생일").isAscending());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-363
|
||||
*/
|
||||
@Test
|
||||
public void parsesNestedSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_KoreanNumbersSymbols() {
|
||||
|
||||
PartTree tree = new PartTree( //
|
||||
"findBy" + "이름" //
|
||||
+ "And" + "OrderId" //
|
||||
+ "And" + "Anders" //
|
||||
+ "And" + "Property1" //
|
||||
+ "And" + "Øre" //
|
||||
+ "And" + "År" //
|
||||
+ "Or" + "NestedOrderId" //
|
||||
+ "And" + "Nested_property1" // we use _ here to mark the beginning of a new property reference "이름"
|
||||
+ "And" + "Property1" //
|
||||
+ "OrderBy" + "생일" + "Asc", DomainObjectWithSpecialChars.class);
|
||||
|
||||
Iterator<OrPart> parts = tree.iterator();
|
||||
assertPartsIn(parts.next(), new Part[] { //
|
||||
new Part("이름", DomainObjectWithSpecialChars.class), //
|
||||
new Part("order.id", DomainObjectWithSpecialChars.class), //
|
||||
new Part("anders", DomainObjectWithSpecialChars.class), //
|
||||
new Part("property1", DomainObjectWithSpecialChars.class), //
|
||||
new Part("øre", DomainObjectWithSpecialChars.class), //
|
||||
new Part("år", DomainObjectWithSpecialChars.class) //
|
||||
});
|
||||
assertPartsIn(parts.next(), new Part[] { //
|
||||
new Part("nested.order.id", DomainObjectWithSpecialChars.class), //
|
||||
new Part("nested.property1", DomainObjectWithSpecialChars.class), //
|
||||
new Part("property1", DomainObjectWithSpecialChars.class) //
|
||||
});
|
||||
|
||||
assertTrue(tree.getSort().getOrderFor("생일").isAscending());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-303
|
||||
*/
|
||||
@@ -374,6 +462,14 @@ public class PartTreeUnitTests {
|
||||
assertThat(new PartTree("findByAnders", Product.class), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-368
|
||||
*/
|
||||
@Test
|
||||
public void detectPropertyPathWithOrKeywordPart() {
|
||||
assertThat(new PartTree("findByOrderId", Product.class), is(notNullValue()));
|
||||
}
|
||||
|
||||
private static void assertType(Iterable<String> sources, Type type, String property) {
|
||||
assertType(sources, type, property, 1, true);
|
||||
}
|
||||
@@ -411,18 +507,23 @@ public class PartTreeUnitTests {
|
||||
}
|
||||
|
||||
private void assertPart(PartTree tree, Part[]... parts) {
|
||||
Iterator<OrPart> iterator = tree.iterator();
|
||||
|
||||
Iterator<OrPart> orParts = tree.iterator();
|
||||
for (Part[] part : parts) {
|
||||
assertThat(iterator.hasNext(), is(true));
|
||||
Iterator<Part> partIterator = iterator.next().iterator();
|
||||
for (int k = 0; k < part.length; k++) {
|
||||
assertThat(String.format("Expected %d parts but have %d", part.length, k), partIterator.hasNext(), is(true));
|
||||
Part next = partIterator.next();
|
||||
assertThat(String.format("Expected %s but got %s!", part[k], next), part[k], is(next));
|
||||
}
|
||||
assertThat("Too many parts!", partIterator.hasNext(), is(false));
|
||||
assertThat(orParts.hasNext(), is(true));
|
||||
assertPartsIn(orParts.next(), part);
|
||||
}
|
||||
assertThat("Too many or parts!", iterator.hasNext(), is(false));
|
||||
assertThat("Too many or parts!", orParts.hasNext(), is(false));
|
||||
}
|
||||
|
||||
private void assertPartsIn(OrPart orPart, Part[] part) {
|
||||
Iterator<Part> partIterator = orPart.iterator();
|
||||
for (int k = 0; k < part.length; k++) {
|
||||
assertThat(String.format("Expected %d parts but have %d", part.length, k), partIterator.hasNext(), is(true));
|
||||
Part next = partIterator.next();
|
||||
assertThat(String.format("Expected %s but got %s!", part[k], next), part[k], is(next));
|
||||
}
|
||||
assertThat("Too many parts!", partIterator.hasNext(), is(false));
|
||||
}
|
||||
|
||||
private static <T> Collection<T> toCollection(Iterable<T> iterable) {
|
||||
@@ -452,9 +553,16 @@ public class PartTreeUnitTests {
|
||||
String øre;
|
||||
String år;
|
||||
|
||||
public Order getOrder() {
|
||||
return null;
|
||||
}
|
||||
String 생일; // Birthday
|
||||
String 이름; // Name
|
||||
|
||||
int property1;
|
||||
|
||||
Order order;
|
||||
|
||||
Anders anders;
|
||||
|
||||
DomainObjectWithSpecialChars nested;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
|
||||
Reference in New Issue
Block a user