
com.tailf.jnc.Path Maven / Gradle / Ivy
package com.tailf.jnc;
import java.util.ArrayList;
/**
* A path expression. This is a small subset of the W3C recommendations of
* XPath 1.0 expression evaluator and parser.
*
* The result of evaluating an expression is a set of {@link Element} nodes
* that fulfill the location steps, with node tests, and predicates. The path
* expression operates on an element tree.
*
* Example:
*
*
* Path expr = new Path("/hosts/host[name='kalle']/ip");
* NodeSet s = path.eval(element_tree);
*
*
**/
public class Path {
/**
* Constructor for a Path from a path expression string.
*/
public Path(String pathStr) throws JNCException {
create = false;
this.pathStr = pathStr;
locationSteps = parse(tokenize(pathStr));
}
/**
* Dummy constructor
*/
Path() {
}
/**
* Evaluates the Path given a contextNode.
*
* Makes a selection by traversing the locationSteps, return an ArrayList
* (NodeSet) of Element nodes.
*
* @param contextNode The context node to evaluate expressions on
* @return A nodeSet of elements
*/
public NodeSet eval(Element contextNode) throws JNCException {
trace("eval(): " + this);
NodeSet nodeSet = new NodeSet();
nodeSet.add(contextNode);
for (final LocationStep step : locationSteps) {
nodeSet = step.step(nodeSet);
}
return nodeSet;
}
/**
* Evaluate the Path given a NodeSet.
*
* Makes a selection by traversing ONE locationStep. Returns an updated
* NodeSet of Element nodes.
*/
NodeSet evalStep(NodeSet nodeSet, int step) throws JNCException {
if (step < 0 || step >= locationSteps.size()) {
throw new JNCException(JNCException.PATH_ERROR,
"cannot eval location step: " + step + " in path");
}
final LocationStep locStep = locationSteps.get(step);
trace("evalStep(): step=" + step + ", " + locStep);
nodeSet = locStep.step(nodeSet);
return nodeSet;
}
/**
* Return the number of LocationSteps in this Path. Steps are numbered from
* 0 to NumberOfSteps-1. Use with evalStep above.
*/
int steps() {
return locationSteps.size();
}
/* Internal section */
/** AXIS in LocationStep */
static final int AXIS_CHILD = 1;
static final int AXIS_SELF = 2;
static final int AXIS_PARENT = 3;
static final int AXIS_ROOT = 4;
/**
* Flag says if it's a 'path' or a 'create path' expression.
*/
boolean create = false;
/**
* The list of location steps (LocationStep).
*/
ArrayList locationSteps;
/**
* The original path string.
*/
String pathStr;
/**
* Location step.
*
* A location step has three parts:
*
* - an axis, which specifies the tree relationship between the nodes
* selected by the location step and the context node,
*
-
* a node test, which specifies the node type and expanded-name of the
* nodes selected by the location step, and
*
-
* zero or more predicates, which use arbitrary expressions to further
* refine the set of nodes selected by the location step.
*
*/
class LocationStep {
int axis;
String name;
String prefix;
ArrayList predicates; // list of Expr (Predicates)
LocationStep(int axis) {
this.axis = axis;
}
LocationStep(int axis, String name) {
this.axis = axis;
this.name = name;
}
LocationStep(int axis, String prefix, String name) {
this.axis = axis;
this.prefix = prefix;
this.name = name;
}
/**
* Step down
*/
NodeSet step(NodeSet nodes) throws JNCException {
final NodeSet result = new NodeSet();
for (int i = 0; i < nodes.size(); i++) {
final Element node = nodes.getElement(i);
/** select axis */
switch (axis) {
case AXIS_CHILD:
if (node.children != null) {
result.addAll(nodeTest(node.children));
}
break;
case AXIS_PARENT:
if (node.parent != null) {
result.addAll(nodeTest(new NodeSet(node.parent)));
}
break;
case AXIS_SELF:
result.addAll(nodeTest(new NodeSet(node)));
break;
}
// FIXME: What about AXIS_ROOT and other cases?
}
return result;
}
/**
* perform nodeTest on nodeSet. (only NameTest) since all nodes are
* simplified to be Elements
*/
private NodeSet nodeTest(NodeSet nodeSet) throws JNCException {
NodeSet result = new NodeSet();
/**
* A simple "NameTest" Filter away those with wrong name
*/
for (int i = 0; i < nodeSet.size(); i++) {
final Element node = nodeSet.getElement(i);
if (node.name.equals(name)) {
/* check namespace also, if prefix is given */
String namespace;
if (prefix != null) {
namespace = node.lookupContextPrefix(prefix);
if (node.namespace.equals(namespace)) {
result.add(node);
}
} else {
// skip namespace test when prefix is not given
result.add(node);
}
}
}
if (result.size() == 0) {
return result;
}
/* Apply predicates on nodeSet in order, if any. Rebuild new
* nodeSet, for each predicate level, since contextSet is needed as
* an argument we do not want to change it. */
NodeSet contextSet;
if (predicates != null) {
for (int i = 0; i < predicates.size(); i++) {
final Expr p = predicates.get(i);
contextSet = result;
result = new NodeSet();
for (int j = 0; j < contextSet.size(); j++) {
final Element node = contextSet.getElement(j);
if (p.eval(node, contextSet).booleanValue()) {
result.add(node);
}
}
}
}
return result;
}
/**
* Creates a Element node from this LocationStep. Used from
* PathCreate.eval() to build a structure of elements.
*/
Element createElem(PrefixMap prefixMap, Element parent)
throws JNCException {
trace("createElem() from " + this);
switch (axis) {
case AXIS_ROOT:
return null;
case AXIS_CHILD:
String ns;
if (prefix == null) {
if (parent != null) {
ns = parent.namespace;
} else {
ns = prefixMap.prefixToNs("");
}
} else {
ns = prefixMap.prefixToNs(prefix);
}
if (ns == null) {
throw new JNCException(JNCException.PATH_CREATE_ERROR,
"missing namespace for prefix: \"" + prefix
+ "\"");
}
final Element elem = new Element(ns, name);
// need to do createElem on predicates as well
if (predicates != null) {
for (int i = 0; i < predicates.size(); i++) {
final Expr expr = predicates.get(i);
expr.evalCreate(elem);
}
}
return elem;
case AXIS_SELF:
case AXIS_PARENT:
default:
throw new JNCException(JNCException.PATH_CREATE_ERROR,
"unknown axis in create path");
}
}
/**
* Return the string representation of this LocationStep.
*/
@Override
public String toString() {
String s = "LocationStep{";
switch (axis) {
case AXIS_CHILD:
s = s + "AXIS_CHILD";
break;
case AXIS_PARENT:
s = s + "AXIS_PARENT";
break;
case AXIS_SELF:
s = s + "AXIS_SELF";
break;
case AXIS_ROOT:
s = s + "AXIS_ROOT";
break;
default:
s = s + "AXIS_UNKNOWN(" + axis + ")";
}
if (prefix != null) {
s = s + ",prefix=" + prefix;
}
if (name != null) {
s = s + ",name=" + name;
}
if (predicates != null && predicates.size() > 0) {
s = s + "," + predicates;
}
s = s + "}";
return s;
}
/**
*
*/
private void trace(String s) {
if (Element.debugLevel >= Element.DEBUG_LEVEL_LOCATIONSTEP) {
System.err.println("*LocationStep: " + s);
}
}
}
/**
* Predicate expression Examples: [@AttrName=Value, ...] [@AttrName>Value,
* ...] [@AttrName, ...] ; value=null denotes that AttrName exists
*
*/
class Expr {
int op; // See OP codes above
Object lvalue; // Expr|Boolean|String|Integer|NodeSet
Object rvalue; // Expr|Boolean|String|Integer|NodeSet
/** constructor */
Expr(int op, Object lvalue) {
this.op = op;
this.lvalue = lvalue;
}
Expr(int op, Object lvalue, Object rvalue) {
this.op = op;
this.lvalue = lvalue;
this.rvalue = rvalue;
}
public Boolean eval(Element node, NodeSet contextSet)
throws JNCException {
return f_boolean(eval2(node, contextSet));
}
private Object eval2(Element node, NodeSet contextSet)
throws JNCException {
Object lval, rval; // results
lval = lvalue;
rval = rvalue;
// eval sub expressions
if (lval instanceof Expr) {
lval = ((Expr) lval).eval2(node, contextSet);
}
if (rval instanceof Expr) {
rval = ((Expr) rval).eval(node, contextSet);
}
// the type of lval and rval is now : String | Boolean | Integer
switch (op) {
case ATTR_VALUE:
return node.getAttrValue((String) lval);
case CHILD_VALUE:
return node.getValueOfChild((String) lval);
case OR:
// rvalue not evaluated if lval is 'true'
if (f_boolean(lval).booleanValue()) {
return true;
} else {
return f_boolean(rval);
}
case AND:
// rvalue not evaluated if lval is 'false'
if (!f_boolean(lval).booleanValue()) {
return false;
} else {
return f_boolean(rval);
}
case EQ: // '='
case NEQ: // '!='
if (isBoolean(lval)) {
rval = f_boolean(rval);
} else if (isBoolean(rval)) {
lval = f_boolean(lval);
} else if (isNumber(lval)) {
rval = f_number(rval);
} else if (isNumber(rval)) {
lval = f_number(lval);
} else {
lval = f_string(lval);
rval = f_string(rval);
}
if (op == EQ) {
return compare(lval, rval) == 0;
} else // op==NEQ
return compare(lval, rval) != 0;
case GT: // '>'
return compare(f_number(lval), f_number(rval)) > 0;
case GTE: // '>='
return compare(f_number(lval), f_number(rval)) >= 0;
case LT: // '<'
return compare(f_number(lval), f_number(rval)) < 0;
case LTE: // '<='
return compare(f_number(lval), f_number(rval)) <= 0;
// FUNCTIONS
case FUN_STRING:
return f_string(lval);
case FUN_NUMBER:
return f_number(lval);
case FUN_BOOLEAN:
return f_boolean(lval);
case FUN_POSITION:
return Integer.valueOf(contextSet.indexOf(node) + 1);
case FUN_LAST:
return Integer.valueOf(contextSet.size());
case FUN_COUNT:
return Integer.valueOf(f_nodeSet(lval).size());
// STRING FUNCTIONS:
case FUN_CONCAT:
return f_string(lval) + f_string(rval);
// BOOLEAN FUNCTIONS:
case FUN_NOT:
return !f_boolean(lval).booleanValue();
case FUN_TRUE:
return true;
case FUN_FALSE:
return false;
// NUMBER FUNCTIONS:
case FUN_NEG: // '- x' (UNARY)
return neg(f_number(lval));
case MINUS: // 'x - y'
return minus(f_number(lval), f_number(rval));
case PLUS: // 'x + y'
return plus(f_number(lval), f_number(rval));
default:
throw new JNCException(JNCException.PATH_ERROR,
"illegal operator: " + op);
}
}
/** compare */
private int compare(Object x, Object y) throws JNCException {
if ((x instanceof Boolean) && (y instanceof Boolean)) {
if (((Boolean) x).booleanValue() == ((Boolean) y)
.booleanValue()) {
return 0;
} else {
return -1;
}
}
if ((x instanceof Integer) && (y instanceof Integer)) {
return ((Integer) x).compareTo((Integer) y);
}
if (x instanceof Number) {
return ((Float) x).compareTo(f_float(y));
}
if ((x instanceof String) && (y instanceof String)) {
if (((String) x).equals(y)) {
return 0;
} else {
return -1;
}
}
if (x == null) {
return -1;
}
if (y == null) {
return +1;
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to compare (not of same type): " + x + ", " + y);
}
private boolean isBoolean(Object x) {
return (x instanceof Boolean);
}
private boolean isNumber(Object x) {
return (x instanceof Float) || (x instanceof Integer);
}
/** boolean() function */
private Boolean f_boolean(Object x) throws JNCException {
if (x instanceof Float) {
final float f = (Float) x;
return Boolean.valueOf(f > 0.000001 || f < 0.000001);
} else if (x instanceof Integer) {
final int n = (Integer) x;
return Boolean.valueOf(n != 0);
} else if (x instanceof String) {
return Boolean.valueOf(((String) x).length() > 0);
} else if (x instanceof Boolean) {
return (Boolean) x;
} else if (x == null) {
return null;
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to function boolean(): " + x);
}
/** number() function. */
private Number f_number(Object x) throws JNCException {
if (x instanceof Float) {
return (Float) x;
} else if (x instanceof Integer) {
return (Integer) x;
} else if (x instanceof Boolean) {
return Integer.valueOf((((Boolean) x).booleanValue()) ? 1 : 0);
} else if (x instanceof String) {
final String s = (String) x;
try {
return Integer.valueOf(s);
} catch (final NumberFormatException e1) {
try {
return new Float(s);
} catch (final NumberFormatException e2) {
return new Float(Float.NaN);
}
}
} else if (x instanceof NodeSet) {
return f_number(f_string(x));
} else if (x == null) {
return null;
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to function number(): " + x);
}
/** nodeset() function */
private NodeSet f_nodeSet(Object x) throws JNCException {
if (x instanceof NodeSet) {
return (NodeSet) x;
} else if (x == null) {
return null;
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to function nodeset(): " + x);
}
/** neg. Unary minus "-x" */
private Number neg(Object x) throws JNCException {
if (x instanceof Integer) {
return Integer.valueOf(-((Integer) x).intValue());
} else if (x instanceof Float) {
return new Float(-((Float) x).floatValue());
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to function neg(): " + x);
}
/** minus "x - y" */
private Number minus(Number x, Number y) throws JNCException {
if ((x instanceof Integer) && (y instanceof Integer)) {
return Integer.valueOf(((Integer) x).intValue()
- ((Integer) y).intValue());
}
final Float xf = f_float(x);
final Float yf = f_float(y);
return new Float(xf.floatValue() - yf.floatValue());
}
/** plus "x + y" */
private Number plus(Number x, Number y) throws JNCException {
if ((x instanceof Integer) && (y instanceof Integer)) {
return Integer.valueOf(((Integer) x).intValue()
+ ((Integer) y).intValue());
}
final Float xf = f_float(x);
final Float yf = f_float(y);
return new Float(xf.floatValue() + yf.floatValue());
}
/** the float() function. */
private Float f_float(Object x) throws JNCException {
if (x instanceof Float) {
return (Float) x;
} else if (x instanceof Integer) {
return new Float(((Integer) x).floatValue());
} else if (x == null) {
return null;
}
throw new JNCException(JNCException.PATH_ERROR,
"badarg to function float(): " + x);
}
/** the string() function. */
private String f_string(Object x) throws JNCException {
if (x instanceof Integer) {
return ((Integer) x).toString();
} else if (x instanceof Float) {
return ((Float) x).toString();
} else if (x instanceof String) {
return (String) x;
} else if (x instanceof Boolean) {
return ((Boolean) x) ? "true" : "false";
} else if (x == null) {
return null;
}
return x.toString();
}
/**
* evalCreate will create attributes and values on the Elementent that
* is being created.
*/
Object evalCreate(Element node) throws JNCException {
trace("evalCreate(): Expr= " + this);
Object lval, rval; // results
lval = lvalue;
rval = rvalue;
// eval sub expressions
if (lval instanceof Expr) {
lval = ((Expr) lval).evalCreate(node);
// if (rval instanceof Expr)
// rval = ((Expr)rval).eval(node);
}
// the type of lval and rval is now : String | Boolean | Integer
switch (op) {
case CHILD_VALUE:
// create child value, inherit namnespace from parent
// TODO: match out prefix possible here?
String name = (String) lval;
final String ns = node.namespace;
Element elem = new Element(ns, name);
node.addChild(elem);
return elem;
case ATTR_VALUE:
name = (String) lval;
return node.setAttr(name, "");
case EQ: // '='
if (lval instanceof Element) {
elem = (Element) lval;
elem.setValue(f_string(rval));
return elem;
}
if (lval instanceof Attribute) {
final Attribute attr = (Attribute) lval;
attr.setValue(f_string(rval));
return attr;
}
throw new JNCException(JNCException.PATH_CREATE_ERROR,
"illegal path create expr: " + this);
default:
throw new JNCException(JNCException.PATH_CREATE_ERROR,
"illegal path create expr:" + this);
}
}
/**
* Returns a string representation of this Expr.
*/
@Override
public String toString() {
switch (op) {
case AND:
return "AND(" + lvalue + "," + rvalue + ")";
case OR:
return "OR(" + lvalue + "," + rvalue + ")";
case EQ:
return "EQ(" + lvalue + "," + rvalue + ")";
case NEQ:
return "NEQ(" + lvalue + "," + rvalue + ")";
case GT:
return "GT(" + lvalue + "," + rvalue + ")";
case GTE:
return "GTE(" + lvalue + "," + rvalue + ")";
case LT:
return "LT(" + lvalue + "," + rvalue + ")";
case LTE:
return "LTE(" + lvalue + "," + rvalue + ")";
case PLUS:
return "PLUS(" + lvalue + "," + rvalue + ")";
case MINUS:
return "MINUS(" + lvalue + "," + rvalue + ")";
case FUN_BOOLEAN:
return "FUN_BOOLEAN(" + lvalue + ")";
case FUN_NUMBER:
return "FUN_NUMBER(" + lvalue + ")";
case FUN_STRING:
return "FUN_STRING(" + lvalue + ")";
case FUN_POSITION:
return "FUN_POSITION()";
case FUN_LAST:
return "FUN_LAST()";
case FUN_COUNT:
return "FUN_COUNT(" + lvalue + ")";
case FUN_CONCAT:
return "FUN_COUNT(" + lvalue + "," + rvalue + ")";
case FUN_NOT:
return "FUN_NOT(" + lvalue + ")";
case FUN_TRUE:
return "FUN_TRUE()";
case FUN_FALSE:
return "FUN_FALSE()";
case FUN_NEG:
return "FUN_NEG(" + lvalue + ")";
case ATTR_VALUE:
return "ATTR_VALUE(" + lvalue + ")";
case CHILD_VALUE:
return "CHILD_VALUE(" + lvalue + ")";
default:
return "Expr{op=" + op + "," + lvalue + "," + rvalue + "}";
}
}
/**
*
*/
private void trace(String s) {
if (Element.debugLevel >= Element.DEBUG_LEVEL_EXPR) {
System.err.println("*Expr: " + s);
}
}
}
/* Parser */
/** OP codes in Expr (Predicate) */
static final int AND = 1;
static final int OR = 2;
static final int EQ = 3;
static final int NEQ = 4;
static final int GT = 5;
static final int GTE = 6;
static final int LT = 7;
static final int LTE = 8;
static final int PLUS = 9;
static final int MINUS = 10;
static final int FUN_BOOLEAN = 11;
static final int FUN_NUMBER = 12;
static final int FUN_STRING = 13;
static final int FUN_POSITION = 14;
static final int FUN_LAST = 15;
static final int FUN_COUNT = 16;
static final int FUN_CONCAT = 17;
static final int FUN_NOT = 18;
static final int FUN_TRUE = 19;
static final int FUN_FALSE = 20;
static final int FUN_NEG = 21;
static final int ATTR_VALUE = 22;
static final int CHILD_VALUE = 23;
/**
* Returns a list of LocationSteps for a path expression.
*
*/
ArrayList parse(TokenList tokens) throws JNCException {
final ArrayList steps = new ArrayList();
try {
Token tok1, tok2, tok3, tok4, tok5;
LocationStep step;
int sz = tokens.size();
while (sz > 0) {
trace("parse(): " + tokens);
/* peek at tokens */
if (sz >= 1) {
tok1 = tokens.getToken(0);
} else {
tok1 = new Token();
}
if (sz >= 2) {
tok2 = tokens.getToken(1);
} else {
tok2 = new Token();
}
if (sz >= 3) {
tok3 = tokens.getToken(2);
} else {
tok3 = new Token();
}
if (sz >= 4) {
tok4 = tokens.getToken(3);
} else {
tok4 = new Token();
}
if (sz >= 5) {
tok5 = tokens.getToken(4);
} else {
tok5 = new Token();
}
/* "/" (root) */
if (tok1.type == SLASH) {
step = new LocationStep(AXIS_ROOT);
steps.add(step);
tokens.remove(0);
}
/* AXIS::PREFIX:TAG */
else if (tok1.type == ATOM && tok2.type == COLONCOLON
&& tok3.type == ATOM && tok4.type == COLON
&& tok5.type == ATOM) {
step = new LocationStep(parseAxis(tok1.value),
tok3.value, tok5.value);
steps.add(step);
tokens.removeRange(0, 5);
parsePredicates(tokens, step);
}
/* AXIS::TAG */
else if (tok1.type == ATOM && tok2.type == COLONCOLON
&& tok3.type == ATOM) {
step = new LocationStep(parseAxis(tok1.value), tok3.value);
steps.add(step);
tokens.removeRange(0, 3);
parsePredicates(tokens, step);
}
/* PREFIX:TAG */
else if (tok1.type == ATOM && tok2.type == COLON
&& tok3.type == ATOM) {
step = new LocationStep(AXIS_CHILD, tok1.value,
tok3.value);
steps.add(step);
tokens.removeRange(0, 3);
parsePredicates(tokens, step);
}
/* TAG */
else if (tok1.type == ATOM) {
step = new LocationStep(AXIS_CHILD, tok1.value);
steps.add(step);
tokens.remove(0);
parsePredicates(tokens, step);
}
/* '//' (SLASHSLASH) */
else if (tok1.type == SLASHSLASH) {
/* expand into '/descendant-or-self::node()/' */
tokens.add(0, new Token(SLASH, "/"));
tokens.add(1, new Token(ATOM, "descendant-or-self"));
tokens.add(2, new Token(COLONCOLON, "::"));
tokens.add(3, new Token(ATOM, "node"));
tokens.add(4, new Token(LBRACER, "("));
tokens.add(5, new Token(RBRACER, ")"));
tokens.add(6, new Token(SLASH, "/"));
}
/* parse error */
else {
parseError(tokens);
}
// trace("parse(): sz= "+tokens.size());
sz = tokens.size();
}
} catch (final Exception e) {
final int errorCode = JNCException.PATH_ERROR;
throw new JNCException(errorCode, "parse error: " + e);
}
trace("parse() -> " + steps);
return steps;
}
/**
* check axis
*/
int parseAxis(String str) throws JNCException {
if (str.equals("child")) {
return AXIS_CHILD;
}
if (str.equals("self")) {
return AXIS_SELF;
}
throw new JNCException(JNCException.PATH_ERROR,
"unsupported or unknown axis: " + str);
}
/**
* parse out predicates
*/
void parsePredicates(TokenList tokens, LocationStep step)
throws JNCException {
trace("parsePredicates(): " + tokens);
final int sz = tokens.size();
if (sz >= 1) {
Token tok1 = tokens.getToken(0);
if (tok1.type == LPRED) {
/* search for matching RPRED */
int i = 1;
try {
while ((tokens.getToken(i)).type != RPRED) {
i++;
}
} catch (final Exception e) {
throw new JNCException(JNCException.PATH_ERROR,
"unmatched '[' in expression");
}
if (step.predicates == null) {
step.predicates = new ArrayList();
}
/* search for sequence */
int start = 1;
for (int j = 1; (j + 1) < i; j++) {
tok1 = tokens.getToken(j);
final Token tok2 = tokens.getToken(j + 1);
if (tok1.type == COMMA && tok2.type != COMMA) {
final Expr pred = parsePredicate(tokens, start, j);
step.predicates.add(pred);
start = j + 1;
}
}
final Expr pred = parsePredicate(tokens, start, i);
step.predicates.add(pred);
tokens.removeRange(0, i + 1);
parsePredicates(tokens, step);
} else if (tok1.type == SLASH) {
tokens.remove(0);
} else if (tok1.type != SLASHSLASH) {
parseError(tokens);
}
}
}
/**
* Parses a predicate expression. must consume all tokens from 'from' to
* 'to'.
*/
Expr parsePredicate(TokenList tokens, int from, int to)
throws JNCException {
Token tok1, tok2, tok3;
final int i = from;
while (i < to) {
/* peek at tokens */
tok1 = tokens.getToken(i);
if ((i + 1) < to) {
tok2 = tokens.getToken(i + 1);
} else {
tok2 = null;
}
if ((i + 2) < to) {
tok3 = tokens.getToken(i + 2);
} else {
tok3 = null;
}
trace("parsePredicate(): from=" + from + " to=" + to + " ["
+ tok1 + "," + tok2 + "," + tok3 + ", ...]");
/* ATOM = */
if (tok1.type == ATOM && (tok2 != null ? tok2.type : 0) == COMPARE && tok3 != null) {
final Object rexpr = parsePredicate_rvalue(tokens, i + 2, to);
return new Expr(tok2.op, new Expr(CHILD_VALUE, tok1.value),
rexpr);
}
/* ATTR = */
else if (tok1.type == ATTR && (tok2 != null ? tok2.type : 0) == COMPARE
&& tok3 != null) {
final Object rexpr = parsePredicate_rvalue(tokens, i + 2, to);
return new Expr(tok2.op, new Expr(ATTR_VALUE, tok1.value),
rexpr);
}
/* It's a parse error */else {
parseError(tokens, from, to);
}
}
return null;
}
/**
* Parses an rvalue of the predicate expression. must consume all tokens
* from 'from' to 'to'.
*/
Object parsePredicate_rvalue(TokenList tokens, int from, int to)
throws JNCException {
Token tok1, tok2, tok3;
final int i = from;
while (i < to) {
/* peek at tokens */
tok1 = tokens.getToken(i);
if ((i + 1) < to) {
tok2 = tokens.getToken(i + 1);
} else {
tok2 = null;
}
if ((i + 2) < to) {
tok3 = tokens.getToken(i + 2);
} else {
tok3 = null;
}
trace("parsePredicate_rvalue(): from=" + from + " to=" + to
+ " [" + tok1 + "," + tok2 + "," + tok3 + ", ...]");
if (tok1.type == ATOM && tok2 == null) {
return new Expr(CHILD_VALUE, tok1.value);
} else if (tok1.type == ATTR && tok2 == null) {
return new Expr(ATTR_VALUE, tok1.value);
} else if (tok1.type == STRING && tok2 == null) {
return tok1.value;
} else if (tok1.type == NUMBER && tok2 == null) {
return tok1.value;
}
else {
parseError(tokens, from, to);
}
}
return null;
}
/**
* Throws a parse error exception.
*/
void parseError(TokenList tokens) throws JNCException {
/* show 5 next tokens */
parseError(tokens, 0, 5);
}
void parseError(TokenList tokens, int from, int to) throws JNCException {
String errStr = "parse error: \"";
final int sz = tokens.size();
for (int i = from; i < to; i++) {
if (i < sz) {
errStr = errStr + tokens.getToken(i).value;
} else {
break;
}
}
errStr = errStr + "...\"";
throw new JNCException(JNCException.PATH_ERROR, errStr);
}
/* Tokenizer */
/** Tokens */
static final int SLASH = 1;
static final int SLASHSLASH = 2;
static final int DOT = 3;
static final int DOTDOT = 4;
static final int COLON = 5;
static final int COLONCOLON = 6;
static final int OP = 7;
static final int COMPARE = 8;
static final int ATOM = 9;
static final int ATTR = 10;
static final int NUMBER = 11;
static final int STRING = 12;
static final int LBRACER = 13;
static final int RBRACER = 14;
static final int LPRED = 15;
static final int RPRED = 16;
static final int COMMA = 17;
/**
* A list of Tokens
*/
class TokenList extends ArrayList {
private static final long serialVersionUID = 1L;
public Token getToken(int i) {
return super.get(i);
}
@Override
public void removeRange(int from, int to) {
super.removeRange(from, to);
}
@Override
public String toString() {
String s = "TokenList[";
boolean comma = false;
for (int i = 0; i < size(); i++) {
if (comma) {
s = s + ",";
}
s = s + getToken(i);
comma = true;
}
s = s + "]";
return s;
}
}
/**
* A Token
*/
class Token {
Token() {
}
Token(int type, String value) {
this.type = type;
this.value = value;
}
Token(int type, String value, Number number) {
this.type = type;
this.value = value;
this.number = number;
}
Token(int type, int op, String value) {
this.type = type;
this.op = op;
this.value = value;
}
int type;
int op;
String value;
Number number;
@Override
public String toString() {
switch (type) {
case SLASH:
return "SLASH";
case SLASHSLASH:
return "SLASHSLASH";
case DOT:
return "DOT";
case DOTDOT:
return "DOTDOT";
case COLON:
return "COLON";
case COLONCOLON:
return "COLONCOLON";
case OP:
return "OP(" + value + ")";
case COMPARE:
return "COMPARE(" + value + ")";
case ATOM:
return "ATOM(" + value + ")";
case ATTR:
return "ATTR(" + value + ")";
case NUMBER:
return "NUMBER(" + value + ")";
case STRING:
return "STRING(" + value + ")";
case LBRACER:
return "LBRACER";
case RBRACER:
return "RBRACER";
case LPRED:
return "LPRED";
case RPRED:
return "RPRED";
case COMMA:
return "COMMA";
default:
return "UNKOWN(type=" + type + ",value=" + value + ")";
}
}
}
/**
* Returns a TokenList (ArrayList) of Tokens.
*/
TokenList tokenize(String s) throws JNCException {
final TokenList tokens = new TokenList();
final byte[] buf = s.getBytes();
byte curr, next;
int i = 0, j;
while (i < s.length()) {
if ((i + 1) < buf.length) {
next = buf[i + 1];
} else {
next = 0;
}
curr = buf[i];
if (curr == ' ' || curr == '\t' || curr == '\n') {
// whitespace - ignore
i++;
} else if ((curr >= 'a' && curr <= 'z')
|| (curr >= 'A' && curr <= 'Z') || (curr == '\\')) {
boolean escape = (curr == '\\');
j = i + 1;
while (j < buf.length
&& ((buf[j] >= 'a' && buf[j] <= 'z')
|| (buf[j] >= 'A' && buf[j] <= 'Z')
|| (buf[j] >= '0' && buf[j] <= '9')
|| (buf[j] == '-') || (buf[j] == '_')
|| (buf[j] == '\\') || escape)) {
if (buf[j] == '\\') {
escape = true;
} else if (escape) {
escape = false;
}
j++;
}
final String newToken = new String(buf, i, j - i);
tokens.add(new Token(ATOM, newToken.replaceAll("\\\\", "")));
i = j;
} else if (curr == '@'
&& ((next >= 'a' && next <= 'z') || next >= 'A'
&& next <= 'Z')) {
i++;
j = i + 1;
while (j < buf.length
&& ((buf[j] >= 'a' && buf[j] <= 'z')
|| (buf[j] >= 'A' && buf[j] <= 'Z')
|| (buf[j] >= '0' && buf[j] <= '9')
|| (buf[j] == '-') || (buf[j] == '_'))) {
j++;
}
tokens.add(new Token(ATTR, new String(buf, i, j - i)));
i = j;
} else if (curr == '\'') {
i++;
j = i;
while (j < buf.length && buf[j] != '\'') {
j++;
}
if (j == buf.length) {
throw new JNCException(JNCException.PATH_ERROR,
"unterminated value: "
+ new String(buf, i, buf.length - i));
}
tokens.add(new Token(STRING, new String(buf, i, j - i)));
i = j + 1;
} else if (curr == '\"') {
i++;
j = i;
while (j < buf.length && buf[j] != '\"') {
j++;
}
if (j == buf.length) {
throw new JNCException(JNCException.PATH_ERROR,
"unterminated value: "
+ new String(buf, i, buf.length - i));
}
tokens.add(new Token(STRING, new String(buf, i, j - i)));
i = j + 1;
} else if (curr >= '0' && curr <= '9') {
j = i + 1;
while (j < buf.length && (buf[j] >= '0' && buf[j] <= '9')) {
j++;
}
String value;
Number number;
if ((j + 1) < buf.length && buf[j] == '.'
&& (buf[j + 1] >= '0' && buf[j + 1] <= '9')) { // float
j++;
while (j < buf.length && (buf[j] >= '0' && buf[j] <= '9')) {
j++;
}
value = new String(buf, i, j - i);
number = new Float(value);
} else {
value = new String(buf, i, j - i);
number = Integer.valueOf(value);
}
tokens.add(new Token(NUMBER, value, number));
i = j;
} else if (curr == '!' && next == '=') {
tokens.add(new Token(COMPARE, "!="));
i = i + 2;
} else {
switch (curr) {
case '[':
tokens.add(new Token(LPRED, "["));
break;
case ']':
tokens.add(new Token(RPRED, "]"));
break;
case '/':
if (next == '/') {
tokens.add(new Token(SLASHSLASH, "//"));
i++;
} else {
tokens.add(new Token(SLASH, "/"));
}
break;
case ':':
if (next == ':') {
tokens.add(new Token(COLONCOLON, "::"));
i++;
} else {
tokens.add(new Token(COLON, ":"));
}
break;
case '.':
if (next == '.') {
tokens.add(new Token(DOTDOT, ".."));
i++;
} else {
tokens.add(new Token(DOT, "."));
}
break;
case ',':
tokens.add(new Token(COMMA, ","));
break;
case '+':
case '-':
case '*':
tokens.add(new Token(OP, new String(new byte[] { curr })));
break;
case '=':
if (next == '>') {
tokens.add(new Token(COMPARE, GTE, ">="));
i++;
} else if (next == '<') {
tokens.add(new Token(COMPARE, LTE, "<="));
i++;
} else {
tokens.add(new Token(COMPARE, EQ, "="));
}
break;
case '>':
if (next == '=') {
tokens.add(new Token(COMPARE, GTE, ">="));
i++;
} else {
tokens.add(new Token(COMPARE, GT, ">"));
}
break;
case '<':
if (next == '=') {
tokens.add(new Token(COMPARE, LTE, "<="));
i++;
} else {
tokens.add(new Token(COMPARE, LT, "="));
}
break;
default:
throw new JNCException(JNCException.PATH_ERROR,
"illegal character in expression: " + curr);
}
i++;
}
}
trace("tokenize() -> " + tokens);
return tokens;
}
/* help functions */
/**
* Returns a string representation of this Path. It's a parse tree.
*/
@Override
public String toString() {
StringBuffer s = new StringBuffer();
s.append("Path[");
for (LocationStep lstep : locationSteps) {
s.append(lstep);
}
s.append("]");
return s.toString();
}
private static void trace(String s) {
if (Element.debugLevel >= Element.DEBUG_LEVEL_PATH) {
System.err.println("*Path: " + s);
}
}
}