Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 1,614   Methods: 69
NCLOC: 1,247   Classes: 7
 
 Source file Conditionals Statements Methods TOTAL
XPathQueryResolver.java 43% 56.6% 60.9% 53.4%
coverage coverage
 1    /*
 2    * Licensed to the Apache Software Foundation (ASF) under one or more
 3    * contributor license agreements. See the NOTICE file distributed with
 4    * this work for additional information regarding copyright ownership.
 5    * The ASF licenses this file to You under the Apache License, Version 2.0
 6    * (the "License"); you may not use this file except in compliance with
 7    * the License. You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    *
 17    * $Id: XPathQueryResolver.java 831182 2009-10-30 02:01:12Z natalia $
 18    */
 19   
 20    package org.apache.xindice.core.query;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.core.Collection;
 25    import org.apache.xindice.core.DBException;
 26    import org.apache.xindice.core.query.ftsearch.SpecialQueryParser;
 27    import org.apache.xindice.core.data.Entry;
 28    import org.apache.xindice.core.data.Key;
 29    import org.apache.xindice.core.data.NodeSet;
 30    import org.apache.xindice.core.data.RecordSet;
 31    import org.apache.xindice.core.data.Value;
 32    import org.apache.xindice.core.indexer.IndexManager;
 33    import org.apache.xindice.core.indexer.IndexMatch;
 34    import org.apache.xindice.core.indexer.IndexPattern;
 35    import org.apache.xindice.core.indexer.IndexQuery;
 36    import org.apache.xindice.core.indexer.Indexer;
 37    import org.apache.xindice.core.indexer.LuceneIndexer;
 38    import org.apache.xindice.core.indexer.helpers.IndexQueryANY;
 39    import org.apache.xindice.core.indexer.helpers.IndexQueryEQ;
 40    import org.apache.xindice.core.indexer.helpers.IndexQueryGEQ;
 41    import org.apache.xindice.core.indexer.helpers.IndexQueryGT;
 42    import org.apache.xindice.core.indexer.helpers.IndexQueryLEQ;
 43    import org.apache.xindice.core.indexer.helpers.IndexQueryLT;
 44    import org.apache.xindice.core.indexer.helpers.IndexQueryNEQ;
 45    import org.apache.xindice.core.indexer.helpers.IndexQuerySW;
 46    import org.apache.xindice.util.Configuration;
 47    import org.apache.xindice.util.SimpleConfigurable;
 48    import org.apache.xindice.util.XindiceException;
 49    import org.apache.xindice.util.XindiceRuntimeException;
 50    import org.apache.xindice.xml.NamespaceMap;
 51    import org.apache.xindice.xml.SymbolTable;
 52    import org.apache.xindice.xml.dom.DBDocument;
 53    import org.apache.xindice.xml.dom.DBNode;
 54    import org.apache.xindice.xml.dom.DocumentImpl;
 55    import org.apache.xindice.xml.dom.TextImpl;
 56    import org.apache.xml.utils.DefaultErrorHandler;
 57    import org.apache.xml.utils.PrefixResolver;
 58    import org.apache.xml.utils.PrefixResolverDefault;
 59    import org.apache.xpath.Expression;
 60    import org.apache.xpath.XPath;
 61    import org.apache.xpath.XPathContext;
 62    import org.apache.xpath.compiler.Compiler;
 63    import org.apache.xpath.compiler.FunctionTable;
 64    import org.apache.xpath.compiler.OpCodes;
 65    import org.apache.xpath.compiler.XPathParser;
 66    import org.apache.xpath.objects.XBoolean;
 67    import org.apache.xpath.objects.XNumber;
 68    import org.apache.xpath.objects.XObject;
 69    import org.apache.xpath.objects.XString;
 70    import org.apache.lucene.analysis.Analyzer;
 71   
 72    import org.w3c.dom.DOMException;
 73    import org.w3c.dom.Element;
 74    import org.w3c.dom.Node;
 75    import org.w3c.dom.traversal.NodeFilter;
 76    import org.w3c.dom.traversal.NodeIterator;
 77    import org.xmldb.api.base.ErrorCodes;
 78    import org.xmldb.api.base.XMLDBException;
 79   
 80    import javax.xml.transform.ErrorListener;
 81    import javax.xml.transform.SourceLocator;
 82    import javax.xml.transform.TransformerException;
 83   
 84    import java.lang.reflect.Constructor;
 85    import java.lang.reflect.Method;
 86    import java.util.ArrayList;
 87    import java.util.Iterator;
 88    import java.util.List;
 89    import java.util.SortedSet;
 90    import java.util.TreeSet;
 91    import java.util.HashMap;
 92    import java.util.Map;
 93    import java.util.LinkedList;
 94   
 95    /**
 96    * XPathQueryResolver
 97    *
 98    * @version $Revision: 831182 $, $Date: 2009-10-30 02:01:12 +0000 (Fri, 30 Oct 2009) $
 99    */
 100    public final class XPathQueryResolver extends SimpleConfigurable
 101    implements QueryResolver {
 102   
 103    private static final Log log = LogFactory.getLog(XPathQueryResolver.class);
 104   
 105    private static final String WILDCARD = "*";
 106   
 107    public static final String STYLE_XPATH = "XPath";
 108   
 109    static final String PARAM_COLLECTION = "collection";
 110    static final String PARAM_ANALYZER = "analyzer";
 111   
 112    // Maps Xalan Comparisons To IndexQuery
 113    private static final int[] OPMAP = {
 114    IndexQuery.NEQ, IndexQuery.EQ, IndexQuery.LEQ, IndexQuery.LT, IndexQuery.GEQ, IndexQuery.GT
 115    };
 116   
 117   
 118    // Flag set if Xalan Compiler constructor has 3 arguments
 119    private static final boolean XCOMPILER3;
 120    // Xalan Compiler constructor
 121    private static final Constructor XCOMPILER;
 122    // XPath constructor
 123    private static final Constructor XPATH;
 124   
 125    static {
 126  19 boolean c3;
 127  19 Constructor c;
 128  19 Constructor x;
 129  19 try {
 130  19 c = Compiler.class.getConstructor(
 131    new Class[] { ErrorListener.class, SourceLocator.class, FunctionTable.class });
 132   
 133  19 x = XPath.class.getConstructor(
 134    new Class[] { String.class, SourceLocator.class, PrefixResolver.class,
 135    int.class, ErrorListener.class, FunctionTable.class });
 136   
 137  19 c3 = true;
 138    } catch (NoSuchMethodException nsme) {
 139  0 try {
 140  0 c = Compiler.class.getConstructor(
 141    new Class[] { ErrorListener.class, SourceLocator.class });
 142   
 143  0 x = XPath.class.getConstructor(
 144    new Class[] { String.class, SourceLocator.class, PrefixResolver.class,
 145    int.class, ErrorListener.class });
 146   
 147  0 c3 = false;
 148    } catch (NoSuchMethodException e) {
 149    // Should not happen
 150  0 throw new RuntimeException("Could not obtain org.apache.xpath.compiler.Compiler constructor. " +
 151    "Incompatible Xalan version?");
 152    }
 153    }
 154   
 155  19 XCOMPILER3 = c3;
 156  19 XCOMPILER = c;
 157  19 XPATH = x;
 158    }
 159   
 160   
 161    private DefaultErrorHandler errorListener;
 162    private FunctionTable functionTable;
 163    private boolean autoIndex;
 164    private int funcFTContainsId;
 165   
 166   
 167  134 public XPathQueryResolver() {
 168  134 super();
 169   
 170  134 errorListener = new DefaultErrorHandler();
 171   
 172  134 Object num = null;
 173  134 try {
 174  134 Method install;
 175  134 Object function;
 176   
 177  134 if (XCOMPILER3) {
 178  134 functionTable = new FunctionTable();
 179  134 function = FuncFTContains.class;
 180  134 install = functionTable.getClass().getMethod("installFunction",
 181    new Class[] {String.class, Class.class});
 182  134 num = install.invoke(functionTable, new Object[] {"ftcontains", function});
 183    } else {
 184  0 function = new FuncFTContains();
 185  0 install = FunctionTable.class.getMethod("installFunction",
 186    new Class[] {String.class, Expression.class});
 187  0 num = install.invoke(FunctionTable.class, new Object[] {"ftcontains", function});
 188    }
 189    } catch (Exception e) {
 190    // no extentions will be available
 191  0 log.error("Could not invoke installFunction method. Incompatible Xalan version?");
 192    }
 193   
 194  134 if (num != null) {
 195  134 funcFTContainsId = ((Integer) num).intValue();
 196    } else {
 197  0 log.error("Could not install ftcontains function.");
 198    }
 199    }
 200   
 201  123 public void setConfig(Configuration config) throws XindiceException {
 202  123 super.setConfig(config);
 203  123 this.autoIndex = config.getBooleanAttribute("autoindex", false);
 204    }
 205   
 206  123 public String getQueryStyle() {
 207  123 return STYLE_XPATH;
 208    }
 209   
 210  123 public void setQueryEngine(QueryEngine engine) {
 211    // FIXME Not used: this.engine = engine;
 212    }
 213   
 214  0 public Query compileQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys)
 215    throws QueryException {
 216  0 return new XPathQuery(context, query, nsMap, keys);
 217    }
 218   
 219  365 public NodeSet query(Collection context, String query, NamespaceMap nsMap, Key[] keys)
 220    throws QueryException {
 221  365 XPathQuery xq = new XPathQuery(context, query, nsMap, keys);
 222  365 return xq.execute();
 223    }
 224   
 225  365 private Compiler createCompiler() {
 226  365 try {
 227  365 if (XCOMPILER3) {
 228  365 return (Compiler) XCOMPILER.newInstance(
 229    new Object[] {errorListener, null, functionTable});
 230    } else {
 231  0 return (Compiler) XCOMPILER.newInstance(
 232    new Object[] {errorListener, null});
 233    }
 234    } catch (Exception e) {
 235  0 throw new RuntimeException("Could not instantiate Compiler: " + e);
 236    }
 237    }
 238   
 239    /**
 240    * XPathQuery
 241    */
 242    private class XPathQuery implements Query {
 243    public Collection context;
 244    public IndexManager idxMgr;
 245    public NamespaceMap nsMap;
 246    public PrefixResolver pr;
 247    public SymbolTable symbols;
 248    public String query;
 249    public Compiler cmp;
 250    public XPath xp;
 251    public Key[] keys;
 252    public Analyzer analyzer;
 253    private HashMap parameters;
 254   
 255  365 public XPathQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys)
 256    throws QueryException {
 257  365 this.context = context;
 258  365 this.query = query;
 259  365 this.nsMap = nsMap;
 260  365 this.keys = keys;
 261   
 262  365 parameters = new HashMap();
 263  365 parameters.put(PARAM_COLLECTION, context);
 264   
 265  365 Expression ex;
 266  365 try {
 267  365 if (nsMap != null) {
 268  24 Node n = nsMap.getContextNode();
 269  24 pr = new PrefixResolverDefault(n);
 270    }
 271   
 272  365 cmp = createCompiler();
 273  365 XPathParser parser = new XPathParser(errorListener, null);
 274  365 parser.initXPath(cmp, query, pr);
 275  365 ex = cmp.compile(0);
 276   
 277  365 symbols = context.getSymbols();
 278  365 idxMgr = context.getIndexManager();
 279    } catch (Exception e) {
 280  0 throw new CompilationException("Error Compiling XPath Expression: " + e.getMessage(), e);
 281    }
 282   
 283  365 if (ex == null) {
 284  0 throw new CompilationException("Error Compiling XPath Expression: XPath Compiler.compile returned null");
 285    }
 286    }
 287   
 288  0 public String getQueryStyle() {
 289  0 return STYLE_XPATH;
 290    }
 291   
 292  0 public String getQueryString() {
 293  0 return query;
 294    }
 295   
 296  0 public Collection getQueryContext() {
 297  0 return context;
 298    }
 299   
 300  0 public NamespaceMap getNamespaceMap() {
 301  0 return nsMap;
 302    }
 303   
 304  0 public Key[] getKeySet() {
 305  0 return keys;
 306    }
 307   
 308  365 public NodeSet execute() throws QueryException {
 309  365 try {
 310  365 Key[] keySet = keys;
 311   
 312    // TODO: Add logic to do an indexed check on provided
 313    // keySets that are larger than a certain minimum
 314   
 315  365 if (keys == null && idxMgr != null) {
 316    // Issue the query using Indexes
 317  365 try {
 318  365 Object obj = evaluate(null, 0);
 319  365 if (obj instanceof NamedKeys) {
 320  351 keySet = ((NamedKeys) obj).keys;
 321    }
 322    } catch (Exception e) {
 323  0 if (log.isWarnEnabled()) {
 324  0 log.warn("ignored exception", e);
 325    }
 326    }
 327    }
 328   
 329  365 if (keySet == null) {
 330    // Fall back to a Collection scan
 331  237 SortedSet set = new TreeSet();
 332  237 RecordSet rs = context.getFiler().getRecordSet();
 333  237 while (rs.hasMoreRecords()) {
 334  32166 set.add(rs.getNextKey());
 335    }
 336  237 keySet = (Key[]) set.toArray(new Key[set.size()]);
 337    }
 338   
 339  365 return new ResultSet(context, pr, keySet, query, parameters);
 340    } catch (Exception e) {
 341  0 if (e instanceof QueryException) {
 342  0 throw (QueryException) e.fillInStackTrace();
 343    }
 344  0 throw new ProcessingException("Error executing XPath query: " + e.getMessage(), e);
 345    }
 346    }
 347   
 348  2339 private Key[] andKeys(List list) {
 349  2339 if (list.isEmpty()) {
 350  2039 return null;
 351    }
 352   
 353  300 if (list.size() > 1) {
 354  0 Key[][] keys = (Key[][]) list.toArray(new Key[list.size()][]);
 355  0 return QueryEngine.andKeySets(keys);
 356    }
 357   
 358  300 return (Key[]) list.get(0);
 359    }
 360   
 361    // Evaluation Methods
 362   
 363    /**
 364    * evaluate does a partial evaluation of the XPath in order to
 365    * determine the optimal indexes to prepare for the query and retrieve
 366    * the Document subset that will be used for the actual XPath query.
 367    *
 368    * <p>This will return an instance of one of the following classes:
 369    * <pre>
 370    * String If the sub-expression resolves to a Node Name
 371    * XNumber If the sub-expression resolves to a Number
 372    * XString If the sub-expression resolves to a String
 373    * XBoolean If the sub-expression resolves to a Boolean
 374    * NamedKeys If the sub-expression resolves to a Key set
 375    * null If the sub-expression resolves to anything else
 376    * </pre>
 377    *
 378    * @param owner The parent node name for this context
 379    * @param pos The position to start at (recursively called)
 380    * @return Some Object result
 381    */
 382  5532 private Object evaluate(NodePath owner, int pos) throws Exception {
 383  5532 int op = cmp.getOp(pos);
 384  5532 if (op == -1) {
 385  0 return null;
 386    }
 387   
 388  5532 switch (op) {
 389  740 case OpCodes.OP_LOCATIONPATH:
 390  740 return evalLocationPath(owner, pos);
 391   
 392  268 case OpCodes.OP_ARGUMENT:
 393  365 case OpCodes.OP_XPATH:
 394  345 case OpCodes.OP_PREDICATE:
 395  978 return evaluate(owner, Compiler.getFirstChildPos(pos));
 396   
 397  13 case OpCodes.OP_OR:
 398  36 case OpCodes.OP_AND:
 399  49 return evalSetComparison(op, owner, pos);
 400   
 401  0 case OpCodes.OP_NOTEQUALS:
 402  120 case OpCodes.OP_EQUALS:
 403  2 case OpCodes.OP_LTE:
 404  4 case OpCodes.OP_LT:
 405  8 case OpCodes.OP_GTE:
 406  4 case OpCodes.OP_GT:
 407  138 return evalValComparison(op, owner, pos);
 408   
 409  6 case OpCodes.OP_PLUS:
 410  3 case OpCodes.OP_MINUS:
 411  0 case OpCodes.OP_MULT:
 412  0 case OpCodes.OP_DIV:
 413  0 case OpCodes.OP_MOD:
 414  0 case OpCodes.OP_QUO:
 415  9 return evalMathOperation(op, owner, pos);
 416   
 417  0 case OpCodes.OP_NEG:
 418  0 case OpCodes.OP_STRING:
 419  0 case OpCodes.OP_BOOL:
 420  0 case OpCodes.OP_NUMBER:
 421  0 return evalUnaryOperation(op, owner, pos);
 422   
 423  0 case OpCodes.OP_UNION:
 424  0 return evalUnion(owner, pos);
 425   
 426  0 case OpCodes.OP_VARIABLE:
 427  0 break;
 428   
 429  13 case OpCodes.OP_GROUP:
 430  13 return evaluate(owner, Compiler.getFirstChildPos(pos));
 431   
 432  0 case OpCodes.OP_EXTFUNCTION:
 433  0 break;
 434   
 435  155 case OpCodes.OP_FUNCTION:
 436  155 return evalFunction(owner, pos);
 437   
 438  1 case OpCodes.FROM_ANCESTORS:
 439  0 case OpCodes.FROM_ANCESTORS_OR_SELF:
 440  211 case OpCodes.FROM_ATTRIBUTES:
 441  706 case OpCodes.FROM_CHILDREN:
 442  0 case OpCodes.FROM_DESCENDANTS:
 443  258 case OpCodes.FROM_DESCENDANTS_OR_SELF:
 444  0 case OpCodes.FROM_FOLLOWING:
 445  0 case OpCodes.FROM_FOLLOWING_SIBLINGS:
 446  2 case OpCodes.FROM_PARENT:
 447  0 case OpCodes.FROM_PRECEDING:
 448  0 case OpCodes.FROM_PRECEDING_SIBLINGS:
 449  0 case OpCodes.FROM_NAMESPACE:
 450  45 case OpCodes.FROM_SELF:
 451  376 case OpCodes.FROM_ROOT:
 452  1599 return evalAxis(op, owner, pos);
 453   
 454  875 case OpCodes.NODENAME:
 455  233 case OpCodes.OP_LITERAL:
 456  19 case OpCodes.OP_NUMBERLIT:
 457  1127 return evalLiteral(owner, pos);
 458   
 459  18 case OpCodes.NODETYPE_TEXT:
 460  324 case OpCodes.NODETYPE_NODE:
 461  342 return WILDCARD;
 462   
 463  0 case OpCodes.NODETYPE_ANYELEMENT:
 464  0 case OpCodes.ELEMWILDCARD:
 465  0 return WILDCARD;
 466   
 467  376 case OpCodes.NODETYPE_ROOT:
 468  6 case OpCodes.NODETYPE_COMMENT:
 469  0 case OpCodes.NODETYPE_PI:
 470  0 case OpCodes.NODETYPE_FUNCTEST:
 471  382 break;
 472   
 473  0 default :
 474  0 if (log.isWarnEnabled()) {
 475  0 log.warn("Unknown: " + op);
 476    }
 477    }
 478  382 return null;
 479    }
 480   
 481  740 private Object evalLocationPath(NodePath owner, int pos) throws Exception {
 482  740 int lp = Compiler.getFirstChildPos(pos);
 483  740 List ks = new ArrayList();
 484   
 485  740 NodePath name = owner != null ? new NodePath(owner) : new NodePath();
 486  740 boolean attr = false;
 487  740 while (cmp.getOp(lp) != -1) {
 488  1599 Object obj = evaluate(name, lp);
 489  1599 if (obj instanceof NamedKeys) {
 490  1599 NamedKeys nk = (NamedKeys) obj;
 491  1599 if (nk.name != null) {
 492  1599 attr = nk.attribute;
 493  1599 name = nk.name;
 494    }
 495   
 496  1599 if (nk.keys != null) {
 497  119 ks.add(nk.keys);
 498  1480 } else if (name != null && !name.isEmpty()) {
 499    // Try to use a NameIndex to resolve the path component
 500  841 IndexPattern pattern = new IndexPattern(symbols, name.toString(), nsMap);
 501  841 Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODENAME, pattern);
 502  841 if (idx != null) {
 503  62 IndexMatch[] matches = idx.queryMatches(new IndexQueryANY(pattern));
 504  62 Key[] keys = QueryEngine.getUniqueKeys(matches);
 505  62 ks.add(keys);
 506    }
 507    }
 508    }
 509  1599 lp = cmp.getNextOpPos(lp);
 510    }
 511  740 return new NamedKeys(name, attr, andKeys(ks));
 512    }
 513   
 514  0 private Object evalUnion(NodePath owner, int pos) throws Exception {
 515  0 int l = Compiler.getFirstChildPos(pos);
 516  0 int r = cmp.getNextOpPos(l);
 517  0 Object left = evaluate(owner, l);
 518   
 519  0 if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) {
 520  0 Object right = evaluate(owner, r);
 521   
 522  0 if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
 523  0 Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
 524  0 return new NamedKeys(null, false, QueryEngine.orKeySets(keys));
 525    }
 526    }
 527   
 528    // no index query of left part of union
 529    // or no index query of right part of union => must do
 530    // collection scan
 531  0 return null;
 532    }
 533   
 534  49 private Object evalSetComparison(int op, NodePath owner, int pos) throws Exception {
 535  49 int l = Compiler.getFirstChildPos(pos);
 536  49 int r = cmp.getNextOpPos(l);
 537  49 Object left = evaluate(owner, l);
 538   
 539  49 if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) {
 540    // have left keys
 541  1 if (((NamedKeys) left).keys.length == 0 && op == OpCodes.OP_AND) {
 542    // left keyset empty implies result of AND would be empty
 543  0 return new NamedKeys(null, false, ((NamedKeys) left).keys);
 544    }
 545   
 546  1 Object right = evaluate(owner, r);
 547  1 if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
 548    // have keys for both left and right
 549  1 if (op == OpCodes.OP_AND) {
 550  0 if (((NamedKeys) right).keys.length == 0) {
 551    // right keyset empty implies result of AND would be empty
 552  0 return new NamedKeys(null, false, ((NamedKeys) right).keys);
 553    }
 554   
 555  0 Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
 556  0 return new NamedKeys(null, false, QueryEngine.andKeySets(keys));
 557    } else {
 558    // OR operation
 559  1 if (((NamedKeys) left).keys.length == 0) {
 560    // OR operation and left empty implies result is right set
 561  0 return new NamedKeys(null, false, ((NamedKeys) right).keys);
 562    }
 563   
 564  1 if (((NamedKeys) right).keys.length == 0) {
 565    // OR operation and right empty implies result is left set
 566  0 return new NamedKeys(null, false, ((NamedKeys) left).keys);
 567    }
 568   
 569  1 Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
 570  1 return new NamedKeys(null, false, QueryEngine.orKeySets(keys));
 571    }
 572    } else {
 573    // have left keys but not right can infer that AND operation
 574    // result cannot contain more than left set so return that
 575  0 if (op == OpCodes.OP_AND) {
 576  0 return new NamedKeys(null, false, ((NamedKeys) left).keys);
 577    }
 578    }
 579    } else {
 580    // do not have left keys
 581  48 Object right = evaluate(owner, r);
 582  48 if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
 583    // have right keys but not left can infer that AND operation
 584    // result cannot contain more than right set so return that
 585  0 if (op == OpCodes.OP_AND) {
 586  0 return new NamedKeys(null, false, ((NamedKeys) right).keys);
 587    }
 588    }
 589    }
 590   
 591    // punt
 592  48 return null;
 593    }
 594   
 595  138 private Object evalValComparison(int op, NodePath owner, int pos) throws Exception {
 596  138 int l = Compiler.getFirstChildPos(pos);
 597  138 int r = cmp.getNextOpPos(l);
 598   
 599  138 Object left = evaluate(owner, l);
 600  138 if (!(left instanceof XObject || left instanceof NamedKeys)) {
 601    // can't evaluate
 602  19 return null;
 603    }
 604   
 605  119 Object right = evaluate(owner, r);
 606  119 if ((left instanceof NamedKeys && right instanceof XObject)
 607    || (left instanceof XObject && right instanceof NamedKeys)) {
 608    // try to evaluate through indexed search
 609  119 return queryComparison(op, owner, left, right);
 610    }
 611   
 612    // could handle comparison of nodeset to boolean here
 613    // boolean converts to 1.0 (true) or 0.0 (false)
 614    // nodeset converts to 1.0 (non-empty) or 0.0 (empty)
 615    // but since this is a rare and odd comparison we don't bother now...
 616  0 if (left instanceof XObject && right instanceof XObject) {
 617  0 switch (op) {
 618  0 case OpCodes.OP_NOTEQUALS:
 619  0 return new XBoolean(((XObject) left).notEquals((XObject) right));
 620  0 case OpCodes.OP_EQUALS:
 621  0 return new XBoolean(((XObject) left).equals((XObject) right));
 622  0 case OpCodes.OP_LTE:
 623  0 return new XBoolean(((XObject) left).lessThanOrEqual((XObject) right));
 624  0 case OpCodes.OP_LT:
 625  0 return new XBoolean(((XObject) left).lessThan((XObject) right));
 626  0 case OpCodes.OP_GTE:
 627  0 return new XBoolean(((XObject) left).greaterThanOrEqual((XObject) right));
 628  0 case OpCodes.OP_GT:
 629  0 return new XBoolean(((XObject) left).greaterThan((XObject) right));
 630  0 default :
 631  0 return null; // Won't happen
 632    }
 633    }
 634   
 635    // can't evaluate here...
 636  0 return null;
 637    }
 638   
 639  9 private strictfp Object evalMathOperation(int op, NodePath owner, int pos) throws Exception {
 640  9 int lc = Compiler.getFirstChildPos(pos);
 641  9 int rc = cmp.getNextOpPos(lc);
 642  9 Object left = evaluate(owner, lc);
 643   
 644  9 if (left instanceof XObject) {
 645  1 Object right = evaluate(owner, rc);
 646  1 if (right instanceof XObject) {
 647  1 switch (op) {
 648  1 case OpCodes.OP_PLUS:
 649  1 return new XNumber(((XObject) left).num() + ((XObject) right).num());
 650  0 case OpCodes.OP_MINUS:
 651  0 return new XNumber(((XObject) left).num() - ((XObject) right).num());
 652  0 case OpCodes.OP_MULT:
 653  0 return new XNumber(((XObject) left).num() * ((XObject) right).num());
 654  0 case OpCodes.OP_DIV:
 655  0 return new XNumber(((XObject) left).num() / ((XObject) right).num());
 656  0 case OpCodes.OP_MOD:
 657  0 return new XNumber(((XObject) left).num() % ((XObject) right).num());
 658  0 case OpCodes.OP_QUO:
 659  0 return new XNumber(((XObject) left).num() / ((XObject) right).num());
 660  0 default :
 661  0 return null; // Won't happen
 662    }
 663    }
 664    }
 665   
 666    // can't evaluate
 667  8 return null;
 668    }
 669   
 670  0 private Object evalUnaryOperation(int op, NodePath owner, int pos) throws Exception {
 671  0 Object val = evaluate(owner, Compiler.getFirstChildPos(pos));
 672  0 if (val instanceof XObject) {
 673  0 switch (op) {
 674  0 case OpCodes.OP_NEG:
 675  0 return new XNumber(-((XObject) val).num());
 676  0 case OpCodes.OP_STRING:
 677  0 return new XString(((XObject) val).str());
 678  0 case OpCodes.OP_BOOL:
 679  0 return new XBoolean(((XObject) val).bool());
 680  0 case OpCodes.OP_NUMBER:
 681  0 return new XNumber(((XObject) val).num());
 682  0 default:
 683  0 return null; // Won't happen
 684    }
 685    }
 686  0 if (val instanceof NamedKeys) {
 687  0 NamedKeys nk = (NamedKeys) val;
 688    // we may be able to convert the nodeset to the proper type
 689    // and return an answer numeric operations imply conversion to
 690    // string and then to number
 691    // we can't convert to string
 692    // we can handle conversion to boolean (empty or not empty)
 693  0 if (nk.keys != null && op == OpCodes.OP_BOOL) {
 694  0 return nk.keys.length == 0 ? XBoolean.S_FALSE : XBoolean.S_TRUE;
 695    }
 696    }
 697  0 return null;
 698    }
 699   
 700  155 private Object evalFunction(NodePath owner, int pos) throws Exception {
 701  155 int idx = Compiler.getFirstChildPos(pos);
 702  155 int id = cmp.getOp(idx);
 703   
 704    // NOTE: In the XPath op table, the
 705    // op code is stored at the current position index passed to us
 706    // the size of the current op is stored in the next location
 707    // thus, the index of the location just beyond the function
 708    // arguments for the current op (which is also the index of the next op)
 709    // is equal to the current postion + the size of the current operation - 1
 710    // (the getOp(index) method merely returns the int value stored in the op table
 711    // at the specified index. That value is an op code, a size, or the index
 712    // of an token or ... (see org.apache.xpath.compiler.OpCodes for the
 713    // various items that can be part of an operation)
 714  155 int endFunc = pos + cmp.getOp(pos + 1) - 1;
 715   
 716  155 List args = new ArrayList();
 717  155 int lp = idx + 1;
 718  155 while (lp < endFunc) {
 719  268 args.add(evaluate(owner, lp));
 720  268 lp = cmp.getNextOpPos(lp);
 721    }
 722   
 723  155 switch (id) {
 724  3 case FunctionTable.FUNC_BOOLEAN:
 725  3 return funcBoolean(args);
 726  0 case FunctionTable.FUNC_CEILING:
 727  0 return funcCeiling(args);
 728  0 case FunctionTable.FUNC_CONCAT:
 729  0 return funcConcat(args);
 730  7 case FunctionTable.FUNC_CONTAINS:
 731  7 return funcContains(args);
 732  0 case FunctionTable.FUNC_FALSE:
 733  0 return XBoolean.S_FALSE;
 734  0 case FunctionTable.FUNC_FLOOR:
 735  0 return funcFloor(args);
 736  0 case FunctionTable.FUNC_NORMALIZE_SPACE:
 737  0 return funcNormalizeSpace(args);
 738  0 case FunctionTable.FUNC_NOT:
 739  0 return funcNot(args);
 740  6 case FunctionTable.FUNC_NUMBER:
 741  6 return funcNumber(args);
 742  0 case FunctionTable.FUNC_ROUND:
 743  0 return funcRound(args);
 744  94 case FunctionTable.FUNC_STARTS_WITH:
 745  94 return funcStartsWith(owner, args);
 746  3 case FunctionTable.FUNC_STRING:
 747  3 return funcString(args);
 748  9 case FunctionTable.FUNC_STRING_LENGTH:
 749  9 return funcStringLength(args);
 750  9 case FunctionTable.FUNC_SUBSTRING:
 751  9 return funcSubstring(args);
 752  0 case FunctionTable.FUNC_SUBSTRING_AFTER:
 753  0 return funcSubstringAfter(args);
 754  0 case FunctionTable.FUNC_SUBSTRING_BEFORE:
 755  0 return funcSubstringBefore(args);
 756  0 case FunctionTable.FUNC_TRANSLATE:
 757  0 return funcTranslate(args);
 758  0 case FunctionTable.FUNC_TRUE:
 759  0 return XBoolean.S_TRUE;
 760  24 default:
 761    // custom extention
 762  24 if (id == funcFTContainsId) {
 763  19 return funcFTContains(owner, args);
 764    }
 765  5 return null;
 766    }
 767    }
 768   
 769  1599 private Object evalAxis(int op, NodePath owner, int pos) throws Exception {
 770  1599 String nsURI = cmp.getStepNS(pos);
 771  1599 String name = (String) evaluate(owner, Compiler.getFirstChildPosOfStep(pos));
 772    // owner = cmp.getStepLocalName(pos);
 773   
 774  1599 if (nsURI != null && nsMap != null) {
 775    // We have to determine the prefix that was used
 776    // There has to be an easier way to do this with Xalan
 777  33 String pfx = null;
 778  33 Iterator i = nsMap.keySet().iterator();
 779  33 while (i.hasNext()) {
 780  33 String p = (String) i.next();
 781  33 if (nsMap.getNamespaceURI(p).equals(nsURI)) {
 782  33 pfx = p;
 783  33 break;
 784    }
 785    }
 786  33 if (pfx != null) {
 787  33 StringBuffer sb = new StringBuffer(32);
 788  33 sb.append(pfx);
 789  33 sb.append(':');
 790  33 sb.append(name);
 791  33 name = sb.toString();
 792    }
 793    }
 794   
 795  1599 owner.makeStep(op, name);
 796   
 797  1599 int rp = cmp.getFirstPredicateOpPos(pos);
 798   
 799  1599 List ks = new ArrayList();
 800  1599 while (rp < pos + cmp.getOp(pos + 1)) {
 801  345 Object obj = evaluate(owner, rp);
 802  345 if (obj instanceof NamedKeys) {
 803  193 NamedKeys nk = (NamedKeys) obj;
 804  193 if (nk.keys != null) {
 805  119 ks.add(nk.keys);
 806    }
 807    }
 808  345 rp = cmp.getNextOpPos(rp);
 809    }
 810  1599 return new NamedKeys(owner, (op == OpCodes.FROM_ATTRIBUTES), andKeys(ks));
 811    }
 812   
 813  1127 private Object evalLiteral(NodePath owner, int pos) {
 814  1127 int idx = cmp.getOp(Compiler.getFirstChildPos(pos));
 815  1127 switch (idx) {
 816  0 case OpCodes.EMPTY:
 817  0 return owner;
 818  38 case OpCodes.ELEMWILDCARD:
 819  38 return WILDCARD;
 820  1089 default:
 821  1089 return cmp.getToken(idx);
 822    }
 823    }
 824   
 825    // XPath Functions
 826   
 827  3 private Object funcBoolean(List args) throws Exception {
 828  3 if (args.size() == 1) {
 829  3 Object o = args.get(0);
 830  3 if (o instanceof XObject) {
 831  0 if (((XObject) o).bool()) {
 832  0 return XBoolean.S_TRUE;
 833    } else {
 834  0 return XBoolean.S_FALSE;
 835    }
 836    } else {
 837  3 if (o instanceof NamedKeys) {
 838  3 NamedKeys nk = (NamedKeys) o;
 839  3 if (nk.keys == null) {
 840  3 return null;
 841    }
 842  0 if (nk.keys.length == 0) {
 843    // nodeset empty converts to boolean false
 844  0 return XBoolean.S_FALSE;
 845    }
 846  0 return XBoolean.S_TRUE;
 847    }
 848    }
 849    }
 850  0 return null;
 851    }
 852   
 853  0 private Object funcCeiling(List args) throws Exception {
 854  0 if (args.size() == 1) {
 855  0 Object o = args.get(0);
 856  0 if (o instanceof XObject) {
 857  0 return new XNumber(Math.ceil(((XObject) o).num()));
 858    }
 859    }
 860  0 return null;
 861    }
 862   
 863  0 private Object funcConcat(List args) {
 864  0 StringBuffer sb = new StringBuffer();
 865  0 for (int i = 0; i < args.size(); i++) {
 866  0 Object o = args.get(i);
 867  0 if (o instanceof XObject) {
 868  0 sb.append(((XObject) o).str());
 869    } else {
 870  0 return null;
 871    }
 872    }
 873  0 return new XString(sb.toString());
 874    }
 875   
 876  7 private Object funcContains(List args) {
 877  7 if (args.size() == 2) {
 878  7 Object o = args.get(0);
 879  7 Object s = args.get(1);
 880  7 if (o instanceof XObject && s instanceof XObject) {
 881  0 if (((XObject) o).str().indexOf(((XObject) s).str()) != -1) {
 882  0 return XBoolean.S_TRUE;
 883    } else {
 884  0 return XBoolean.S_FALSE;
 885    }
 886    }
 887    }
 888  7 return null;
 889    }
 890   
 891  19 private Object funcFTContains(NodePath owner, List args) throws Exception {
 892  19 if (args.size() != 1) {
 893  0 return null;
 894    }
 895   
 896  19 if (parameters == null) {
 897  0 parameters = new HashMap();
 898    }
 899   
 900  19 Object o = args.get(0);
 901   
 902  19 if (o instanceof XString) {
 903    // extract text query
 904  19 String query = ((XString) o).str();
 905  19 return queryTextIndex(owner, query);
 906    }
 907   
 908  0 return null;
 909    }
 910   
 911  0 private Object funcFloor(List args) throws Exception {
 912  0 if (args.size() == 1) {
 913  0 Object o = args.get(0);
 914  0 if (o instanceof XObject) {
 915  0 return new XNumber(Math.floor(((XObject) o).num()));
 916    }
 917    }
 918  0 return null;
 919    }
 920   
 921  0 private Object funcNormalizeSpace(List args) {
 922  0 if (args.size() == 1) {
 923  0 Object o = args.get(0);
 924  0 if (o instanceof XObject) {
 925  0 return new XString(QueryEngine.normalizeString(((XObject) o).str()));
 926    }
 927    }
 928  0 return null;
 929    }
 930   
 931  0 private Object funcNot(List args) throws Exception {
 932  0 if (args.size() == 1) {
 933  0 Object o = args.get(0);
 934  0 if (o instanceof XObject) {
 935  0 if (((XObject) o).bool()) {
 936  0 return XBoolean.S_FALSE;
 937    } else {
 938  0 return XBoolean.S_TRUE;
 939    }
 940    } else {
 941  0 if (o instanceof NamedKeys) {
 942  0 NamedKeys nk = (NamedKeys) o;
 943  0 if (nk.keys == null) {
 944  0 return null;
 945    }
 946  0 if (nk.keys.length == 0) {
 947    // nodeset empty converts to boolean false => not false => true
 948  0 return XBoolean.S_TRUE;
 949    }
 950  0 return XBoolean.S_FALSE;
 951    }
 952  0 return null;
 953    }
 954   
 955    }
 956  0 return null;
 957    }
 958   
 959  6 private Object funcNumber(List args) throws Exception {
 960  6 if (args.size() == 1) {
 961  6 Object o = args.get(0);
 962  6 if (o instanceof XObject) {
 963  0 return new XNumber(((XObject) o).num());
 964    }
 965    }
 966  6 return null;
 967    }
 968   
 969  0 private Object funcRound(List args) throws Exception {
 970  0 if (args.size() == 1) {
 971  0 Object o = args.get(0);
 972  0 if (o instanceof XObject) {
 973  0 return new XNumber(Math.round(((XObject) o).num()));
 974    }
 975    }
 976  0 return null;
 977    }
 978   
 979  94 private Object funcStartsWith(NodePath owner, List args) throws Exception {
 980  94 if (args.size() == 2) {
 981  94 Object o = args.get(0);
 982  94 Object s = args.get(1);
 983   
 984  94 if (o instanceof XObject && s instanceof XObject) {
 985  0 if (((XObject) o).str().startsWith(((XObject) s).str())) {
 986  0 return XBoolean.S_TRUE;
 987    } else {
 988  0 return XBoolean.S_FALSE;
 989    }
 990  94 } else if (o instanceof NamedKeys && s instanceof XObject) {
 991  94 NamedKeys nk = (NamedKeys) o;
 992  94 if (nk.name != null) {
 993  94 String ps;
 994  94 if (nk.attribute && nk.name.attr == null) {
 995  0 ps = owner + "@" + nk.name;
 996    } else {
 997  94 ps = nk.name.toString();
 998    }
 999   
 1000  94 IndexPattern pattern = new IndexPattern(symbols, ps, nsMap);
 1001   
 1002  94 XObject obj = (XObject) s;
 1003  94 Value val1 = new Value(obj.str());
 1004   
 1005  94 IndexQuery iq = new IndexQuerySW(pattern, val1);
 1006  94 return queryIndexes(nk, iq, ps, obj.getType());
 1007    }
 1008    }
 1009    }
 1010  0 return null;
 1011    }
 1012   
 1013  3 private Object funcString(List args) {
 1014  3 if (args.size() == 1) {
 1015  3 Object o = args.get(0);
 1016  3 if (o instanceof XObject) {
 1017  0 return new XString(((XObject) o).str());
 1018    }
 1019    }
 1020  3 return null;
 1021    }
 1022   
 1023  9 private Object funcStringLength(List args) {
 1024  9 if (args.size() == 1) {
 1025  9 Object o = args.get(0);
 1026  9 if (o instanceof XObject) {
 1027  0 return new XNumber(((XObject) o).str().length());
 1028    }
 1029    }
 1030  9 return null;
 1031    }
 1032   
 1033  9 private Object funcSubstring(List args) throws Exception {
 1034  9 if (args.size() == 2 || args.size() == 3) {
 1035  9 Object o = args.get(0);
 1036  9 Object pos = args.get(1);
 1037  9 Object len = args.size() == 3 ? args.get(2) : null;
 1038  9 if (o instanceof XObject && pos instanceof XObject && (len == null || len instanceof XObject)) {
 1039  0 int ipos = (int) ((XObject) pos).num() - 1;
 1040  0 if (len != null) {
 1041  0 int ilen = (int) ((XObject) len).num();
 1042  0 return new XString(((XObject) o).str().substring(ipos, ipos + ilen));
 1043    } else
 1044  0 return new XString(((XObject) o).str().substring(ipos));
 1045    }
 1046    }
 1047  9 return null;
 1048    }
 1049   
 1050  0 private Object funcSubstringAfter(List args) {
 1051  0 if (args.size() == 2) {
 1052  0 Object o = args.get(0);
 1053  0 Object s = args.get(1);
 1054  0 if (o instanceof XObject && s instanceof XObject) {
 1055  0 String val = ((XObject) o).str();
 1056  0 String sub = ((XObject) s).str();
 1057  0 int i = val.indexOf(sub);
 1058  0 if (i == -1) {
 1059  0 return new XString("");
 1060    } else {
 1061  0 return new XString(val.substring(i + sub.length()));
 1062    }
 1063    }
 1064    }
 1065  0 return null;
 1066    }
 1067   
 1068  0 private Object funcSubstringBefore(List args) {
 1069  0 if (args.size() == 2) {
 1070  0 Object o = args.get(0);
 1071  0 Object s = args.get(1);
 1072  0 if (o instanceof XObject && s instanceof XObject) {
 1073  0 String val = ((XObject) o).str();
 1074  0 String sub = ((XObject) s).str();
 1075  0 int i = val.indexOf(sub);
 1076  0 if (i == -1) {
 1077  0 return new XString("");
 1078    } else {
 1079  0 return new XString(val.substring(0, i));
 1080    }
 1081    }
 1082    }
 1083  0 return null;
 1084    }
 1085   
 1086  0 private Object funcTranslate(List args) {
 1087  0 if (args.size() == 3) {
 1088  0 Object o = args.get(0);
 1089  0 Object c1 = args.get(1);
 1090  0 Object c2 = args.get(2);
 1091  0 if (o instanceof XObject && c1 instanceof XObject && c2 instanceof XObject) {
 1092  0 char ch1 = ((XObject) c1).str().charAt(0);
 1093  0 char ch2 = ((XObject) c2).str().charAt(0);
 1094  0 return new XString(((XObject) o).str().replace(ch1, ch2));
 1095    }
 1096    }
 1097  0 return null;
 1098    }
 1099   
 1100    // The Actual Querying Methods
 1101   
 1102    /**
 1103    * queryIndexes actually performs index-based querying on behalf of the evaluation methods.
 1104    *
 1105    * @param nk The NamedKeys instance to use for matches
 1106    * @param iq The actual IndexQuery to use for resolution
 1107    * @param ps The pattern String to possibly use for index gen
 1108    * @param objType The object type to possibly use for index gen
 1109    * @return The resulting Keys (if any)
 1110    */
 1111  213 private Object queryIndexes(NamedKeys nk, IndexQuery iq, String ps, int objType) {
 1112  213 try {
 1113    // TODO: Add logic to use an EmptyKeySet if a name doesn't already
 1114    // exist in the SymbolTable. This will eliminate the need
 1115    // to do a collection scan in those cases where somebody
 1116    // typed an element or attribute name incorrectly.
 1117   
 1118  213 IndexPattern pattern = iq.getPattern();
 1119   
 1120  213 Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODEVALUE, pattern);
 1121  213 if (idx != null) {
 1122  58 return new NamedKeys(nk.name, nk.attribute, QueryEngine.getUniqueKeys(idx.queryMatches(iq)));
 1123    }
 1124   
 1125  155 if (autoIndex) {
 1126    // TODO: This has to *not* be hardcoded
 1127  0 Element e = new DocumentImpl().createElement("index");
 1128  0 e.setAttribute("class", "org.apache.xindice.core.indexer.ValueIndexer");
 1129  0 e.setAttribute("name", "xp_" + ps);
 1130  0 e.setAttribute("pattern", ps);
 1131   
 1132    // Set the type for the index
 1133  0 String type = null;
 1134  0 switch (objType) {
 1135  0 case XObject.CLASS_BOOLEAN:
 1136  0 type = "boolean";
 1137  0 break;
 1138  0 case XObject.CLASS_NUMBER:
 1139  0 type = "double";
 1140  0 break;
 1141  0 case XObject.CLASS_STRING:
 1142  0 if (ps.indexOf('@') != -1) {
 1143  0 type = "string";
 1144    } else {
 1145  0 type = "trimmed";
 1146    }
 1147  0 break;
 1148  0 default :
 1149  0 if (log.isWarnEnabled()) {
 1150  0 log.warn("invalid object type : " + objType);
 1151    }
 1152    }
 1153   
 1154  0 if (type != null) {
 1155  0 e.setAttribute("type", type);
 1156    }
 1157   
 1158  0 idxMgr.create(new Configuration(e));
 1159    } else {
 1160  155 log.debug("Query performance may be improved by using ValueIndex with pattern '" + ps + "'");
 1161    }
 1162    } catch (Exception e) {
 1163  0 if (log.isWarnEnabled()) {
 1164  0 log.warn("ignored exception", e);
 1165    }
 1166    }
 1167  155 return null;
 1168    }
 1169   
 1170  19 private Object queryTextIndex(NodePath ps, String query) throws Exception {
 1171  19 IndexPattern pattern = new IndexPattern(symbols, ps.toString(), nsMap);
 1172   
 1173    // check if there is full text indexer for this collection
 1174  19 Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_FULLTEXT, pattern);
 1175  19 if (idx instanceof LuceneIndexer) {
 1176  9 LuceneIndexer textInd = ((LuceneIndexer) idx);
 1177  9 analyzer = textInd.getAnalyzer();
 1178  9 parameters.put(PARAM_ANALYZER, analyzer);
 1179   
 1180    // see if index has matching pattern
 1181  9 String alias = textInd.getPatternAlias(pattern);
 1182   
 1183  9 if (alias != null) {
 1184    // Queries that contain 'NOT', '!', '-' operators cannot be used here
 1185    // because LuceneIndexer searches for documents, in that context
 1186    // "NOT term" query means to find documents where 'term' does not
 1187    // appear in certain field at all. For XPath, however, it means that
 1188    // 'term' must not appear in text of the element that currently under
 1189    // evaluation, but may appear in the other elements that match the
 1190    // same IndexPattern.
 1191    //
 1192    // To make sure that all potentially matching documents are returned
 1193    // by the search, all subqueries with these operators are ignored
 1194    // on this step.
 1195  9 org.apache.lucene.search.Query parsedQuery = new SpecialQueryParser(alias, analyzer).parse(query);
 1196  9 IndexMatch[] matches = textInd.queryMatches(parsedQuery);
 1197  9 Key[] keys = QueryEngine.getUniqueKeys(matches);
 1198   
 1199  9 return new NamedKeys(ps, ps.attr != null, keys);
 1200    }
 1201    } else {
 1202    // there is no Lucene indexer, fall back to default analyzer
 1203  10 log.debug("Query performance may be improved by using LuceneIndex with pattern '" + ps + "'");
 1204  10 analyzer = (Analyzer) Class.forName(LuceneIndexer.DEFANALYZER).newInstance();
 1205  10 parameters.put(PARAM_ANALYZER, analyzer);
 1206    }
 1207   
 1208  10 return null;
 1209    }
 1210   
 1211    /**
 1212    * queryComparison performs a comparison query use the operands that are passed to it, and returns the resulting
 1213    * Keys.
 1214    *
 1215    * @param op The Operator
 1216    * @param owner The Owner Node
 1217    * @param left The left Operand
 1218    * @param right The right Operand
 1219    * @return The resulting Keys (if any)
 1220    */
 1221  119 private Object queryComparison(int op, NodePath owner, Object left, Object right) throws Exception {
 1222  119 op = OPMAP[op - OpCodes.OP_NOTEQUALS];
 1223   
 1224  119 if (left instanceof XObject) {
 1225    // Check if we have to switch the operation
 1226  0 if (op == IndexQuery.GT || op == IndexQuery.LT || op == IndexQuery.GEQ || op == IndexQuery.LEQ) {
 1227  0 op = -op;
 1228    }
 1229    // Swap the operands
 1230  0 Object tmp = left;
 1231  0 left = right;
 1232  0 right = tmp;
 1233    }
 1234   
 1235  119 if (!(left instanceof NamedKeys && right instanceof XObject)) {
 1236    // can't handle it
 1237  0 return null;
 1238    }
 1239   
 1240  119 NamedKeys nk = (NamedKeys) left;
 1241  119 XObject obj = (XObject) right;
 1242   
 1243  119 if (nk.name != null) {
 1244  119 String ps;
 1245  119 if (nk.attribute && nk.name.attr == null) {
 1246  0 ps = owner + "@" + nk.name;
 1247    } else {
 1248  119 ps = nk.name.toString();
 1249    }
 1250   
 1251  119 IndexQuery iq;
 1252  119 IndexPattern pattern = new IndexPattern(symbols, ps, nsMap);
 1253  119 String value = obj.str();
 1254   
 1255  119 switch (op) {
 1256  0 case IndexQuery.NEQ:
 1257  0 iq = new IndexQueryNEQ(pattern, value);
 1258  0 break;
 1259  111 case IndexQuery.EQ:
 1260  111 iq = new IndexQueryEQ(pattern, value);
 1261  111 break;
 1262  0 case IndexQuery.LEQ:
 1263  0 iq = new IndexQueryLEQ(pattern, value);
 1264  0 break;
 1265  4 case IndexQuery.LT:
 1266  4 iq = new IndexQueryLT(pattern, value);
 1267  4 break;
 1268  0 case IndexQuery.GEQ:
 1269  0 iq = new IndexQueryGEQ(pattern, value);
 1270  0 break;
 1271  4 case IndexQuery.GT:
 1272  4 iq = new IndexQueryGT(pattern, value);
 1273  4 break;
 1274  0 default :
 1275  0 iq = null; // Won't happen
 1276    }
 1277   
 1278  119 return queryIndexes(nk, iq, ps, obj.getType());
 1279    } else {
 1280  0 return null;
 1281    }
 1282    }
 1283    }
 1284   
 1285    /**
 1286    * NamedKeys
 1287    */
 1288    private static class NamedKeys {
 1289    public boolean attribute;
 1290    public NodePath name;
 1291    public Key[] keys;
 1292   
 1293  2407 public NamedKeys(NodePath name, boolean attribute, Key[] keys) {
 1294  2407 this.name = name;
 1295  2407 this.attribute = attribute;
 1296  2407 this.keys = keys;
 1297    }
 1298    }
 1299   
 1300    private static final ErrorListener XPATH_ERROR_LISTENER = new ErrorListener() {
 1301  0 public void fatalError(TransformerException te) {
 1302  0 if (log.isFatalEnabled()) {
 1303  0 log.fatal("No message", te);
 1304    }
 1305    }
 1306   
 1307  0 public void error(TransformerException te) {
 1308  0 if (log.isErrorEnabled()) {
 1309  0 log.error("No message", te);
 1310    }
 1311    }
 1312   
 1313  0 public void warning(TransformerException te) {
 1314  0 if (log.isWarnEnabled()) {
 1315  0 log.warn("No message", te);
 1316    }
 1317    }
 1318    };
 1319   
 1320    /**
 1321    * ResultSet
 1322    */
 1323    private class ResultSet implements NodeSet {
 1324    private Collection context;
 1325    private String query;
 1326    private PrefixResolver pr;
 1327    private XPathResolverContext xpc;
 1328    private XPath xp;
 1329   
 1330    private Key[] keySet;
 1331    private int keyPos = 0;
 1332    private NodeIterator ni;
 1333    private Object node;
 1334    private Map parameters;
 1335   
 1336  365 public ResultSet(Collection context, PrefixResolver pr, Key[] keySet, String query, Map parameters) {
 1337  365 this.context = context;
 1338  365 this.pr = pr;
 1339  365 this.keySet = keySet;
 1340  365 this.query = query;
 1341  365 this.parameters = parameters;
 1342   
 1343  365 try {
 1344  365 prepareNextNode();
 1345    } catch (Exception e) {
 1346  0 throw new XindiceRuntimeException(e);
 1347    }
 1348    }
 1349   
 1350  32280 private XPath createXPath(PrefixResolver pfx) {
 1351  32280 try {
 1352  32280 if (XCOMPILER3) {
 1353  32280 return (XPath) XPATH.newInstance(
 1354    new Object[] {query, null, pfx, new Integer(XPath.SELECT), XPATH_ERROR_LISTENER, functionTable});
 1355    } else {
 1356  0 return (XPath) XPATH.newInstance(
 1357    new Object[] {query, null, pfx, new Integer(XPath.SELECT), XPATH_ERROR_LISTENER});
 1358    }
 1359    } catch (Exception e) {
 1360  0 throw new RuntimeException("Could not instantiate Compiler: " + e);
 1361    }
 1362    }
 1363   
 1364  842 private void prepareNextNode() throws XMLDBException, TransformerException, DBException {
 1365  842 node = null;
 1366   
 1367  842 while (keyPos < keySet.length) {
 1368  32329 final Key key = keySet[keyPos++];
 1369   
 1370  32329 Entry entry = context.getEntry(key);
 1371  32329 if (entry == null || entry.getEntryType() != Entry.DOCUMENT) {
 1372  1 continue;
 1373    }
 1374   
 1375  32328 DBDocument d = (DBDocument) entry.getValue();
 1376   
 1377  32328 final Node n = d.getDocumentElement();
 1378  32328 if (n == null) {
 1379  0 if (log.isInfoEnabled()) {
 1380  0 log.info("Document " + context.getCanonicalDocumentName(key) + " is empty, skipping.");
 1381    }
 1382  0 continue;
 1383    }
 1384   
 1385  32328 if (xpc == null) {
 1386  365 xpc = new XPathResolverContext(parameters);
 1387    } else {
 1388  31963 xpc.reset();
 1389    }
 1390   
 1391  32328 PrefixResolver pfx;
 1392  32328 if (pr == null) {
 1393  32256 pfx = new PrefixResolverDefault(d.getDocumentElement());
 1394  32256 xp = createXPath(pfx);
 1395    } else {
 1396  72 pfx = pr;
 1397  72 if (xp == null) {
 1398  24 xp = createXPath(pfx);
 1399    }
 1400    }
 1401   
 1402  32328 final XObject xobject = xp.execute(xpc, n, pfx);
 1403  32328 switch (xobject.getType()) {
 1404    // case XObject.CLASS_RTREEEFRAG :
 1405  0 default :
 1406  0 throw new XMLDBException(ErrorCodes.NOT_IMPLEMENTED,
 1407    "Unsupported result type: " + xobject.getTypeString());
 1408   
 1409  32305 case XObject.CLASS_NODESET:
 1410  32305 ni = xobject.nodeset();
 1411  32305 node = ni.nextNode();
 1412  32305 break;
 1413   
 1414  6 case XObject.CLASS_BOOLEAN:
 1415  6 ni = EMPTY_NODE_ITERATOR;
 1416   
 1417  6 node = new DocumentImpl().createTextNode(String.valueOf(xobject.bool()));
 1418  6 if (n instanceof DBNode) {
 1419  6 ((TextImpl) node).setSource(((DBNode) n).getSource());
 1420    }
 1421  6 break;
 1422   
 1423  6 case XObject.CLASS_STRING:
 1424  6 ni = EMPTY_NODE_ITERATOR;
 1425   
 1426  6 node = new DocumentImpl().createTextNode(xobject.str());
 1427  6 if (n instanceof DBNode) {
 1428  6 ((TextImpl) node).setSource(((DBNode) n).getSource());
 1429    }
 1430  6 break;
 1431   
 1432  11 case XObject.CLASS_NUMBER:
 1433  11 ni = EMPTY_NODE_ITERATOR;
 1434   
 1435  11 node = new DocumentImpl().createTextNode(Double.toString(xobject.num()));
 1436  11 if (n instanceof DBNode) {
 1437  11 ((TextImpl) node).setSource(((DBNode) n).getSource());
 1438    }
 1439  11 break;
 1440    }
 1441   
 1442  32328 if (node != null) {
 1443  477 break;
 1444    }
 1445    }
 1446    }
 1447   
 1448  905 public boolean hasMoreNodes() {
 1449  905 return node != null;
 1450    }
 1451   
 1452  547 public Object getNextNode() {
 1453  547 Object n = node;
 1454   
 1455  547 node = ni.nextNode();
 1456  547 if (node == null) {
 1457  477 try {
 1458  477 prepareNextNode();
 1459    } catch (Exception e) {
 1460  0 throw new XindiceRuntimeException(e);
 1461    }
 1462    }
 1463   
 1464  547 return n;
 1465    }
 1466    }
 1467   
 1468    /* This only implements what we need internally */
 1469    private static class EmptyNodeIterator implements NodeIterator {
 1470  0 public int getWhatToShow() {
 1471  0 throw new UnsupportedOperationException();
 1472    }
 1473   
 1474  0 public void detach() {
 1475  0 throw new UnsupportedOperationException();
 1476    }
 1477   
 1478  0 public boolean getExpandEntityReferences() {
 1479  0 throw new UnsupportedOperationException();
 1480    }
 1481   
 1482  0 public Node getRoot() {
 1483  0 throw new UnsupportedOperationException();
 1484    }
 1485   
 1486  23 public Node nextNode() throws DOMException {
 1487  23 return null;
 1488    }
 1489   
 1490  0 public Node previousNode() throws DOMException {
 1491  0 throw new UnsupportedOperationException();
 1492    }
 1493   
 1494  0 public NodeFilter getFilter() {
 1495  0 throw new UnsupportedOperationException();
 1496    }
 1497    }
 1498   
 1499    private final static NodeIterator EMPTY_NODE_ITERATOR = new EmptyNodeIterator();
 1500   
 1501    /**
 1502    * XPathContext with optional parameters
 1503    */
 1504    static class XPathResolverContext extends XPathContext {
 1505    private Map parameters;
 1506   
 1507  365 public XPathResolverContext(Map parameters) {
 1508  365 this.parameters = parameters;
 1509    }
 1510   
 1511  44 public Object getParameter(String name) {
 1512  44 return parameters.get(name);
 1513    }
 1514   
 1515  0 public void setParameter(String name, Object object) {
 1516  0 parameters.put(name, object);
 1517    }
 1518    }
 1519   
 1520    /**
 1521    * Helper class to track path to a node in the XPath expression
 1522    */
 1523    private static class NodePath {
 1524    private boolean absolute;
 1525    private LinkedList path = new LinkedList();
 1526    private String attr;
 1527   
 1528  364 public NodePath() {
 1529    }
 1530   
 1531    // copy constructor
 1532  376 public NodePath(NodePath copy) {
 1533  376 absolute = copy.absolute;
 1534  376 attr = copy.attr;
 1535  376 path = (LinkedList) copy.path.clone();
 1536    }
 1537   
 1538  1599 public void makeStep(int op, String name) {
 1539  1599 switch (op) {
 1540  211 case OpCodes.FROM_ATTRIBUTES:
 1541  211 attr = name;
 1542  211 break;
 1543  706 case OpCodes.FROM_CHILDREN:
 1544  706 path.addLast(name);
 1545  706 break;
 1546  376 case OpCodes.FROM_ROOT:
 1547  376 absolute = true;
 1548  376 path.clear();
 1549  376 break;
 1550  258 case OpCodes.FROM_DESCENDANTS_OR_SELF:
 1551  0 case OpCodes.FROM_ANCESTORS_OR_SELF:
 1552  258 absolute = false;
 1553  258 path.clear();
 1554  258 if (!WILDCARD.equals(name)) {
 1555  0 path.addLast(name);
 1556    }
 1557  258 break;
 1558  0 case OpCodes.FROM_PRECEDING:
 1559  0 case OpCodes.FROM_FOLLOWING:
 1560  1 case OpCodes.FROM_ANCESTORS:
 1561  0 case OpCodes.FROM_DESCENDANTS:
 1562  1 absolute = false;
 1563  1 path.clear();
 1564  1 path.addLast(name);
 1565  1 break;
 1566  2 case OpCodes.FROM_PARENT:
 1567  2 if (path.size() > 0) {
 1568  2 path.removeLast();
 1569    }
 1570  2 if (!WILDCARD.equals(name)) {
 1571  0 if (path.size() > 0) {
 1572  0 path.removeLast();
 1573    }
 1574  0 path.addLast(name);
 1575    }
 1576  2 break;
 1577  0 case OpCodes.FROM_FOLLOWING_SIBLINGS:
 1578  0 case OpCodes.FROM_PRECEDING_SIBLINGS:
 1579  45 case OpCodes.FROM_SELF:
 1580  45 if (!WILDCARD.equals(name)) {
 1581  0 if (path.size() > 0) {
 1582  0 path.removeLast();
 1583    }
 1584  0 path.addLast(name);
 1585    }
 1586  45 break;
 1587  0 case OpCodes.FROM_NAMESPACE:
 1588  0 absolute = false;
 1589  0 path.clear();
 1590  0 break;
 1591    }
 1592    }
 1593   
 1594  1480 public boolean isEmpty() {
 1595  1480 return path.size() == 0;
 1596    }
 1597   
 1598  1083 public String toString() {
 1599  1083 StringBuffer buf = absolute ? new StringBuffer("/") : new StringBuffer();
 1600  1083 for (Iterator i = path.iterator(); i.hasNext(); ) {
 1601  1699 buf.append(i.next());
 1602  1699 if (i.hasNext()) {
 1603  617 buf.append('/');
 1604    }
 1605    }
 1606   
 1607  1083 if (attr != null) {
 1608  310 buf.append('@').append(attr);
 1609    }
 1610   
 1611  1083 return buf.toString();
 1612    }
 1613    }
 1614    }