Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 353   Methods: 13
NCLOC: 220   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
QueryEngine.java 15.4% 23.2% 53.8% 22.2%
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: QueryEngine.java 541508 2007-05-25 01:54:12Z vgritsenko $
 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.Database;
 27    import org.apache.xindice.core.data.Key;
 28    import org.apache.xindice.core.data.NodeSet;
 29    import org.apache.xindice.core.indexer.IndexMatch;
 30    import org.apache.xindice.util.Configuration;
 31    import org.apache.xindice.util.ConfigurationCallback;
 32    import org.apache.xindice.util.SimpleConfigurable;
 33    import org.apache.xindice.util.XindiceException;
 34    import org.apache.xindice.xml.NamespaceMap;
 35   
 36    import java.util.ArrayList;
 37    import java.util.HashMap;
 38    import java.util.List;
 39    import java.util.Map;
 40    import java.util.SortedSet;
 41    import java.util.TreeSet;
 42   
 43    /**
 44    * QueryEngine is the Xindice Query Engine. Its purpose is to orchestrate
 45    * query operations against the Xindice repository. The QueryEngine
 46    * basically just manages a set of QueryResolvers that actually perform
 47    * the work.
 48    *
 49    * @version $Revision: 541508 $, $Date: 2007-05-24 18:54:12 -0700 (Thu, 24 May 2007) $
 50    */
 51    public class QueryEngine extends SimpleConfigurable {
 52   
 53    private static final Log log = LogFactory.getLog(QueryEngine.class);
 54   
 55    private static final String[] EmptyStrings = new String[0];
 56    private static final Key[] EmptyKeys = new Key[0];
 57   
 58    private static final String RESOLVER = "resolver";
 59    private static final String CLASS = "class";
 60   
 61    private final Database db;
 62    private final Map resolvers;
 63   
 64   
 65  123 public QueryEngine(Database db) {
 66  123 this.db = db;
 67  123 this.resolvers = new HashMap();
 68    }
 69   
 70  123 public void setConfig(Configuration config) throws XindiceException {
 71  123 super.setConfig(config);
 72  123 config.processChildren(RESOLVER, new ConfigurationCallback() {
 73  249 public void process(Configuration cfg) {
 74  249 final String className = cfg.getAttribute(CLASS);
 75  249 try {
 76  249 QueryResolver res = (QueryResolver) Class.forName(className).newInstance();
 77  249 res.setConfig(cfg);
 78  249 res.setQueryEngine(QueryEngine.this);
 79  249 resolvers.put(res.getQueryStyle(), res);
 80    } catch (Exception e) {
 81  0 if (log.isWarnEnabled()) {
 82  0 log.warn("Unable to load query resolver: " + className + ". " +
 83    "This query resolver will not be available.", e);
 84    }
 85    }
 86    }
 87    });
 88    }
 89   
 90  0 public Database getDatabase() {
 91  0 return db;
 92    }
 93   
 94    /**
 95    * listStyles returns a list of styles supported by the
 96    * QueryEngine (ex: XPath, XUpdate)
 97    *
 98    * @return The supported styles
 99    */
 100  0 public String[] listStyles() {
 101  0 return (String[]) resolvers.keySet().toArray(EmptyStrings);
 102    }
 103   
 104  372 private QueryResolver getResolver(String style) throws QueryException {
 105  372 QueryResolver res = (QueryResolver) resolvers.get(style);
 106  372 if (res == null) {
 107  0 throw new StyleNotFoundException("No Resolver available for '" + style + "' queries");
 108    }
 109  372 return res;
 110    }
 111   
 112    /**
 113    * query performs the specified query and returns a NodeSet with
 114    * any possible results from that query. The query is performed
 115    * in the context of a Collection.
 116    *
 117    * @param col The Collection context
 118    * @param style The query style (XPath, Fulltext, etc...)
 119    * @param query The Query
 120    * @param nsMap The namespace Map (if any)
 121    * @param keys The initial Key set to use (if any)
 122    * @return A NodeSet with the query results
 123    */
 124  372 public NodeSet query(Collection col, String style, String query, NamespaceMap nsMap, Key[] keys) throws DBException, QueryException {
 125  372 QueryResolver res = getResolver(style);
 126  372 return res.query(col, query, nsMap, keys);
 127    }
 128   
 129    /**
 130    * compileQuery compiles a Query against the specified Collection
 131    * context and returns the compiled Query. This DOES NOT actually
 132    * run the query, merely just parses it and primes any possible
 133    * Indexers that the query might need.
 134    *
 135    * @param col The Collection context
 136    * @param style The query style (XPath, Fulltext, etc...)
 137    * @param query The Query
 138    * @param nsMap The namespace Map (if any)
 139    * @param keys The initial Key set to use (if any)
 140    * @return The compiled Query
 141    */
 142  0 public Query compileQuery(Collection col, String style, String query, NamespaceMap nsMap, Key[] keys) throws DBException, QueryException {
 143  0 QueryResolver res = getResolver(style);
 144  0 return res.compileQuery(col, query, nsMap, keys);
 145    }
 146   
 147    // Utility methods
 148   
 149    /**
 150    * getUniqueKeys takes a set of IndexMatch objects and extracts
 151    * all of its unique Keys in sorted order.
 152    *
 153    * @param matches The Match Set
 154    * @return The unique Keys in order
 155    */
 156  154 public static Key[] getUniqueKeys(IndexMatch[] matches) {
 157  154 SortedSet set = new TreeSet();
 158  154 for (int i = 0; i < matches.length; i++) {
 159  248 set.add(matches[i].getKey());
 160    }
 161  154 return (Key[]) set.toArray(EmptyKeys);
 162    }
 163   
 164    /**
 165    * andKeySets takes several sets of unique Keys and returns the
 166    * ANDed set (elements that exist in all sets). The first dimension
 167    * of the array holds the individual sets, the second holds the
 168    * actual Keys.
 169    *
 170    * @param keySets 2-dimensional set of Keys
 171    * @return The ANDed set of Keys in order
 172    */
 173  0 public static Key[] andKeySets(Key[][] keySets) {
 174  0 int[] ptrs;
 175   
 176  0 if (keySets.length == 0) {
 177  0 return EmptyKeys;
 178  0 } else if (keySets.length == 1) {
 179  0 return keySets[0];
 180    } else {
 181  0 ptrs = new int[keySets.length];
 182  0 for (int i = 0; i < keySets.length; i++) {
 183  0 if (keySets[i].length == 0) {
 184  0 return EmptyKeys;
 185    } else {
 186  0 ptrs[i] = 0;
 187    }
 188    }
 189    }
 190   
 191  0 SortedSet set = new TreeSet();
 192  0 boolean done = false;
 193  0 List highs = new ArrayList();
 194  0 Key highest = null;
 195  0 while (!done) {
 196  0 boolean eq = true;
 197   
 198  0 for (int i = 0; i < ptrs.length; i++) {
 199  0 Key comp = keySets[i][ptrs[i]];
 200  0 if (highest == null) {
 201  0 highest = comp;
 202  0 highs.add(new Integer(i));
 203    } else {
 204  0 int c = highest.compareTo(comp);
 205  0 if (c != 0) {
 206  0 eq = false;
 207    }
 208  0 if (c < 0) {
 209  0 highest = comp;
 210  0 highs.clear();
 211  0 highs.add(new Integer(i));
 212  0 } else if (c == 0) {
 213  0 highs.add(new Integer(i));
 214    }
 215    }
 216    }
 217  0 if (eq) {
 218  0 set.add(highest);
 219  0 highs.clear();
 220  0 highest = null;
 221    }
 222  0 for (int i = 0; i < ptrs.length; i++) {
 223  0 if (!highs.contains(new Integer(i))) {
 224  0 ptrs[i]++;
 225    }
 226  0 if (ptrs[i] >= keySets[i].length) {
 227  0 done = true;
 228    }
 229    }
 230  0 if (!eq) {
 231  0 highs.clear();
 232    }
 233    }
 234   
 235  0 return (Key[]) set.toArray(EmptyKeys);
 236    }
 237   
 238    /**
 239    * orKeySets takes several sets of unique Keys and returns the
 240    * ORed set (all unique elements). The first dimension of the
 241    * array holds the individual sets, the second holds the actual
 242    * Keys.
 243    *
 244    * @param keySets 2-dimensional set of Keys
 245    * @return The ORed set of Keys in order
 246    */
 247  1 public static Key[] orKeySets(Key[][] keySets) {
 248  1 if (keySets.length == 0) {
 249  0 return EmptyKeys;
 250  1 } else if (keySets.length == 1) {
 251  0 return keySets[0];
 252  1 } else if (keySets.length == 2) {
 253    // Optimization since most ORs will be 2 sets only
 254  1 if (keySets[1].length == 0) {
 255  0 return keySets[0];
 256  1 } else if (keySets[0].length == 0) {
 257  0 return keySets[1];
 258    }
 259    }
 260   
 261  1 SortedSet set = new TreeSet();
 262  1 for (int i = 0; i < keySets.length; i++) {
 263  2 for (int j = 0; j < keySets[i].length; j++) {
 264  9 set.add(keySets[i][j]);
 265    }
 266    }
 267   
 268  1 return (Key[]) set.toArray(EmptyKeys);
 269    }
 270   
 271    /**
 272    * normalizeString normalizes the specific String by stripping
 273    * all leading, trailing, and continuous runs of white space.
 274    *
 275    * @param value The value to normalize
 276    * @return The result
 277    */
 278  0 public static String normalizeString(String value) {
 279  0 char[] c = value.toCharArray();
 280  0 char[] n = new char[c.length];
 281  0 boolean white = true;
 282  0 int pos = 0;
 283  0 for (int i = 0; i < c.length; i++) {
 284  0 if (" \t\n\r".indexOf(c[i]) != -1) {
 285  0 if (!white) {
 286  0 n[pos++] = ' ';
 287  0 white = true;
 288    }
 289    } else {
 290  0 n[pos++] = c[i];
 291  0 white = false;
 292    }
 293    }
 294  0 if (white && pos > 0) {
 295  0 pos--;
 296    }
 297   
 298  0 return new String(n, 0, pos);
 299    }
 300   
 301    /**
 302    * expandEntities expands the String's pre-defined XML entities
 303    * (&lt;, &gt;, etc...) into their actual character representations.
 304    *
 305    * @param value The value to expand entities for
 306    * @return The expanded String
 307    */
 308  0 public static String expandEntities(String value) {
 309  0 int idx = value.indexOf('&');
 310  0 if (idx == -1) {
 311  0 return value;
 312    }
 313   
 314  0 StringBuffer sb = new StringBuffer(value.length());
 315  0 int pos = 0;
 316  0 while (pos < value.length()) {
 317  0 if (idx != -1) {
 318  0 if (idx > pos) {
 319  0 sb.append(value.substring(pos, idx));
 320    }
 321   
 322  0 int end = value.indexOf(';', idx) + 1;
 323  0 if (end == 0) {
 324    // Some sort of error
 325  0 return value;
 326    }
 327   
 328  0 String token = value.substring(idx + 1, end - 1);
 329  0 if (token.equals("apos")) {
 330  0 sb.append("'");
 331  0 } else if (token.equals("quot")) {
 332  0 sb.append("\"");
 333  0 } else if (token.equals("amp")) {
 334  0 sb.append("&");
 335  0 } else if (token.equals("lt")) {
 336  0 sb.append("<");
 337  0 } else if (token.equals("gt")) {
 338  0 sb.append(">");
 339    } else {
 340    // Some sort of error
 341  0 return value;
 342    }
 343   
 344  0 pos = end;
 345  0 idx = value.indexOf('&', pos);
 346    } else {
 347  0 sb.append(value.substring(pos));
 348  0 break;
 349    }
 350    }
 351  0 return sb.toString();
 352    }
 353    }