blob: 61449c6b5afeba31697da73a9a9facfb1709acf7 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation
*
* 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.apache.felix.framework.util.ldap;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
public class Parser
{
//
// Parser contants.
//
// End of file.
public static final int EOF = -1;
// Special characters in parse
public static final char LPAREN = '(';
public static final char RPAREN = ')';
public static final char STAR = '*';
// Define the list of legal leading and trailing
// characters in an attribute name.
public static final String ATTRIBUTECHARS0 =
".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
// Define the list of legal internal characters in an attribute name.
public static final String ATTRIBUTECHARS1 =
".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_ ";
// Define an enum for substring procedure
public static final int SIMPLE = 0;
public static final int PRESENT = 1;
public static final int SUBSTRING = 2;
// different from =|>|<|~
public static final int NOOP = 0;
// Comparison operators.
public static final int EQUAL = 0;
public static final int GREATER_EQUAL = 1;
public static final int LESS_EQUAL = 2;
public static final int APPROX = 3;
// Criteria in % to accept something as approximate.
public static final int APPROX_CRITERIA = 10;
// Flag indicating presense of BigInteger/Decimal.
private static boolean m_hasBigNumbers = false;
static
{
try
{
Class.forName("java.math.BigDecimal");
m_hasBigNumbers = true;
}
catch (Exception ex)
{
// Ignore.
}
}
//
// Instance variables.
//
private LdapLexer lexer = null;
private List program;
public Parser()
{
reset();
}
public Parser(LdapLexer l)
{
reset(l);
}
public void reset()
{
lexer = null;
if (program == null)
{
program = new ArrayList();
}
program.clear();
}
public void reset(LdapLexer l)
{
reset();
lexer = l;
}
public Object[] getProgram()
{
return program.toArray(new Object[program.size()]);
}
// Define the recursive descent procedures
/*
<start>::= <filter> <EOF>
*/
public boolean start() throws ParseException, IOException
{
boolean ok = filter();
if (!ok)
{
return ok;
}
int ch = lexer.get();
if (ch != EOF)
{
throw new ParseException(
"expected <EOF>; found '" + ((char) ch) + "'");
}
return ok;
}
/*
<filter> ::= '(' <filtercomp> ')'
*/
boolean filter() throws ParseException, IOException
{
debug("filter");
if (lexer.peeknw() != LPAREN)
{
return false;
}
lexer.get();
if (!filtercomp())
{
throw new ParseException("expected filtercomp");
}
if (lexer.getnw() != RPAREN)
{
throw new ParseException("expected )");
}
return true;
}
/*
<filtercomp> ::= <and> | <or> | <not> | <item>
<and> ::= '&' <filterlist>
<or> ::= '|' <filterlist>
<not> ::= '!' <filter>
*/
boolean filtercomp() throws ParseException, IOException
{
debug("filtercomp");
int c = lexer.peeknw();
switch (c)
{
case '&' :
case '|' :
lexer.get();
int cnt = filterlist();
if (cnt == 0)
{
return false;
}
// Code: [And|Or](cnt)
program.add(
c == '&'
? (Operator) new AndOperator(cnt)
: (Operator) new OrOperator(cnt));
return true;
case '!' :
lexer.get();
if (!filter())
{
return false;
}
// Code: Not()
program.add(new NotOperator());
return true;
case EOF :
return false;
default :
// check for key
if (ATTRIBUTECHARS0.indexOf(c) <= 0)
{
return false;
}
boolean b = item();
return b;
}
}
/*
<filterlist> ::= <filter> | <filter> <filterlist>
*/
int filterlist() throws ParseException, IOException
{
debug("filterlist");
int cnt = 0;
if (filter())
{
do
{
cnt++;
}
while (filter());
}
return (cnt);
}
/*
<item> ::= <simple> | <present> | <substring>
<simple> ::= <attr> <filtertype> <value>
<filtertype> ::= <equal> | <approx> | <greater> | <less>
<present> ::= <attr> '=*'
<substring> ::= <attr> '=' <initial> <any> <final>
*/
boolean item() throws ParseException, IOException
{
debug("item");
StringBuffer attr = new StringBuffer();
if (!attribute(attr))
{
return false;
}
lexer.skipwhitespace(); // assume allowable before equal operator
// note: I treat the =* case as = followed by a special substring
int op = equalop();
if (op == NOOP)
{
String oplist = "=|~=|>=|<=";
throw new ParseException("expected " + oplist);
}
ArrayList pieces = new ArrayList();
int kind = substring(pieces);
// Get some of the illegal cases out of the way
if (op != '=' && kind != SIMPLE)
{
// We assume that only the = operator can work
// with right sides containing stars. If not correct
// then this code must change.
throw new ParseException("expected value|substring|*");
}
switch (kind)
{
case SIMPLE :
// Code: Push(attr); Constant(pieces.get(0)); <operator>();
program.add(new PushOperator(attr.toString()));
program.add(new ConstOperator(pieces.get(0)));
switch (op)
{
case '<' :
program.add(new LessEqualOperator());
break;
case '>' :
program.add(new GreaterEqualOperator());
break;
case '~' :
program.add(new ApproxOperator());
break;
case '=' :
default :
program.add(new EqualOperator());
}
break;
case PRESENT :
// Code: Present(attr);
program.add(new PresentOperator(attr.toString()));
break;
case SUBSTRING :
generateSubStringCode(attr.toString(), pieces);
break;
default :
throw new ParseException("expected value|substring|*");
}
return true;
}
// Generating code for substring right side is mildly
// complicated.
void generateSubStringCode(String attr, ArrayList pieces)
{
// Code: Push(attr)
program.add(new PushOperator(attr.toString()));
// Convert the pieces arraylist to a String[]
String[] list =
(String[]) pieces.toArray(new String[pieces.size()]);
// Code: SubString(list)
program.add(new SubStringOperator(list));
}
/*
<attr> is a string representing an attributte,
or key, in the properties
objects of the registered services. Attribute names are not case
sensitive; that is cn and CN both refer to the same attribute.
Attribute names may have embedded spaces, but not leading or
trailing spaces.
*/
boolean attribute(StringBuffer buf) throws ParseException, IOException
{
debug("attribute");
lexer.skipwhitespace();
buf.setLength(0);
int c = lexer.peek(); // need to make sure there
// is at least one KEYCHAR
if (c == EOF)
{
return false;
}
if (ATTRIBUTECHARS0.indexOf(c) < 0)
{
return false;
}
do
{
buf.append((char) lexer.get());
}
while (ATTRIBUTECHARS1.indexOf(lexer.peek()) >= 0);
// The above may have accumulated trailing blanks that must be removed
int i = buf.length() - 1;
while (i > 0 && buf.charAt(i) == ' ')
{
i--;
}
buf.setLength(i + 1);
return true;
}
/*
<equal> ::= '='
<approx> ::= '~='
<greater> ::= '>='
<less> ::= '<='
<present> ::= <attr> '=*'
*/
int equalop() throws ParseException, IOException
{
debug("equalop");
lexer.skipwhitespace();
int op = lexer.peek();
switch (op)
{
case '=' :
lexer.get();
break;
case '~' :
case '<' :
case '>' :
// skip main operator char
int c = lexer.get();
// make sure that the next char is '='
c = lexer.get();
if (c != '=')
{
throw new ParseException("expected ~=|>=|<=");
}
break;
default :
op = NOOP;
}
return op;
}
/*
<substring> ::= <attr> '=' <initial> <any> <final>
<initial> ::= NULL | <value>
<any> ::= '*' <starval>
<starval> ::= NULL | <value> '*' <starval>
<final> ::= NULL | <value>
<value> ::= ...
*/
/*
This procedure handles all cases on right side of an item
*/
int substring(ArrayList pieces) throws ParseException, IOException
{
debug("substring");
pieces.clear();
StringBuffer ss = new StringBuffer();
// int kind = SIMPLE; // assume until proven otherwise
boolean wasStar = false; // indicates last piece was a star
boolean leftstar = false; // track if the initial piece is a star
boolean rightstar = false; // track if the final piece is a star
// We assume (sub)strings can contain leading and trailing blanks
loop: for (;;)
{
int c = lexer.peek();
switch (c)
{
case RPAREN :
if (wasStar)
{
// insert last piece as "" to handle trailing star
rightstar = true;
}
else
{
pieces.add(ss.toString());
// accumulate the last piece
// note that in the case of
// (cn=); this might be
// the string "" (!=null)
}
ss.setLength(0);
break loop;
case '\\' :
wasStar = false;
lexer.get();
c = lexer.get();
if (c != EOF)
{
throw new ParseException("unexpected EOF");
}
ss.append((char) c);
break;
case EOF :
if (pieces.size() > 0)
{
throw new ParseException("expected ')'");
}
else
{
throw new ParseException("expected value|substring");
}
case '*' :
if (wasStar)
{
// encountered two successive stars;
// I assume this is illegal
throw new ParseException("unexpected '**'");
}
lexer.get();
if (ss.length() > 0)
{
pieces.add(ss.toString()); // accumulate the pieces
// between '*' occurrences
}
ss.setLength(0);
// if this is a leading star, then track it
if (pieces.size() == 0)
{
leftstar = true;
}
ss.setLength(0);
wasStar = true;
break;
default :
wasStar = false;
ss.append((char) lexer.get());
}
}
if (pieces.size() == 0)
{
return PRESENT;
}
if (leftstar || rightstar || pieces.size() > 1)
{
// insert leading and/or trailing "" to anchor ends
if (rightstar)
{
pieces.add("");
}
if (leftstar)
{
pieces.add(0, "");
}
return SUBSTRING;
}
// assert !leftstar && !rightstar && pieces.size == 1
return SIMPLE;
}
// Debug stuff
static boolean debug = false;
PrintStream dbgout = null;
public void setDebug(PrintStream out)
{
debug = true;
dbgout = out;
}
void debug(String proc)
{
if (!debug || dbgout == null)
{
return;
}
dbgout.println("parsing " + proc + ":" + lexer.charno());
dbgout.flush();
}
// Exclusive inner classes
private static class AndOperator extends Operator
{
private int operandCount;
public AndOperator(int opcnt)
{
operandCount = opcnt;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
// Determine result using short-circuit evaluation.
boolean result = true;
for (int i = 0; i < operandCount; i++)
{
if (operands.empty())
{
fewOperands("AND");
}
// For short-circuited evaluation, once the AND
// becomes false, we can ignore the remaining
// expressions, but we must still pop them off.
if (!result)
{
operands.pop();
}
else
{
result = ((Boolean) operands.pop()).booleanValue();
}
}
operands.push(new Boolean(result));
}
public String toString()
{
return "&(" + operandCount + ")";
}
public void buildTree(Stack operands)
{
children = new Operator[operandCount];
// need to preserve stack order
for (int i = 0; i < operandCount; i++)
{
children[(operandCount - 1) - i] =
(Operator) operands.pop();
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(&");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
o.toStringInfix(b);
}
b.append(")");
}
}
private static class OrOperator extends Operator
{
private int operandCount;
public OrOperator(int opcnt)
{
operandCount = opcnt;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
// Determine result using short-circuit evaluation.
boolean result = false;
for (int i = 0; i < operandCount; i++)
{
if (operands.empty())
{
fewOperands("OR");
}
// For short-circuited evaluation, once the OR
// becomes true, we can ignore the remaining
// expressions, but we must still pop them off.
if (result)
{
operands.pop();
}
else
{
result = ((Boolean) operands.pop()).booleanValue();
}
}
operands.push(new Boolean(result));
}
public String toString()
{
return "|(" + operandCount + ")";
}
public void buildTree(Stack operands)
{
children = new Operator[operandCount];
// need to preserve stack order
for (int i = 0; i < operandCount; i++)
{
children[(operandCount - 1) - i] =
(Operator) operands.pop();
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(|");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
o.toStringInfix(b);
}
b.append(")");
}
}
private static class NotOperator extends Operator
{
public NotOperator()
{
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands("NOT");
}
boolean result = !((Boolean) operands.pop()).booleanValue();
operands.push(new Boolean(result));
}
public String toString()
{
return "!()";
}
public void buildTree(Stack operands)
{
children = new Operator[1];
children[0] = (Operator) operands.pop();
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(!");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
o.toStringInfix(b);
}
b.append(")");
}
}
private static class EqualOperator extends Operator
{
public EqualOperator()
{
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands("=");
}
// We cheat and use the knowledge that top (right) operand
// will always be a string because of the way code was generated
String rhs = (String) operands.pop();
if (operands.empty())
{
fewOperands("=");
}
Object lhs = operands.pop();
operands.push(new Boolean(compare(lhs, rhs, EQUAL)));
}
public String toString()
{
return "=()";
}
public void buildTree(Stack operands)
{
children = new Operator[2];
// need to preserve stack order
for (int i = 0; i < 2; i++)
{
Operator o = (Operator) operands.pop();
children[1 - i] = o;
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
if (i > 0)
{
b.append("=");
}
o.toStringInfix(b);
}
b.append(")");
}
}
private static class GreaterEqualOperator extends Operator
{
public GreaterEqualOperator()
{
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands(">=");
}
// We cheat and use the knowledge that top (right) operand
// will always be a string because of the way code was generated
String rhs = (String) operands.pop();
if (operands.empty())
{
fewOperands(">=");
}
Object lhs = operands.pop();
operands.push(new Boolean(compare(lhs, rhs, GREATER_EQUAL)));
}
public String toString()
{
return ">=()";
}
public void buildTree(Stack operands)
{
children = new Operator[2];
// need to preserve stack order
for (int i = 0; i < 2; i++)
{
children[1 - i] = (Operator) operands.pop();
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
if (i > 0)
{
b.append(">=");
}
o.toStringInfix(b);
}
b.append(")");
}
}
private static class LessEqualOperator extends Operator
{
public LessEqualOperator()
{
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands("<=");
}
// We cheat and use the knowledge that top (right) operand
// will always be a string because of the way code was generated
String rhs = (String) operands.pop();
if (operands.empty())
{
fewOperands("<=");
}
Object lhs = (Object) operands.pop();
operands.push(new Boolean(compare(lhs, rhs, LESS_EQUAL)));
}
public String toString()
{
return "<=()";
}
public void buildTree(Stack operands)
{
children = new Operator[2];
// need to preserve stack order
for (int i = 0; i < 2; i++)
{
children[1 - i] = (Operator) operands.pop();
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
if (i > 0)
{
b.append("<=");
}
o.toStringInfix(b);
}
b.append(")");
}
}
private static class ApproxOperator extends Operator
{
public ApproxOperator()
{
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands("~=");
}
// We cheat and use the knowledge that top (right) operand
// will always be a string because of the way code was generated
String rhs = (String) operands.pop();
if (operands.empty())
{
fewOperands("~=");
}
Object lhs = operands.pop();
operands.push(new Boolean(compare(lhs, rhs, APPROX)));
}
public String toString()
{
return "~=()";
}
public void buildTree(Stack operands)
{
children = new Operator[2];
// need to preserve stack order
for (int i = 0; i < 2; i++)
{
children[1 - i] = (Operator) operands.pop();
}
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
for (int i = 0; i < children.length; i++)
{
Operator o = (Operator) children[i];
if (i > 0)
{
b.append("~=");
}
o.toStringInfix(b);
}
b.append(")");
}
}
private static class PresentOperator extends Operator
{
String attribute;
public PresentOperator(String attribute)
{
this.attribute = attribute;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
Object value = mapper.lookup(attribute);
operands.push(new Boolean(value != null));
}
public String toString()
{
return attribute + "=*";
}
public void buildTree(Stack operands)
{
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
b.append(attribute + "=*");
b.append(")");
}
}
private static class PushOperator extends Operator
{
String attribute;
public PushOperator(String attribute)
{
this.attribute = attribute;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
// find and push the value of a given attribute
Object value = mapper.lookup(attribute);
if (value == null)
{
throw new AttributeNotFoundException(
"attribute " + attribute + " not found");
}
operands.push(value);
}
public String toString()
{
return "push(" + attribute + ")";
}
public String toStringInfix()
{
return attribute;
}
public void buildTree(Stack operands)
{
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append(attribute);
}
}
private static class ConstOperator extends Operator
{
Object val;
public ConstOperator(Object val)
{
this.val = val;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
operands.push(val);
}
public String toString()
{
return "const(" + val + ")";
}
public String toStringInfix()
{
return val.toString();
}
public void buildTree(Stack operands)
{
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append(val.toString());
}
}
private static class SubStringOperator extends Operator
implements OperatorConstants
{
String[] pieces;
public SubStringOperator(String[] pieces)
{
this.pieces = pieces;
}
public void execute(Stack operands, Mapper mapper)
throws EvaluationException
{
if (operands.empty())
{
fewOperands("SUBSTRING");
}
Object op = operands.pop();
// The operand can either be a string or an array of strings.
if (op instanceof String)
{
operands.push(check((String) op));
}
else if (op instanceof String[])
{
// If one element of the array matches, then push true.
String[] ops = (String[]) op;
boolean result = false;
for (int i = 0; !result && (i < ops.length); i++)
{
if (check(ops[i]) == Boolean.TRUE)
{
result = true;
}
}
operands.push((result) ? Boolean.TRUE : Boolean.FALSE);
}
else
{
unsupportedType("SUBSTRING", op.getClass());
}
}
private Boolean check(String s)
{
// Walk the pieces to match the string
// There are implicit stars between each piece,
// and the first and last pieces might be "" to anchor the match.
// assert (pieces.length > 1)
// minimal case is <string>*<string>
Boolean result = Boolean.FALSE;
int len = pieces.length;
loop : for (int i = 0; i < len; i++)
{
String piece = (String) pieces[i];
int index = 0;
if (i == len - 1)
{
// this is the last piece
if (s.endsWith(piece))
{
result = Boolean.TRUE;
}
else
{
result = Boolean.FALSE;
}
break loop;
}
// initial non-star; assert index == 0
else if (i == 0)
{
if (!s.startsWith(piece))
{
result = Boolean.FALSE;
break loop;
}
}
// assert i > 0 && i < len-1
else
{
// Sure wish stringbuffer supported e.g. indexOf
index = s.indexOf(piece, index);
if (index < 0)
{
result = Boolean.FALSE;
break loop;
}
}
// start beyond the matching piece
index += piece.length();
}
return result;
}
public String toString()
{
StringBuffer b = new StringBuffer();
b.append("substring(");
for (int i = 0; i < pieces.length; i++)
{
String piece = pieces[i];
if (i > 0)
{
b.append("*");
}
b.append(escape(piece));
}
b.append(")");
return b.toString();
}
public String escape(String s)
{
int len = s.length();
StringBuffer buf = new StringBuffer(len);
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c == ')' || c == '*')
buf.append('\\');
buf.append(c);
}
return buf.toString();
}
public void buildTree(Stack operands)
{
children = new Operator[1];
children[0] = (Operator) operands.pop();
operands.push(this);
}
public void toStringInfix(StringBuffer b)
{
b.append("(");
children[0].toStringInfix(b); // dump attribute
b.append("=");
for (int i = 0; i < pieces.length; i++)
{
String piece = (String) pieces[i];
if (i > 0)
{
b.append("*");
}
b.append(piece);
}
b.append(")");
}
}
// Utility classes and Interfaces
private interface OperatorConstants
{
static final int SSINIT = 0;
static final int SSFINAL = 1;
static final int SSMIDDLE = 2;
static final int SSANY = 3;
}
/**
* Compare two operands in an expression with respect
* to the following operators =, <=, >= and ~=
*
* Example: value=100
*
* @param lhs an object that implements comparable or an array of
* objects that implement comparable.
* @param rhs a string representing the right operand.
* @param operator an integer that represents the operator.
* @return <tt>true</tt> or <tt>false</tt> according to the evaluation.
* @throws EvaluationException if it is not possible to do the comparison.
**/
public static boolean compare(Object lhs, String rhs, int operator)
throws EvaluationException
{
// Determine class of LHS.
Class lhsClass = null;
// If LHS is an array, then call compare() on each element
// of the array until a match is found.
if (lhs.getClass().isArray())
{
// First, if this is an array of primitives, then convert
// the entire array to an array of the associated
// primitive wrapper class instances.
if (lhs.getClass().getComponentType().isPrimitive())
{
lhs = convertPrimitiveArray(lhs);
}
// Now call compare on each element of array.
Object[] array = (Object[]) lhs;
for (int i = 0; i < array.length; i++)
{
if (compare(array[i], rhs, operator))
{
return true;
}
}
}
// If LHS is a vector, then call compare() on each element
// of the vector until a match is found.
else if (lhs instanceof Vector)
{
for (Enumeration e = ((Vector) lhs).elements(); e.hasMoreElements();)
{
if (compare(e.nextElement(), rhs, operator))
{
return true;
}
}
}
else
{
// Get the class of LHS.
lhsClass = lhs.getClass();
// At this point we are expecting the LHS to be a comparable,
// but Boolean is a special case since it is the only primitive
// wrapper class that does not implement comparable; deal with
// Boolean separately.
if (lhsClass == Boolean.class)
{
return compareBoolean(lhs, rhs, operator);
}
// If LHS is not a Boolean, then verify it is a comparable
// and perform comparison.
if (!(Comparable.class.isAssignableFrom(lhsClass)))
{
String opName = null;
switch (operator)
{
case EQUAL :
opName = "=";
case GREATER_EQUAL :
opName = ">=";
case LESS_EQUAL :
opName = "<=";
case APPROX:
opName = "~=";
default:
opName = "UNKNOWN OP";
}
unsupportedType(opName, lhsClass);
}
// We will try to create a comparable object from the
// RHS string.
Comparable rhsComparable = null;
try
{
// We are expecting to be able to construct a comparable
// instance from the RHS string by passing it into the
// constructor of the corresponing comparable class. The
// Character class is a special case, since its constructor
// does not take a string, so handle it separately.
if (lhsClass == Character.class)
{
rhsComparable = new Character(rhs.charAt(0));
}
else
{
rhsComparable = (Comparable) lhsClass
.getConstructor(new Class[] { String.class })
.newInstance(new Object[] { rhs });
}
}
catch (Exception ex)
{
String msg = (ex.getCause() == null)
? ex.toString() : ex.getCause().toString();
throw new EvaluationException(
"Could not instantiate class "
+ lhsClass.getName()
+ " with constructor String parameter "
+ rhs + " " + msg);
}
Comparable lhsComparable = (Comparable) lhs;
switch (operator)
{
case EQUAL :
return (lhsComparable.compareTo(rhsComparable) == 0);
case GREATER_EQUAL :
return (lhsComparable.compareTo(rhsComparable) >= 0);
case LESS_EQUAL :
return (lhsComparable.compareTo(rhsComparable) <= 0);
case APPROX:
return compareToApprox(lhsComparable, rhsComparable);
default:
throw new EvaluationException("Unknown comparison operator..."
+ operator);
}
}
return false;
}
/**
* This is an ugly utility method to convert an array of primitives
* to an array of primitive wrapper objects. This method simplifies
* processing LDAP filters since the special case of primitive arrays
* can be ignored.
* @param array
* @return
**/
private static Object[] convertPrimitiveArray(Object array)
{
Class clazz = array.getClass().getComponentType();
if (clazz == Boolean.TYPE)
{
boolean[] src = (boolean[]) array;
array = new Boolean[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Boolean(src[i]);
}
}
else if (clazz == Character.TYPE)
{
char[] src = (char[]) array;
array = new Character[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Character(src[i]);
}
}
else if (clazz == Byte.TYPE)
{
byte[] src = (byte[]) array;
array = new Byte[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Byte(src[i]);
}
}
else if (clazz == Short.TYPE)
{
byte[] src = (byte[]) array;
array = new Byte[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Byte(src[i]);
}
}
else if (clazz == Integer.TYPE)
{
int[] src = (int[]) array;
array = new Integer[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Integer(src[i]);
}
}
else if (clazz == Long.TYPE)
{
long[] src = (long[]) array;
array = new Long[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Long(src[i]);
}
}
else if (clazz == Float.TYPE)
{
float[] src = (float[]) array;
array = new Float[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Float(src[i]);
}
}
else if (clazz == Double.TYPE)
{
double[] src = (double[]) array;
array = new Double[src.length];
for (int i = 0; i < src.length; i++)
{
((Object[]) array)[i] = new Double(src[i]);
}
}
return (Object[]) array;
}
private static boolean compareBoolean(Object lhs, String rhs, int operator)
throws EvaluationException
{
Boolean rhsBoolean = new Boolean(rhs);
if (lhs.getClass().isArray())
{
Object[] objs = (Object[]) lhs;
for (int i = 0; i < objs.length; i++)
{
switch (operator)
{
case EQUAL :
case GREATER_EQUAL :
case LESS_EQUAL :
case APPROX:
if (objs[i].equals(rhsBoolean))
{
return true;
}
break;
default:
throw new EvaluationException(
"Unknown comparison operator: " + operator);
}
}
return false;
}
else
{
switch (operator)
{
case EQUAL :
case GREATER_EQUAL :
case LESS_EQUAL :
case APPROX:
return (lhs.equals(rhsBoolean));
default:
throw new EvaluationException("Unknown comparison operator..."
+ operator);
}
}
}
/**
* Test if two objects are approximate. The two objects that are passed must
* have the same type.
*
* Approximate for numerical values involves a difference of less than APPROX_CRITERIA
* Approximate for string values is calculated by using the Levenshtein distance
* between strings. Less than APPROX_CRITERIA of difference is considered as approximate.
*
* Supported types only include the following subclasses of Number:
* - Byte
* - Double
* - Float
* - Int
* - Long
* - Short
* - BigInteger
* - BigDecimal
* As subclasses of Number must provide methods to convert the represented numeric value
* to byte, double, float, int, long, and short. (see API)
*
* @param obj1
* @param obj2
* @return true if they are approximate
* @throws EvaluationException if it the two objects cannot be approximated
**/
private static boolean compareToApprox(Object obj1, Object obj2) throws EvaluationException
{
if (obj1 instanceof Byte)
{
byte value1 = ((Byte)obj1).byteValue();
byte value2 = ((Byte)obj2).byteValue();
return (value2 >= (value1-((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100))
&& value2 <= (value1+((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100)));
}
else if (obj1 instanceof Character)
{
char value1 = ((Character)obj1).charValue();
char value2 = ((Character)obj2).charValue();
return (value2 >= (value1-((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100))
&& value2 <= (value1+((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100)));
}
else if (obj1 instanceof Double)
{
double value1 = ((Double)obj1).doubleValue();
double value2 = ((Double)obj2).doubleValue();
return (value2 >= (value1-((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100))
&& value2 <= (value1+((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100)));
}
else if (obj1 instanceof Float)
{
float value1 = ((Float)obj1).floatValue();
float value2 = ((Float)obj2).floatValue();
return (value2 >= (value1-((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100))
&& value2 <= (value1+((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100)));
}
else if (obj1 instanceof Integer)
{
int value1 = ((Integer)obj1).intValue();
int value2 = ((Integer)obj2).intValue();
return (value2 >= (value1-((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100))
&& value2 <= (value1+((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100)));
}
else if (obj1 instanceof Long)
{
long value1 = ((Long)obj1).longValue();
long value2 = ((Long)obj2).longValue();
return (value2 >= (value1-((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100))
&& value2 <= (value1+((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100)));
}
else if (obj1 instanceof Short)
{
short value1 = ((Short)obj1).shortValue();
short value2 = ((Short)obj2).shortValue();
return (value2 >= (value1-((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100))
&& value2 <= (value1+((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100)));
}
else if (obj1 instanceof String)
{
int distance = getDistance(obj1.toString(),obj2.toString());
int size = ((String)obj1).length();
return (distance <= ((size*APPROX_CRITERIA)/100));
}
else if (m_hasBigNumbers && (obj1 instanceof BigInteger))
{
BigInteger value1 = (BigInteger)obj1;
BigInteger value2 = (BigInteger)obj2;
BigInteger delta = value1.abs().multiply(
BigInteger.valueOf(APPROX_CRITERIA)
.divide(BigInteger.valueOf(100)));
BigInteger low = value1.subtract(delta);
BigInteger high = value1.add(delta);
return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
}
else if (m_hasBigNumbers && (obj1 instanceof BigDecimal))
{
BigDecimal value1 = (BigDecimal)obj1;
BigDecimal value2 = (BigDecimal)obj2;
BigDecimal delta = value1.abs().multiply(
BigDecimal.valueOf(APPROX_CRITERIA)
.divide(BigDecimal.valueOf(100), BigDecimal.ROUND_HALF_DOWN));
BigDecimal low = value1.subtract(delta);
BigDecimal high = value1.add(delta);
return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
}
throw new EvaluationException(
"Approximate operator not supported for type "
+ obj1.getClass().getName());
}
/**
* Calculate the Levenshtein distance (LD) between two strings.
* The Levenshteing distance is a measure of the similarity between
* two strings, which we will refer to as the source string (s) and
* the target string (t). The distance is the number of deletions,
* insertions, or substitutions required to transform s into t.
*
* Algorithm from: http://www.merriampark.com/ld.htm
*
* @param s the first string
* @param t the second string
* @return
*/
private static int getDistance(String s, String t)
{
int d[][]; // matrix
int n; // length of s
int m; // length of t
int i; // iterates through s
int j; // iterates through t
char s_i; // ith character of s
char t_j; // jth character of t
int cost; // cost
// Step 1
n = s.length();
m = t.length();
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
d = new int[n + 1][m + 1];
// Step 2
for (i = 0; i <= n; i++)
{
d[i][0] = i;
}
for (j = 0; j <= m; j++)
{
d[0][j] = j;
}
// Step 3
for (i = 1; i <= n; i++)
{
s_i = s.charAt(i - 1);
// Step 4
for (j = 1; j <= m; j++)
{
t_j = t.charAt(j - 1);
// Step 5
if (s_i == t_j)
{
cost = 0;
}
else
{
cost = 1;
}
// Step 6
d[i][j] =
Minimum(
d[i - 1][j] + 1,
d[i][j - 1] + 1,
d[i - 1][j - 1] + cost);
}
}
// Step 7
return d[n][m];
}
/**
* Calculate the minimum between three values
*
* @param a
* @param b
* @param c
* @return
*/
private static int Minimum(int a, int b, int c)
{
int mi;
mi = a;
if (b < mi)
{
mi = b;
}
if (c < mi)
{
mi = c;
}
return mi;
}
private static void fewOperands(String op) throws EvaluationException
{
throw new EvaluationException(op + ": too few operands");
}
private static void unsupportedType(String opStr, Class clazz)
throws EvaluationException
{
throw new EvaluationException(
opStr + ": unsupported type " + clazz.getName(), clazz);
}
}