vgritsenko 2004/03/04 05:52:20
Modified: java/src/org/apache/xindice/core/query XPathQueryResolver.java Log: Zap tabs; reformat. Revision Changes Path 1.29 +1190 -1410xml-xindice/java/src/org/apache/xindice/core/query/XPathQueryResolver.java Index: XPathQueryResolver.java =================================================================== RCS file: /home/cvs/xml-xindice/java/src/org/apache/xindice/core/query/XPathQueryResolver.java,v retrieving revision 1.28 retrieving revision 1.29 diff -u -r1.28 -r1.29 --- XPathQueryResolver.java 4 Mar 2004 13:18:49 -0000 1.28 +++ XPathQueryResolver.java 4 Mar 2004 13:52:20 -0000 1.29 @@ -85,1464 +85,1244 @@ * * @version CVS $Revision$, $Date$ */ -public final class XPathQueryResolver extends SimpleConfigurable implements QueryResolver -{ - private static final Log log = LogFactory.getLog(XPathQueryResolver.class); - - private static final Key[] EMPTY_KEYS = new Key[0]; - private static final Key[][] EMPTY_KEYSET = new Key[0][0]; - private static final String WILDCARD = "*"; - // private static final String THISNODE = "."; - // private static final String PARENTNODE = ".."; +public final class XPathQueryResolver extends SimpleConfigurable implements QueryResolver { + private static final Log log = LogFactory.getLog(XPathQueryResolver.class); - private static final String AUTOINDEX = "autoindex"; + private static final Key[] EMPTY_KEYS = new Key[0]; + private static final Key[][] EMPTY_KEYSET = new Key[0][0]; + private static final String WILDCARD = "*"; + // private static final String THISNODE = "."; + // private static final String PARENTNODE = ".."; - public static final String STYLE_XPATH = "XPath"; + private static final String AUTOINDEX = "autoindex"; - // Maps Xalan Comparisons To IndexQuery - private static final int[] OpMap = { + public static final String STYLE_XPATH = "XPath"; + + // Maps Xalan Comparisons To IndexQuery + private static final int[] OpMap = { IndexQuery.NEQ, IndexQuery.EQ, IndexQuery.LEQ, IndexQuery.LT, IndexQuery.GEQ, IndexQuery.GT }; - private DefaultErrorHandler errorListener = new DefaultErrorHandler(); - private boolean autoIndex = false; + private DefaultErrorHandler errorListener = new DefaultErrorHandler(); + private boolean autoIndex = false; - public void setConfig(Configuration config) throws XindiceException - { - super.setConfig(config); - autoIndex = config.getBooleanAttribute(AUTOINDEX, autoIndex); - } - - public String getQueryStyle() - { - return STYLE_XPATH; - } - - public void setQueryEngine(QueryEngine engine) - { - // Not used: this.engine = engine; - } - - public Query compileQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys) throws QueryException - { - return new XPathQuery(context, query, nsMap, keys); - } - - public NodeSet query(Collection context, String query, NamespaceMap nsMap, Key[] keys) throws QueryException - { - XPathQuery xq = new XPathQuery(context, query, nsMap, keys); - return xq.execute(); - } + public void setConfig(Configuration config) throws XindiceException { + super.setConfig(config); + autoIndex = config.getBooleanAttribute(AUTOINDEX, autoIndex); + } + + public String getQueryStyle() { + return STYLE_XPATH; + } + + public void setQueryEngine(QueryEngine engine) { + // Not used: this.engine = engine; + } + + public Query compileQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys) + throws QueryException { + return new XPathQuery(context, query, nsMap, keys); + } + + public NodeSet query(Collection context, String query, NamespaceMap nsMap, Key[] keys) + throws QueryException { + XPathQuery xq = new XPathQuery(context, query, nsMap, keys); + return xq.execute(); + } /** - * XPathQuery - */ - private class XPathQuery implements Query - { - public Collection context; - public IndexManager idxMgr; - public NamespaceMap nsMap; - public PrefixResolver pr; - public SymbolTable symbols; - public String query; - public Compiler cmp; - public XPath xp; - public Key[] keys; - - public XPathQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys) throws QueryException - { - this.context = context; - this.query = query; - this.nsMap = nsMap; - this.keys = keys; - - Expression ex = null; - - try - { - if (nsMap != null) - { - Node n = nsMap.getContextNode(); - pr = new PrefixResolverDefault(n); - } - - XPathParser parser = new XPathParser(errorListener, null); - cmp = new Compiler(errorListener, null); - parser.initXPath(cmp, query, pr); - ex = cmp.compile(0); - - symbols = context.getSymbols(); - idxMgr = context.getIndexManager(); - } - catch (Exception e) - { - throw new CompilationException("Error Compiling XPath Expression", e); - } - if (ex == null) - { - throw new CompilationException("Error Compiling XPath Expression: XPath Compiler.compile returned null"); - } - } - - public String getQueryStyle() - { - return STYLE_XPATH; - } - - public String getQueryString() - { - return query; - } - - public Collection getQueryContext() - { - return context; - } - - public NamespaceMap getNamespaceMap() - { - return nsMap; - } - - public Key[] getKeySet() - { - return keys; - } - - public NodeSet execute() throws QueryException - { - try - { - Key[] keySet = keys; - - // TODO: Add logic to do an indexed check on provided - // keySets that are larger than a certain minimum - - if (keys == null && idxMgr != null) - { - // Issue the query using Indexes - try - { - Object obj = evaluate(null, 0); - if (obj instanceof NamedKeys) - { - keySet = ((NamedKeys) obj).keys; - } - } - catch (Exception e) - { - if (log.isWarnEnabled()) - { - log.warn("ignored exception", e); - } - } - } - - if (keySet == null) - { - // Fall back to a Collection scan - SortedSet set = new TreeSet(); - RecordSet rs = context.getFiler().getRecordSet(); - while (rs.hasMoreRecords()) - { - set.add(rs.getNextKey()); - } - keySet = (Key[]) set.toArray(EMPTY_KEYS); - } - - return new ResultSet(context, pr, keySet, query); - } - catch (Exception e) - { - if (e instanceof QueryException) - { - throw (QueryException) e.fillInStackTrace(); - } - throw new ProcessingException("Error executing XPath query: " + e.getMessage()); - } - } - - private Key[] andKeys(List list) - { - if (!list.isEmpty()) - { - if (list.size() > 1) - { - Key[][] keys = (Key[][]) list.toArray(EMPTY_KEYSET); - return QueryEngine.andKeySets(keys); - } - else - return (Key[]) list.get(0); - } - else - return null; - } + * XPathQuery + */ + private class XPathQuery implements Query { + public Collection context; + public IndexManager idxMgr; + public NamespaceMap nsMap; + public PrefixResolver pr; + public SymbolTable symbols; + public String query; + public Compiler cmp; + public XPath xp; + public Key[] keys; + + public XPathQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys) + throws QueryException { + this.context = context; + this.query = query; + this.nsMap = nsMap; + this.keys = keys; - // Evaluation Methods + Expression ex = null; + try { + if (nsMap != null) { + Node n = nsMap.getContextNode(); + pr = new PrefixResolverDefault(n); + } + + XPathParser parser = new XPathParser(errorListener, null); + cmp = new Compiler(errorListener, null); + parser.initXPath(cmp, query, pr); + ex = cmp.compile(0); - /** - * evaluate does a partial evaluation of the XPath in - * order to determine the optimal indexes to prepare for - * the query and retrieve the Document subset that will be - * used for the actual XPath query. - * <br><br> - * This will return an instance of one of the following classes: - * <pre> - * String If the sub-expression resolves to a Node Name - * XNumber If the sub-expression resolves to a Number - * XString If the sub-expression resolves to a String - * XBoolean If the sub-expression resolves to a Boolean - * NamedKeys If the sub-expression resolves to a Key set - * null If the sub-expression resolves to anything else - * </pre> - * - * @param owner The parent node name for this context - * @param pos The position to start at (recursively called) - * @return Some Object result - */ - private Object evaluate(String owner, int pos) throws Exception - { - int op = cmp.getOp(pos); - if (op == -1) - { - return null; - } - - switch (op) - { - - case OpCodes.OP_LOCATIONPATH : - return evalLocationPath(owner, pos); - - case OpCodes.OP_ARGUMENT : - case OpCodes.OP_XPATH : - case OpCodes.OP_PREDICATE : - return evaluate(owner, Compiler.getFirstChildPos(pos)); - - case OpCodes.OP_OR : - case OpCodes.OP_AND : - return evalSetComparison(op, owner, pos); - - case OpCodes.OP_NOTEQUALS : - case OpCodes.OP_EQUALS : - case OpCodes.OP_LTE : - case OpCodes.OP_LT : - case OpCodes.OP_GTE : - case OpCodes.OP_GT : - return evalValComparison(op, owner, pos); - - case OpCodes.OP_PLUS : - case OpCodes.OP_MINUS : - case OpCodes.OP_MULT : - case OpCodes.OP_DIV : - case OpCodes.OP_MOD : - case OpCodes.OP_QUO : - return evalMathOperation(op, owner, pos); - - case OpCodes.OP_NEG : - case OpCodes.OP_STRING : - case OpCodes.OP_BOOL : - case OpCodes.OP_NUMBER : - return evalUnaryOperation(op, owner, pos); - - case OpCodes.OP_UNION : - return evalUnion(owner, pos); - - case OpCodes.OP_VARIABLE : - break; - - case OpCodes.OP_GROUP : - return evaluate(owner, Compiler.getFirstChildPos(pos)); - - case OpCodes.OP_EXTFUNCTION : - break; - - case OpCodes.OP_FUNCTION : - return evalFunction(owner, pos); - - case OpCodes.FROM_ANCESTORS : - case OpCodes.FROM_ANCESTORS_OR_SELF : - case OpCodes.FROM_ATTRIBUTES : - case OpCodes.FROM_CHILDREN : - case OpCodes.FROM_DESCENDANTS : - case OpCodes.FROM_DESCENDANTS_OR_SELF : - case OpCodes.FROM_FOLLOWING : - case OpCodes.FROM_FOLLOWING_SIBLINGS : - case OpCodes.FROM_PARENT : - case OpCodes.FROM_PRECEDING : - case OpCodes.FROM_PRECEDING_SIBLINGS : - case OpCodes.FROM_NAMESPACE : - case OpCodes.FROM_SELF : - case OpCodes.FROM_ROOT : - return evalAxis(op, owner, pos); - - case OpCodes.NODENAME : - case OpCodes.OP_LITERAL : - case OpCodes.OP_NUMBERLIT : - return evalLiteral(owner, pos); - - case OpCodes.NODETYPE_TEXT : - case OpCodes.NODETYPE_NODE : - return owner; - - case OpCodes.NODETYPE_ANYELEMENT : - case OpCodes.ELEMWILDCARD : - return WILDCARD; - - case OpCodes.NODETYPE_ROOT : - case OpCodes.NODETYPE_COMMENT : - case OpCodes.NODETYPE_PI : - case OpCodes.NODETYPE_FUNCTEST : - break; - - default : - if (log.isWarnEnabled()) - { - log.warn("Unknown: " + op); - } - } - return null; - } - - private Object evalLocationPath(String owner, int pos) throws Exception - { - int lp = Compiler.getFirstChildPos(pos); - List ks = new ArrayList(); - String name = null; - boolean attr = false; - while (cmp.getOp(lp) != -1) - { - Object obj = evaluate(owner, lp); - if (obj instanceof NamedKeys) - { - NamedKeys nk = (NamedKeys) obj; - if (nk.name != null) - { - attr = nk.attribute; - if (attr) - { - if (name == null) - { - name = owner; - } - if (name != null) - { - StringBuffer sb = new StringBuffer(32); - sb.append(name); - sb.append('@'); - sb.append(nk.name); - name = sb.toString(); - } - } - else - { - name = nk.name; - } - } - if (nk.keys != null) - { - ks.add(nk.keys); - } - else if (name != null) - { - // Try to use a NameIndex to resolve the path component - // can match a wildcard node name here if pattern is "*" then every document matches - if (attr == false && "*".equals(name)) - { - SortedSet set = new TreeSet(); - RecordSet rs = context.getFiler().getRecordSet(); - while (rs.hasMoreRecords()) - { - set.add(rs.getNextKey()); - } - ks.add(set.toArray(EMPTY_KEYS)); - } - else - { - // Try to use a NameIndex to resolve the path component - IndexPattern pattern = new IndexPattern(symbols, name, nsMap); - Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODENAME, pattern); - if (idx != null) - { - IndexMatch[] matches = idx.queryMatches(new IndexQueryANY(pattern)); - Key[] keys = QueryEngine.getUniqueKeys(matches); - ks.add(keys); - } - } - } - } - lp = cmp.getNextOpPos(lp); - } - return new NamedKeys(name, attr, andKeys(ks)); - } - - private Object evalUnion(String owner, int pos) throws Exception - { - int l = Compiler.getFirstChildPos(pos); - int r = cmp.getNextOpPos(l); - Object left = evaluate(owner, l); - - if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) - { - Object right = evaluate(owner, r); - - if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) - { - Key[][] keys = new Key[][] {((NamedKeys) left).keys, ((NamedKeys) right).keys }; - return new NamedKeys(null, false, QueryEngine.orKeySets(keys)); - } - } - // no index query of left part of union - // or no index query of right part of union => must do - /// collection scan - return null; - } - - private Object evalSetComparison(int op, String owner, int pos) throws Exception - { - int l = Compiler.getFirstChildPos(pos); - int r = cmp.getNextOpPos(l); - Object left = evaluate(owner, l); - - if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) - { - // have left keys - if (((NamedKeys) left).keys.length == 0 && op == OpCodes.OP_AND) - { - // left keyset empty implies result of AND would be empty - return new NamedKeys(null, false, ((NamedKeys) left).keys); - } - - Object right = evaluate(owner, r); - if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) - { - // have keys for both left and right - - if (op == OpCodes.OP_AND) - { - if (((NamedKeys) right).keys.length == 0) - { - // right keyset empty implies result of AND would be empty - return new NamedKeys(null, false, ((NamedKeys) right).keys); - } - - Key[][] keys = new Key[][] {((NamedKeys) left).keys, ((NamedKeys) right).keys }; - return new NamedKeys(null, false, QueryEngine.andKeySets(keys)); - } - else - { - - // OR operation - if (((NamedKeys) left).keys.length == 0) - { - // OR operation and left empty implies result is right set - return new NamedKeys(null, false, ((NamedKeys) right).keys); - } - - if (((NamedKeys) right).keys.length == 0) - { - // OR operation and right empty implies result is left set - return new NamedKeys(null, false, ((NamedKeys) left).keys); - } - - Key[][] keys = new Key[][] {((NamedKeys) left).keys, ((NamedKeys) right).keys }; - return new NamedKeys(null, false, QueryEngine.orKeySets(keys)); - } - } - else - { - // have left keys but not right can infer that AND operation - // result cannot contain more than left set so return that - if (op == OpCodes.OP_AND) - { - return new NamedKeys(null, false, ((NamedKeys) left).keys); - } - } - } - else - { - // do not have left keys - Object right = evaluate(owner, r); - if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) - { - // have right keys but not left can infer that AND operation - // result cannot contain more than right set so return that - if (op == OpCodes.OP_AND) - { - return new NamedKeys(null, false, ((NamedKeys) right).keys); - } - } - } - - // punt - return null; - } - - private Object evalValComparison(int op, String owner, int pos) throws Exception - { - - int l = Compiler.getFirstChildPos(pos); - int r = cmp.getNextOpPos(l); - - Object left = evaluate(owner, l); - if (!(left instanceof XObject || left instanceof NamedKeys)) - { - // can't evaluate - return null; - } - - Object right = evaluate(owner, r); - if ((left instanceof NamedKeys && right instanceof XObject) - || (left instanceof XObject && right instanceof NamedKeys)) - { - // try to evaluate through indexed search - return queryComparison(op, owner, left, right); - } - - // could handle comparison of nodeset to boolean here - // boolean converts to 1.0 (true) or 0.0 (false) - // nodeset converts to 1.0 (non-empty) or 0.0 (empty) - // but since this is a rare and odd comparison we don't bother now... - if (left instanceof XObject && right instanceof XObject) - { - switch (op) - { - case OpCodes.OP_NOTEQUALS : - return new XBoolean(((XObject) left).notEquals((XObject) right)); - case OpCodes.OP_EQUALS : - return new XBoolean(((XObject) left).equals((XObject) right)); - case OpCodes.OP_LTE : - return new XBoolean(((XObject) left).lessThanOrEqual((XObject) right)); - case OpCodes.OP_LT : - return new XBoolean(((XObject) left).lessThan((XObject) right)); - case OpCodes.OP_GTE : - return new XBoolean(((XObject) left).greaterThanOrEqual((XObject) right)); - case OpCodes.OP_GT : - return new XBoolean(((XObject) left).greaterThan((XObject) right)); - default : - return null; // Won't happen - } - } - - // can't evaluate here... - return null; - } - - private strictfp Object evalMathOperation(int op, String owner, int pos) throws Exception - { - int lc = Compiler.getFirstChildPos(pos); - int rc = cmp.getNextOpPos(lc); - Object left = evaluate(owner, lc); - - if (left instanceof XObject) - { - Object right = evaluate(owner, rc); - if (right instanceof XObject) - { - switch (op) - { - case OpCodes.OP_PLUS : - return new XNumber(((XObject) left).num() + ((XObject) right).num()); - case OpCodes.OP_MINUS : - return new XNumber(((XObject) left).num() - ((XObject) right).num()); - case OpCodes.OP_MULT : - return new XNumber(((XObject) left).num() * ((XObject) right).num()); - case OpCodes.OP_DIV : - return new XNumber(((XObject) left).num() / ((XObject) right).num()); - case OpCodes.OP_MOD : - return new XNumber(((XObject) left).num() % ((XObject) right).num()); - case OpCodes.OP_QUO : - return new XNumber(((XObject) left).num() / ((XObject) right).num()); - default : - return null; // Won't happen - } - } - } - - // can't evaluate - return null; - } - - private Object evalUnaryOperation(int op, String owner, int pos) throws Exception - { - Object val = evaluate(owner, Compiler.getFirstChildPos(pos)); - if (val instanceof XObject) - { - switch (op) - { - case OpCodes.OP_NEG : - return new XNumber(- ((XObject) val).num()); - case OpCodes.OP_STRING : - return new XString(((XObject) val).str()); - case OpCodes.OP_BOOL : - return new XBoolean(((XObject) val).bool()); - case OpCodes.OP_NUMBER : - return new XNumber(((XObject) val).num()); - default : - return null; // Won't happen - } - } - if (val instanceof NamedKeys) - { - NamedKeys nk = (NamedKeys) val; - // we may be able to convert the nodeset to the proper type - // and return an answer numeric operations imply conversion to - // string and then to number - // we can't convert to string - // we can handle conversion to boolean (empty or not empty) - if (nk.keys != null && op == OpCodes.OP_BOOL) - { - return nk.keys.length == 0 ? XBoolean.S_FALSE : XBoolean.S_TRUE; - } - } - return null; - } - - private Object evalFunction(String owner, int pos) throws Exception - { - int idx = Compiler.getFirstChildPos(pos); - int id = cmp.getOp(idx); - // note: in the XPath op table, the - // op code is stored at the current position index passed to us - // the size of the current op is stored in the next location - // thus, the index of the location just beyond the function - // arguments for the current op (which is also the index of the next op) - // is equal to the current postion + the size of the current operation - 1 - // (the getOp(index) method merely returns the int value stored in the op table - // at the specified index. That value is an op code, a size, or the index - // of an token or ... (see org.apache.xpath.compiler.OpCodes for the - // various items that can be part of an operation) - int endFunc = pos + cmp.getOp(pos + 1) - 1; - - List args = new ArrayList(); - int lp = idx + 1; - while (lp < endFunc) - { - args.add(evaluate(owner, lp)); - lp = cmp.getNextOpPos(lp); - } - - switch (id) - { - case FunctionTable.FUNC_BOOLEAN : - return funcBoolean(args); - case FunctionTable.FUNC_CEILING : - return funcCeiling(args); - case FunctionTable.FUNC_CONCAT : - return funcConcat(args); - case FunctionTable.FUNC_CONTAINS : - return funcContains(args); - case FunctionTable.FUNC_FALSE : - return XBoolean.S_FALSE; - case FunctionTable.FUNC_FLOOR : - return funcFloor(args); - case FunctionTable.FUNC_NORMALIZE_SPACE : - return funcNormalizeSpace(args); - case FunctionTable.FUNC_NOT : - return funcNot(args); - case FunctionTable.FUNC_NUMBER : - return funcNumber(args); - case FunctionTable.FUNC_ROUND : - return funcRound(args); - case FunctionTable.FUNC_STARTS_WITH : - return funcStartsWith(owner, args); - case FunctionTable.FUNC_STRING : - return funcString(args); - case FunctionTable.FUNC_STRING_LENGTH : - return funcStringLength(args); - case FunctionTable.FUNC_SUBSTRING : - return funcSubstring(args); - case FunctionTable.FUNC_SUBSTRING_AFTER : - return funcSubstringAfter(args); - case FunctionTable.FUNC_SUBSTRING_BEFORE : - return funcSubstringBefore(args); - case FunctionTable.FUNC_TRANSLATE : - return funcTranslate(args); - case FunctionTable.FUNC_TRUE : - return XBoolean.S_TRUE; - default : - return null; - } - } - - private Object evalAxis(int op, String owner, int pos) throws Exception - { - String nsURI = cmp.getStepNS(pos); - owner = (String) evaluate(owner, Compiler.getFirstChildPosOfStep(pos)); - //owner = cmp.getStepLocalName(pos); - - if (nsURI != null && nsMap != null) - { - // We have to determine the prefix that was used - // There has to be an easier way to do this with Xalan - String pfx = null; - Iterator i = nsMap.keySet().iterator(); - while (i.hasNext()) - { - String p = (String) i.next(); - if (nsMap.getNamespaceURI(p).equals(nsURI)) - { - pfx = p; - break; - } - } - if (pfx != null) - { - StringBuffer sb = new StringBuffer(32); - sb.append(pfx); - sb.append(':'); - sb.append(owner); - owner = sb.toString(); - } - } - - int rp = cmp.getFirstPredicateOpPos(pos); - - List ks = new ArrayList(); - while (rp < pos + cmp.getOp(pos + 1)) - { - Object obj = evaluate(owner, rp); - if (obj instanceof NamedKeys) - { - NamedKeys nk = (NamedKeys) obj; - if (nk.keys != null) - { - ks.add(nk.keys); - } - } - rp = cmp.getNextOpPos(rp); - } - return new NamedKeys(owner, (op == OpCodes.FROM_ATTRIBUTES), andKeys(ks)); - } - - private Object evalLiteral(String owner, int pos) - { - int idx = cmp.getOp(Compiler.getFirstChildPos(pos)); - switch (idx) - { - case OpCodes.EMPTY : - return owner; - case OpCodes.ELEMWILDCARD : - return WILDCARD; - default : - return cmp.getToken(idx); - } - } - - // XPath Functions - - private Object funcBoolean(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - if (((XObject) o).bool()) - { - return XBoolean.S_TRUE; - } - else - { - return XBoolean.S_FALSE; - } - } - else - { - if (o instanceof NamedKeys) - { - NamedKeys nk = (NamedKeys) o; - if (nk.keys == null) - { - return null; - } - if (nk.keys.length == 0) - { - // nodeset empty converts to boolean false - return XBoolean.S_FALSE; - } - return XBoolean.S_TRUE; - } - } - } - return null; - } - - private Object funcCeiling(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XNumber(Math.ceil(((XObject) o).num())); - } - } - return null; - } - - private Object funcConcat(List args) - { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < args.size(); i++) - { - Object o = args.get(i); - if (o instanceof XObject) - { - sb.append(((XObject) o).str()); - } - else - { - return null; - } - } - return new XString(sb.toString()); - } - - private Object funcContains(List args) - { - if (args.size() == 2) - { - Object o = args.get(0); - Object s = args.get(1); - if (o instanceof XObject && s instanceof XObject) - { - if (((XObject) o).str().indexOf(((XObject) s).str()) != -1) - { - return XBoolean.S_TRUE; - } - else - { - return XBoolean.S_FALSE; - } - } - } - return null; - } - - private Object funcFloor(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XNumber(Math.floor(((XObject) o).num())); - } - } - return null; - } - - private Object funcNormalizeSpace(List args) - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XString(QueryEngine.normalizeString(((XObject) o).str())); - } - } - return null; - } - - private Object funcNot(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - if (((XObject) o).bool()) - { - return XBoolean.S_FALSE; - } - else - { - return XBoolean.S_TRUE; - } - } - else - { - if (o instanceof NamedKeys) - { - NamedKeys nk = (NamedKeys) o; - if (nk.keys == null) - { - return null; - } - if (nk.keys.length == 0) - { - // nodeset empty converts to boolean false => not false => true - return XBoolean.S_TRUE; - } - return XBoolean.S_FALSE; - } - return null; - } - - } - return null; - } - - private Object funcNumber(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XNumber(((XObject) o).num()); - } - } - return null; - } - - private Object funcRound(List args) throws Exception - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XNumber(Math.round(((XObject) o).num())); - } - } - return null; - } - - private Object funcStartsWith(String owner, List args) - { - if (args.size() == 2) - { - Object o = args.get(0); - Object s = args.get(1); - - if (o instanceof XObject && s instanceof XObject) - { - if (((XObject) o).str().startsWith(((XObject) s).str())) - { - return XBoolean.S_TRUE; - } - else - { - return XBoolean.S_FALSE; - } - } - else if (o instanceof NamedKeys && s instanceof XObject) - { - NamedKeys nk = (NamedKeys) o; - String ps; - if (nk.attribute && nk.name.indexOf('@') == -1) - { - ps = owner + "@" + nk.name; - } - else - { - ps = nk.name; - } - - IndexPattern pattern = new IndexPattern(symbols, ps, nsMap); - - XObject obj = (XObject) s; - Value val1 = new Value(obj.str()); - - IndexQuery iq = new IndexQuerySW(pattern, val1); - return queryIndexes(nk, iq, ps, obj.getType()); - } - } - return null; - } - - private Object funcString(List args) - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XString(((XObject) o).str()); - } - } - return null; - } - - private Object funcStringLength(List args) - { - if (args.size() == 1) - { - Object o = args.get(0); - if (o instanceof XObject) - { - return new XNumber(((XObject) o).str().length()); - } - } - return null; - } - - private Object funcSubstring(List args) throws Exception - { - if (args.size() == 2 || args.size() == 3) - { - Object o = args.get(0); - Object pos = args.get(1); - Object len = args.size() == 3 ? args.get(2) : null; - if (o instanceof XObject && pos instanceof XObject && (len == null || len instanceof XObject)) - { - int ipos = (int) ((XObject) pos).num() - 1; - if (len != null) - { - int ilen = (int) ((XObject) len).num(); - return new XString(((XObject) o).str().substring(ipos, ipos + ilen)); - } - else - return new XString(((XObject) o).str().substring(ipos)); - } - } - return null; - } - - private Object funcSubstringAfter(List args) - { - if (args.size() == 2) - { - Object o = args.get(0); - Object s = args.get(1); - if (o instanceof XObject && s instanceof XObject) - { - String val = ((XObject) o).str(); - String sub = ((XObject) s).str(); - int i = val.indexOf(sub); - if (i == -1) - { - return new XString(""); - } - else - { - return new XString(val.substring(i + sub.length())); - } - } - } - return null; - } - - private Object funcSubstringBefore(List args) - { - if (args.size() == 2) - { - Object o = args.get(0); - Object s = args.get(1); - if (o instanceof XObject && s instanceof XObject) - { - String val = ((XObject) o).str(); - String sub = ((XObject) s).str(); - int i = val.indexOf(sub); - if (i == -1) - { - return new XString(""); - } - else - { - return new XString(val.substring(0, i)); - } - } - } - return null; - } - - private Object funcTranslate(List args) - { - if (args.size() == 3) - { - Object o = args.get(0); - Object c1 = args.get(1); - Object c2 = args.get(2); - if (o instanceof XObject && c1 instanceof XObject && c2 instanceof XObject) - { - char ch1 = ((XObject) c1).str().charAt(0); - char ch2 = ((XObject) c2).str().charAt(0); - return new XString(((XObject) o).str().replace(ch1, ch2)); - } - } - return null; - } + symbols = context.getSymbols(); + idxMgr = context.getIndexManager(); + } catch (Exception e) { + throw new CompilationException("Error Compiling XPath Expression: " + e.getMessage(), e); + } - // The Actual Querying Methods + if (ex == null) { + throw new CompilationException("Error Compiling XPath Expression: XPath Compiler.compile returned null"); + } + } + + public String getQueryStyle() { + return STYLE_XPATH; + } + + public String getQueryString() { + return query; + } + + public Collection getQueryContext() { + return context; + } + + public NamespaceMap getNamespaceMap() { + return nsMap; + } + + public Key[] getKeySet() { + return keys; + } - /** - * queryIndexes actually performs index-based querying on behalf of - * the evaluation methods. - * - * @param nk The NamedKeys instance to use for matches - * @param iq The actual IndexQuery to use for resolution - * @param ps The pattern String to possibly use for index gen - * @param objType The object type to possibly use for index gen - * @return The resulting Keys (if any) - */ - private Object queryIndexes(NamedKeys nk, IndexQuery iq, String ps, int objType) - { - try - { - // TODO: Add logic to use an EmptyKeySet if a name doesn't already - // exist in the SymbolTable. This will eliminate the need - // to do a collection scan in those cases where somebody - // typed an element or attribute name incorrectly. - - IndexPattern pattern = iq.getPattern(); - - Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODEVALUE, pattern); - if (idx != null) - { - return new NamedKeys(nk.name, nk.attribute, QueryEngine.getUniqueKeys(idx.queryMatches(iq))); - } - else if (autoIndex) - { - // TODO: This has to *not* be hardcoded - Element e = new DocumentImpl().createElement("index"); - e.setAttribute("class", "org.apache.xindice.core.indexer.ValueIndexer"); - e.setAttribute("name", "xp_" + ps); - e.setAttribute("pattern", ps); - - // Set the type for the index - String type = null; - switch (objType) - { - case XObject.CLASS_BOOLEAN : - type = "boolean"; - break; - case XObject.CLASS_NUMBER : - type = "double"; - break; - case XObject.CLASS_STRING : - if (ps.indexOf('@') != -1) - { - type = "string"; - } - else - { - type = "trimmed"; - } - break; - default : - if (log.isWarnEnabled()) - { - log.warn("invalid object type : " + objType); - } - } - if (type != null) - { - e.setAttribute("type", type); - } - - idxMgr.create(new Configuration(e)); - } - } - catch (Exception e) - { - if (log.isWarnEnabled()) - { - log.warn("ignored exception", e); - } - } - return null; - } + public NodeSet execute() throws QueryException { + try { + Key[] keySet = keys; + + // TODO: Add logic to do an indexed check on provided + // keySets that are larger than a certain minimum + + if (keys == null && idxMgr != null) { + // Issue the query using Indexes + try { + Object obj = evaluate(null, 0); + if (obj instanceof NamedKeys) { + keySet = ((NamedKeys) obj).keys; + } + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("ignored exception", e); + } + } + } + + if (keySet == null) { + // Fall back to a Collection scan + SortedSet set = new TreeSet(); + RecordSet rs = context.getFiler().getRecordSet(); + while (rs.hasMoreRecords()) { + set.add(rs.getNextKey()); + } + keySet = (Key[]) set.toArray(EMPTY_KEYS); + } + + return new ResultSet(context, pr, keySet, query); + } catch (Exception e) { + if (e instanceof QueryException) { + throw (QueryException) e.fillInStackTrace(); + } + throw new ProcessingException("Error executing XPath query: " + e.getMessage(), e); + } + } + + private Key[] andKeys(List list) { + if (!list.isEmpty()) { + if (list.size() > 1) { + Key[][] keys = (Key[][]) list.toArray(EMPTY_KEYSET); + return QueryEngine.andKeySets(keys); + } else + return (Key[]) list.get(0); + } else { + return null; + } + } + + // Evaluation Methods /** - * queryComparison performs a comparison query use the operands that - * are passed to it, and returns the resulting Keys. - * - * @param op The Operator - * @param owner The Owner Node - * @param left The left Operand - * @param right The right Operand - * @return The resulting Keys (if any) - */ - private Object queryComparison(int op, String owner, Object left, Object right) - { - op = OpMap[op - OpCodes.OP_NOTEQUALS]; - - if (left instanceof XObject) - { - // Check if we have to switch the operation - if (op == IndexQuery.GT || op == IndexQuery.LT || op == IndexQuery.GEQ || op == IndexQuery.LEQ) - { - op = -op; - } - // Swap the operands - Object tmp = left; - left = right; - right = tmp; - } - - if (!(left instanceof NamedKeys && right instanceof XObject)) - { - // can't handle it - return null; - } - - NamedKeys nk = (NamedKeys) left; - XObject obj = (XObject) right; - - String ps; - if (nk.attribute && nk.name.indexOf('@') == -1) - { - ps = owner + "@" + nk.name; - } - else - { - ps = nk.name; - } - - IndexQuery iq; - IndexPattern pattern = new IndexPattern(symbols, ps, nsMap); - String value = obj.str(); - - switch (op) - { - case IndexQuery.NEQ : - iq = new IndexQueryNEQ(pattern, value); - break; - case IndexQuery.EQ : - iq = new IndexQueryEQ(pattern, value); - break; - case IndexQuery.LEQ : - iq = new IndexQueryLEQ(pattern, value); - break; - case IndexQuery.LT : - iq = new IndexQueryLT(pattern, value); - break; - case IndexQuery.GEQ : - iq = new IndexQueryGEQ(pattern, value); - break; - case IndexQuery.GT : - iq = new IndexQueryGT(pattern, value); - break; - default : - iq = null; // Won't happen - } - - return queryIndexes(nk, iq, ps, obj.getType()); - } - } + * evaluate does a partial evaluation of the XPath in order to + * determine the optimal indexes to prepare for the query and retrieve + * the Document subset that will be used for the actual XPath query. + * + * <p>This will return an instance of one of the following classes: + * <pre> + * String If the sub-expression resolves to a Node Name + * XNumber If the sub-expression resolves to a Number + * XString If the sub-expression resolves to a String + * XBoolean If the sub-expression resolves to a Boolean + * NamedKeys If the sub-expression resolves to a Key set + * null If the sub-expression resolves to anything else + * </pre> + * + * @param owner The parent node name for this context + * @param pos The position to start at (recursively called) + * @return Some Object result + */ + private Object evaluate(String owner, int pos) throws Exception { + int op = cmp.getOp(pos); + if (op == -1) { + return null; + } + + switch (op) { + case OpCodes.OP_LOCATIONPATH: + return evalLocationPath(owner, pos); + + case OpCodes.OP_ARGUMENT: + case OpCodes.OP_XPATH: + case OpCodes.OP_PREDICATE: + return evaluate(owner, Compiler.getFirstChildPos(pos)); + + case OpCodes.OP_OR: + case OpCodes.OP_AND: + return evalSetComparison(op, owner, pos); + + case OpCodes.OP_NOTEQUALS: + case OpCodes.OP_EQUALS: + case OpCodes.OP_LTE: + case OpCodes.OP_LT: + case OpCodes.OP_GTE: + case OpCodes.OP_GT: + return evalValComparison(op, owner, pos); + + case OpCodes.OP_PLUS: + case OpCodes.OP_MINUS: + case OpCodes.OP_MULT: + case OpCodes.OP_DIV: + case OpCodes.OP_MOD: + case OpCodes.OP_QUO: + return evalMathOperation(op, owner, pos); + + case OpCodes.OP_NEG: + case OpCodes.OP_STRING: + case OpCodes.OP_BOOL: + case OpCodes.OP_NUMBER: + return evalUnaryOperation(op, owner, pos); + + case OpCodes.OP_UNION: + return evalUnion(owner, pos); + + case OpCodes.OP_VARIABLE: + break; + + case OpCodes.OP_GROUP: + return evaluate(owner, Compiler.getFirstChildPos(pos)); + + case OpCodes.OP_EXTFUNCTION: + break; + + case OpCodes.OP_FUNCTION: + return evalFunction(owner, pos); + + case OpCodes.FROM_ANCESTORS: + case OpCodes.FROM_ANCESTORS_OR_SELF: + case OpCodes.FROM_ATTRIBUTES: + case OpCodes.FROM_CHILDREN: + case OpCodes.FROM_DESCENDANTS: + case OpCodes.FROM_DESCENDANTS_OR_SELF: + case OpCodes.FROM_FOLLOWING: + case OpCodes.FROM_FOLLOWING_SIBLINGS: + case OpCodes.FROM_PARENT: + case OpCodes.FROM_PRECEDING: + case OpCodes.FROM_PRECEDING_SIBLINGS: + case OpCodes.FROM_NAMESPACE: + case OpCodes.FROM_SELF: + case OpCodes.FROM_ROOT: + return evalAxis(op, owner, pos); + + case OpCodes.NODENAME: + case OpCodes.OP_LITERAL: + case OpCodes.OP_NUMBERLIT: + return evalLiteral(owner, pos); + + case OpCodes.NODETYPE_TEXT: + case OpCodes.NODETYPE_NODE: + return owner; + + case OpCodes.NODETYPE_ANYELEMENT: + case OpCodes.ELEMWILDCARD: + return WILDCARD; + + case OpCodes.NODETYPE_ROOT: + case OpCodes.NODETYPE_COMMENT: + case OpCodes.NODETYPE_PI: + case OpCodes.NODETYPE_FUNCTEST: + break; + + default : + if (log.isWarnEnabled()) { + log.warn("Unknown: " + op); + } + } + return null; + } + + private Object evalLocationPath(String owner, int pos) throws Exception { + int lp = Compiler.getFirstChildPos(pos); + List ks = new ArrayList(); + String name = null; + boolean attr = false; + while (cmp.getOp(lp) != -1) { + Object obj = evaluate(owner, lp); + if (obj instanceof NamedKeys) { + NamedKeys nk = (NamedKeys) obj; + if (nk.name != null) { + attr = nk.attribute; + if (attr) { + if (name == null) { + name = owner; + } + if (name != null) { + StringBuffer sb = new StringBuffer(32); + sb.append(name); + sb.append('@'); + sb.append(nk.name); + name = sb.toString(); + } + } else { + name = nk.name; + } + } + if (nk.keys != null) { + ks.add(nk.keys); + } + else if (name != null) { + // Try to use a NameIndex to resolve the path component + // can match a wildcard node name here if pattern is "*" then every document matches + if (attr == false && "*".equals(name)) { + SortedSet set = new TreeSet(); + RecordSet rs = context.getFiler().getRecordSet(); + while (rs.hasMoreRecords()) { + set.add(rs.getNextKey()); + } + ks.add(set.toArray(EMPTY_KEYS)); + } else { + // Try to use a NameIndex to resolve the path component + IndexPattern pattern = new IndexPattern(symbols, name, nsMap); + Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODENAME, pattern); + if (idx != null) { + IndexMatch[] matches = idx.queryMatches(new IndexQueryANY(pattern)); + Key[] keys = QueryEngine.getUniqueKeys(matches); + ks.add(keys); + } + } + } + } + lp = cmp.getNextOpPos(lp); + } + return new NamedKeys(name, attr, andKeys(ks)); + } + + private Object evalUnion(String owner, int pos) throws Exception { + int l = Compiler.getFirstChildPos(pos); + int r = cmp.getNextOpPos(l); + Object left = evaluate(owner, l); + + if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) { + Object right = evaluate(owner, r); + + if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) { + Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys}; + return new NamedKeys(null, false, QueryEngine.orKeySets(keys)); + } + } + + // no index query of left part of union + // or no index query of right part of union => must do + /// collection scan + return null; + } + + private Object evalSetComparison(int op, String owner, int pos) throws Exception { + int l = Compiler.getFirstChildPos(pos); + int r = cmp.getNextOpPos(l); + Object left = evaluate(owner, l); + + if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) { + // have left keys + if (((NamedKeys) left).keys.length == 0 && op == OpCodes.OP_AND) { + // left keyset empty implies result of AND would be empty + return new NamedKeys(null, false, ((NamedKeys) left).keys); + } + + Object right = evaluate(owner, r); + if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) { + // have keys for both left and right + if (op == OpCodes.OP_AND) { + if (((NamedKeys) right).keys.length == 0) { + // right keyset empty implies result of AND would be empty + return new NamedKeys(null, false, ((NamedKeys) right).keys); + } - /** - * NamedKeys - */ + Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys}; + return new NamedKeys(null, false, QueryEngine.andKeySets(keys)); + } else { + // OR operation + if (((NamedKeys) left).keys.length == 0) { + // OR operation and left empty implies result is right set + return new NamedKeys(null, false, ((NamedKeys) right).keys); + } + + if (((NamedKeys) right).keys.length == 0) { + // OR operation and right empty implies result is left set + return new NamedKeys(null, false, ((NamedKeys) left).keys); + } + + Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys}; + return new NamedKeys(null, false, QueryEngine.orKeySets(keys)); + } + } else { + // have left keys but not right can infer that AND operation + // result cannot contain more than left set so return that + if (op == OpCodes.OP_AND) { + return new NamedKeys(null, false, ((NamedKeys) left).keys); + } + } + } else { + // do not have left keys + Object right = evaluate(owner, r); + if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) { + // have right keys but not left can infer that AND operation + // result cannot contain more than right set so return that + if (op == OpCodes.OP_AND) { + return new NamedKeys(null, false, ((NamedKeys) right).keys); + } + } + } + + // punt + return null; + } + + private Object evalValComparison(int op, String owner, int pos) throws Exception { + int l = Compiler.getFirstChildPos(pos); + int r = cmp.getNextOpPos(l); + + Object left = evaluate(owner, l); + if (!(left instanceof XObject || left instanceof NamedKeys)) { + // can't evaluate + return null; + } + + Object right = evaluate(owner, r); + if ((left instanceof NamedKeys && right instanceof XObject) + || (left instanceof XObject && right instanceof NamedKeys)) { + // try to evaluate through indexed search + return queryComparison(op, owner, left, right); + } + + // could handle comparison of nodeset to boolean here + // boolean converts to 1.0 (true) or 0.0 (false) + // nodeset converts to 1.0 (non-empty) or 0.0 (empty) + // but since this is a rare and odd comparison we don't bother now... + if (left instanceof XObject && right instanceof XObject) { + switch (op) { + case OpCodes.OP_NOTEQUALS: + return new XBoolean(((XObject) left).notEquals((XObject) right)); + case OpCodes.OP_EQUALS: + return new XBoolean(((XObject) left).equals((XObject) right)); + case OpCodes.OP_LTE: + return new XBoolean(((XObject) left).lessThanOrEqual((XObject) right)); + case OpCodes.OP_LT: + return new XBoolean(((XObject) left).lessThan((XObject) right)); + case OpCodes.OP_GTE: + return new XBoolean(((XObject) left).greaterThanOrEqual((XObject) right)); + case OpCodes.OP_GT: + return new XBoolean(((XObject) left).greaterThan((XObject) right)); + default : + return null; // Won't happen + } + } + + // can't evaluate here... + return null; + } + + private strictfp Object evalMathOperation(int op, String owner, int pos) throws Exception { + int lc = Compiler.getFirstChildPos(pos); + int rc = cmp.getNextOpPos(lc); + Object left = evaluate(owner, lc); + + if (left instanceof XObject) { + Object right = evaluate(owner, rc); + if (right instanceof XObject) { + switch (op) { + case OpCodes.OP_PLUS: + return new XNumber(((XObject) left).num() + ((XObject) right).num()); + case OpCodes.OP_MINUS: + return new XNumber(((XObject) left).num() - ((XObject) right).num()); + case OpCodes.OP_MULT: + return new XNumber(((XObject) left).num() * ((XObject) right).num()); + case OpCodes.OP_DIV: + return new XNumber(((XObject) left).num() / ((XObject) right).num()); + case OpCodes.OP_MOD: + return new XNumber(((XObject) left).num() % ((XObject) right).num()); + case OpCodes.OP_QUO: + return new XNumber(((XObject) left).num() / ((XObject) right).num()); + default : + return null; // Won't happen + } + } + } + + // can't evaluate + return null; + } + + private Object evalUnaryOperation(int op, String owner, int pos) throws Exception { + Object val = evaluate(owner, Compiler.getFirstChildPos(pos)); + if (val instanceof XObject) { + switch (op) { + case OpCodes.OP_NEG: + return new XNumber(-((XObject) val).num()); + case OpCodes.OP_STRING: + return new XString(((XObject) val).str()); + case OpCodes.OP_BOOL: + return new XBoolean(((XObject) val).bool()); + case OpCodes.OP_NUMBER: + return new XNumber(((XObject) val).num()); + default: + return null; // Won't happen + } + } + if (val instanceof NamedKeys) { + NamedKeys nk = (NamedKeys) val; + // we may be able to convert the nodeset to the proper type + // and return an answer numeric operations imply conversion to + // string and then to number + // we can't convert to string + // we can handle conversion to boolean (empty or not empty) + if (nk.keys != null && op == OpCodes.OP_BOOL) { + return nk.keys.length == 0 ? XBoolean.S_FALSE : XBoolean.S_TRUE; + } + } + return null; + } + + private Object evalFunction(String owner, int pos) throws Exception { + int idx = Compiler.getFirstChildPos(pos); + int id = cmp.getOp(idx); + + // NOTE: In the XPath op table, the + // op code is stored at the current position index passed to us + // the size of the current op is stored in the next location + // thus, the index of the location just beyond the function + // arguments for the current op (which is also the index of the next op) + // is equal to the current postion + the size of the current operation - 1 + // (the getOp(index) method merely returns the int value stored in the op table + // at the specified index. That value is an op code, a size, or the index + // of an token or ... (see org.apache.xpath.compiler.OpCodes for the + // various items that can be part of an operation) + int endFunc = pos + cmp.getOp(pos + 1) - 1; + + List args = new ArrayList(); + int lp = idx + 1; + while (lp < endFunc) { + args.add(evaluate(owner, lp)); + lp = cmp.getNextOpPos(lp); + } + + switch (id) { + case FunctionTable.FUNC_BOOLEAN: + return funcBoolean(args); + case FunctionTable.FUNC_CEILING: + return funcCeiling(args); + case FunctionTable.FUNC_CONCAT: + return funcConcat(args); + case FunctionTable.FUNC_CONTAINS: + return funcContains(args); + case FunctionTable.FUNC_FALSE: + return XBoolean.S_FALSE; + case FunctionTable.FUNC_FLOOR: + return funcFloor(args); + case FunctionTable.FUNC_NORMALIZE_SPACE: + return funcNormalizeSpace(args); + case FunctionTable.FUNC_NOT: + return funcNot(args); + case FunctionTable.FUNC_NUMBER: + return funcNumber(args); + case FunctionTable.FUNC_ROUND: + return funcRound(args); + case FunctionTable.FUNC_STARTS_WITH: + return funcStartsWith(owner, args); + case FunctionTable.FUNC_STRING: + return funcString(args); + case FunctionTable.FUNC_STRING_LENGTH: + return funcStringLength(args); + case FunctionTable.FUNC_SUBSTRING: + return funcSubstring(args); + case FunctionTable.FUNC_SUBSTRING_AFTER: + return funcSubstringAfter(args); + case FunctionTable.FUNC_SUBSTRING_BEFORE: + return funcSubstringBefore(args); + case FunctionTable.FUNC_TRANSLATE: + return funcTranslate(args); + case FunctionTable.FUNC_TRUE: + return XBoolean.S_TRUE; + default: + return null; + } + } + + private Object evalAxis(int op, String owner, int pos) throws Exception { + String nsURI = cmp.getStepNS(pos); + owner = (String) evaluate(owner, Compiler.getFirstChildPosOfStep(pos)); + // owner = cmp.getStepLocalName(pos); + + if (nsURI != null && nsMap != null) { + // We have to determine the prefix that was used + // There has to be an easier way to do this with Xalan + String pfx = null; + Iterator i = nsMap.keySet().iterator(); + while (i.hasNext()) { + String p = (String) i.next(); + if (nsMap.getNamespaceURI(p).equals(nsURI)) { + pfx = p; + break; + } + } + if (pfx != null) { + StringBuffer sb = new StringBuffer(32); + sb.append(pfx); + sb.append(':'); + sb.append(owner); + owner = sb.toString(); + } + } + + int rp = cmp.getFirstPredicateOpPos(pos); + + List ks = new ArrayList(); + while (rp < pos + cmp.getOp(pos + 1)) { + Object obj = evaluate(owner, rp); + if (obj instanceof NamedKeys) { + NamedKeys nk = (NamedKeys) obj; + if (nk.keys != null) { + ks.add(nk.keys); + } + } + rp = cmp.getNextOpPos(rp); + } + return new NamedKeys(owner, (op == OpCodes.FROM_ATTRIBUTES), andKeys(ks)); + } + + private Object evalLiteral(String owner, int pos) { + int idx = cmp.getOp(Compiler.getFirstChildPos(pos)); + switch (idx) { + case OpCodes.EMPTY: + return owner; + case OpCodes.ELEMWILDCARD: + return WILDCARD; + default: + return cmp.getToken(idx); + } + } + + // XPath Functions + + private Object funcBoolean(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + if (((XObject) o).bool()) { + return XBoolean.S_TRUE; + } else { + return XBoolean.S_FALSE; + } + } else { + if (o instanceof NamedKeys) { + NamedKeys nk = (NamedKeys) o; + if (nk.keys == null) { + return null; + } + if (nk.keys.length == 0) { + // nodeset empty converts to boolean false + return XBoolean.S_FALSE; + } + return XBoolean.S_TRUE; + } + } + } + return null; + } + + private Object funcCeiling(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XNumber(Math.ceil(((XObject) o).num())); + } + } + return null; + } + + private Object funcConcat(List args) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < args.size(); i++) { + Object o = args.get(i); + if (o instanceof XObject) { + sb.append(((XObject) o).str()); + } else { + return null; + } + } + return new XString(sb.toString()); + } + + private Object funcContains(List args) { + if (args.size() == 2) { + Object o = args.get(0); + Object s = args.get(1); + if (o instanceof XObject && s instanceof XObject) { + if (((XObject) o).str().indexOf(((XObject) s).str()) != -1) { + return XBoolean.S_TRUE; + } else { + return XBoolean.S_FALSE; + } + } + } + return null; + } + + private Object funcFloor(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XNumber(Math.floor(((XObject) o).num())); + } + } + return null; + } + + private Object funcNormalizeSpace(List args) { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XString(QueryEngine.normalizeString(((XObject) o).str())); + } + } + return null; + } + + private Object funcNot(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + if (((XObject) o).bool()) { + return XBoolean.S_FALSE; + } else { + return XBoolean.S_TRUE; + } + } else { + if (o instanceof NamedKeys) { + NamedKeys nk = (NamedKeys) o; + if (nk.keys == null) { + return null; + } + if (nk.keys.length == 0) { + // nodeset empty converts to boolean false => not false => true + return XBoolean.S_TRUE; + } + return XBoolean.S_FALSE; + } + return null; + } + + } + return null; + } + + private Object funcNumber(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XNumber(((XObject) o).num()); + } + } + return null; + } + + private Object funcRound(List args) throws Exception { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XNumber(Math.round(((XObject) o).num())); + } + } + return null; + } + + private Object funcStartsWith(String owner, List args) { + if (args.size() == 2) { + Object o = args.get(0); + Object s = args.get(1); + + if (o instanceof XObject && s instanceof XObject) { + if (((XObject) o).str().startsWith(((XObject) s).str())) { + return XBoolean.S_TRUE; + } else { + return XBoolean.S_FALSE; + } + } else if (o instanceof NamedKeys && s instanceof XObject) { + NamedKeys nk = (NamedKeys) o; + String ps; + if (nk.attribute && nk.name.indexOf('@') == -1) { + ps = owner + "@" + nk.name; + } else { + ps = nk.name; + } + + IndexPattern pattern = new IndexPattern(symbols, ps, nsMap); + + XObject obj = (XObject) s; + Value val1 = new Value(obj.str()); + + IndexQuery iq = new IndexQuerySW(pattern, val1); + return queryIndexes(nk, iq, ps, obj.getType()); + } + } + return null; + } + + private Object funcString(List args) { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XString(((XObject) o).str()); + } + } + return null; + } + + private Object funcStringLength(List args) { + if (args.size() == 1) { + Object o = args.get(0); + if (o instanceof XObject) { + return new XNumber(((XObject) o).str().length()); + } + } + return null; + } + + private Object funcSubstring(List args) throws Exception { + if (args.size() == 2 || args.size() == 3) { + Object o = args.get(0); + Object pos = args.get(1); + Object len = args.size() == 3 ? args.get(2) : null; + if (o instanceof XObject && pos instanceof XObject && (len == null || len instanceof XObject)) { + int ipos = (int) ((XObject) pos).num() - 1; + if (len != null) { + int ilen = (int) ((XObject) len).num(); + return new XString(((XObject) o).str().substring(ipos, ipos + ilen)); + } else + return new XString(((XObject) o).str().substring(ipos)); + } + } + return null; + } + + private Object funcSubstringAfter(List args) { + if (args.size() == 2) { + Object o = args.get(0); + Object s = args.get(1); + if (o instanceof XObject && s instanceof XObject) { + String val = ((XObject) o).str(); + String sub = ((XObject) s).str(); + int i = val.indexOf(sub); + if (i == -1) { + return new XString(""); + } else { + return new XString(val.substring(i + sub.length())); + } + } + } + return null; + } + + private Object funcSubstringBefore(List args) { + if (args.size() == 2) { + Object o = args.get(0); + Object s = args.get(1); + if (o instanceof XObject && s instanceof XObject) { + String val = ((XObject) o).str(); + String sub = ((XObject) s).str(); + int i = val.indexOf(sub); + if (i == -1) { + return new XString(""); + } else { + return new XString(val.substring(0, i)); + } + } + } + return null; + } + + private Object funcTranslate(List args) { + if (args.size() == 3) { + Object o = args.get(0); + Object c1 = args.get(1); + Object c2 = args.get(2); + if (o instanceof XObject && c1 instanceof XObject && c2 instanceof XObject) { + char ch1 = ((XObject) c1).str().charAt(0); + char ch2 = ((XObject) c2).str().charAt(0); + return new XString(((XObject) o).str().replace(ch1, ch2)); + } + } + return null; + } + + // The Actual Querying Methods + + /** + * queryIndexes actually performs index-based querying on behalf of the evaluation methods. + * + * @param nk The NamedKeys instance to use for matches + * @param iq The actual IndexQuery to use for resolution + * @param ps The pattern String to possibly use for index gen + * @param objType The object type to possibly use for index gen + * @return The resulting Keys (if any) + */ + private Object queryIndexes(NamedKeys nk, IndexQuery iq, String ps, int objType) { + try { + // TODO: Add logic to use an EmptyKeySet if a name doesn't already + // exist in the SymbolTable. This will eliminate the need + // to do a collection scan in those cases where somebody + // typed an element or attribute name incorrectly. + + IndexPattern pattern = iq.getPattern(); + + Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODEVALUE, pattern); + if (idx != null) { + return new NamedKeys(nk.name, nk.attribute, QueryEngine.getUniqueKeys(idx.queryMatches(iq))); + } else if (autoIndex) { + // TODO: This has to *not* be hardcoded + Element e = new DocumentImpl().createElement("index"); + e.setAttribute("class", "org.apache.xindice.core.indexer.ValueIndexer"); + e.setAttribute("name", "xp_" + ps); + e.setAttribute("pattern", ps); + + // Set the type for the index + String type = null; + switch (objType) { + case XObject.CLASS_BOOLEAN: + type = "boolean"; + break; + case XObject.CLASS_NUMBER: + type = "double"; + break; + case XObject.CLASS_STRING: + if (ps.indexOf('@') != -1) { + type = "string"; + } else { + type = "trimmed"; + } + break; + default : + if (log.isWarnEnabled()) { + log.warn("invalid object type : " + objType); + } + } + + if (type != null) { + e.setAttribute("type", type); + } - private class NamedKeys - { - public boolean attribute = false; - public String name; - public Key[] keys; - - public NamedKeys(String name, boolean attribute, Key[] keys) - { - this.name = name; - this.attribute = attribute; - this.keys = keys; - } - } + idxMgr.create(new Configuration(e)); + } + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("ignored exception", e); + } + } + return null; + } + + /** + * queryComparison performs a comparison query use the operands that are passed to it, and returns the resulting + * Keys. + * + * @param op The Operator + * @param owner The Owner Node + * @param left The left Operand + * @param right The right Operand + * @return The resulting Keys (if any) + */ + private Object queryComparison(int op, String owner, Object left, Object right) { + op = OpMap[op - OpCodes.OP_NOTEQUALS]; + + if (left instanceof XObject) { + // Check if we have to switch the operation + if (op == IndexQuery.GT || op == IndexQuery.LT || op == IndexQuery.GEQ || op == IndexQuery.LEQ) { + op = -op; + } + // Swap the operands + Object tmp = left; + left = right; + right = tmp; + } + + if (!(left instanceof NamedKeys && right instanceof XObject)) { + // can't handle it + return null; + } + + NamedKeys nk = (NamedKeys) left; + XObject obj = (XObject) right; + + String ps; + if (nk.attribute && nk.name.indexOf('@') == -1) { + ps = owner + "@" + nk.name; + } else { + ps = nk.name; + } + + IndexQuery iq; + IndexPattern pattern = new IndexPattern(symbols, ps, nsMap); + String value = obj.str(); + + switch (op) { + case IndexQuery.NEQ: + iq = new IndexQueryNEQ(pattern, value); + break; + case IndexQuery.EQ: + iq = new IndexQueryEQ(pattern, value); + break; + case IndexQuery.LEQ: + iq = new IndexQueryLEQ(pattern, value); + break; + case IndexQuery.LT: + iq = new IndexQueryLT(pattern, value); + break; + case IndexQuery.GEQ: + iq = new IndexQueryGEQ(pattern, value); + break; + case IndexQuery.GT: + iq = new IndexQueryGT(pattern, value); + break; + default : + iq = null; // Won't happen + } + + return queryIndexes(nk, iq, ps, obj.getType()); + } + } + + /** + * NamedKeys + */ + + private class NamedKeys { + public boolean attribute = false; + public String name; + public Key[] keys; + + public NamedKeys(String name, boolean attribute, Key[] keys) { + this.name = name; + this.attribute = attribute; + this.keys = keys; + } + } /** - * ResultSet - */ + * ResultSet + */ - private class ResultSet implements NodeSet { - public Collection context; - public String query; - public ErrorListener errors; - public PrefixResolver pr; - public XPath xp; - - public Key[] keySet; - public int keyPos = 0; - public NodeIterator ni; - public Object node; + private class ResultSet implements NodeSet { + public Collection context; + public String query; + public ErrorListener errors; + public PrefixResolver pr; + public XPath xp; + + public Key[] keySet; + public int keyPos = 0; + public NodeIterator ni; + public Object node; public ResultSet(Collection context, PrefixResolver pr, Key[] keySet, String query) { - this.context = context; - this.pr = pr; - this.keySet = keySet; - this.query = query; - - errors = new ErrorListener() { - public void fatalError(TransformerException te) { - if (log.isFatalEnabled()) { - log.fatal("No message", te); - } - } - - public void error(TransformerException te) { - if (log.isErrorEnabled()) { - log.error("No message", te); - } - } - - public void warning(TransformerException te) { - if (log.isWarnEnabled()) { - log.warn("No message", te); - } - } - }; + this.context = context; + this.pr = pr; + this.keySet = keySet; + this.query = query; + + errors = new ErrorListener() { + public void fatalError(TransformerException te) { + if (log.isFatalEnabled()) { + log.fatal("No message", te); + } + } + + public void error(TransformerException te) { + if (log.isErrorEnabled()) { + log.error("No message", te); + } + } + + public void warning(TransformerException te) { + if (log.isWarnEnabled()) { + log.warn("No message", te); + } + } + }; - try { - prepareNextNode(); + try { + prepareNextNode(); } catch (Exception e) { - throw new XindiceRuntimeException(e.getMessage()); - } - } + throw new XindiceRuntimeException(e.getMessage()); + } + } private void prepareNextNode() throws XMLDBException, TransformerException, DBException { - node = null; + node = null; while (keyPos < keySet.length) { - DBDocument d = (DBDocument) context.getDocument(keySet[keyPos++]); - if (d == null) { - continue; - } - - Node n = d.getDocumentElement(); - - XPathContext xpc = new XPathContext(); - PrefixResolver pfx; - if (pr == null) { - pfx = new PrefixResolverDefault(d.getDocumentElement()); - xp = new XPath(query, null, pfx, XPath.SELECT, errors); - } else { - pfx = pr; - if (xp == null) { - xp = new XPath(query, null, pfx, XPath.SELECT, errors); - } - } + DBDocument d = (DBDocument) context.getDocument(keySet[keyPos++]); + if (d == null) { + continue; + } + + Node n = d.getDocumentElement(); + + XPathContext xpc = new XPathContext(); + PrefixResolver pfx; + if (pr == null) { + pfx = new PrefixResolverDefault(d.getDocumentElement()); + xp = new XPath(query, null, pfx, XPath.SELECT, errors); + } else { + pfx = pr; + if (xp == null) { + xp = new XPath(query, null, pfx, XPath.SELECT, errors); + } + } - final XObject xobject = xp.execute(xpc, n, pfx); + final XObject xobject = xp.execute(xpc, n, pfx); switch (xobject.getType()) { // case XObject.CLASS_RTREEEFRAG : - default : - throw new XMLDBException( - ErrorCodes.NOT_IMPLEMENTED, - "Unsupported result type: " + xobject.getTypeString()); - - case XObject.CLASS_NODESET : - ni = xobject.nodeset(); - node = ni.nextNode(); - break; + default : + throw new XMLDBException(ErrorCodes.NOT_IMPLEMENTED, + "Unsupported result type: " + xobject.getTypeString()); + + case XObject.CLASS_NODESET: + ni = xobject.nodeset(); + node = ni.nextNode(); + break; - case XObject.CLASS_BOOLEAN : - ni = EMPTY_NODE_ITERATOR; + case XObject.CLASS_BOOLEAN: + ni = EMPTY_NODE_ITERATOR; node = new DocumentImpl().createTextNode(Boolean.toString(xobject.bool())); if (n instanceof DBNode) { - ((TextImpl)node).setSource(((DBNode)n).getSource()); + ((TextImpl) node).setSource(((DBNode) n).getSource()); } - break; + break; - case XObject.CLASS_STRING : - ni = EMPTY_NODE_ITERATOR; + case XObject.CLASS_STRING: + ni = EMPTY_NODE_ITERATOR; node = new DocumentImpl().createTextNode(xobject.str()); if (n instanceof DBNode) { - ((TextImpl)node).setSource(((DBNode)n).getSource()); + ((TextImpl) node).setSource(((DBNode) n).getSource()); } - break; + break; - case XObject.CLASS_NUMBER : - ni = EMPTY_NODE_ITERATOR; + case XObject.CLASS_NUMBER: + ni = EMPTY_NODE_ITERATOR; node = new DocumentImpl().createTextNode(Double.toString(xobject.num())); if (n instanceof DBNode) { - ((TextImpl)node).setSource(((DBNode)n).getSource()); + ((TextImpl) node).setSource(((DBNode) n).getSource()); } - break; - } + break; + } - if (node != null) { - break; - } - } - } - - public boolean hasMoreNodes() { - return node != null; - } - - public Object getNextNode() { - Object n = node; - - node = ni.nextNode(); - if (node == null) { - try { - prepareNextNode(); - } catch (Exception e) { - throw new XindiceRuntimeException(e.getMessage()); - } - } - - return n; - } - } + if (node != null) { + break; + } + } + } + + public boolean hasMoreNodes() { + return node != null; + } + + public Object getNextNode() { + Object n = node; + + node = ni.nextNode(); + if (node == null) { + try { + prepareNextNode(); + } catch (Exception e) { + throw new XindiceRuntimeException(e.getMessage()); + } + } + + return n; + } + } /* This only implements what we need internally */ - private static class EmptyNodeIterator implements NodeIterator - { + private static class EmptyNodeIterator implements NodeIterator { - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#getWhatToShow() - */ - public int getWhatToShow() - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#detach() - */ - public void detach() - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#getExpandEntityReferences() - */ - public boolean getExpandEntityReferences() - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#getRoot() - */ - public Node getRoot() - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#nextNode() - */ - public Node nextNode() throws DOMException - { - return null; - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#previousNode() - */ - public Node previousNode() throws DOMException - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see org.w3c.dom.traversal.NodeIterator#getFilter() - */ - public NodeFilter getFilter() - { - throw new UnsupportedOperationException(); - } - } + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#getWhatToShow() + */ + public int getWhatToShow() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#detach() + */ + public void detach() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#getExpandEntityReferences() + */ + public boolean getExpandEntityReferences() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#getRoot() + */ + public Node getRoot() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#nextNode() + */ + public Node nextNode() throws DOMException { + return null; + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#previousNode() + */ + public Node previousNode() throws DOMException { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.w3c.dom.traversal.NodeIterator#getFilter() + */ + public NodeFilter getFilter() { + throw new UnsupportedOperationException(); + } + } - private final static NodeIterator EMPTY_NODE_ITERATOR = new EmptyNodeIterator(); + private final static NodeIterator EMPTY_NODE_ITERATOR = new EmptyNodeIterator(); }