/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* CVS $Id: XPathQueryResolver.java,v 1.30 2004/04/09 11:56:03 vgritsenko Exp $
*/
package org.apache.xindice.core.query;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.TransformerException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.core.data.NodeSet;
import org.apache.xindice.core.data.RecordSet;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.indexer.IndexManager;
import org.apache.xindice.core.indexer.IndexMatch;
import org.apache.xindice.core.indexer.IndexPattern;
import org.apache.xindice.core.indexer.IndexQuery;
import org.apache.xindice.core.indexer.Indexer;
import org.apache.xindice.core.indexer.helpers.IndexQueryANY;
import org.apache.xindice.core.indexer.helpers.IndexQueryEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryGEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryGT;
import org.apache.xindice.core.indexer.helpers.IndexQueryLEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryLT;
import org.apache.xindice.core.indexer.helpers.IndexQueryNEQ;
import org.apache.xindice.core.indexer.helpers.IndexQuerySW;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.util.SimpleConfigurable;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.util.XindiceRuntimeException;
import org.apache.xindice.xml.NamespaceMap;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.dom.DBDocument;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.apache.xindice.xml.dom.TextImpl;
import org.apache.xindice.xml.dom.DBNode;
import org.apache.xml.utils.DefaultErrorHandler;
import org.apache.xml.utils.PrefixResolver;
import org.apache.xml.utils.PrefixResolverDefault;
import org.apache.xpath.Expression;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;
import org.apache.xpath.compiler.Compiler;
import org.apache.xpath.compiler.FunctionTable;
import org.apache.xpath.compiler.OpCodes;
import org.apache.xpath.compiler.XPathParser;
import org.apache.xpath.objects.XBoolean;
import org.apache.xpath.objects.XNumber;
import org.apache.xpath.objects.XObject;
import org.apache.xpath.objects.XString;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.XMLDBException;
/**
* XPathQueryResolver
*
* @version CVS $Revision: 1.30 $, $Date: 2004/04/09 11:56:03 $
*/
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 = "..";
private static final String AUTOINDEX = "autoindex";
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;
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.getMessage(), 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(), 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
/**
* 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);
}
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);
}
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
*/
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);
}
}
};
try {
prepareNextNode();
} catch (Exception e) {
throw new XindiceRuntimeException(e.getMessage());
}
}
private void prepareNextNode() throws XMLDBException, TransformerException, DBException {
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);
}
}
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;
case XObject.CLASS_BOOLEAN:
ni = EMPTY_NODE_ITERATOR;
node = new DocumentImpl().createTextNode(String.valueOf(xobject.bool()));
if (n instanceof DBNode) {
((TextImpl) node).setSource(((DBNode) n).getSource());
}
break;
case XObject.CLASS_STRING:
ni = EMPTY_NODE_ITERATOR;
node = new DocumentImpl().createTextNode(xobject.str());
if (n instanceof DBNode) {
((TextImpl) node).setSource(((DBNode) n).getSource());
}
break;
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());
}
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;
}
}
/* This only implements what we need internally */
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();
}
}
private final static NodeIterator EMPTY_NODE_ITERATOR = new EmptyNodeIterator();
}