提交 4d437688 编写于 作者: A Andy Clement

Removing functionality. Removed soundslike/distanceto/like operators

上级 59a44275
......@@ -19,7 +19,6 @@ would have taken? At the moment ternary expressions are just considered NOT wri
Syntax
- are distanceto or soundslike any use?
- should the 'is' operator change to 'instanceof' ?
- in this expression we hit the problem of not being able to write chars, since '' always means string:
evaluate("new java.lang.String('hello').charAt(2).equals('l'.charAt(0))", true, Boolean.class);
......
/*
* Copyright 2004-2008 the original author or authors.
*
* Licensed 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.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* The distanceto operator uses an implementation of the levenshtein distance measurement for determining the 'edit
* distance' between two strings (the two operands to distanceto). http://en.wikipedia.org/wiki/Levenshtein_distance
*
* @author Andy Clement
*/
public class OperatorDistanceTo extends Operator {
private final static boolean DEBUG = false;
public OperatorDistanceTo(Token payload) {
super(payload);
}
@Override
public String getOperatorName() {
return "distanceto";
}
@Override
public Object getValue(ExpressionState state) throws EvaluationException {
try {
Object left = getLeftOperand().getValue(state);
Object right = getRightOperand().getValue(state);
return computeDistanceTo((String) left, (String) right);
} catch (SpelException ee) {
throw ee;
}
}
private int computeDistanceTo(String from, String to) {
if (from.equals(to))
return 0;
int[][] d = new int[from.length() + 1][to.length() + 1];
for (int i = 0; i <= from.length(); i++)
d[i][0] = i;
for (int j = 0; j <= to.length(); j++)
d[0][j] = j;
for (int i = 1; i <= from.length(); i++) {
for (int j = 1; j <= to.length(); j++) {
int cost;
if (from.charAt(i - 1) == to.charAt(j - 1))
cost = 0;
else
cost = 1;
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);// del,ins,subst
}
}
if (DEBUG) {
// Display the table of values
System.out.print(" ");
for (int j = 0; j < from.length(); j++) {
System.out.print(from.charAt(j) + " ");
}
System.out.println();
for (int j = 0; j < to.length() + 1; j++) {
System.out.print((j > 0 ? to.charAt(j - 1) : " ") + " ");
for (int i = 0; i < from.length() + 1; i++) {
System.out.print(d[i][j]);
if (i == from.length() && j == to.length())
System.out.print("<");
else if (i == from.length() - 1 && j == to.length())
System.out.print(">");
else
System.out.print(" ");
}
System.out.println();
}
}
return d[from.length()][to.length()];
}
private int min(int i, int j, int k) {
int min = i;
if (j < min)
min = j;
if (k < min)
min = k;
return min;
}
}
/*
* Copyright 2004-2008 the original author or authors.
*
* Licensed 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.springframework.expression.spel.ast;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.SpelMessages;
/**
* Implements the like operator. The like operator behaves the same as the SQL LIKE operator. The first operand is
* compared against the expression supplied as the second operand. This expression supports two wildcards: % meaning any
* string of any length, and _ meaning any single character.
*
* @author Andy Clement
*/
public class OperatorLike extends Operator {
public OperatorLike(Token payload) {
super(payload);
}
@Override
public String getOperatorName() {
return "like";
}
@Override
public Boolean getValue(ExpressionState state) throws EvaluationException {
SpelNode leftOp = getLeftOperand();
SpelNode rightOp = getRightOperand();
Object left = leftOp.getValue(state, String.class);
Object right = getRightOperand().getValue(state);
try {
if (!(left instanceof String)) {
throw new SpelException(leftOp.getCharPositionInLine(),
SpelMessages.INVALID_FIRST_OPERAND_FOR_LIKE_OPERATOR, left);
}
if (!(right instanceof String)) {
throw new SpelException(rightOp.getCharPositionInLine(),
SpelMessages.INVALID_SECOND_OPERAND_FOR_LIKE_OPERATOR, right);
}
// Translate that pattern to a java regex
// not really the best option, what if the right operand already had regex related chars in it?
String likePattern = (String) right;
likePattern = likePattern.replace('_', '.');
likePattern = likePattern.replaceAll("%", ".*");
Pattern pattern = Pattern.compile(likePattern);
Matcher matcher = pattern.matcher((String) left);
return matcher.matches();
} catch (PatternSyntaxException pse) {
throw new SpelException(rightOp.getCharPositionInLine(), pse, SpelMessages.INVALID_PATTERN, right);
}
}
}
/*
* Copyright 2004-2008 the original author or authors.
*
* Licensed 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.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.SpelMessages;
public class OperatorSoundsLike extends Operator {
public OperatorSoundsLike(Token payload) {
super(payload);
}
@Override
public Object getValue(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValue(state);
Object right = getRightOperand().getValue(state);
if (!(left instanceof String)) {
throw new SpelException(getCharPositionInLine(), SpelMessages.SOUNDSLIKE_NEEDS_STRING_OPERAND, left
.getClass().getName());
}
if (!(right instanceof String)) {
throw new SpelException(getCharPositionInLine(), SpelMessages.SOUNDSLIKE_NEEDS_STRING_OPERAND, right
.getClass().getName());
}
String leftSoundex = computeSoundex((String) left);
String rightSoundex = computeSoundex((String) right);
return state.getTypeComparator().compare(leftSoundex, rightSoundex) == 0;
}
// TODO if we keep soundslike, improve upon this basic implementation
private String computeSoundex(String input) {
if (input == null || input.length() == 0)
return "0000";
input = input.toUpperCase();
StringBuilder soundex = new StringBuilder();
soundex.append(input.charAt(0));
for (int i = 1; i < input.length(); i++) {
char ch = input.charAt(i);
if ("HW".indexOf(ch) != -1)
continue; // remove HWs now
if ("BFPV".indexOf(ch) != -1) {
soundex.append("1");
} else if ("CGJKQSXZ".indexOf(ch) != -1) {
soundex.append("2");
} else if ("DT".indexOf(ch) != -1) {
soundex.append("3");
} else if ("L".indexOf(ch) != -1) {
soundex.append("4");
} else if ("MN".indexOf(ch) != -1) {
soundex.append("5");
} else if ("R".indexOf(ch) != -1) {
soundex.append("6");
} else {
soundex.append(ch);
}
}
StringBuilder shorterSoundex = new StringBuilder();
shorterSoundex.append(soundex.charAt(0));
for (int i = 1; i < soundex.length(); i++) {
if ((i + 1) < soundex.length() && soundex.charAt(i) == soundex.charAt(i + 1))
continue;
if ("AEIOUY".indexOf(soundex.charAt(i)) != -1)
continue;
shorterSoundex.append(soundex.charAt(i));
}
shorterSoundex.append("0000");
return shorterSoundex.substring(0, 4);
}
// wikipedia:
// The Soundex code for a name consists of a letter followed by three numbers: the letter is the first letter of the
// name, and the numbers encode the remaining consonants. Similar sounding consonants share the same number so, for
// example, the labial B, F, P and V are all encoded as 1. Vowels can affect the coding, but are never coded
// directly unless they appear at the start of the name.
// The exact algorithm is as follows:
// Retain the first letter of the string
// Remove all occurrences of the following letters, unless it is the first letter: a, e, h, i, o, u, w, y
// Assign numbers to the remaining letters (after the first) as follows:
// b, f, p, v = 1
// c, g, j, k, q, s, x, z = 2
// d, t = 3
// l = 4
// m, n = 5
// r = 6
// If two or more letters with the same number were adjacent in the original name (before step 1), or adjacent
// except for any intervening h and w (American census only), then omit all but the first.
// Return the first four characters, right-padding with zeroes if there are fewer than four.
// Using this algorithm, both "Robert" and "Rupert" return the same string "R163" while "Rubin" yields "R150".
@Override
public String getOperatorName() {
return "soundslike";
}
}
......@@ -86,17 +86,10 @@ relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
sumExpression
: productExpression ( (PLUS^ | MINUS^) productExpression)*;
// : left=productExpression (PLUS right+=productExpression)+ -> ^(ADD $left $right)
// | left=productExpression (MINUS right+=productExpression)+ -> ^(SUBTRACT $left $right)
// | productExpression;
// TODO could really do with changing ast node types here
productExpression
: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
// : left=powerExpr (STAR right+=powerExpr) -> ^(MULTIPLY $left $right)
// | left=powerExpr (DIV right+=powerExpr) -> ^(DIVIDE $left $right)
// | left=powerExpr (MOD right+=powerExpr) -> ^(MODULUS $left $right)
// | powerExpr;
powerExpr : unaryExpression (POWER^ unaryExpression)? ;
......@@ -126,7 +119,6 @@ startNode
| listInitializer
| mapInitializer
| lambda
// | attribute
;
node
......@@ -288,10 +280,7 @@ relationalOperator
| IN
| IS
| BETWEEN
| LIKE
| MATCHES
| SOUNDSLIKE
| DISTANCETO
;
ASSIGN: '=';
......@@ -301,14 +290,9 @@ LESS_THAN: '<';
LESS_THAN_OR_EQUAL: '<=';
GREATER_THAN: '>';
GREATER_THAN_OR_EQUAL: '>=';
SOUNDSLIKE
: 'soundslike';
DISTANCETO
: 'distanceto';
IN: 'in';
IS: 'is';
BETWEEN:'between';
LIKE: 'like';
MATCHES:'matches';
NULL_LITERAL: 'null';
......
COMMA=51
GREATER_THAN_OR_EQUAL=79
EXPRESSIONLIST=4
SELECT_FIRST=58
COMMA=51
HOLDER=14
GREATER_THAN=78
TYPE=60
EXPRESSIONLIST=4
MINUS=41
MAP_ENTRY=25
SELECT_LAST=59
NUMBER=29
ARGLIST=11
BANG=46
LESS_THAN=76
METHOD=26
BANG=46
ARGLIST=11
FALSE=70
METHOD=26
PROPERTY_OR_FIELD=9
LBRACKET=53
MOD=44
INDEXER=10
CONSTRUCTOR_ARRAY=15
FUNCTIONREF=17
NULL_LITERAL=66
NAMED_ARGUMENT=16
OR=38
PIPE=62
DOT=47
AND=39
RCURLY=56
EXPRESSION=6
AND=39
LCURLY=63
DATE_LITERAL=13
REAL_TYPE_SUFFIX=92
QUALIFIED_IDENTIFIER=7
SELECT=57
REAL_TYPE_SUFFIX=89
STRING_LITERAL=64
SUBTRACT=28
SELECT=57
QUALIFIED_IDENTIFIER=7
RBRACKET=54
RPAREN=33
BETWEEN=82
SIGN=93
PLUS=40
INTEGER_LITERAL=5
AT=52
RANGE=19
SOUNDSLIKE=85
WS=89
DOLLAR=50
LESS_THAN_OR_EQUAL=77
HEXADECIMAL_INTEGER_LITERAL=67
LAMBDA=61
SEMI=31
EQUAL=74
DOT_ESCAPED=88
QMARK=36
COLON=37
PROJECT=55
DIV=43
REAL_LITERAL=68
ADD=27
TRUE=69
EXPONENT_PART=91
POUND=48
HOLDER=14
SELECT_FIRST=58
TYPE=60
MAP_ENTRY=25
SELECT_LAST=59
LBRACKET=53
MOD=44
FUNCTIONREF=17
OR=38
RCURLY=56
SUBTRACT=28
ASSIGN=34
BETWEEN=82
RPAREN=33
SIGN=90
LPAREN=30
HEX_DIGIT=73
PLUS=40
LIST_INITIALIZER=21
APOS=87
APOS=84
INTEGER_LITERAL=5
AT=52
ID=49
NOT_EQUAL=75
RANGE=19
POWER=45
TYPEREF=18
DISTANCETO=86
DECIMAL_DIGIT=71
WS=86
IS=81
DOLLAR=50
LESS_THAN_OR_EQUAL=77
SEMIRPAREN=32
DQ_STRING_LITERAL=65
LIKE=83
HEXADECIMAL_INTEGER_LITERAL=67
MAP_INITIALIZER=22
LAMBDA=61
LOCALFUNC=24
IN=80
CONSTRUCTOR=12
SEMI=31
INTEGER_TYPE_SUFFIX=72
MATCHES=84
UPTO=90
EQUAL=74
MATCHES=83
DOT_ESCAPED=85
UPTO=87
QMARK=36
REFERENCE=8
PROJECT=55
DEFAULT=35
COLON=37
DIV=43
LOCALVAR=23
STAR=42
REAL_LITERAL=68
VARIABLEREF=20
'date'=95
'new'=94
EXPONENT_PART=88
TRUE=69
ADD=27
POUND=48
'date'=92
'new'=91
......@@ -39,7 +39,6 @@ import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OperatorAnd;
import org.springframework.expression.spel.ast.OperatorBetween;
import org.springframework.expression.spel.ast.OperatorDistanceTo;
import org.springframework.expression.spel.ast.OperatorDivide;
import org.springframework.expression.spel.ast.OperatorEquality;
import org.springframework.expression.spel.ast.OperatorGreaterThan;
......@@ -49,7 +48,6 @@ import org.springframework.expression.spel.ast.OperatorInequality;
import org.springframework.expression.spel.ast.OperatorIs;
import org.springframework.expression.spel.ast.OperatorLessThan;
import org.springframework.expression.spel.ast.OperatorLessThanOrEqual;
import org.springframework.expression.spel.ast.OperatorLike;
import org.springframework.expression.spel.ast.OperatorMatches;
import org.springframework.expression.spel.ast.OperatorMinus;
import org.springframework.expression.spel.ast.OperatorModulus;
......@@ -57,7 +55,6 @@ import org.springframework.expression.spel.ast.OperatorMultiply;
import org.springframework.expression.spel.ast.OperatorNot;
import org.springframework.expression.spel.ast.OperatorOr;
import org.springframework.expression.spel.ast.OperatorPlus;
import org.springframework.expression.spel.ast.OperatorSoundsLike;
import org.springframework.expression.spel.ast.Placeholder;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
......@@ -110,10 +107,6 @@ public class SpelTreeAdaptor extends CommonTreeAdaptor {
return new OperatorLessThanOrEqual(payload);
case SpringExpressionsLexer.GREATER_THAN_OR_EQUAL:
return new OperatorGreaterThanOrEqual(payload);
case SpringExpressionsLexer.SOUNDSLIKE:
return new OperatorSoundsLike(payload);
case SpringExpressionsLexer.DISTANCETO:
return new OperatorDistanceTo(payload);
case SpringExpressionsLexer.PLUS:
return new OperatorPlus(payload);
case SpringExpressionsLexer.MINUS:
......@@ -187,8 +180,6 @@ public class SpelTreeAdaptor extends CommonTreeAdaptor {
case SpringExpressionsLexer.IN:
return new OperatorIn(payload);
case SpringExpressionsLexer.LIKE:
return new OperatorLike(payload);
case SpringExpressionsLexer.BETWEEN:
return new OperatorBetween(payload);
case SpringExpressionsLexer.MATCHES:
......
......@@ -57,26 +57,6 @@ public class EvaluationTests extends ExpressionTestCase {
evaluate("name in {null, \"Anonymous\"}", "false", Boolean.class);
}
public void testRelOperatorsLike01() {
evaluate("'Abc' like 'A%_'", "true", Boolean.class);
}
public void testRelOperatorsLike02() {
evaluate("'Abc' like '..'", "false", Boolean.class);
}
public void testRelOperatorsLike03() {
evaluateAndCheckError("null like '.'", SpelMessages.INVALID_FIRST_OPERAND_FOR_LIKE_OPERATOR);
}
public void testRelOperatorsLike04() {
evaluateAndCheckError("'abc' like 2.0", SpelMessages.INVALID_SECOND_OPERAND_FOR_LIKE_OPERATOR);
}
public void testRelOperatorsLike05() {
evaluate("27 like '__'", "true", Boolean.class); // conversion int>string
}
public void testRelOperatorsBetween01() {
evaluate("1 between {1, 5}", "true", Boolean.class);
}
......@@ -137,7 +117,6 @@ public class EvaluationTests extends ExpressionTestCase {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string
}
// mixing operators
public void testMixingOperators01() {
evaluate("true and 5>3", "true", Boolean.class);
......@@ -484,43 +463,6 @@ public class EvaluationTests extends ExpressionTestCase {
evaluate("(#sqrt={|n| T(Math).sqrt($n)};#delegate={|f,n| $f($n)};#delegate(#sqrt,4))", "2.0", Double.class);
}
// Soundex
public void testSoundex01() {
evaluate("'Rob' soundslike 'Rod'", "false", Boolean.class);
}
public void testSoundex02() {
evaluate("'Robert' soundslike 'Rupert'", "true", Boolean.class);
}
public void testSoundex03() {
evaluate("'Andy' soundslike 'Christian'", "false", Boolean.class);
}
public void testSoundex04() {
evaluate("@(fruits:).values().?{#this.colorName soundslike 'gren'}!=null", "true", Boolean.class);
}
public void testSoundex05() {
evaluate("@(fruits:).values().?{colorName soundslike 'gren'}!=null", "true", Boolean.class);
}
public void testSoundex06() {
evaluate("'Adrian' soundslike 'Adrain'", "true", Boolean.class);
}
// Word distance
public void testDistanceTo01() {
evaluate("'Saturday' distanceto 'Sunday'", "3", Integer.class);
evaluate("'Saturday' distanceto 'Monday'", "5", Integer.class);
evaluate("'Saturday' distanceto 'Saturdaz'", "1", Integer.class);
evaluate("'Saturday' distanceto 'Saturdab'", "1", Integer.class);
}
public void testDistanceTo02() {
evaluate("'Kitten' distanceto 'Sitting'", "3", Integer.class);
}
public void testVariableReferences() {
evaluate("(#answer=42;#answer)", "42", Integer.class, true);
evaluate("($answer=42;$answer)", "42", Integer.class, true);
......
......@@ -27,7 +27,7 @@ import org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Common superclass for expression tests.
*
*
* @author Andy Clement
*/
public abstract class ExpressionTestCase extends TestCase {
......@@ -36,9 +36,9 @@ public abstract class ExpressionTestCase extends TestCase {
protected final static boolean SHOULD_BE_WRITABLE = true;
protected final static boolean SHOULD_NOT_BE_WRITABLE = false;
protected static SpelExpressionParser parser = new SpelExpressionParser();
protected static StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
protected final static SpelExpressionParser parser = new SpelExpressionParser();
protected final static StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
/**
* Evaluate an expression and check that the actual result matches the expectedValue and the class of the result
......@@ -137,7 +137,7 @@ public abstract class ExpressionTestCase extends TestCase {
* Evaluate an expression and check that the actual result matches the expectedValue and the class of the result
* matches the expectedClassOfResult. This method can also check if the expression is writable (for example, it is a
* variable or property reference).
*
*
* @param expression The expression to evaluate
* @param expectedValue the expected result for evaluating the expression
* @param expectedClassOfResult the expected class of the evaluation result
......@@ -335,7 +335,7 @@ public abstract class ExpressionTestCase extends TestCase {
/**
* Produce a nice string representation of the input object.
*
*
* @param value object to be formatted
* @return a nice string
*/
......
......@@ -22,7 +22,7 @@ import org.springframework.expression.ParseException;
/**
* Parse some expressions and check we get the AST we expect. Rather than inspecting each node in the AST, we ask it to
* write itself to a string form and check that is as expected.
*
*
* @author Andy Clement
*/
public class ParsingTests extends TestCase {
......@@ -137,14 +137,6 @@ public class ParsingTests extends TestCase {
parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
}
public void testRelOperatorsLike01() {
parseCheck("'Abc' like '[A-Z]b*'", "('Abc' like '[A-Z]b*')");
}
public void testRelOperatorsLike02() {
parseCheck("'Abc' like '?'", "('Abc' like '?')");
}
public void testRelOperatorsBetween01() {
parseCheck("1 between {1, 5}", "(1 between {1,5})");
}
......@@ -418,7 +410,7 @@ public class ParsingTests extends TestCase {
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
* as the original expression.
*
*
* @param expression the expression to parse *and* the expected value of the string form of the resultant AST
*/
public void parseCheck(String expression) {
......@@ -428,7 +420,7 @@ public class ParsingTests extends TestCase {
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the
* expected value.
*
*
* @param expression the expression to parse
* @param expectedStringFormOfAST the expected string form of the AST
*/
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册