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:
Thomas Darimont
2013-10-01 14:59:34 +02:00
committed by Oliver Gierke
parent 93b2d39cf1
commit 43b7a54294
2 changed files with 134 additions and 14 deletions

View File

@@ -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".

View File

@@ -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 {