/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: MemValueIndexer.java 571949 2007-09-02 10:52:38Z vgritsenko $
*/
package org.apache.xindice.core.indexer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.FaultCodes;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.query.QueryEngine;
import org.apache.xindice.util.Configuration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Provides an in-memory implementation of Xindice Indexer
* based upon the Java TreeSet class. This implementation
* is not persistent. A MemValueIndexer is useful for indexing
* of temporary Collections (e.g. those based on MemFiler). It is also
* useful for temporary indexing of persistent Collections.
*
* @author Terry Rosenbaum (terry@amicas.com)
* @version $Revision: 571949 $, $Date: 2007-09-02 06:52:38 -0400 (Sun, 02 Sep 2007) $
*/
public class MemValueIndexer implements Indexer {
/**
* Log used by this indexer
*/
private static final Log log = LogFactory.getLog(MemValueIndexer.class);
//
// Class constants
//
private static final int STRING = 0;
private static final int TRIMMED = 1;
private static final int SHORT = 2;
private static final int INTEGER = 3;
private static final int LONG = 4;
private static final int FLOAT = 5;
private static final int DOUBLE = 6;
private static final int BYTE = 7;
private static final int CHAR = 8;
private static final int BOOLEAN = 9;
private static final String NAME = "name";
private static final String PATTERN = "pattern";
private static final String TYPE = "type";
// private static final String PAGESIZE = "pagesize";
// private static final String MAXKEYSIZE = "maxkeysize";
private static final String STRING_VAL = "string";
private static final String TRIMMED_VAL = "trimmed";
private static final String SHORT_VAL = "short";
private static final String INT_VAL = "int";
private static final String LONG_VAL = "long";
private static final String FLOAT_VAL = "float";
private static final String DOUBLE_VAL = "double";
private static final String BYTE_VAL = "byte";
private static final String CHAR_VAL = "char";
private static final String BOOLEAN_VAL = "boolean";
private final static IndexMatch[] EMPTY_INDEX_MATCH_ARRAY = new IndexMatch[0];
//
// Instance variables
//
/**
* TODO: Unused: the indexed Collection
private org.apache.xindice.core.Collection itsCollection;
*/
/**
* this object's configuration
*/
private Configuration itsConfig;
/**
* name of this index
*/
private String itsName;
/**
* pattern for this index
*/
private IndexPattern itsPattern;
/**
* TODO: Unused: indicates if wildcard index
private boolean itsWildcard = false;
*/
/**
* value type of this index
*/
private int itsValueType = STRING;
/**
* value type name of this index
*/
private String itsValueTypeName = STRING_VAL;
/**
* storage for map of values to tree Key objects locating value matches in Collection
*/
private TreeMap itsValues = null;
/**
* count of ValueLocator objects we currently manage
*/
private int itsValueLocatorCount = 0;
/**
* tracks the open/closed state for open(), close(), and isOpened()
*/
private boolean itsOpen = false;
private org.apache.xindice.core.Collection itsCollection;
private IndexerEventHandler handler;
/**
* Sets the parent Collection of this indexer.
*
* @param theCollection The owner Collection
*/
public void setCollection(org.apache.xindice.core.Collection theCollection) {
itsCollection = theCollection;
}
/**
* Provides the index style. Different query languages
* will need to draw from different indexing styles. For example, A
* query that is written in quilt will require XPath indexing.
*
* @return the index style
*/
public String getIndexStyle() {
return STYLE_NODEVALUE;
}
public void addDocument(Key key) throws DBException {
}
public void removeDocument(Key key) throws DBException {
}
/**
* Provides the pattern recognized by this Indexer. Patterns
* must be in the form of (elem|*)[@(attr|*)] to tell the IndexManager
* which element types to send to it, so for example:
* <pre>
* contact@name Indexes all contacts by name attribute
* memo Indexes the text of all memo elements
* contact@* Indexes all contact attributes
* *@name Indexes the name attribute for all elements
* * Indexes the text of all elements
* *@* Indexes all attributes of all elements
* </pre>
* These patterns are used by the IndexManager when handling SAX events.
* All events that match the specified pattern will result in an add or
* remove call to the Indexer.
*
* @return the pattern used by this index
*/
public IndexPattern[] getPatterns() {
return new IndexPattern[] { itsPattern };
}
/**
* Provides a set of MatchEntry instances that match
* the specified query. The matches are then used by the QueryEngine
* in co-sequential processing. If this indexer doesn't support the
* passed value, it should return 'null'. If no matches are found,
* it should return an empty set. queryMatches will typically be
* used in XPath processing.
*
* @param theQuery The IndexQuery to use
* @return The resulting matches
*/
public synchronized IndexMatch[] queryMatches(IndexQuery theQuery) throws DBException {
IndexMatch[] aResult = null;
// get the list of Value objects from the query
// there may be 1 value (unary operation e.g. not equals),
// 2 values (binary operation e.g. range matching),
// or a set of N values (set operation e.g. IN)
Value[] aQueryValueList = theQuery.getValues();
Object[] aMatchValueArray = new Object[aQueryValueList.length];
// convert the query values to the type of Object we use to represent
// those values in our value map (e.g. String, Integer, Byte, Double, etc.)
if (itsValueType != STRING) {
for (int anIndex = 0; anIndex < aQueryValueList.length; ++anIndex) {
aMatchValueArray[anIndex] = getTypedValue(aQueryValueList[anIndex].toString());
}
} else {
for (int anIndex = 0; anIndex < aQueryValueList.length; ++anIndex) {
aMatchValueArray[anIndex] = aQueryValueList[anIndex].toString();
}
}
TreeSet aLocatorSet;
Object aLowEndpoint = aMatchValueArray[0];
Object aHighEndpoint = aMatchValueArray[aMatchValueArray.length - 1];
Iterator aValueIterator;
int anOperator = theQuery.getOperator();
// perform the requested matching type
switch (anOperator) {
default: // unsupported operator
throw new DBException(FaultCodes.IDX_NOT_SUPPORTED,
"Unimplemented index query operation code (code = " + anOperator + ")");
case IndexQuery.EQ: // exact match
aLocatorSet = (TreeSet) itsValues.get(aLowEndpoint);
if (aLocatorSet != null) {
aResult = new IndexMatch[aLocatorSet.size()];
addMatches(aResult, 0, aLocatorSet);
}
break;
case IndexQuery.NEQ: // all except exact match
// see whether or not we even have the value to be excluded
TreeSet anExcludedLocatorSet = (TreeSet) itsValues.get(aLowEndpoint);
aValueIterator = itsValues.entrySet().iterator();
int aResultIndex = 0;
if (anExcludedLocatorSet == null) {
// allocate return array to include all locators since none are excluded
aResult = new IndexMatch[itsValueLocatorCount];
// iterate over the values adding locators for each to result
// no need to filter while iterating
while (aValueIterator.hasNext()) {
// iterate over locators for current value adding each to result
Iterator aLocatorIterator = ((TreeSet) ((Map.Entry) aValueIterator.next()).getValue()).iterator();
for (; aLocatorIterator.hasNext(); ++aResultIndex) {
ValueLocator aLocator = (ValueLocator) aLocatorIterator.next();
aResult[aResultIndex] = new IndexMatch(
aLocator.getKey(), aLocator.getPosition(), aLocator.getLength(), aLocator.getElementID(), aLocator.getAttributeID());
}
}
} else {
// allocate return array to include all locators except those attached to excluded value
aResult = new IndexMatch[itsValueLocatorCount - anExcludedLocatorSet.size()];
// iterate over the values adding locators for each to result
// must filter out the locator set for the excluded value while iterating values
while (aValueIterator.hasNext()) {
aLocatorSet = (TreeSet) ((Map.Entry) aValueIterator.next()).getValue();
// apply the exclusion filter for the matched value
if (aLocatorSet != anExcludedLocatorSet) {
// iterate over locators for current value adding each to result
Iterator aLocatorIterator = ((TreeSet) ((Map.Entry) aValueIterator.next()).getValue()).iterator();
for (; aLocatorIterator.hasNext(); ++aResultIndex) {
ValueLocator aLocator = (ValueLocator) aLocatorIterator.next();
aResult[aResultIndex] = new IndexMatch(
aLocator.getKey(), aLocator.getPosition(), aLocator.getLength(), aLocator.getElementID(), aLocator.getAttributeID());
}
}
}
}
break;
case IndexQuery.BWX: // Between (Exclusive)
// notes on BWX and BW:
// TreeMap always returns a half-open range for the subMap operation
// (includes the low endpoint but excludes the high endpoint)
// so we must adjust the endpoints to achieve either a closed range (Inclusive)
// or an open range (Exclusive)
// if either endpoint is already the minimum or maximum for the data type
// (e.g. Byte data and low endpoint == 0) then we must retrieve a
// tail map as appropriate so that we don't need to use the
// next value above/below the endpoint that is already maximum/minimum possible
// assuming that the next/previous values for both endpoints can be computed,
// we use the subMap operation with the following endpoints:
// Operation select type Range for select operation
// ---------------------------------------------------------------------------------
// BWX subMap nextValueOf(low_endpoint) - high_endpoint
// BW subMap low_endpoint - nextValueOf(high_endpoint)
//
// if the low endpoint is already at maximum possible:
// BWX none return empty result
// BW none return result set containing low endpoint matches
//
// if the high endpoint is already at maximum possible:
// BWX subMap nextValueOf(low_endpoint) - high_endpoint
// BW tailMap low_endpoint - end
// if low_endpoint >= high_endpoint, result is empty
aLowEndpoint = getNextValueOf(aLowEndpoint);
if (aLowEndpoint == null || ((Comparable) aLowEndpoint).compareTo(aHighEndpoint) >= 0) {
// empty result
aResult = new IndexMatch[0];
} else {
// return locators in sub map exclusive of endpoints
aResult = getIndexMatchArray(itsValues.subMap(aLowEndpoint, aHighEndpoint));
}
break;
case IndexQuery.BW: // Between (Inclusive)
aResult = getIndexMatchArray(getBWSubmap(aLowEndpoint, aHighEndpoint));
break;
case IndexQuery.SW: // starts-with
// handled simlar to BW case, except that we
// always compare against the String form of the comparator
// to ensure that comparisons happen as Strings as specified by XPath
// for starts-with
if(! (aLowEndpoint instanceof String) )
{
aLowEndpoint = aLowEndpoint.toString();
}
// get the matching submap forcing String comparisons to be used regardless of stored type
aResult = getIndexMatchArray(getSWSubmap((String)aLowEndpoint));
break;
case IndexQuery.IN: // In the (presumed sorted) set of specified query values
// this is handled as a BW query followed by include filtering
// of only those matches in the range that are contained
// in the set of query values (IN)
// the match high endpoint is at the top of the array (already set)
// get the matching submap forcing String comparisons to be used regardless of stored type
aResult = getIndexMatchArray(getBWSubmap(aLowEndpoint, aLowEndpoint), aMatchValueArray, false); // false => include style filtering applied
break;
case IndexQuery.NBWX: // Not between (Exclusive)
// implement as LT or GT
aResult = getIndexMatchArray(getLTSubmap(aLowEndpoint), getGTSubmap(aHighEndpoint));
break;
case IndexQuery.NBW: // Not between (Inclusive)
// implement as LEQ or GEQ
aResult = getIndexMatchArray(getLEQSubmap(aLowEndpoint), getGEQSubmap(aHighEndpoint));
break;
case IndexQuery.NSW: // Not starts-with
// implement as LT or GT forcing String comparisons
// we get the raw String match value and use that instead of the typed value
// to force String comparisons (as required for starts-with)
if(! (aLowEndpoint instanceof String) )
{
aLowEndpoint = aLowEndpoint.toString();
}
// get all matches below starts-with range and above starts-with range
// use of String key forces String matching
aResult = getIndexMatchArray(getLTSubmap(aLowEndpoint), getGTSubmap(aLowEndpoint));
break;
case IndexQuery.LT: // Less than
aResult = getIndexMatchArray(getLTSubmap(aLowEndpoint));
break;
case IndexQuery.LEQ: // Less than or equal
aResult = getIndexMatchArray(getLEQSubmap(aLowEndpoint));
break;
case IndexQuery.GT: // Greater than
aResult = getIndexMatchArray(getGTSubmap(aLowEndpoint));
break;
case IndexQuery.GEQ: // Greater than or equal
aResult = getIndexMatchArray(getGEQSubmap(aLowEndpoint));
break;
case IndexQuery.NIN: // Not in specified set of query values
// scan all values excluding those specified in match value set
// includes all those entries LT the low endpoint, GT the high endpoint
// and the exclude-filtered set BW of the low and high endpoints
aResult = getIndexMatchArray(getLTSubmap(aLowEndpoint), getBWSubmap(aLowEndpoint, aLowEndpoint), // exclude-filter this
getGTSubmap(aHighEndpoint), aMatchValueArray, true); // true => exclude-style filtering applied
break;
case IndexQuery.ANY: // return all values
aResult = getIndexMatchArray(itsValues);
break;
}
return aResult == null ? EMPTY_INDEX_MATCH_ARRAY : aResult;
}
/**
* Provides the submap containing the half-open range (inclusive of Low Endpoint but not High Endpoint)
* between the theLowEndpoint and getNextValueOf(theLowEndpoint, STRING).
*
* @param theLowEndpoint low endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getSWSubmap(String theLowEndpoint) {
SortedMap aSubmap;
// force computation of next value as STRING if key is String
// otherwise, next value will be of same type as stored values
String aHighEndpoint = (String) getNextValueOf(theLowEndpoint, STRING);
if (aHighEndpoint == null) {
// return locators in tail map from low endpoint
aSubmap = itsValues.tailMap(theLowEndpoint);
} else {
// return locators in sub map inclusive of endpoints
aSubmap = itsValues.subMap(theLowEndpoint, aHighEndpoint);
}
return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
}
/**
* Provides the submap containing the closed range (inclusive of both endpoints)
* between the specified endpoints.
*
* @param theLowEndpoint low endpoint to use
* @param theHighEndpoint high endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getBWSubmap(Object theLowEndpoint, Object theHighEndpoint) {
SortedMap aSubmap = null;
TreeSet aLocatorSet;
// anOperator == IndexQuery.BW
int aComparison = ((Comparable) theLowEndpoint).compareTo(theHighEndpoint);
if (aComparison == 0) {
// low endpoint == high endpoint
// return result set containing just the low endpoint matches, if any
aLocatorSet = (TreeSet) itsValues.get(theLowEndpoint);
if (aLocatorSet != null) {
aSubmap = new TreeMap(MemValueIndexer.ValueComparator.INSTANCE);
aSubmap.put(theLowEndpoint, aLocatorSet);
}
} else {
if (aComparison < 0) {
// force computation of next value as STRING if key is String
// otherwise, next value will be of same type as stored values
theHighEndpoint = theHighEndpoint instanceof String ? getNextValueOf(theHighEndpoint, STRING) : getNextValueOf(theHighEndpoint);
if (theHighEndpoint == null) {
// return locators in tail map from low endpoint
aSubmap = itsValues.tailMap(theLowEndpoint);
} else {
// return locators in sub map inclusive of endpoints
aSubmap = itsValues.subMap(theLowEndpoint, theHighEndpoint);
}
}
// else low > high => empty result
}
return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
}
/**
* Provides the submap containing the matches less than the specified key
*
* @param theHighEndpoint high endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getLTSubmap(Object theHighEndpoint) {
SortedMap aSubmap = itsValues.headMap(theHighEndpoint);
return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
}
/**
* Provides the submap containing the matches less than or equal to the specified key.
*
* @param theHighEndpoint high endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getLEQSubmap(Object theHighEndpoint) {
// force computation of next value as STRING if key is String
// otherwise, next value will be of same type as stored values
theHighEndpoint = theHighEndpoint instanceof String ? getNextValueOf(theHighEndpoint, STRING) : getNextValueOf(theHighEndpoint);
if (theHighEndpoint == null) {
// high endpoint already at max => result is all values
return itsValues;
}
// less-than submap for next key of theHighEndpoint is LEQ result
return getLTSubmap(theHighEndpoint);
}
/**
* Provides the submap containing the matches greater than the specified key.
*
* @param theLowEndpoint high endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getGTSubmap(Object theLowEndpoint) {
// force computation of next value as STRING if key is String
// otherwise, next value will be of same type as stored values
theLowEndpoint = theLowEndpoint instanceof String ? getNextValueOf(theLowEndpoint, STRING) : getNextValueOf(theLowEndpoint);
if (theLowEndpoint == null) {
// low endpoint already at max => result is empty
return null;
}
// greater than or equal to submap for next key of theLowEndpoint is GT result
return getGEQSubmap(theLowEndpoint);
}
/**
* Provides the submap containing the matches greater than or equal to the specified key
*
* @param theLowEndpoint high endpoint to use
* @return a SortedMap containing the matches or null if no matches
*/
private SortedMap getGEQSubmap(Object theLowEndpoint) {
SortedMap aSubmap = itsValues.tailMap(theLowEndpoint);
return aSubmap == null || aSubmap.size() == 0 ? null : aSubmap;
}
/**
* Provides an IndexMatch array containing IndexMatch objects
* for the locators in the specified Map.
*
* @param theMap Map containing Sets of ValueLocator objects as values
* @return a new array of IndexMatch objects
*/
private IndexMatch[] getIndexMatchArray(Map theMap) {
IndexMatch[] aResult;
// count results to size result array
if (theMap != null) {
aResult = new IndexMatch[countLocators(theMap)];
// add the matches to the result array
addMatches(aResult, 0, theMap);
} else {
aResult = new IndexMatch[0];
}
return aResult;
}
/**
* Provides an IndexMatch array containing IndexMatch objects
* for the locators in the specified Maps. Results from theFirstMap
* will appear first in the array. Ordering is dependent on ordering
* of the map's iterators.
*
* @param theFirstMap Map containing Sets of ValueLocator objects as values
* @param theSecondMap Map containing Sets of ValueLocator objects as values
* @return a new array of IndexMatch objects
*/
private IndexMatch[] getIndexMatchArray(Map theFirstMap, Map theSecondMap) {
int aLocatorCount = 0;
// count results to size result array
aLocatorCount += countLocators(theFirstMap);
aLocatorCount += countLocators(theSecondMap);
IndexMatch[] aResult = new IndexMatch[aLocatorCount];
aLocatorCount = 0;
aLocatorCount = addMatches(aResult, aLocatorCount, theFirstMap);
addMatches(aResult, aLocatorCount, theSecondMap);
return aResult;
}
/**
* Provides an IndexMatch array containing IndexMatch objects
* for the locators in the specified Map. If theExcludeFlag is
* true, all values in theFilterList will be excluded from the
* result. If theExcludeFlag is false, only values appearing in
* the theFilterList will be included in the result.
*
* @param theMap Map containing Sets of ValueLocator objects as values
* @param theFilterList list of values to filter result (exclude or include filtering)
* @param theExcludeFlag filtering is exclude if true, include if false
* @return a new array of IndexMatch objects
*/
private IndexMatch[] getIndexMatchArray(Map theMap, Object[] theFilterList, boolean theExcludeFlag) {
if (theMap == null) {
return new IndexMatch[0];
}
LinkedList aResultList = new LinkedList();
Iterator aValueIterator;
int aLocatorCount = 0;
aValueIterator = theMap.entrySet().iterator();
// iterate over the values adding locators for each matched to result
while (aValueIterator.hasNext()) {
Map.Entry anEntry = (Map.Entry) aValueIterator.next();
Object aKey = anEntry.getKey();
boolean aValueInFilterList = Arrays.binarySearch(theFilterList, aKey, MemValueIndexer.ValueComparator.INSTANCE) >= 0;
if (theExcludeFlag && !aValueInFilterList || !theExcludeFlag && aValueInFilterList) {
// key passes filter => add to return
TreeSet aSet = (TreeSet) anEntry.getValue();
aLocatorCount += aSet.size();
aResultList.add(aSet);
}
}
IndexMatch[] aResult = new IndexMatch[aLocatorCount];
// add the list of locators to the result
aValueIterator = aResultList.iterator();
aLocatorCount = 0;
while (aValueIterator.hasNext()) {
aLocatorCount = addMatches(aResult, aLocatorCount, (Collection) aValueIterator.next());
}
return aResult;
}
/**
* Provides an IndexMatch array containing IndexMatch objects
* for the locators in the specified Maps. Results from theFirstMap
* will appear first in the array. Results from theFilterThisMap
* will appear second in the array. Results from theThirdMap
* will appear third in the array. Ordering is dependent on ordering
* of the map's iterators. Include or Exclude filtering (depending on
* theExcludeFlag) will be applied to results from theFilterThisMap.
* Filtering will include or exclude values appearing in theFilterList.
*
* @param theFirstMap Map containing Sets of ValueLocator objects as values
* @param theFilterThisMap Map containing Sets of ValueLocator objects as values (will be filtered)
* @param theThirdMap Map containing Sets of ValueLocator objects as values
* @param theFilterList list of values to filter result (exclude or include filtering)
* @param theExcludeFlag filtering is exclude if true, include if false
* @return a new array of IndexMatch objects
*/
private IndexMatch[] getIndexMatchArray(Map theFirstMap, Map theFilterThisMap, Map theThirdMap,
Object[] theFilterList, boolean theExcludeFlag) {
if (theFilterThisMap == null) {
return getIndexMatchArray(theFirstMap, theThirdMap);
}
LinkedList aResultList = new LinkedList();
Iterator aValueIterator;
int aLocatorCount = 0;
// count results to size result array
// count results to size result array
aLocatorCount += countLocators(theFirstMap);
// qualify the results for filter operation
// adding those qualifying to aResultCollection and counting filtered results
aValueIterator = theFirstMap.entrySet().iterator();
// iterate over the values adding locators for each matched to result
while (aValueIterator.hasNext()) {
Map.Entry anEntry = (Map.Entry) aValueIterator.next();
Object aKey = anEntry.getKey();
boolean aValueInFilterList = Arrays.binarySearch(theFilterList, aKey, MemValueIndexer.ValueComparator.INSTANCE) >= 0;
if (theExcludeFlag && !aValueInFilterList || !theExcludeFlag && aValueInFilterList) {
// key passes filter => add to return
TreeSet aSet = (TreeSet) anEntry.getValue();
aLocatorCount += aSet.size();
aResultList.add(aSet);
}
}
// count locators in third map
aLocatorCount += countLocators(theThirdMap);
// allocate array for combined results
IndexMatch[] aResult = new IndexMatch[aLocatorCount];
aLocatorCount = 0;
// add first map's contents to result
aLocatorCount = addMatches(aResult, aLocatorCount, theFirstMap);
// add the filtered list of locators to the result
aValueIterator = aResultList.iterator();
while (aValueIterator.hasNext()) {
aLocatorCount = addMatches(aResult, aLocatorCount, (Collection) aValueIterator.next());
}
// add the third map's contents to the result
addMatches(aResult, aLocatorCount, theThirdMap);
return aResult;
}
/**
* flush forcefully flushes any unwritten buffers to disk.
*/
public void flush() throws DBException {
// nothing to flush to...
}
public IndexerEventHandler getIndexerEventHandler() {
return handler;
}
// org.apache.xindice.util.Configurable facet
/**
* setConfig sets the configuration information for the Configurable
* object instance.
*
* @param theConfig The configuration Node
*/
public void setConfig(Configuration theConfig) {
itsConfig = theConfig;
try {
itsName = theConfig.getAttribute(NAME);
String itsPattern = theConfig.getAttribute(PATTERN);
// TODO: Unused: itsWildcard = itsPattern.indexOf('*') != -1;
// Determine the Index Type
String type = theConfig.getAttribute(TYPE, STRING_VAL).toLowerCase();
if (type.equals(STRING_VAL)) {
itsValueType = STRING;
} else if (type.equals(TRIMMED_VAL)) {
itsValueType = TRIMMED;
} else if (type.equals(SHORT_VAL)) {
itsValueType = SHORT;
} else if (type.equals(INT_VAL)) {
itsValueType = INTEGER;
} else if (type.equals(LONG_VAL)) {
itsValueType = LONG;
} else if (type.equals(FLOAT_VAL)) {
itsValueType = FLOAT;
} else if (type.equals(DOUBLE_VAL)) {
itsValueType = DOUBLE;
} else if (type.equals(BYTE_VAL)) {
itsValueType = BYTE;
} else if (type.equals(CHAR_VAL)) {
itsValueType = CHAR;
} else if (type.equals(BOOLEAN_VAL)) {
itsValueType = BOOLEAN;
} else {
if (itsPattern.indexOf('@') != -1) {
log.warn("Unrecognized value type, defaulting to '" + STRING_VAL + "'");
itsValueType = STRING;
} else {
log.warn("Unrecognized value type, defaulting to '" + TRIMMED_VAL + "'");
itsValueType = TRIMMED;
}
}
this.itsPattern = new IndexPattern(itsCollection.getSymbols(), itsPattern, null);
setupHandler();
} catch (Exception e) {
log.warn("Exception while setting configuration", e);
}
}
private void setupHandler() {
handler = new BasicIndexerEventHandler() {
public synchronized void onValueAdded(IndexPattern pattern, String value, Key key, int pos, int len,
short elemID, short attrID) {
Object aValue;
if (itsValueType != STRING) {
// convert from String to the primitive container object
// type (e.g. Integer, Double, etc.) we store
aValue = getTypedValue(value);
} else {
aValue = value;
}
// get the set of locations for the value being added
TreeSet aLocatorSet = (TreeSet) itsValues.get(aValue);
// if not found, create a new the set of locations for the value being added
if (aLocatorSet == null) {
aLocatorSet = new TreeSet();
// add new set to the value-locators map
itsValues.put(aValue, aLocatorSet);
}
// add a value locator to the set of locations for the value being added
aLocatorSet.add(new ValueLocator(key, pos, len, elemID, attrID));
// keep track of how many locators we manage
++itsValueLocatorCount;
}
public synchronized void onValueDeleted(IndexPattern pattern, String value, Key key, int pos, int len,
short elemID, short attrID) {
Object aValue;
if (itsValueType != STRING) {
// convert from String to the primitive container object
// type (e.g. Integer, Double, etc.) we store
aValue = getTypedValue(value);
} else {
aValue = value;
}
// get the set of locations for the value being added
TreeSet aLocatorSet = (TreeSet) itsValues.get(aValue);
// if found locator set, try to remove locator for value being removed
if (aLocatorSet != null) {
if (aLocatorSet.remove(new ValueLocator(key, pos, len, elemID, attrID))) {
// removed a ValueLocator
// set could now be empty
// if so, remove empty set from value-locatorset map
if (aLocatorSet.size() == 0) {
itsValues.remove(value);
}
// keep track of how many locators we manage
--itsValueLocatorCount;
}
}
}
};
}
/**
* getConfig retrieves the configuration information for the
* Configurable object instance.
*
* @return The configuration Node
*/
public Configuration getConfig() {
return itsConfig;
}
// org.apache.xindice.core.DBObject facet
/**
* Creates a new MemValueIndexer.
*
* @return Whether or not the DBObject was created
*/
public boolean create() throws DBException {
itsOpen = false;
// create our ValueLocator set by value map
// specifying our custom Comparator that handles
// EmptyValue objects correctly
itsValues = new TreeMap(ValueComparator.INSTANCE);
return true;
}
/**
* Opens the index so it can be used.
* Since MemValueIndexer uses memory only as a backing store,
* it must always be created before it can be opened. If a
* call to open() has not been preceeded by a call to create(),
* the open() will return false. Also, this object must be
* configured via setConfig() before it can be opened.
*
* @return true if index was opened successfully, false otherwise
*/
public synchronized boolean open() throws DBException {
if (itsValues == null || itsConfig == null) {
return false;
}
itsOpen = true;
return true;
}
/**
* Indicates whether or not this indexer is open.
*
* @return true if open, false otherwise
*/
public synchronized boolean isOpened() {
return itsValues != null && itsOpen;
}
/**
* Indicates whether or not this index exists. Since this
* is a memory-buffered index, it only exists after having
* been created.
*
* @return Whether or not the physical resource exists
*/
public synchronized boolean exists() throws DBException {
return itsValues != null;
}
/**
* Removes this index from existence.
* The indexes owner(s) is/are responsible for removing any
* references to the index in its own context.
*
* @return Whether or not the DBObject was dropped
*/
public synchronized boolean drop() throws DBException {
itsOpen = false;
itsValues = null;
return true;
}
/**
* Closes this index. Does nothing more than
* set the state returned to isOpened() to false.
*
* @return Whether or not the index was closed
*/
public synchronized boolean close() throws DBException {
itsOpen = false;
return true;
}
// org.apache.xindice.util.Named facet
/**
* Provides the name of this index.
*
* @return index's name
*/
public String getName() {
return itsName;
}
/**
* Provides an Object representing the specified value
* converted from String to the value type supported by this inbdex.
* If you modify the type of Object used to store the value internally
* (the type of Object returned by this method for a given value of
* itsValueType) you must also modify getNextValueOf method below to
* correctly compute/return the proper type.
*
* @param theValue String from which to derive the value
* @return an Object representing the value extracted from theValue
*/
private Object getTypedValue(String theValue) {
if (itsValueType != STRING && itsValueType != TRIMMED) {
theValue = theValue.trim();
} else {
if (itsValueType == TRIMMED) {
theValue = QueryEngine.normalizeString(theValue);
}
}
if (theValue.length() == 0) {
return EmptyValue.INSTANCE;
}
try {
switch (itsValueType) {
default :
break;
case STRING:
case TRIMMED:
return theValue;
//break;
case SHORT:
return new Short(theValue);
//break;
case INTEGER:
return new Integer(theValue);
//break;
case LONG:
return new Long(theValue);
//break;
case FLOAT:
return new Float(theValue);
//break;
case DOUBLE:
return new Double(theValue);
//break;
case BYTE:
return new Byte(theValue);
//break;
case CHAR:
return new Character(theValue.charAt(0));
//break;
case BOOLEAN:
// represented a a Byte of 0 (false) or 1 (true) so Comparable is implemented (true > false)
return "[true][yes][1][y][on]".indexOf("[" + theValue.toLowerCase() + "]") == -1 ? new Byte((byte) 0) : new Byte((byte) 1);
//break;
}
} catch (Exception anException) {
if (log.isDebugEnabled()) {
log.debug("Exception while converting value \"" + theValue + "\" from String to " + itsValueTypeName, anException);
}
}
return "";
}
/**
* Provides an Object representing the next sort order value of the specified value.
* Dependent upon the types returned by getTypedValue().
*
* @param theValue Object to return next value of
* @return an Object representing the next value above specified value or null if theValue is already at maximum for type
*/
private Object getNextValueOf(Object theValue) {
return getNextValueOf(theValue, itsValueType);
}
/**
* Provides an Object representing the next sort order value of the specified value.
* Dependent upon the types returned by getTypedValue(). Types returned by this
* method must be the same types returned by getTypedValue method.
*
* @param theValue Object to return next value of
* @param theType type of Object to return
* @return an Object representing the next value above specified value or null if theValue is already at maximum for type
*/
private Object getNextValueOf(Object theValue, int theType) {
if (theValue instanceof EmptyValue) {
return "\0";
}
Object aReturn = null;
switch (theType) {
default :
break;
case STRING:
case TRIMMED:
// provide a value appropriate for use in normal searches as well as in starts-with searches
int aLength = ((String) theValue).length();
if (aLength == 0) {
// make the value 1 character longer using the lowest possible character value
return theValue + "\0";
}
char aLastChar = ((String) theValue).charAt(aLength - 1);
if (aLastChar == Character.MAX_VALUE) {
// make the value 1 character longer using the lowest possible character value
return theValue + "\0";
}
// return a string of the same length with the final character incremented by 1
// be sure to avoid upcasting to int which would happen if you do "a" + (aLastChar + 1)
aLastChar += 1;
aReturn = ((String) theValue).substring(0, aLength - 1) + aLastChar;
break;
case SHORT:
{
short aValue = ((Short) theValue).shortValue();
aReturn = aValue == Short.MAX_VALUE ? null : new Short((short) (aValue + 1));
}
break;
case INTEGER:
{
int aValue = ((Integer) theValue).intValue();
aReturn = aValue == Integer.MAX_VALUE ? null : new Integer(aValue + 1);
}
break;
case LONG:
{
long aValue = ((Long) theValue).longValue();
aReturn = aValue == Long.MAX_VALUE ? null : new Long(aValue + 1);
}
break;
case FLOAT:
{
float aValue = ((Float) theValue).floatValue();
// note that Float.MIN_VALUE returns the smallest possible POSITIVE value of type float
aReturn = aValue == Float.MAX_VALUE ? null : new Float(aValue + Float.MIN_VALUE);
}
break;
case DOUBLE:
{
double aValue = ((Double) theValue).doubleValue();
// note that Double.MIN_VALUE returns the smallest possible POSITIVE value of type double
aReturn = aValue == Double.MAX_VALUE ? null : new Double(aValue + Double.MIN_VALUE);
}
break;
case CHAR:
{
// Character.compareTo(Character) uses math on char, so we do too...
char aValue = ((Character) theValue).charValue();
aReturn = aValue == Character.MAX_VALUE ? null : new Character((char) (aValue + 1));
}
break;
case BOOLEAN:
// BOOLEAN type stored internally as (byte)0 or (byte)1
case BYTE:
{
byte aValue = ((Byte) theValue).byteValue();
aReturn = aValue == Byte.MAX_VALUE ? null : new Byte((byte) (aValue + 1));
}
break;
}
return aReturn;
}
/**
* Counts the number of ValueLocator objects
* stored in the TreeSet objects contained as values
* in the specified map. If theMap is null, 0 is returned.
*
* @param theMap map containing sets of ValueLocator objects to add as IndexMatches
* @return the count of ValueLocator objects
*/
private int countLocators(Map theMap) {
int aCount = 0;
if (theMap != null) {
Iterator aValueIterator = theMap.entrySet().iterator();
// iterate over the values adding locators for each to result
// no need to filter while iterating
while (aValueIterator.hasNext()) {
aCount += ((Set) ((Map.Entry) aValueIterator.next()).getValue()).size();
}
}
return aCount;
}
/**
* Adds the ValueLocators from the all the sets
* of value locators found in the specified map
* as IndexMatches to the specified array of IndexMatches.
*
* @param theArray array to add matches to
* @param theStartIndex index to start adding at
* @param theMap map containing sets of ValueLocator objects to add as IndexMatches
* @return the next index beyond the last entry added
*/
private int addMatches(IndexMatch[] theArray, int theStartIndex, Map theMap) {
if (theMap == null) {
return theStartIndex;
}
Iterator aValueIterator = theMap.entrySet().iterator();
// iterate over the values adding locators for each to result
// no need to filter while iterating
while (aValueIterator.hasNext()) {
// iterate over locators for current value adding each to result
Iterator aLocatorIterator = ((TreeSet) ((Map.Entry) aValueIterator.next()).getValue()).iterator();
for (; aLocatorIterator.hasNext(); ++theStartIndex) {
ValueLocator aLocator = (ValueLocator) aLocatorIterator.next();
theArray[theStartIndex] = new IndexMatch(
aLocator.getKey(), aLocator.getPosition(), aLocator.getLength(), aLocator.getElementID(), aLocator.getAttributeID());
}
}
return theStartIndex;
}
/**
* Adds the ValueLocators from the specified set
* as IndexMatches to the specified array of IndexMatches.
*
* @param theArray array to add matches to
* @param theStartIndex index to start adding at
* @param theSet Set conatining ValueLocator objects to add as IndexMatches
* @return the next index beyond the last entry added
*/
private int addMatches(IndexMatch[] theArray, int theStartIndex, java.util.Collection theSet) {
if (theSet == null) {
return theStartIndex;
}
Iterator aLocatorIterator = theSet.iterator();
for (; aLocatorIterator.hasNext(); ++theStartIndex) {
ValueLocator aLocator = (ValueLocator) aLocatorIterator.next();
theArray[theStartIndex] = new IndexMatch(
aLocator.getKey(), aLocator.getPosition(), aLocator.getLength(), aLocator.getElementID(), aLocator.getAttributeID());
}
return theStartIndex;
}
/**
* Implements an Object representing any empty value.
* For comparisons against any other value type,
* convert an EmptyValue and the other operand to String
* and compare as String values. Any EmptyValue converted
* to String is an empty String.
*/
private static class EmptyValue implements Comparable {
/**
* Creates a new object.
*/
EmptyValue() {
}
/**
* Provides a negative integer, 0, or a positive integer
* indicating whether this object is less than (negative return),
* equal to (0 return), or greater than (positive return) theCompareTo
* Object.
*
* @param theCompareTo Object to compare this object to
* @return an integer indicating less than (negative), equal to (0), or greater than (positive)
*/
public int compareTo(Object theCompareTo) {
if (theCompareTo instanceof EmptyValue) {
return 0;
}
return "".compareTo(theCompareTo.toString());
}
/**
* Indicates whether or not this object is equal to theCompareTo Object.
*
* @param theCompareTo Object to compare this object to
* @return true if this object is equal to theCompareTo Object, false otherwise
*/
public boolean equals(Object theCompareTo) {
//noinspection SimplifiableIfStatement
if (theCompareTo instanceof EmptyValue) {
return true;
}
return theCompareTo.toString().length() == 0;
}
/**
* Provides a hash code value for this object.
*
* @return a hash code for this object
*/
public int hashCode() {
return 1;
}
/**
* Provides a String representing this object.
*
* @return an empty String
*/
public String toString() {
return EMPTY_STRING;
}
/**
* Provides a shareable static instance of this class.
*
* @return a shareable static instance of this class
*/
public EmptyValue getInstance() {
return INSTANCE;
}
/**
* an empty String
*/
private static final String EMPTY_STRING = "";
/**
* an instance of EmptyValue
*/
public final static EmptyValue INSTANCE = new EmptyValue();
}
/**
* Implements a comparator used for comparing the
* values we store in our value map. Handles comparisons
* involving EmptyValue objects correctly.
*/
private static class ValueComparator implements Comparator {
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.
*
* @param theObject1 first object to compare
* @param theObject2 second object to compare
* @return negative integer, 0, or positive integer indicating ordering of theObject1 and theObject2
*/
public int compare(Object theObject1, Object theObject2) {
if (theObject1 instanceof Comparable && theObject1.getClass() == theObject2.getClass()) {
return ((Comparable) theObject1).compareTo(theObject2);
}
return theObject1.toString().compareTo(theObject2.toString());
}
/**
* Indicates whether or not theCompareTo is an
* instance of ValueComparator and thus imposes the
* same ordering as this Object.
*
* @return true if theCompareTo is an instance of ValueComparator
*/
public boolean equals(Object theCompareTo) {
return theCompareTo instanceof ValueComparator;
}
/**
* Provides a shareable static instance of this class.
*
* @return a shareable static instance of this class
*/
public ValueComparator getInstance() {
return INSTANCE;
}
/**
* a shareable static instance of this class
*/
public static final ValueComparator INSTANCE = new ValueComparator();
}
/**
* Implements a container for locating instances of indexed
* values within Collection and individual Documents within
* the Collection.
*/
private class ValueLocator implements Comparable {
/**
* the key of the Document containing the value
*/
private Key itsKey;
/**
* the value's position in the document stream
*/
private final int itsPosition;
/**
* the value's length
*/
private final int itsLength;
/**
* the ID of the Element containing the value
*/
private final short itsElementID;
/**
* the ID of the Attribute containing the value
*/
private final short itsAttributeID;
/**
* Creates a new object.
*
* @param theKey Key of the value (only the data member is used)
* @param thePosition the value's position in the document stream
* @param theLength length of the value in the document stream
* @param theElementID the ID of the Element containing the value
* @param theAttributeID the ID of the Attribute containing the value
*
* NOTE: I do not know if there are use cases where theKey.length != theLength
* If there are none, then length could be carried as itsKey.length
* instead of as a separate member.
*/
public ValueLocator(Key theKey, int thePosition, int theLength, short theElementID, short theAttributeID) {
itsKey = theKey;
itsPosition = thePosition;
itsLength = theLength;
itsElementID = theElementID;
itsAttributeID = theAttributeID;
}
/**
* Provides the key of the Document containing the value.
*
* @return the key of the Document containing the value
*/
public Key getKey() {
return itsKey;
}
/**
* Provides the value's position in the document stream.
*
* @return the position
*/
public final int getPosition() {
return itsPosition;
}
/**
* Provides the value's length.
*
* @return the value's length
*/
public final int getLength() {
return itsLength;
}
/**
* Provides the ID of the Element containing the value.
*
* @return the ID of the Element containing the value
*/
public final short getElementID() {
return itsElementID;
}
/**
* Provides the ID of the Attribute containing the value.
*
* @return the ID of the Attribute containing the value
*/
public final short getAttributeID() {
return itsAttributeID;
}
/** Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
*
* The implementor must ensure <tt>sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>x.compareTo(y)</tt> must throw an exception iff
* <tt>y.compareTo(x)</tt> throws an exception.)<p>
*
* The implementor must also ensure that the relation is transitive:
* <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
* <tt>x.compareTo(z)>0</tt>.<p>
*
* Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
* implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
* all <tt>z</tt>.<p>
*
* It is strongly recommended, but <i>not</i> strictly required that
* <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
* class that implements the <tt>Comparable</tt> interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* @param theObject the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this Object.
*
*/
public final int compareTo(Object theObject) {
if (!(theObject instanceof ValueLocator)) {
throw new ClassCastException("Can't compare object of type " + theObject.getClass().getName() + " to ValueLocator");
}
ValueLocator aCompareTo = (ValueLocator) theObject;
// compare keys
int result = itsKey.compareTo(aCompareTo.itsKey);
if (result != 0) {
return result;
}
// compare position
if (itsPosition != aCompareTo.itsPosition) {
return itsPosition > aCompareTo.itsPosition ? 1 : -1;
}
// compare length
if (itsLength != aCompareTo.itsLength) {
return itsLength > aCompareTo.itsLength ? 1 : -1;
}
// compare element ID
if (itsElementID != aCompareTo.itsElementID) {
return itsElementID > aCompareTo.itsElementID ? 1 : -1;
}
// compare attribute ID
if (itsAttributeID != aCompareTo.itsAttributeID) {
return itsAttributeID > aCompareTo.itsAttributeID ? 1 : -1;
}
// equal
return 0;
}
}
}