From eaa6fd8460aec512e92e3f894a93bf592b4a6f53 Mon Sep 17 00:00:00 2001 From: pn4v4rr0 Date: Tue, 17 May 2016 14:17:51 +0200 Subject: [PATCH] Added 'count by' query generation --- .../addon/finder/addon/parser/PartTree.java | 19 +++- .../addon/finder/addon/parser/Subject.java | 97 ++++++++++++++----- .../finder/addon/parser/PartTreeUnitTest.java | 88 +++++++++++------ 3 files changed, 147 insertions(+), 57 deletions(-) diff --git a/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/PartTree.java b/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/PartTree.java index bcc1a017a8..d7a0e96413 100644 --- a/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/PartTree.java +++ b/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/PartTree.java @@ -40,11 +40,11 @@ public class PartTree { private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\z))"; - private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(" + Subject.QUERY_PATTERN - + ")((\\p{Lu}.*?))??By"); + private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(" + Subject.QUERY_PATTERN + "|" + + Subject.COUNT_PATTERN + ")((\\p{Lu}.*?))??By"); /** - * Subject is delimited by the query prefix (find, read or query) and {@literal By} delimiter, for + * Subject is delimited by a prefix (find, read , query or count) and {@literal By} delimiter, for * example "findDistinctUserByNameOrderByAge" would have the subject * "DistinctUser". */ @@ -138,6 +138,10 @@ private JavaType extractReturnType(MemberDetails entityDetails) { Pair property = subject.getProperty(); JavaType type = null; + // Count subject returns Long + if (subject.isCountProjection()) { + return JavaType.LONG_OBJECT; + } if (property != null && property.getLeft() != null) { // Returns the property type if it is specified @@ -347,6 +351,15 @@ public boolean isDistinct() { return subject.isDistinct(); } + /** + * Returns whether a count projection shall be applied. + * + * @return + */ + public Boolean isCountProjection() { + return subject.isCountProjection(); + } + @Override public String toString() { diff --git a/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/Subject.java b/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/Subject.java index dd9926708e..80e90ae279 100644 --- a/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/Subject.java +++ b/addon-finder/addon/src/main/java/org/springframework/roo/addon/finder/addon/parser/Subject.java @@ -6,6 +6,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; @@ -36,20 +37,30 @@ public class Subject { private static final String[] QUERY_TYPE = {"find", "query", "read"}; public static final String QUERY_PATTERN = StringUtils.join(QUERY_TYPE, "|"); + public static final String COUNT_PATTERN = "count"; private static final String DISTINCT = "Distinct"; private static final String LIMITING_QUERY_PATTERN = "((First|Top)(\\d+)?)?"; + // Complete subject structures + private static final Pattern COMPLETE_COUNT_BY_TEMPLATE = Pattern.compile("^" + COUNT_PATTERN + + "(\\p{Lu}.*?)??By"); private static final Pattern COMPLETE_QUERY_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN + ")(" + DISTINCT + ")?" + LIMITING_QUERY_PATTERN + "(\\p{Lu}.*?)??By"); - private static final Pattern CORRECT_QUERY_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN - + ")?(" + DISTINCT + ")?(First|Top)?(\\d*)?(\\p{Lu}.*)?"); + // Accepted subject structure to extract parameters + private static final Pattern CORRECT_SUBJECT_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN + + "|" + COUNT_PATTERN + ")?(" + DISTINCT + ")?(First|Top)?(\\d*)?(\\p{Lu}.*)?"); + + // Template to check if subject is a count projection + private static final Pattern COUNT_BY_TEMPLATE = Pattern.compile("^" + COUNT_PATTERN + + "(\\p{Lu}.*)?"); private boolean distinct; private Integer maxResults; private String operation = ""; private String limit = ""; private boolean isComplete = false; + private boolean isCount = false; private Pair property = null; private List fields; @@ -69,9 +80,47 @@ public Subject(PartTree partTree, String source, List fields) { this.currentPartTreeInstance = partTree; this.fields = fields; - this.isComplete = isComplete(source); + this.isComplete = isValid(source); + this.isCount = PartTree.matches(source, COUNT_BY_TEMPLATE); + + if (isCount) { + buildCountSubject(source); + } else { + buildQuerySubject(source); + } + } + + /** + * Extract count parameters from subject + * + * @param subject + */ + private void buildCountSubject(String subject) { + Matcher grp = CORRECT_SUBJECT_TEMPLATE.matcher(subject); + + // Checks if query format and parameters are correct (not complete) + if (!grp.find()) { + return; + } + + // Extract query type + operation = grp.group(1); + + // Extract property + property = extractValidField(grp.group(5), fields); + + distinct = false; + limit = null; + maxResults = null; + } - Matcher grp = CORRECT_QUERY_TEMPLATE.matcher(source); + /** + * Extract query parameters from subject + * + * @param subject + */ + private void buildQuerySubject(String subject) { + Matcher grp = CORRECT_SUBJECT_TEMPLATE.matcher(subject); // Checks if query format and parameters are correct (not complete) if (!grp.find()) { @@ -82,7 +131,7 @@ public Subject(PartTree partTree, String source, List fields) { operation = grp.group(1); // Extract Distinct - this.distinct = source == null ? false : source.contains(DISTINCT); + this.distinct = subject == null ? false : subject.contains(DISTINCT); // Extract if there is a limitation expression limit = grp.group(3); @@ -142,20 +191,6 @@ public Pair extractValidField(String source, List getOptions() { // Checks if subject has an operation if (StringUtils.isBlank(operation)) { - return Arrays.asList(QUERY_TYPE); + return Arrays.asList(ArrayUtils.add(QUERY_TYPE, COUNT_PATTERN)); } // Once operation is included subject definition can end, so "By" option is always available @@ -263,7 +308,7 @@ public List getOptions() { } // Check if subject has a limiting expression. It can only be added before the property - if (StringUtils.isBlank(limit)) { + if (StringUtils.isBlank(limit) && !isCountProjection()) { options.add(query.concat("First")); options.add(query.concat("Top")); @@ -280,7 +325,7 @@ public List getOptions() { } } - } else if (maxResults == null) { + } else if (maxResults == null && !isCountProjection()) { // Optionally, a limiting expression can have a number as parameter options.add(query + "[Number]"); diff --git a/addon-finder/addon/src/test/java/org/springframework/roo/addon/finder/addon/parser/PartTreeUnitTest.java b/addon-finder/addon/src/test/java/org/springframework/roo/addon/finder/addon/parser/PartTreeUnitTest.java index 7980a0b72b..437148bcc2 100644 --- a/addon-finder/addon/src/test/java/org/springframework/roo/addon/finder/addon/parser/PartTreeUnitTest.java +++ b/addon-finder/addon/src/test/java/org/springframework/roo/addon/finder/addon/parser/PartTreeUnitTest.java @@ -35,7 +35,8 @@ public class PartTreeUnitTest { - private String[] PREFIXES = {"find", "read", "query"}; + private String[] PREFIXES = {"find", "read", "query", "count"}; + private String[] QUERIES = {"find", "read", "query"}; private String[] DISTINCT = {"", "Distinct"}; private String[] LIMIT = {"Top", "First"}; private String[] TEST_LIMIT = {"Top", "First", "", "Top10", "First10"}; @@ -131,7 +132,7 @@ public void rejectsNullMemberDetails() throws Exception { } @Test - public void returnsQuerys() throws Exception { + public void returnsOperations() throws Exception { test("", PREFIXES); } @@ -156,6 +157,11 @@ public void badDistinct() throws Exception { assertEquals(new PartTree("findByDistinct", memberDetails).isValid(), false); } + @Test + public void badDistinctCount() throws Exception { + assertEquals(new PartTree("countDistinctByText", memberDetails).isValid(), false); + } + @Test public void badLimit() throws Exception { assertFalse(new PartTree("Topfind", memberDetails).isValid()); @@ -166,6 +172,12 @@ public void badLimit() throws Exception { assertFalse(new PartTree("findTopaBy", memberDetails).isValid()); } + @Test + public void badLimitCount() throws Exception { + assertFalse(new PartTree("countDistinctTopByText", memberDetails).isValid()); + assertFalse(new PartTree("countFirstByText", memberDetails).isValid()); + assertFalse(new PartTree("countTop1ByText", memberDetails).isValid()); + } @Test public void badSubjectField() throws Exception { @@ -405,6 +417,15 @@ public void disablesFindFirstKImplicitIfNotPresent() { assertTrue(partTree.isValid() && partTree.getMaxResults() == null); } + @Test + public void identifiesCount() { + PartTree partTree = new PartTree("countByText", memberDetails); + assertTrue(partTree.isValid() && partTree.isCountProjection()); + + partTree = new PartTree("countTextByText", memberDetails); + assertTrue(partTree.isValid() && partTree.isCountProjection()); + } + @Test public void identifiesFindFirstImplicit() { PartTree partTree = new PartTree("findFirstByText", memberDetails); @@ -465,7 +486,7 @@ public void parsesInContainingCorrectly() { @Test public void detectsDistinctCorrectly() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { PartTree partTree = new PartTree(prefix + "DistinctByText", memberDetails); assertTrue(partTree.isValid() && partTree.isDistinct()); partTree = new PartTree(prefix + "DistinctTextByText", memberDetails); @@ -594,6 +615,17 @@ public void validateReturnsTypePrimitiveInteger() throws Exception { memberDetails).getReturnType()); } + @Test + public void validateReturnsCount() throws Exception { + + assertEquals(JavaType.LONG_OBJECT, + new PartTree("countPrimitiveIntByText", memberDetails).getReturnType()); + assertEquals(JavaType.LONG_OBJECT, + new PartTree("countNumberByText", memberDetails).getReturnType()); + assertEquals(JavaType.LONG_OBJECT, new PartTree("countByText", memberDetails).getReturnType()); + assertEquals(JavaType.LONG_OBJECT, + new PartTree("countDateByText", memberDetails).getReturnType()); + } @Test public void validateOneParameter() throws Exception { @@ -602,7 +634,7 @@ public void validateOneParameter() throws Exception { parameters.add(new FinderParameter(JavaType.STRING, new JavaSymbolName("text"))); assertEqualsParameters(parameters, - new PartTree("findByTextContaining", memberDetails).getParameters()); + new PartTree("countByTextContaining", memberDetails).getParameters()); parameters.add(new FinderParameter(JavaType.INT_OBJECT, new JavaSymbolName("number"))); assertEqualsParameters(parameters, new PartTree("findByTextContainingAndNumberIsLessThan", @@ -631,7 +663,7 @@ public void validateSeveralParameters() throws Exception { parameters.add(new FinderParameter(JavaType.INT_OBJECT, new JavaSymbolName("number5"))); assertEqualsParameters(parameters, new PartTree( - "findByNumberBetweenAndNumberBetweenAndNumberLessThan", memberDetails).getParameters()); + "countByNumberBetweenAndNumberBetweenAndNumberLessThan", memberDetails).getParameters()); } @@ -662,15 +694,19 @@ public void validateInParameters() throws Exception { @Test public void optionsAfterQueryPrefix() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { test(prefix, ArrayUtils.addAll(PROPERTIES, new String[] {"Distinct", "First", "Top", "By"})); } } + @Test + public void optionsAfterCountPrefix() throws Exception { + test("count", ArrayUtils.addAll(PROPERTIES, "By")); + } @Test public void optionsAfterDistinct() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { prefix += "Distinct"; test(prefix, ArrayUtils.addAll(PROPERTIES, new String[] {"First", "Top", "By"})); } @@ -678,7 +714,7 @@ public void optionsAfterDistinct() throws Exception { @Test public void optionsAfterLimit() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : LIMIT) { String query = prefix + distinct + limit; @@ -690,7 +726,7 @@ public void optionsAfterLimit() throws Exception { @Test public void optionsAfterLimitNumber() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : LIMIT) { for (String number : NUMBERS) { @@ -709,7 +745,7 @@ public void optionsAfterLimitNumber() throws Exception { @Test public void optionsAfterField() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : TEST_LIMIT) { for (String field : PROPERTIES) { @@ -718,17 +754,17 @@ public void optionsAfterField() throws Exception { } } } + + for (String field : PROPERTIES) { + test("count" + field, new String[] {"By"}); + } } @Test public void optionsAfterBy() throws Exception { for (String prefix : PREFIXES) { - for (String distinct : DISTINCT) { - for (String limit : TEST_LIMIT) { - for (String field : ArrayUtils.addAll(PROPERTIES, "")) { - test(prefix + distinct + limit + field + "By", PROPERTIES); - } - } + for (String field : ArrayUtils.addAll(PROPERTIES, "")) { + test(prefix + field + "By", PROPERTIES); } } } @@ -738,21 +774,17 @@ public void optionsAfterBy() throws Exception { @Test public void optionsAfterPredicateStringField() throws Exception { for (String prefix : PREFIXES) { - for (String distinct : DISTINCT) { - for (String limit : TEST_LIMIT) { - for (String field : ArrayUtils.addAll(PROPERTIES, "")) { - test(prefix + distinct + limit + field + "ByText", ArrayUtils.addAll( - ArrayUtils.addAll(STRING_OP, ArrayUtils.addAll(CONJUCTIONS, "")), - ArrayUtils.addAll(ArrayUtils.addAll(IGNORE_CASE, ALL_IGNORE_CASE), "OrderBy"))); - } - } + for (String field : ArrayUtils.addAll(PROPERTIES, "")) { + test(prefix + field + "ByText", ArrayUtils.addAll( + ArrayUtils.addAll(STRING_OP, ArrayUtils.addAll(CONJUCTIONS, "")), + ArrayUtils.addAll(ArrayUtils.addAll(IGNORE_CASE, ALL_IGNORE_CASE), "OrderBy"))); } } } @Test public void optionsAfterPredicateNumberField() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : TEST_LIMIT) { for (String field : ArrayUtils.addAll(PROPERTIES, "")) { @@ -770,7 +802,7 @@ public void optionsAfterPredicateNumberField() throws Exception { @Test public void optionsAfterPredicateDateField() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : TEST_LIMIT) { for (String field : ArrayUtils.addAll(PROPERTIES, "")) { @@ -786,7 +818,7 @@ public void optionsAfterPredicateDateField() throws Exception { @Test public void optionsAfterPredicateprimitiveNumberField() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : TEST_LIMIT) { for (String field : ArrayUtils.addAll(PROPERTIES, "")) { @@ -802,7 +834,7 @@ public void optionsAfterPredicateprimitiveNumberField() throws Exception { @Test public void optionsAfterOperator() throws Exception { - for (String prefix : PREFIXES) { + for (String prefix : QUERIES) { for (String distinct : DISTINCT) { for (String limit : TEST_LIMIT) { for (String field : ArrayUtils.addAll(PROPERTIES, "")) {