Package org.apache.xindice.core.indexer

Source Code of org.apache.xindice.core.indexer.MemValueIndexer$ValueLocator

/*
* 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)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
         * <tt>x.compareTo(z)&gt;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;
        }
    }
}
TOP

Related Classes of org.apache.xindice.core.indexer.MemValueIndexer$ValueLocator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');