You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/22 15:43:07 UTC
[2/5] cayenne git commit: CAY-1971 Variants of Property.like(..) :
contains(..), startsWith(..), endsWith(..)
CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/9587d047
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/9587d047
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/9587d047
Branch: refs/heads/master
Commit: 9587d0475b3c3f44150fcf8fc50ea772e9ea40ea
Parents: f11fc5c
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 14:46:31 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 16:25:50 2014 +0300
----------------------------------------------------------------------
.../apache/cayenne/exp/ExpressionFactory.java | 179 ++++++++++++-------
.../cayenne/exp/LikeExpressionHelper.java | 73 ++++++++
.../java/org/apache/cayenne/exp/Property.java | 99 +++++++++-
.../org/apache/cayenne/exp/PropertyTest.java | 21 +++
4 files changed, 298 insertions(+), 74 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index dc1d815..cbef42d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -93,20 +93,14 @@ public class ExpressionFactory {
// make sure all types are small integers, then we can use
// them as indexes in lookup array
- int[] allTypes = new int[] { Expression.AND, Expression.OR,
- Expression.NOT, Expression.EQUAL_TO, Expression.NOT_EQUAL_TO,
- Expression.LESS_THAN, Expression.GREATER_THAN,
- Expression.LESS_THAN_EQUAL_TO,
- Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN,
- Expression.IN, Expression.LIKE, Expression.LIKE_IGNORE_CASE,
- Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY,
- Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH,
- Expression.DB_PATH, Expression.LIST, Expression.NOT_BETWEEN,
- Expression.NOT_IN, Expression.NOT_LIKE,
- Expression.NOT_LIKE_IGNORE_CASE, Expression.TRUE,
- Expression.FALSE, Expression.BITWISE_NOT,
- Expression.BITWISE_AND, Expression.BITWISE_OR,
- Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT,
+ int[] allTypes = new int[] { Expression.AND, Expression.OR, Expression.NOT, Expression.EQUAL_TO,
+ Expression.NOT_EQUAL_TO, Expression.LESS_THAN, Expression.GREATER_THAN, Expression.LESS_THAN_EQUAL_TO,
+ Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN, Expression.IN, Expression.LIKE,
+ Expression.LIKE_IGNORE_CASE, Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY,
+ Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH, Expression.DB_PATH, Expression.LIST,
+ Expression.NOT_BETWEEN, Expression.NOT_IN, Expression.NOT_LIKE, Expression.NOT_LIKE_IGNORE_CASE,
+ Expression.TRUE, Expression.FALSE, Expression.BITWISE_NOT, Expression.BITWISE_AND,
+ Expression.BITWISE_OR, Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT,
Expression.BITWISE_RIGHT_SHIFT };
int max = 0;
@@ -322,19 +316,15 @@ public class ExpressionFactory {
int splitEnd = path.indexOf(Entity.PATH_SEPARATOR, split + 1);
- String beforeSplit = split > 0 ? path.substring(0, split) + "."
- : "";
- String afterSplit = splitEnd > 0 ? "."
- + path.substring(splitEnd + 1) : "";
+ String beforeSplit = split > 0 ? path.substring(0, split) + "." : "";
+ String afterSplit = splitEnd > 0 ? "." + path.substring(splitEnd + 1) : "";
String aliasBase = "split" + autoAliasId++ + "_";
- String splitChunk = splitEnd > 0 ? path.substring(split + 1,
- splitEnd) : path.substring(split + 1);
+ String splitChunk = splitEnd > 0 ? path.substring(split + 1, splitEnd) : path.substring(split + 1);
// fix the path - replace split with dot if it's in the middle, or
// strip it if
// it's in the beginning
- path = split == 0 ? path.substring(1) : path.replace(
- SPLIT_SEPARATOR, '.');
+ path = split == 0 ? path.substring(1) : path.replace(SPLIT_SEPARATOR, '.');
int i = 0;
for (Object value : values) {
@@ -344,8 +334,7 @@ public class ExpressionFactory {
i++;
ASTPath pathExp = new ASTObjPath(aliasedPath);
- pathExp.setPathAliases(Collections.singletonMap(alias,
- splitChunk));
+ pathExp.setPathAliases(Collections.singletonMap(alias, splitChunk));
matches.add(new ASTEqual(pathExp, value));
}
} else {
@@ -572,8 +561,7 @@ public class ExpressionFactory {
/**
* A convenience shortcut for building BETWEEN expressions.
*/
- public static Expression betweenExp(String pathSpec, Object value1,
- Object value2) {
+ public static Expression betweenExp(String pathSpec, Object value1, Object value2) {
return new ASTBetween(new ASTObjPath(pathSpec), value1, value2);
}
@@ -582,16 +570,14 @@ public class ExpressionFactory {
*
* @since 3.0
*/
- public static Expression betweenDbExp(String pathSpec, Object value1,
- Object value2) {
+ public static Expression betweenDbExp(String pathSpec, Object value1, Object value2) {
return new ASTBetween(new ASTDbPath(pathSpec), value1, value2);
}
/**
* A convenience shortcut for building NOT_BETWEEN expressions.
*/
- public static Expression notBetweenExp(String pathSpec, Object value1,
- Object value2) {
+ public static Expression notBetweenExp(String pathSpec, Object value1, Object value2) {
return new ASTNotBetween(new ASTObjPath(pathSpec), value1, value2);
}
@@ -600,8 +586,7 @@ public class ExpressionFactory {
*
* @since 3.0
*/
- public static Expression notBetweenDbExp(String pathSpec, Object value1,
- Object value2) {
+ public static Expression notBetweenDbExp(String pathSpec, Object value1, Object value2) {
return new ASTNotBetween(new ASTDbPath(pathSpec), value1, value2);
}
@@ -609,7 +594,7 @@ public class ExpressionFactory {
* A convenience shortcut for building LIKE expression.
*/
public static Expression likeExp(String pathSpec, Object value) {
- return new ASTLike(new ASTObjPath(pathSpec), value);
+ return likeExpInternal(pathSpec, value, (char) 0);
}
/**
@@ -624,8 +609,11 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression likeExp(String pathSpec, Object value,
- char escapeChar) {
+ public static Expression likeExp(String pathSpec, Object value, char escapeChar) {
+ return likeExpInternal(pathSpec, value, escapeChar);
+ }
+
+ static ASTLike likeExpInternal(String pathSpec, Object value, char escapeChar) {
return new ASTLike(new ASTObjPath(pathSpec), value, escapeChar);
}
@@ -650,8 +638,7 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression likeDbExp(String pathSpec, Object value,
- char escapeChar) {
+ public static Expression likeDbExp(String pathSpec, Object value, char escapeChar) {
return new ASTLike(new ASTDbPath(pathSpec), value, escapeChar);
}
@@ -674,8 +661,7 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression notLikeExp(String pathSpec, Object value,
- char escapeChar) {
+ public static Expression notLikeExp(String pathSpec, Object value, char escapeChar) {
return new ASTNotLike(new ASTObjPath(pathSpec), value, escapeChar);
}
@@ -700,8 +686,7 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression notLikeDbExp(String pathSpec, Object value,
- char escapeChar) {
+ public static Expression notLikeDbExp(String pathSpec, Object value, char escapeChar) {
return new ASTNotLike(new ASTDbPath(pathSpec), value, escapeChar);
}
@@ -709,7 +694,7 @@ public class ExpressionFactory {
* A convenience shortcut for building LIKE_IGNORE_CASE expression.
*/
public static Expression likeIgnoreCaseExp(String pathSpec, Object value) {
- return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value);
+ return likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
}
/**
@@ -724,10 +709,12 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression likeIgnoreCaseExp(String pathSpec, Object value,
- char escapeChar) {
- return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value,
- escapeChar);
+ public static Expression likeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) {
+ return likeIgnoreCaseExpInternal(pathSpec, value, escapeChar);
+ }
+
+ static ASTLikeIgnoreCase likeIgnoreCaseExpInternal(String pathSpec, Object value, char escapeChar) {
+ return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value, escapeChar);
}
/**
@@ -751,8 +738,7 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value,
- char escapeChar) {
+ public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) {
return new ASTLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar);
}
@@ -775,10 +761,8 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression notLikeIgnoreCaseExp(String pathSpec,
- Object value, char escapeChar) {
- return new ASTNotLikeIgnoreCase(new ASTObjPath(pathSpec), value,
- escapeChar);
+ public static Expression notLikeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) {
+ return new ASTNotLikeIgnoreCase(new ASTObjPath(pathSpec), value, escapeChar);
}
/**
@@ -786,8 +770,7 @@ public class ExpressionFactory {
*
* @since 3.0
*/
- public static Expression notLikeIgnoreCaseDbExp(String pathSpec,
- Object value) {
+ public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value) {
return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value);
}
@@ -803,10 +786,77 @@ public class ExpressionFactory {
*
* @since 3.0.1
*/
- public static Expression notLikeIgnoreCaseDbExp(String pathSpec,
- Object value, char escapeChar) {
- return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value,
- escapeChar);
+ public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) {
+ return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar);
+ }
+
+ /**
+ * @return An expression for a database "LIKE" query with the value
+ * converted to a pattern matching anywhere in the String.
+ * @since 4.0
+ */
+ public static Expression containsExp(String pathSpec, String value) {
+ ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toContains(like);
+ return like;
+ }
+
+ /**
+ * @return An expression for a database "LIKE" query with the value
+ * converted to a pattern matching the beginning of the String.
+ * @since 4.0
+ */
+ public static Expression startsWithExp(String pathSpec, String value) {
+ ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toStartsWith(like);
+ return like;
+ }
+
+ /**
+ * @return An expression for a database "LIKE" query with the value
+ * converted to a pattern matching the beginning of the String.
+ * @since 4.0
+ */
+ public static Expression endsWithExp(String pathSpec, String value) {
+ ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toEndsWith(like);
+ return like;
+ }
+
+ /**
+ * Same as {@link #containsExp(String, String)} only using case-insensitive
+ * comparison.
+ *
+ * @since 4.0
+ */
+ public static Expression containsIgnoreCaseExp(String pathSpec, String value) {
+ ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toContains(like);
+ return like;
+ }
+
+ /**
+ * Same as {@link #startsWithExp(String, String)} only using
+ * case-insensitive comparison.
+ *
+ * @since 4.0
+ */
+ public static Expression startsWithIgnoreCaseExp(String pathSpec, String value) {
+ ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toStartsWith(like);
+ return like;
+ }
+
+ /**
+ * Same as {@link #endsWithExp(String, String)} only using case-insensitive
+ * comparison.
+ *
+ * @since 4.0
+ */
+ public static Expression endsWithIgnoreCaseExp(String pathSpec, String value) {
+ ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+ LikeExpressionHelper.toEndsWith(like);
+ return like;
}
/**
@@ -837,8 +887,7 @@ public class ExpressionFactory {
* expression would match any of the expressions.
* </p>
*/
- public static Expression joinExp(int type,
- Collection<Expression> expressions) {
+ public static Expression joinExp(int type, Collection<Expression> expressions) {
int len = expressions.size();
if (len == 0) {
return null;
@@ -872,8 +921,7 @@ public class ExpressionFactory {
* <code>object</code>.
*/
public static Expression matchExp(Persistent object) {
- return matchAllDbExp(object.getObjectId().getIdSnapshot(),
- Expression.EQUAL_TO);
+ return matchAllDbExp(object.getObjectId().getIdSnapshot(), Expression.EQUAL_TO);
}
/**
@@ -971,12 +1019,11 @@ public class ExpressionFactory {
// optimizing parser buffers per CAY-1667...
// adding 1 extra char to the buffer size above the String length, as
// otherwise resizing still occurs at the end of the stream
- int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE
- : expressionString.length() + 1;
+ int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE : expressionString
+ .length() + 1;
Reader reader = new StringReader(expressionString);
JavaCharStream stream = new JavaCharStream(reader, 1, 1, bufferSize);
- ExpressionParserTokenManager tm = new ExpressionParserTokenManager(
- stream);
+ ExpressionParserTokenManager tm = new ExpressionParserTokenManager(stream);
ExpressionParser parser = new ExpressionParser(tm);
try {
http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
new file mode 100644
index 0000000..7725dc9
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
@@ -0,0 +1,73 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.exp;
+
+import org.apache.cayenne.exp.parser.PatternMatchNode;
+
+/**
+ * @since 4.0
+ */
+class LikeExpressionHelper {
+
+ // presumably only "?" can't be an escape char
+ private static final char[] ESCAPE_ALPHABET = new char[] { '\\', '|', '/', ' ' };
+
+ private static final String WILDCARD_SEQUENCE = "%";
+ private static final String WILDCARD_ONE = "_";
+
+ static void toContains(PatternMatchNode exp) {
+ escape(exp);
+ wrap(exp, true, true);
+ }
+
+ static void toStartsWith(PatternMatchNode exp) {
+ escape(exp);
+ wrap(exp, false, true);
+ }
+
+ static void toEndsWith(PatternMatchNode exp) {
+ escape(exp);
+ wrap(exp, true, false);
+ }
+
+ static void escape(PatternMatchNode exp) {
+
+ }
+
+ static void wrap(PatternMatchNode exp, boolean start, boolean end) {
+
+ Object pattern = exp.getOperand(1);
+ if (pattern instanceof String) {
+
+ StringBuilder buffer = new StringBuilder();
+ if (start) {
+ buffer.append(WILDCARD_SEQUENCE);
+ }
+
+ buffer.append(pattern);
+
+ if (end) {
+ buffer.append(WILDCARD_SEQUENCE);
+ }
+
+ exp.setOperand(1, buffer.toString());
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index b42e890..1676315 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -165,34 +165,117 @@ public class Property<E> {
}
/**
- * @return An expression for a Database "Like" query.
+ * @param pattern
+ * a pattern matching property value. Pattern may include "_" and
+ * "%" wildcard symbols to match any single character or a
+ * sequence of characters. To prevent "_" and "%" from being
+ * treated as wildcards, they need to be escaped and escape char
+ * passed with {@link #like(String, char)} method.
+ * @return An expression for a Database "LIKE" query.
*/
- public Expression like(E value) {
- return ExpressionFactory.likeExp(getName(), value);
+ public Expression like(String pattern) {
+ return ExpressionFactory.likeExp(getName(), pattern);
}
/**
- * @return An expression for a case insensitive "Like" query.
+ * @param pattern
+ * a properly escaped pattern matching property value. Pattern
+ * may include "_" and "%" wildcard symbols to match any single
+ * character or a sequence of characters.
+ * @param escapeChar
+ * an escape character used in the pattern to escape "%" and "_".
+ *
+ * @return An expression for a Database "LIKE" query.
+ */
+ public Expression like(String pattern, char escapeChar) {
+ return ExpressionFactory.likeExp(getName(), pattern, escapeChar);
+ }
+
+ /**
+ * @return An expression for a case insensitive "LIKE" query.
*/
- public Expression likeInsensitive(E value) {
- return ExpressionFactory.likeIgnoreCaseExp(getName(), value);
+ public Expression likeInsensitive(String pattern) {
+ return ExpressionFactory.likeIgnoreCaseExp(getName(), pattern);
}
/**
* @return An expression for a Database "NOT LIKE" query.
*/
- public Expression nlike(E value) {
+ public Expression nlike(String value) {
return ExpressionFactory.notLikeExp(getName(), value);
}
/**
* @return An expression for a case insensitive "NOT LIKE" query.
*/
- public Expression nlikeInsensitive(E value) {
+ public Expression nlikeInsensitive(String value) {
return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
}
/**
+ * Creates an expression for a database "LIKE" query with the value
+ * converted to a pattern matching anywhere in the String.
+ *
+ * @param substring
+ * a String to match against property value. "_" and "%" symbols
+ * are NOT treated as wildcards and are escaped when converted to
+ * a LIKE expression.
+ */
+ public Expression contains(String substring) {
+ return ExpressionFactory.containsExp(getName(), substring);
+ }
+
+ /**
+ * Creates an expression for a database "LIKE" query with the value
+ * converted to a pattern matching the beginning of a String.
+ *
+ * @param substring
+ * a String to match against property value. "_" and "%" symbols
+ * are NOT treated as wildcards and are escaped when converted to
+ * a LIKE expression.
+ */
+ public Expression startsWith(String value) {
+ return ExpressionFactory.startsWithExp(getName(), value);
+ }
+
+ /**
+ * Creates an expression for a database "LIKE" query with the value
+ * converted to a pattern matching the tail of a String.
+ *
+ * @param substring
+ * a String to match against property value. "_" and "%" symbols
+ * are NOT treated as wildcards and are escaped when converted to
+ * a LIKE expression.
+ */
+ public Expression endsWith(String value) {
+ return ExpressionFactory.endsWithExp(getName(), value);
+ }
+
+ /**
+ * Same as {@link #contains(String)}, only using case-insensitive
+ * comparison.
+ */
+ public Expression icontains(String value) {
+ return ExpressionFactory.containsIgnoreCaseExp(getName(), value);
+ }
+
+ /**
+ * Same as {@link #startsWith(String)}, only using case-insensitive
+ * comparison.
+ */
+ public Expression istartsWith(String value) {
+ return ExpressionFactory.startsWithIgnoreCaseExp(getName(), value);
+ }
+
+ /**
+ * Same as {@link #endsWith(String)}, only using case-insensitive
+ * comparison.
+ */
+ public Expression iendsWith(String value) {
+ return ExpressionFactory.endsWithIgnoreCaseExp(getName(), value);
+ }
+
+ /**
* @return An expression checking for objects between a lower and upper
* bound inclusive
*
http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
index 1144449..70fd3ce 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
@@ -192,4 +192,25 @@ public class PropertyTest {
Expression e = p.like("ab%c");
assertEquals("prop like \"ab%c\"", e.toString());
}
+
+ @Test
+ public void testContains() {
+ Property<String> p = new Property<String>("prop");
+ Expression e = p.contains("abc");
+ assertEquals("prop like \"%abc%\"", e.toString());
+ }
+
+ @Test
+ public void testStartsWith() {
+ Property<String> p = new Property<String>("prop");
+ Expression e = p.startsWith("abc");
+ assertEquals("prop like \"abc%\"", e.toString());
+ }
+
+ @Test
+ public void testEndsWith() {
+ Property<String> p = new Property<String>("prop");
+ Expression e = p.endsWith("abc");
+ assertEquals("prop like \"%abc\"", e.toString());
+ }
}