Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 1,306   Methods: 46
NCLOC: 590   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
MemValueIndexer.java 54.3% 69.5% 71.7% 65.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: MemValueIndexer.java 628599 2008-02-18 02:33:55Z natalia $
 18    */
 19   
 20    package org.apache.xindice.core.indexer;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.core.DBException;
 25    import org.apache.xindice.core.FaultCodes;
 26    import org.apache.xindice.core.data.Key;
 27    import org.apache.xindice.core.data.Value;
 28    import org.apache.xindice.core.query.QueryEngine;
 29    import org.apache.xindice.util.Configuration;
 30   
 31    import java.util.Arrays;
 32    import java.util.Collection;
 33    import java.util.Comparator;
 34    import java.util.Iterator;
 35    import java.util.LinkedList;
 36    import java.util.Map;
 37    import java.util.SortedMap;
 38    import java.util.TreeMap;
 39    import java.util.TreeSet;
 40   
 41    /**
 42    * Provides an in-memory implementation of Xindice Indexer
 43    * based upon the Java TreeSet class. This implementation
 44    * is not persistent. A MemValueIndexer is useful for indexing
 45    * of temporary Collections (e.g. those based on MemFiler). It is also
 46    * useful for temporary indexing of persistent Collections.
 47    *
 48    * @author Terry Rosenbaum (terry@amicas.com)
 49    * @version $Revision: 628599 $, $Date: 2008-02-18 02:33:55 +0000 (Mon, 18 Feb 2008) $
 50    */
 51    public class MemValueIndexer implements Indexer {
 52   
 53    /**
 54    * Log used by this indexer
 55    */
 56    private static final Log log = LogFactory.getLog(MemValueIndexer.class);
 57   
 58    //
 59    // Class constants
 60    //
 61   
 62    private static final int STRING = 0;
 63    private static final int TRIMMED = 1;
 64    private static final int SHORT = 2;
 65    private static final int INTEGER = 3;
 66    private static final int LONG = 4;
 67    private static final int FLOAT = 5;
 68    private static final int DOUBLE = 6;
 69    private static final int BYTE = 7;
 70    private static final int CHAR = 8;
 71    private static final int BOOLEAN = 9;
 72   
 73    private static final String NAME = "name";
 74    private static final String PATTERN = "pattern";
 75    private static final String TYPE = "type";
 76   
 77    private static final String STRING_VAL = "string";
 78    private static final String TRIMMED_VAL = "trimmed";
 79    private static final String SHORT_VAL = "short";
 80    private static final String INT_VAL = "int";
 81    private static final String LONG_VAL = "long";
 82    private static final String FLOAT_VAL = "float";
 83    private static final String DOUBLE_VAL = "double";
 84    private static final String BYTE_VAL = "byte";
 85    private static final String CHAR_VAL = "char";
 86    private static final String BOOLEAN_VAL = "boolean";
 87   
 88    private final static IndexMatch[] EMPTY_INDEX_MATCH_ARRAY = new IndexMatch[0];
 89   
 90    //
 91    // Instance variables
 92    //
 93   
 94    /**
 95    * the indexed Collection
 96    */
 97    private org.apache.xindice.core.Collection itsCollection;
 98   
 99    /**
 100    * this object's configuration
 101    */
 102    private Configuration itsConfig;
 103   
 104    /**
 105    * name of this index
 106    */
 107    private String itsName;
 108   
 109    /**
 110    * pattern for this index
 111    */
 112    private IndexPattern itsPattern;
 113   
 114    /**
 115    * indicates if wildcard index
 116    */
 117    private boolean itsWildcard = false;
 118   
 119    /**
 120    * value type of this index
 121    */
 122    private int itsValueType = STRING;
 123   
 124    /**
 125    * value type name of this index
 126    */
 127    private String itsValueTypeName = STRING_VAL;
 128   
 129    /**
 130    * storage for map of values to tree Key objects locating value matches in Collection
 131    */
 132    private TreeMap itsValues = null;
 133   
 134    /**
 135    * count of ValueLocator objects we currently manage
 136    */
 137    private int itsValueLocatorCount = 0;
 138   
 139    /**
 140    * tracks the open/closed state for open(), close(), and isOpened()
 141    */
 142    private boolean itsOpen = false;
 143    private IndexerEventHandler handler;
 144   
 145   
 146    /**
 147    * Sets the parent Collection of this indexer.
 148    *
 149    * @param theCollection The owner Collection
 150    */
 151  24 public void setCollection(org.apache.xindice.core.Collection theCollection) {
 152  24 itsCollection = theCollection;
 153    }
 154   
 155    /**
 156    * Provides the index style. Different query languages
 157    * will need to draw from different indexing styles. For example, A
 158    * query that is written in quilt will require XPath indexing.
 159    *
 160    * @return the index style
 161    */
 162  48 public String getIndexStyle() {
 163  48 return STYLE_NODEVALUE;
 164    }
 165   
 166    /**
 167    * Provides the pattern recognized by this Indexer. Patterns
 168    * must be in the form of (elem|*)[@(attr|*)] to tell the IndexManager
 169    * which element types to send to it, so for example:
 170    * <pre>
 171    * contact@name Indexes all contacts by name attribute
 172    * memo Indexes the text of all memo elements
 173    * contact@* Indexes all contact attributes
 174    * *@name Indexes the name attribute for all elements
 175    * * Indexes the text of all elements
 176    * *@* Indexes all attributes of all elements
 177    * </pre>
 178    * These patterns are used by the IndexManager when handling SAX events.
 179    * All events that match the specified pattern will result in an add or
 180    * remove call to the Indexer.
 181    *
 182    * @return the pattern used by this index
 183    */
 184  216 public IndexPattern[] getPatterns() {
 185  216 return new IndexPattern[] { itsPattern };
 186    }
 187   
 188    /**
 189    * Provides a set of MatchEntry instances that match
 190    * the specified query. The matches are then used by the QueryEngine
 191    * in co-sequential processing. If this indexer doesn't support the
 192    * passed value, it should return 'null'. If no matches are found,
 193    * it should return an empty set. queryMatches will typically be
 194    * used in XPath processing.
 195    *
 196    * @param theQuery The IndexQuery to use
 197    * @return The resulting matches
 198    */
 199  35 public synchronized IndexMatch[] queryMatches(IndexQuery theQuery) throws DBException {
 200  35 LinkedList aResult = new LinkedList();
 201   
 202    // get the list of Value objects from the query
 203    // there may be 1 value (unary operation e.g. not equals),
 204    // 2 values (binary operation e.g. range matching),
 205    // or a set of N values (set operation e.g. IN)
 206  35 Value[] aQueryValueList = theQuery.getValues();
 207  35 Object[] aMatchValueArray = new Object[aQueryValueList.length];
 208   
 209    // convert the query values to the type of Object we use to represent
 210    // those values in our value map (e.g. String, Integer, Byte, Double, etc.)
 211  35 if (itsValueType != STRING) {
 212  25 for (int anIndex = 0; anIndex < aQueryValueList.length; ++anIndex) {
 213  32 aMatchValueArray[anIndex] = getTypedValue(aQueryValueList[anIndex].toString());
 214    }
 215    } else {
 216  10 for (int anIndex = 0; anIndex < aQueryValueList.length; ++anIndex) {
 217  14 aMatchValueArray[anIndex] = aQueryValueList[anIndex].toString();
 218    }
 219    }
 220   
 221  35 TreeSet aLocatorSet;
 222   
 223  35 Object aLowEndpoint = aMatchValueArray[0];
 224  35 Object aHighEndpoint = aMatchValueArray[aMatchValueArray.length - 1];
 225  35 Iterator aValueIterator;
 226  35 int anOperator = theQuery.getOperator();
 227  35 IndexPattern queryPattern = theQuery.getPattern();
 228   
 229    // perform the requested matching type
 230  35 switch (anOperator) {
 231  0 default: // unsupported operator
 232  0 throw new DBException(FaultCodes.IDX_NOT_SUPPORTED,
 233    "Unimplemented index query operation code (code = " + anOperator + ")");
 234   
 235  10 case IndexQuery.EQ: // exact match
 236  10 aLocatorSet = (TreeSet) itsValues.get(aLowEndpoint);
 237  10 if (aLocatorSet != null) {
 238  8 addMatches(aResult, aLocatorSet, queryPattern);
 239    }
 240  10 break;
 241   
 242  2 case IndexQuery.NEQ: // all except exact match
 243    // see whether or not we even have the value to be excluded
 244  2 TreeSet anExcludedLocatorSet = (TreeSet) itsValues.get(aLowEndpoint);
 245   
 246  2 aValueIterator = itsValues.entrySet().iterator();
 247  2 if (anExcludedLocatorSet == null) {
 248    // iterate over the values adding locators for each to result
 249    // no need to filter while iterating
 250  0 while (aValueIterator.hasNext()) {
 251    // iterate over locators for current value adding each to result
 252  0 addMatches(aResult, (TreeSet) ((Map.Entry) aValueIterator.next()).getValue(), queryPattern);
 253    }
 254    } else {
 255    // iterate over the values adding locators for each to result
 256    // must filter out the locator set for the excluded value while iterating values
 257  2 while (aValueIterator.hasNext()) {
 258  8 aLocatorSet = (TreeSet) ((Map.Entry) aValueIterator.next()).getValue();
 259   
 260    // apply the exclusion filter for the matched value
 261  8 if (aLocatorSet != anExcludedLocatorSet) {
 262    // iterate over locators for current value adding each to result
 263  6 addMatches(aResult, aLocatorSet, queryPattern);
 264    }
 265    }
 266    }
 267  2 break;
 268   
 269  1 case IndexQuery.BWX: // Between (Exclusive)
 270    // notes on BWX and BW:
 271    // TreeMap always returns a half-open range for the subMap operation
 272    // (includes the low endpoint but excludes the high endpoint)
 273    // so we must adjust the endpoints to achieve either a closed range (Inclusive)
 274    // or an open range (Exclusive)
 275    // if either endpoint is already the minimum or maximum for the data type
 276    // (e.g. Byte data and low endpoint == 0) then we must retrieve a
 277    // tail map as appropriate so that we don't need to use the
 278    // next value above/below the endpoint that is already maximum/minimum possible
 279    // assuming that the next/previous values for both endpoints can be computed,
 280    // we use the subMap operation with the following endpoints:
 281    // Operation select type Range for select operation
 282    // ---------------------------------------------------------------------------------
 283    // BWX subMap nextValueOf(low_endpoint) - high_endpoint
 284    // BW subMap low_endpoint - nextValueOf(high_endpoint)
 285    //
 286    // if the low endpoint is already at maximum possible:
 287    // BWX none return empty result
 288    // BW none return result set containing low endpoint matches
 289    //
 290    // if the high endpoint is already at maximum possible:
 291    // BWX subMap nextValueOf(low_endpoint) - high_endpoint
 292    // BW tailMap low_endpoint - end
 293   
 294    // if low_endpoint >= high_endpoint, result is empty
 295  1 aLowEndpoint = getNextValueOf(aLowEndpoint);
 296  1 if (aLowEndpoint != null && ((Comparable) aLowEndpoint).compareTo(aHighEndpoint) < 0) {
 297    // return locators in sub map exclusive of endpoints
 298  1 addMatches(aResult, itsValues.subMap(aLowEndpoint, aHighEndpoint), queryPattern);
 299    }
 300  1 break;
 301   
 302  2 case IndexQuery.BW: // Between (Inclusive)
 303  2 addMatches(aResult, getBWSubmap(aLowEndpoint, aHighEndpoint), queryPattern);
 304  2 break;
 305   
 306  4 case IndexQuery.SW: // starts-with
 307    // handled simlar to BW case, except that we
 308    // always compare against the String form of the comparator
 309    // to ensure that comparisons happen as Strings as specified by XPath
 310    // for starts-with
 311  4 if (! (aLowEndpoint instanceof String) ) {
 312  0 aLowEndpoint = aLowEndpoint.toString();
 313    }
 314   
 315    // get the matching submap forcing String comparisons to be used regardless of stored type
 316  4 addMatches(aResult, getSWSubmap((String)aLowEndpoint), queryPattern);
 317  4 break;
 318   
 319  1 case IndexQuery.IN: // In the (presumed sorted) set of specified query values
 320    // this is handled as a BW query followed by include filtering
 321    // of only those matches in the range that are contained
 322    // in the set of query values (IN)
 323    // the match high endpoint is at the top of the array (already set)
 324   
 325    // get the matching submap forcing String comparisons to be used regardless of stored type
 326  1 addMatches(aResult, getBWSubmap(aLowEndpoint, aHighEndpoint), aMatchValueArray, false, queryPattern); // false => include style filtering applied
 327  1 break;
 328   
 329  1 case IndexQuery.NBWX: // Not between (Exclusive)
 330    // implement as LT or GT
 331  1 addMatches(aResult, getLTSubmap(aLowEndpoint), queryPattern);
 332  1 addMatches(aResult, getGTSubmap(aHighEndpoint), queryPattern);
 333  1 break;
 334   
 335  1 case IndexQuery.NBW: // Not between (Inclusive)
 336    // implement as LEQ or GEQ
 337  1 addMatches(aResult, getLEQSubmap(aLowEndpoint), queryPattern);
 338  1 addMatches(aResult, getGEQSubmap(aHighEndpoint), queryPattern);
 339  1 break;
 340   
 341  0 case IndexQuery.NSW: // Not starts-with
 342    // implement as LT or GT forcing String comparisons
 343    // we get the raw String match value and use that instead of the typed value
 344    // to force String comparisons (as required for starts-with)
 345  0 if(! (aLowEndpoint instanceof String) )
 346    {
 347  0 aLowEndpoint = aLowEndpoint.toString();
 348    }
 349   
 350    // get all matches below starts-with range and above starts-with range
 351    // use of String key forces String matching
 352  0 addMatches(aResult, getLTSubmap(aLowEndpoint), queryPattern);
 353  0 addMatches(aResult, getGTSubmap(aLowEndpoint), queryPattern);
 354  0 break;
 355   
 356  4 case IndexQuery.LT: // Less than
 357  4 addMatches(aResult, getLTSubmap(aLowEndpoint), queryPattern);
 358  4 break;
 359   
 360  2 case IndexQuery.LEQ: // Less than or equal
 361  2 addMatches(aResult, getLEQSubmap(aLowEndpoint), queryPattern);
 362  2 break;
 363   
 364  4 case IndexQuery.GT: // Greater than
 365  4 addMatches(aResult, getGTSubmap(aLowEndpoint), queryPattern);
 366  4 break;
 367   
 368  2 case IndexQuery.GEQ: // Greater than or equal
 369  2 addMatches(aResult, getGEQSubmap(aLowEndpoint), queryPattern);
 370  2 break;
 371   
 372  1 case IndexQuery.NIN: // Not in specified set of query values
 373    // scan all values excluding those specified in match value set
 374    // includes all those entries LT the low endpoint, GT the high endpoint
 375    // and the exclude-filtered set BW of the low and high endpoints
 376  1 addMatches(aResult, getLTSubmap(aLowEndpoint), queryPattern);
 377  1 addMatches(aResult, getGTSubmap(aHighEndpoint), queryPattern);
 378    // exclude-filter this
 379    // true => exclude-style filtering applied
 380  1 addMatches(aResult, getBWSubmap(aLowEndpoint, aHighEndpoint), aMatchValueArray, true, queryPattern);
 381  1 break;
 382   
 383  0 case IndexQuery.ANY: // return all values
 384  0 addMatches(aResult, itsValues, queryPattern);
 385  0 break;
 386    }
 387   
 388  35 return (IndexMatch[]) aResult.toArray(EMPTY_INDEX_MATCH_ARRAY);
 389    }
 390   
 391    /**
 392    * Provides the submap containing the half-open range (inclusive of Low Endpoint but not High Endpoint)
 393    * between the theLowEndpoint and getNextValueOf(theLowEndpoint, STRING).
 394    *
 395    * @param theLowEndpoint low endpoint to use
 396    * @return a SortedMap containing the matches or null if no matches
 397    */
 398  4 private SortedMap getSWSubmap(String theLowEndpoint) {
 399  4 SortedMap aSubmap;
 400   
 401    // force computation of next value as STRING if key is String
 402    // otherwise, next value will be of same type as stored values
 403  4 String aHighEndpoint = (String) getNextValueOf(theLowEndpoint, STRING);
 404   
 405  4 if (aHighEndpoint == null) {
 406    // return locators in tail map from low endpoint
 407  0 aSubmap = itsValues.tailMap(theLowEndpoint);
 408    } else {
 409    // return locators in sub map inclusive of endpoints
 410  4 aSubmap = itsValues.subMap(theLowEndpoint, aHighEndpoint);
 411    }
 412   
 413  4 return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
 414    }
 415   
 416    /**
 417    * Provides the submap containing the closed range (inclusive of both endpoints)
 418    * between the specified endpoints.
 419    *
 420    * @param theLowEndpoint low endpoint to use
 421    * @param theHighEndpoint high endpoint to use
 422    * @return a SortedMap containing the matches or null if no matches
 423    */
 424  4 private SortedMap getBWSubmap(Object theLowEndpoint, Object theHighEndpoint) {
 425  4 SortedMap aSubmap = null;
 426   
 427    // anOperator == IndexQuery.BW
 428  4 int aComparison = ((Comparable) theLowEndpoint).compareTo(theHighEndpoint);
 429   
 430  4 if (aComparison == 0) {
 431    // low endpoint == high endpoint
 432    // return result set containing just the low endpoint matches, if any
 433  0 TreeSet aLocatorSet = (TreeSet) itsValues.get(theLowEndpoint);
 434  0 if (aLocatorSet != null) {
 435  0 aSubmap = new TreeMap(MemValueIndexer.ValueComparator.INSTANCE);
 436  0 aSubmap.put(theLowEndpoint, aLocatorSet);
 437    }
 438    } else {
 439  4 if (aComparison < 0) {
 440    // force computation of next value as STRING if key is String
 441    // otherwise, next value will be of same type as stored values
 442  4 theHighEndpoint = theHighEndpoint instanceof String ? getNextValueOf(theHighEndpoint, STRING) : getNextValueOf(theHighEndpoint);
 443   
 444  4 if (theHighEndpoint == null) {
 445    // return locators in tail map from low endpoint
 446  0 aSubmap = itsValues.tailMap(theLowEndpoint);
 447    } else {
 448    // return locators in sub map inclusive of endpoints
 449  4 aSubmap = itsValues.subMap(theLowEndpoint, theHighEndpoint);
 450    }
 451    }
 452    // else low > high => empty result
 453    }
 454  4 return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
 455    }
 456   
 457    /**
 458    * Provides the submap containing the matches less than the specified key
 459    *
 460    * @param theHighEndpoint high endpoint to use
 461    * @return a SortedMap containing the matches or null if no matches
 462    */
 463  9 private SortedMap getLTSubmap(Object theHighEndpoint) {
 464  9 SortedMap aSubmap = itsValues.headMap(theHighEndpoint);
 465  9 return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
 466    }
 467   
 468    /**
 469    * Provides the submap containing the matches less than or equal to the specified key.
 470    *
 471    * @param theHighEndpoint high endpoint to use
 472    * @return a SortedMap containing the matches or null if no matches
 473    */
 474  3 private SortedMap getLEQSubmap(Object theHighEndpoint) {
 475    // force computation of next value as STRING if key is String
 476    // otherwise, next value will be of same type as stored values
 477  3 theHighEndpoint = theHighEndpoint instanceof String ? getNextValueOf(theHighEndpoint, STRING) : getNextValueOf(theHighEndpoint);
 478   
 479  3 if (theHighEndpoint == null) {
 480    // high endpoint already at max => result is all values
 481  0 return itsValues;
 482    }
 483   
 484    // less-than submap for next key of theHighEndpoint is LEQ result
 485  3 return getLTSubmap(theHighEndpoint);
 486    }
 487   
 488    /**
 489    * Provides the submap containing the matches greater than the specified key.
 490    *
 491    * @param theLowEndpoint high endpoint to use
 492    * @return a SortedMap containing the matches or null if no matches
 493    */
 494  6 private SortedMap getGTSubmap(Object theLowEndpoint) {
 495    // force computation of next value as STRING if key is String
 496    // otherwise, next value will be of same type as stored values
 497  6 theLowEndpoint = theLowEndpoint instanceof String ? getNextValueOf(theLowEndpoint, STRING) : getNextValueOf(theLowEndpoint);
 498   
 499  6 if (theLowEndpoint == null) {
 500    // low endpoint already at max => result is empty
 501  0 return null;
 502    }
 503   
 504    // greater than or equal to submap for next key of theLowEndpoint is GT result
 505  6 return getGEQSubmap(theLowEndpoint);
 506    }
 507   
 508    /**
 509    * Provides the submap containing the matches greater than or equal to the specified key
 510    *
 511    * @param theLowEndpoint high endpoint to use
 512    * @return a SortedMap containing the matches or null if no matches
 513    */
 514  9 private SortedMap getGEQSubmap(Object theLowEndpoint) {
 515  9 SortedMap aSubmap = itsValues.tailMap(theLowEndpoint);
 516  9 return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
 517    }
 518   
 519    /**
 520    * Collects IndexMatch objects for the locators in the specified Map.
 521    * If theExcludeFlag is true, all values in theFilterList will be
 522    * excluded from the result. If theExcludeFlag is false, only values
 523    * appearing in the theFilterList will be included in the result.
 524    *
 525    * @param aResult list to store the matching document keys
 526    * @param theMap Map containing Sets of ValueLocator objects as values
 527    * @param theFilterList list of values to filter result (exclude or include filtering)
 528    * @param theExcludeFlag filtering is exclude if true, include if false
 529    * @param queryPattern query index pattern
 530    */
 531  2 private void addMatches(LinkedList aResult, Map theMap, Object[] theFilterList, boolean theExcludeFlag, IndexPattern queryPattern) {
 532   
 533  2 if (theMap == null) {
 534  0 return;
 535    }
 536   
 537  2 LinkedList aResultList = new LinkedList();
 538   
 539  2 Iterator aValueIterator = theMap.entrySet().iterator();
 540   
 541    // iterate over the values adding locators for each matched to result
 542  2 while (aValueIterator.hasNext()) {
 543  6 Map.Entry anEntry = (Map.Entry) aValueIterator.next();
 544  6 Object aKey = anEntry.getKey();
 545   
 546  6 boolean aValueInFilterList = Arrays.binarySearch(theFilterList, aKey, MemValueIndexer.ValueComparator.INSTANCE) >= 0;
 547   
 548  6 if (theExcludeFlag ^ aValueInFilterList) {
 549    // key passes filter => add to return
 550  3 TreeSet aSet = (TreeSet) anEntry.getValue();
 551  3 aResultList.add(aSet);
 552    }
 553    }
 554   
 555    // add the list of locators to the result
 556  2 aValueIterator = aResultList.iterator();
 557  2 while (aValueIterator.hasNext()) {
 558  3 addMatches(aResult, (Collection) aValueIterator.next(), queryPattern);
 559    }
 560    }
 561   
 562    /**
 563    * flush forcefully flushes any unwritten buffers to disk.
 564    */
 565  128 public void flush() throws DBException {
 566    // nothing to flush to...
 567    }
 568   
 569  104 public IndexerEventHandler getIndexerEventHandler() {
 570  104 return handler;
 571    }
 572   
 573    // org.apache.xindice.util.Configurable facet
 574   
 575    /**
 576    * setConfig sets the configuration information for the Configurable
 577    * object instance.
 578    *
 579    * @param theConfig The configuration Node
 580    */
 581  24 public void setConfig(Configuration theConfig) {
 582  24 itsConfig = theConfig;
 583  24 try {
 584  24 itsName = theConfig.getAttribute(NAME);
 585  24 String itsPattern = theConfig.getAttribute(PATTERN);
 586  24 itsWildcard = itsPattern.indexOf('*') != -1;
 587   
 588    // Determine the Index Type
 589  24 String type = theConfig.getAttribute(TYPE, STRING_VAL).toLowerCase();
 590  24 if (type.equals(STRING_VAL)) {
 591  7 itsValueType = STRING;
 592  17 } else if (type.equals(TRIMMED_VAL)) {
 593  0 itsValueType = TRIMMED;
 594  17 } else if (type.equals(SHORT_VAL)) {
 595  0 itsValueType = SHORT;
 596  17 } else if (type.equals(INT_VAL)) {
 597  0 itsValueType = INTEGER;
 598  17 } else if (type.equals(LONG_VAL)) {
 599  13 itsValueType = LONG;
 600  4 } else if (type.equals(FLOAT_VAL)) {
 601  1 itsValueType = FLOAT;
 602  3 } else if (type.equals(DOUBLE_VAL)) {
 603  0 itsValueType = DOUBLE;
 604  3 } else if (type.equals(BYTE_VAL)) {
 605  1 itsValueType = BYTE;
 606  2 } else if (type.equals(CHAR_VAL)) {
 607  1 itsValueType = CHAR;
 608  1 } else if (type.equals(BOOLEAN_VAL)) {
 609  1 itsValueType = BOOLEAN;
 610    } else {
 611  0 if (itsPattern.indexOf('@') != -1) {
 612  0 log.warn("Unrecognized value type, defaulting to '" + STRING_VAL + "'");
 613  0 itsValueType = STRING;
 614    } else {
 615  0 log.warn("Unrecognized value type, defaulting to '" + TRIMMED_VAL + "'");
 616  0 itsValueType = TRIMMED;
 617    }
 618    }
 619   
 620  24 this.itsPattern = new IndexPattern(itsCollection.getSymbols(), itsPattern, null);
 621  24 setupHandler();
 622    } catch (Exception e) {
 623  0 log.warn("Exception while setting configuration", e);
 624    }
 625    }
 626   
 627  24 private void setupHandler() {
 628  24 handler = new BasicIndexerEventHandler() {
 629  103 public synchronized void onValueAdded(IndexPattern pattern, String value, Key key, int pos, int len,
 630    short elemID, short attrID) {
 631   
 632  103 Object aValue;
 633  103 if (itsValueType != STRING) {
 634    // convert from String to the primitive container object
 635    // type (e.g. Integer, Double, etc.) we store
 636  77 aValue = getTypedValue(value);
 637    } else {
 638  26 aValue = value;
 639    }
 640   
 641    // get the set of locations for the value being added
 642  103 TreeSet aLocatorSet = (TreeSet) itsValues.get(aValue);
 643   
 644    // if not found, create a new the set of locations for the value being added
 645  103 if (aLocatorSet == null) {
 646  89 aLocatorSet = new TreeSet();
 647   
 648    // add new set to the value-locators map
 649  89 itsValues.put(aValue, aLocatorSet);
 650    }
 651   
 652    // add a value locator to the set of locations for the value being added
 653  103 aLocatorSet.add(new ValueLocator(key, pos, len, elemID, attrID));
 654   
 655    // keep track of how many locators we manage
 656  103 ++itsValueLocatorCount;
 657    }
 658   
 659  1 public synchronized void onValueDeleted(IndexPattern pattern, String value, Key key, int pos, int len,
 660    short elemID, short attrID) {
 661  1 Object aValue;
 662   
 663  1 if (itsValueType != STRING) {
 664    // convert from String to the primitive container object
 665    // type (e.g. Integer, Double, etc.) we store
 666  0 aValue = getTypedValue(value);
 667    } else {
 668  1 aValue = value;
 669    }
 670   
 671    // get the set of locations for the value being added
 672  1 TreeSet aLocatorSet = (TreeSet) itsValues.get(aValue);
 673   
 674    // if found locator set, try to remove locator for value being removed
 675  1 if (aLocatorSet != null) {
 676  1 if (aLocatorSet.remove(new ValueLocator(key, pos, len, elemID, attrID))) {
 677    // removed a ValueLocator
 678    // set could now be empty
 679    // if so, remove empty set from value-locatorset map
 680  1 if (aLocatorSet.size() == 0) {
 681  1 itsValues.remove(value);
 682    }
 683   
 684    // keep track of how many locators we manage
 685  1 --itsValueLocatorCount;
 686    }
 687    }
 688    }
 689    };
 690    }
 691   
 692    /**
 693    * getConfig retrieves the configuration information for the
 694    * Configurable object instance.
 695    *
 696    * @return The configuration Node
 697    */
 698  0 public Configuration getConfig() {
 699  0 return itsConfig;
 700    }
 701   
 702    // org.apache.xindice.core.DBObject facet
 703    /**
 704    * Creates a new MemValueIndexer.
 705    *
 706    * @return Whether or not the DBObject was created
 707    */
 708  24 public boolean create() throws DBException {
 709  24 itsOpen = false;
 710    // create our ValueLocator set by value map
 711    // specifying our custom Comparator that handles
 712    // EmptyValue objects correctly
 713  24 itsValues = new TreeMap(ValueComparator.INSTANCE);
 714  24 return true;
 715    }
 716   
 717    /**
 718    * Opens the index so it can be used.
 719    * Since MemValueIndexer uses memory only as a backing store,
 720    * it must always be created before it can be opened. If a
 721    * call to open() has not been preceeded by a call to create(),
 722    * the open() will return false. Also, this object must be
 723    * configured via setConfig() before it can be opened.
 724    *
 725    * @return true if index was opened successfully, false otherwise
 726    */
 727  24 public synchronized boolean open() throws DBException {
 728  24 if (itsValues == null || itsConfig == null) {
 729  0 return false;
 730    }
 731  24 itsOpen = true;
 732  24 return true;
 733    }
 734   
 735    /**
 736    * Indicates whether or not this indexer is open.
 737    *
 738    * @return true if open, false otherwise
 739    */
 740  0 public synchronized boolean isOpened() {
 741  0 return itsValues != null && itsOpen;
 742    }
 743   
 744    /**
 745    * Indicates whether or not this index exists. Since this
 746    * is a memory-buffered index, it only exists after having
 747    * been created.
 748    *
 749    * @return Whether or not the physical resource exists
 750    */
 751  24 public synchronized boolean exists() throws DBException {
 752  24 return itsValues != null;
 753    }
 754   
 755    /**
 756    * Removes this index from existence.
 757    * The indexes owner(s) is/are responsible for removing any
 758    * references to the index in its own context.
 759    *
 760    * @return Whether or not the DBObject was dropped
 761    */
 762  24 public synchronized boolean drop() throws DBException {
 763  24 itsOpen = false;
 764  24 itsValues = null;
 765  24 return true;
 766    }
 767   
 768    /**
 769    * Closes this index. Does nothing more than
 770    * set the state returned to isOpened() to false.
 771    *
 772    * @return Whether or not the index was closed
 773    */
 774  0 public synchronized boolean close() throws DBException {
 775  0 itsOpen = false;
 776  0 return true;
 777    }
 778   
 779    // org.apache.xindice.util.Named facet
 780   
 781    /**
 782    * Provides the name of this index.
 783    *
 784    * @return index's name
 785    */
 786  48 public String getName() {
 787  48 return itsName;
 788    }
 789   
 790    /**
 791    * Provides an Object representing the specified value
 792    * converted from String to the value type supported by this inbdex.
 793    * If you modify the type of Object used to store the value internally
 794    * (the type of Object returned by this method for a given value of
 795    * itsValueType) you must also modify getNextValueOf method below to
 796    * correctly compute/return the proper type.
 797    *
 798    * @param theValue String from which to derive the value
 799    * @return an Object representing the value extracted from theValue
 800    */
 801  109 private Object getTypedValue(String theValue) {
 802  109 if (itsValueType != STRING && itsValueType != TRIMMED) {
 803  109 theValue = theValue.trim();
 804    } else {
 805  0 if (itsValueType == TRIMMED) {
 806  0 theValue = QueryEngine.normalizeString(theValue);
 807    }
 808    }
 809   
 810  109 if (theValue.length() == 0) {
 811  0 return EmptyValue.INSTANCE;
 812    }
 813   
 814  109 try {
 815  109 switch (itsValueType) {
 816  0 default :
 817  0 break;
 818  0 case STRING:
 819  0 case TRIMMED:
 820  0 return theValue;
 821    //break;
 822  0 case SHORT:
 823  0 return new Short(theValue);
 824    //break;
 825  0 case INTEGER:
 826  0 return new Integer(theValue);
 827    //break;
 828  90 case LONG:
 829  90 return new Long(theValue);
 830    //break;
 831  10 case FLOAT:
 832  10 return new Float(theValue);
 833    //break;
 834  0 case DOUBLE:
 835  0 return new Double(theValue);
 836    //break;
 837  3 case BYTE:
 838  3 return new Byte(theValue);
 839    //break;
 840  3 case CHAR:
 841  3 return new Character(theValue.charAt(0));
 842    //break;
 843  3 case BOOLEAN:
 844    // represented a a Byte of 0 (false) or 1 (true) so Comparable is implemented (true > false)
 845  3 return "[true][yes][1][y][on]".indexOf("[" + theValue.toLowerCase() + "]") == -1 ? new Byte((byte) 0) : new Byte((byte) 1);
 846    //break;
 847    }
 848    } catch (Exception anException) {
 849  0 if (log.isDebugEnabled()) {
 850  0 log.debug("Exception while converting value \"" + theValue + "\" from String to " + itsValueTypeName, anException);
 851    }
 852    }
 853  0 return "";
 854    }
 855   
 856    /**
 857    * Provides an Object representing the next sort order value of the specified value.
 858    * Dependent upon the types returned by getTypedValue().
 859    *
 860    * @param theValue Object to return next value of
 861    * @return an Object representing the next value above specified value or null if theValue is already at maximum for type
 862    */
 863  14 private Object getNextValueOf(Object theValue) {
 864  14 return getNextValueOf(theValue, itsValueType);
 865    }
 866   
 867    /**
 868    * Provides an Object representing the next sort order value of the specified value.
 869    * Dependent upon the types returned by getTypedValue(). Types returned by this
 870    * method must be the same types returned by getTypedValue method.
 871    *
 872    * @param theValue Object to return next value of
 873    * @param theType type of Object to return
 874    * @return an Object representing the next value above specified value or null if theValue is already at maximum for type
 875    */
 876  18 private Object getNextValueOf(Object theValue, int theType) {
 877  18 if (theValue instanceof EmptyValue) {
 878  0 return "\0";
 879    }
 880   
 881  18 Object aReturn = null;
 882   
 883  18 switch (theType) {
 884  0 default :
 885  0 break;
 886  4 case STRING:
 887  0 case TRIMMED:
 888    // provide a value appropriate for use in normal searches as well as in starts-with searches
 889  4 int aLength = ((String) theValue).length();
 890  4 if (aLength == 0) {
 891    // make the value 1 character longer using the lowest possible character value
 892  0 return theValue + "\0";
 893    }
 894  4 char aLastChar = ((String) theValue).charAt(aLength - 1);
 895  4 if (aLastChar == Character.MAX_VALUE) {
 896    // make the value 1 character longer using the lowest possible character value
 897  0 return theValue + "\0";
 898    }
 899    // return a string of the same length with the final character incremented by 1
 900    // be sure to avoid upcasting to int which would happen if you do "a" + (aLastChar + 1)
 901  4 aLastChar += 1;
 902  4 aReturn = ((String) theValue).substring(0, aLength - 1) + aLastChar;
 903  4 break;
 904  0 case SHORT:
 905    {
 906  0 short aValue = ((Short) theValue).shortValue();
 907  0 aReturn = aValue == Short.MAX_VALUE ? null : new Short((short) (aValue + 1));
 908    }
 909  0 break;
 910  0 case INTEGER:
 911    {
 912  0 int aValue = ((Integer) theValue).intValue();
 913  0 aReturn = aValue == Integer.MAX_VALUE ? null : new Integer(aValue + 1);
 914    }
 915  0 break;
 916  13 case LONG:
 917    {
 918  13 long aValue = ((Long) theValue).longValue();
 919  13 aReturn = aValue == Long.MAX_VALUE ? null : new Long(aValue + 1);
 920    }
 921  13 break;
 922  0 case FLOAT:
 923    {
 924  0 float aValue = ((Float) theValue).floatValue();
 925    // note that Float.MIN_VALUE returns the smallest possible POSITIVE value of type float
 926  0 aReturn = aValue == Float.MAX_VALUE ? null : new Float(aValue + Float.MIN_VALUE);
 927    }
 928  0 break;
 929  0 case DOUBLE:
 930    {
 931  0 double aValue = ((Double) theValue).doubleValue();
 932    // note that Double.MIN_VALUE returns the smallest possible POSITIVE value of type double
 933  0 aReturn = aValue == Double.MAX_VALUE ? null : new Double(aValue + Double.MIN_VALUE);
 934    }
 935  0 break;
 936  0 case CHAR:
 937    {
 938    // Character.compareTo(Character) uses math on char, so we do too...
 939  0 char aValue = ((Character) theValue).charValue();
 940  0 aReturn = aValue == Character.MAX_VALUE ? null : new Character((char) (aValue + 1));
 941    }
 942  0 break;
 943  0 case BOOLEAN:
 944    // BOOLEAN type stored internally as (byte)0 or (byte)1
 945  1 case BYTE:
 946    {
 947  1 byte aValue = ((Byte) theValue).byteValue();
 948  1 aReturn = aValue == Byte.MAX_VALUE ? null : new Byte((byte) (aValue + 1));
 949    }
 950  1 break;
 951    }
 952   
 953  18 return aReturn;
 954    }
 955   
 956    /**
 957    * Adds the ValueLocators from the all the sets
 958    * of value locators found in the specified map
 959    * as IndexMatches to the specified list of IndexMatches.
 960    *
 961    * @param theList list to add matches to
 962    * @param theMap map containing sets of ValueLocator objects to add as IndexMatches
 963    * @param queryPattern query index pattern
 964    */
 965  25 private void addMatches(LinkedList theList, Map theMap, IndexPattern queryPattern) {
 966  25 if (theMap == null) {
 967  2 return;
 968    }
 969   
 970  23 Iterator aValueIterator = theMap.entrySet().iterator();
 971    // iterate over the values adding locators for each to result
 972    // no need to filter while iterating
 973  23 while (aValueIterator.hasNext()) {
 974    // iterate over locators for current value adding each to result
 975  42 addMatches(theList, (TreeSet) ((Map.Entry) aValueIterator.next()).getValue(), queryPattern);
 976    }
 977    }
 978   
 979    /**
 980    * Adds the ValueLocators from the specified set
 981    * as IndexMatches to the specified list of IndexMatches.
 982    *
 983    * @param theList list to add matches to
 984    * @param theSet Set conatining ValueLocator objects to add as IndexMatches
 985    * @param queryPattern query index pattern
 986    */
 987  59 private void addMatches(LinkedList theList, Collection theSet, IndexPattern queryPattern) {
 988  59 if (theSet == null) {
 989  0 return;
 990    }
 991   
 992  59 for (Iterator aLocatorIterator = theSet.iterator(); aLocatorIterator.hasNext(); ) {
 993  70 ValueLocator aLocator = (ValueLocator) aLocatorIterator.next();
 994  70 if (itsWildcard) {
 995  2 IndexPattern matchElement = new IndexPattern(itsCollection.getSymbols(), aLocator.getElementID(), aLocator.getAttributeID());
 996  2 IndexPattern queryElement = new IndexPattern(itsCollection.getSymbols(), queryPattern.getElementID(),
 997    queryPattern.getAttributeID());
 998  2 if (matchElement.getMatchLevel(queryElement) > 0) {
 999  1 theList.add(new IndexMatch(aLocator.getKey(), aLocator.getElementID(), aLocator.getAttributeID()));
 1000    }
 1001    } else {
 1002  68 theList.add(new IndexMatch(aLocator.getKey(), aLocator.getElementID(), aLocator.getAttributeID()));
 1003    }
 1004    }
 1005    }
 1006   
 1007    /**
 1008    * Implements an Object representing any empty value.
 1009    * For comparisons against any other value type,
 1010    * convert an EmptyValue and the other operand to String
 1011    * and compare as String values. Any EmptyValue converted
 1012    * to String is an empty String.
 1013    */
 1014    private static class EmptyValue implements Comparable {
 1015   
 1016    /**
 1017    * Creates a new object.
 1018    */
 1019  0 EmptyValue() {
 1020    }
 1021   
 1022    /**
 1023    * Provides a negative integer, 0, or a positive integer
 1024    * indicating whether this object is less than (negative return),
 1025    * equal to (0 return), or greater than (positive return) theCompareTo
 1026    * Object.
 1027    *
 1028    * @param theCompareTo Object to compare this object to
 1029    * @return an integer indicating less than (negative), equal to (0), or greater than (positive)
 1030    */
 1031  0 public int compareTo(Object theCompareTo) {
 1032  0 if (theCompareTo instanceof EmptyValue) {
 1033  0 return 0;
 1034    }
 1035  0 return "".compareTo(theCompareTo.toString());
 1036    }
 1037   
 1038    /**
 1039    * Indicates whether or not this object is equal to theCompareTo Object.
 1040    *
 1041    * @param theCompareTo Object to compare this object to
 1042    * @return true if this object is equal to theCompareTo Object, false otherwise
 1043    */
 1044  0 public boolean equals(Object theCompareTo) {
 1045    //noinspection SimplifiableIfStatement
 1046  0 if (theCompareTo instanceof EmptyValue) {
 1047  0 return true;
 1048    }
 1049   
 1050  0 return theCompareTo.toString().length() == 0;
 1051    }
 1052   
 1053    /**
 1054    * Provides a hash code value for this object.
 1055    *
 1056    * @return a hash code for this object
 1057    */
 1058  0 public int hashCode() {
 1059  0 return 1;
 1060    }
 1061   
 1062    /**
 1063    * Provides a String representing this object.
 1064    *
 1065    * @return an empty String
 1066    */
 1067  0 public String toString() {
 1068  0 return EMPTY_STRING;
 1069    }
 1070   
 1071    /**
 1072    * Provides a shareable static instance of this class.
 1073    *
 1074    * @return a shareable static instance of this class
 1075    */
 1076  0 public EmptyValue getInstance() {
 1077  0 return INSTANCE;
 1078    }
 1079   
 1080    /**
 1081    * an empty String
 1082    */
 1083    private static final String EMPTY_STRING = "";
 1084   
 1085    /**
 1086    * an instance of EmptyValue
 1087    */
 1088    public final static EmptyValue INSTANCE = new EmptyValue();
 1089    }
 1090   
 1091    /**
 1092    * Implements a comparator used for comparing the
 1093    * values we store in our value map. Handles comparisons
 1094    * involving EmptyValue objects correctly.
 1095    */
 1096    private static class ValueComparator implements Comparator {
 1097    /**
 1098    * Compares its two arguments for order. Returns a negative integer,
 1099    * zero, or a positive integer as the first argument is less than, equal
 1100    * to, or greater than the second.
 1101    *
 1102    * @param theObject1 first object to compare
 1103    * @param theObject2 second object to compare
 1104    * @return negative integer, 0, or positive integer indicating ordering of theObject1 and theObject2
 1105    */
 1106  423 public int compare(Object theObject1, Object theObject2) {
 1107  423 if (theObject1 instanceof Comparable && theObject1.getClass() == theObject2.getClass()) {
 1108  423 return ((Comparable) theObject1).compareTo(theObject2);
 1109    }
 1110  0 return theObject1.toString().compareTo(theObject2.toString());
 1111    }
 1112   
 1113    /**
 1114    * Indicates whether or not theCompareTo is an
 1115    * instance of ValueComparator and thus imposes the
 1116    * same ordering as this Object.
 1117    *
 1118    * @return true if theCompareTo is an instance of ValueComparator
 1119    */
 1120  0 public boolean equals(Object theCompareTo) {
 1121  0 return theCompareTo instanceof ValueComparator;
 1122    }
 1123   
 1124    /**
 1125    * Provides a shareable static instance of this class.
 1126    *
 1127    * @return a shareable static instance of this class
 1128    */
 1129  0 public ValueComparator getInstance() {
 1130  0 return INSTANCE;
 1131    }
 1132   
 1133    /**
 1134    * a shareable static instance of this class
 1135    */
 1136    public static final ValueComparator INSTANCE = new ValueComparator();
 1137    }
 1138   
 1139    /**
 1140    * Implements a container for locating instances of indexed
 1141    * values within Collection and individual Documents within
 1142    * the Collection.
 1143    */
 1144    private class ValueLocator implements Comparable {
 1145   
 1146    /**
 1147    * the key of the Document containing the value
 1148    */
 1149    private Key itsKey;
 1150   
 1151    /**
 1152    * the value's position in the document stream
 1153    */
 1154    private final int itsPosition;
 1155   
 1156    /**
 1157    * the value's length
 1158    */
 1159    private final int itsLength;
 1160   
 1161    /**
 1162    * the ID of the Element containing the value
 1163    */
 1164    private final short itsElementID;
 1165   
 1166    /**
 1167    * the ID of the Attribute containing the value
 1168    */
 1169    private final short itsAttributeID;
 1170   
 1171    /**
 1172    * Creates a new object.
 1173    *
 1174    * @param theKey Key of the value (only the data member is used)
 1175    * @param thePosition the value's position in the document stream
 1176    * @param theLength length of the value in the document stream
 1177    * @param theElementID the ID of the Element containing the value
 1178    * @param theAttributeID the ID of the Attribute containing the value
 1179    *
 1180    * NOTE: I do not know if there are use cases where theKey.length != theLength
 1181    * If there are none, then length could be carried as itsKey.length
 1182    * instead of as a separate member.
 1183    */
 1184  104 public ValueLocator(Key theKey, int thePosition, int theLength, short theElementID, short theAttributeID) {
 1185  104 itsKey = theKey;
 1186  104 itsPosition = thePosition;
 1187  104 itsLength = theLength;
 1188  104 itsElementID = theElementID;
 1189  104 itsAttributeID = theAttributeID;
 1190    }
 1191   
 1192    /**
 1193    * Provides the key of the Document containing the value.
 1194    *
 1195    * @return the key of the Document containing the value
 1196    */
 1197  69 public Key getKey() {
 1198  69 return itsKey;
 1199    }
 1200   
 1201    /**
 1202    * Provides the value's position in the document stream.
 1203    *
 1204    * @return the position
 1205    */
 1206  0 public final int getPosition() {
 1207  0 return itsPosition;
 1208    }
 1209   
 1210    /**
 1211    * Provides the value's length.
 1212    *
 1213    * @return the value's length
 1214    */
 1215  0 public final int getLength() {
 1216  0 return itsLength;
 1217    }
 1218   
 1219    /**
 1220    * Provides the ID of the Element containing the value.
 1221    *
 1222    * @return the ID of the Element containing the value
 1223    */
 1224  71 public final short getElementID() {
 1225  71 return itsElementID;
 1226    }
 1227   
 1228    /**
 1229    * Provides the ID of the Attribute containing the value.
 1230    *
 1231    * @return the ID of the Attribute containing the value
 1232    */
 1233  71 public final short getAttributeID() {
 1234  71 return itsAttributeID;
 1235    }
 1236   
 1237    /** Compares this object with the specified object for order. Returns a
 1238    * negative integer, zero, or a positive integer as this object is less
 1239    * than, equal to, or greater than the specified object.<p>
 1240    *
 1241    * The implementor must ensure <tt>sgn(x.compareTo(y)) ==
 1242    * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
 1243    * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
 1244    * <tt>y.compareTo(x)</tt> throws an exception.)<p>
 1245    *
 1246    * The implementor must also ensure that the relation is transitive:
 1247    * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
 1248    * <tt>x.compareTo(z)&gt;0</tt>.<p>
 1249    *
 1250    * Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
 1251    * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
 1252    * all <tt>z</tt>.<p>
 1253    *
 1254    * It is strongly recommended, but <i>not</i> strictly required that
 1255    * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
 1256    * class that implements the <tt>Comparable</tt> interface and violates
 1257    * this condition should clearly indicate this fact. The recommended
 1258    * language is "Note: this class has a natural ordering that is
 1259    * inconsistent with equals."
 1260    *
 1261    * @param theObject the Object to be compared.
 1262    * @return a negative integer, zero, or a positive integer as this object
 1263    * is less than, equal to, or greater than the specified object.
 1264    *
 1265    * @throws ClassCastException if the specified object's type prevents it
 1266    * from being compared to this Object.
 1267    *
 1268    */
 1269  16 public final int compareTo(Object theObject) {
 1270  16 if (!(theObject instanceof ValueLocator)) {
 1271  0 throw new ClassCastException("Can't compare object of type " + theObject.getClass().getName() + " to ValueLocator");
 1272    }
 1273   
 1274  16 ValueLocator aCompareTo = (ValueLocator) theObject;
 1275   
 1276    // compare keys
 1277  16 int result = itsKey.compareTo(aCompareTo.itsKey);
 1278  16 if (result != 0) {
 1279  15 return result;
 1280    }
 1281   
 1282    // compare position
 1283  1 if (itsPosition != aCompareTo.itsPosition) {
 1284  0 return itsPosition > aCompareTo.itsPosition ? 1 : -1;
 1285    }
 1286   
 1287    // compare length
 1288  1 if (itsLength != aCompareTo.itsLength) {
 1289  0 return itsLength > aCompareTo.itsLength ? 1 : -1;
 1290    }
 1291   
 1292    // compare element ID
 1293  1 if (itsElementID != aCompareTo.itsElementID) {
 1294  0 return itsElementID > aCompareTo.itsElementID ? 1 : -1;
 1295    }
 1296   
 1297    // compare attribute ID
 1298  1 if (itsAttributeID != aCompareTo.itsAttributeID) {
 1299  0 return itsAttributeID > aCompareTo.itsAttributeID ? 1 : -1;
 1300    }
 1301   
 1302    // equal
 1303  1 return 0;
 1304    }
 1305    }
 1306    }