Skip to content

Commit d3894f8

Browse files
committed
Added support for Java methods in lambdas.
1 parent 9cfd2fe commit d3894f8

File tree

4 files changed

+78
-8
lines changed

4 files changed

+78
-8
lines changed

README.MD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ Features
4545
---------
4646

4747
Current version works with predicates, functions and supports the following operators: >, >=, <, <=, =, !=, &&, ||, !
48+
49+
It is also possible to achieve ``LIKE`` operations using the String ``startsWith``, ``endsWith`` and ``contains`` methods.
50+
For example, the lambda expression\
51+
``person -> person.getAge() > 18 && person.getName().startsWith("Steve")``\
52+
would translate to:\
53+
``person.age > 18 AND person.name LIKE 'Steve%'``
4854

4955
Feel free to open an issue with any requests you might have.
5056

@@ -56,7 +62,7 @@ You can include the Maven dependency:
5662
<dependency>
5763
<groupId>com.github.collinalpert</groupId>
5864
<artifactId>lambda2sql</artifactId>
59-
<version>1.8.5</version>
65+
<version>2.0</version>
6066
</dependency>
6167
```
6268

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.github.collinalpert</groupId>
88
<artifactId>lambda2sql</artifactId>
9-
<version>1.8.5</version>
9+
<version>2.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>lambda2sql</name>

src/main/java/com/github/collinalpert/lambda2sql/ToSqlVisitor.java

100644100755
Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.trigersoft.jaque.expression.UnaryExpression;
1414

1515
import java.util.List;
16+
import java.util.function.Predicate;
1617
import java.util.stream.Collectors;
1718

1819
/**
@@ -24,11 +25,15 @@ public class ToSqlVisitor implements ExpressionVisitor<StringBuilder> {
2425
private LinkedListStack<List<ConstantExpression>> arguments;
2526
private StringBuilder sb;
2627
private Expression body;
28+
private StringComparisonTypes comparisonType;
29+
private boolean stringComparisonNegation;
30+
private Expression javaMethodParameter;
2731

2832
ToSqlVisitor(String prefix) {
2933
this.prefix = prefix;
3034
this.sb = new StringBuilder();
3135
this.arguments = new LinkedListStack<>();
36+
this.comparisonType = StringComparisonTypes.NONE;
3237
}
3338

3439
/**
@@ -51,8 +56,9 @@ private static String toSqlOp(int expressionType) {
5156
return " IS NOT NULL";
5257
case ExpressionType.Convert:
5358
return "";
59+
default:
60+
return ExpressionType.toString(expressionType);
5461
}
55-
return ExpressionType.toString(expressionType);
5662
}
5763

5864
/**
@@ -81,7 +87,16 @@ public StringBuilder visit(BinaryExpression e) {
8187
if (quote) sb.append('(');
8288

8389
e.getFirst().accept(this);
84-
sb.append(' ').append(toSqlOp(e.getExpressionType())).append(' ');
90+
if (comparisonType == StringComparisonTypes.NONE) {
91+
sb.append(' ').append(toSqlOp(e.getExpressionType())).append(' ');
92+
} else {
93+
if (stringComparisonNegation) {
94+
sb.append(" NOT LIKE ");
95+
stringComparisonNegation = false;
96+
} else {
97+
sb.append(" LIKE ");
98+
}
99+
}
85100
e.getSecond().accept(this);
86101

87102
if (quote) sb.append(')');
@@ -106,7 +121,25 @@ public StringBuilder visit(ConstantExpression e) {
106121
return sb;
107122
}
108123
if (e.getValue() instanceof String) {
109-
return sb.append("'").append(e.getValue().toString()).append("'");
124+
sb.append("'");
125+
switch (comparisonType) {
126+
case STARTS_WITH:
127+
sb.append(e.getValue()).append('%');
128+
comparisonType = StringComparisonTypes.NONE;
129+
break;
130+
case ENDS_WITH:
131+
sb.append('%').append(e.getValue());
132+
comparisonType = StringComparisonTypes.NONE;
133+
break;
134+
case CONTAINS:
135+
sb.append('%').append(e.getValue()).append('%');
136+
comparisonType = StringComparisonTypes.NONE;
137+
break;
138+
case NONE:
139+
default:
140+
sb.append(e.getValue());
141+
}
142+
return sb.append("'");
110143
}
111144
return sb.append(e.getValue().toString());
112145
}
@@ -129,6 +162,10 @@ public StringBuilder visit(InvocationExpression e) {
129162
if (!list.isEmpty()) {
130163
arguments.push(list);
131164
}
165+
Predicate<String> testForJavaMethod = s -> s.endsWith("startsWith") || s.endsWith("endsWith") || s.endsWith("contains");
166+
if (testForJavaMethod.test(e.getTarget().toString())) {
167+
javaMethodParameter = e.getArguments().get(0);
168+
}
132169
return e.getTarget().accept(this);
133170
}
134171

@@ -163,6 +200,19 @@ public StringBuilder visit(DelegateExpression e) {
163200
@Override
164201
public StringBuilder visit(MemberExpression e) {
165202
String name = e.getMember().getName();
203+
//for support for Java methods
204+
if (name.equals("startsWith")) {
205+
comparisonType = StringComparisonTypes.STARTS_WITH;
206+
return Expression.binary(ExpressionType.Equal, e.getInstance(), javaMethodParameter).accept(this);
207+
}
208+
if (name.equals("endsWith")) {
209+
comparisonType = StringComparisonTypes.ENDS_WITH;
210+
return Expression.binary(ExpressionType.Equal, e.getInstance(), javaMethodParameter).accept(this);
211+
}
212+
if (name.equals("contains")) {
213+
comparisonType = StringComparisonTypes.CONTAINS;
214+
return Expression.binary(ExpressionType.Equal, e.getInstance(), javaMethodParameter).accept(this);
215+
}
166216
name = name.replaceAll("^(get)", "");
167217
name = name.substring(0, 1).toLowerCase() + name.substring(1);
168218
if (prefix == null) {
@@ -198,11 +248,21 @@ public StringBuilder visit(ParameterExpression e) {
198248
@Override
199249
public StringBuilder visit(UnaryExpression e) {
200250
if (e.getExpressionType() == ExpressionType.LogicalNot) {
201-
sb.append("!");
251+
//for support for negated Java methods
252+
var name = ((MemberExpression) ((InvocationExpression) e.getFirst()).getTarget()).getMember().getName();
253+
if (name.equals("startsWith") || name.equals("endsWith") || name.equals("contains")) {
254+
stringComparisonNegation = true;
255+
} else {
256+
sb.append("!");
257+
}
202258
return e.getFirst().accept(this);
203259
}
204260
e.getFirst().accept(this);
205261
return sb.append(toSqlOp(e.getExpressionType()));
206262
}
207263

264+
private enum StringComparisonTypes {
265+
NONE, STARTS_WITH, ENDS_WITH, CONTAINS
266+
}
267+
208268
}

src/test/java/com/github/collinalpert/lambda2sql/test/Lambda2SqlTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ void testParentheses() {
116116
assertPredicateEqual("person.age = 18 AND (person.isAdult OR person.name = 'Steve')", p2);
117117
}
118118

119-
//@Test
120-
//This section does not work yet!
119+
@Test
121120
void testJavaFunctions() {
122121
var name = "Steve";
123122
var age = 18;
@@ -127,6 +126,7 @@ void testJavaFunctions() {
127126
assertPredicateEqual("person.name LIKE 'Steve%'", person -> person.getName().startsWith(name));
128127
assertPredicateEqual("person.age >= 18 OR person.name NOT LIKE 'Steve%'", person -> person.getAge() >= age || !person.getName().startsWith(name));
129128
assertPredicateEqual("person.name NOT LIKE 'Steve%'", person -> !person.getName().startsWith(name));
129+
assertPredicateEqual("person.name NOT LIKE 'Steve%' AND !person.isActive", person -> !person.getName().startsWith(name) && !person.isActive());
130130

131131
assertPredicateEqual("person.age >= 18 OR person.name LIKE '%Steve'", person -> person.getAge() >= age || person.getName().endsWith("Steve"));
132132
assertPredicateEqual("person.name LIKE '%Steve'", person -> person.getName().endsWith("Steve"));
@@ -143,6 +143,10 @@ void testJavaFunctions() {
143143
assertPredicateEqual("person.name NOT LIKE '%Steve%'", person -> !person.getName().contains(name));
144144
}
145145

146+
@Test
147+
void testSingle() {
148+
}
149+
146150
private void assertPredicateEqual(String expectedSql, SqlPredicate<IPerson> p) {
147151
var sql = Lambda2Sql.toSql(p, "person");
148152
Assertions.assertEquals(expectedSql, sql);

0 commit comments

Comments
 (0)