Attached are a couple of classes that you may find useful. Not a major piece of work, but I'm donating these to the cause if anyone is interested in checking them in as part of Xindice.
Attachments:
1) MemValueIndexer.java
* 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.
Note that this indexer stores values as Java objects. Comparisons are performed using Java comparison. This indexer correctly handles comparisons of signed values for numeric index types, etc. (unlike existing ValueIndexer).
2) SizeableMemFiler.java
A Filer which is a wrapper around the Java HashMap. Just like existing MemFiler except that the initial size of the HashMap is set via System property named "org.apache.xindice.core.filer.SizeableMemFiler.INITIAL_SIZE".
Using these 2 classes together, you can run Xindice as an in-memory database (non-persistent).
package org.apache.xindice.core.indexer;
/* * The Apache Software License, Version 1.1 * * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Xindice" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== */ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.LinkedList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xindice.util.Configurable; import org.apache.xindice.util.Configuration; import org.apache.xindice.util.Named; import org.apache.xindice.core.DBException; import org.apache.xindice.core.DBObject; import org.apache.xindice.core.data.Key; import org.apache.xindice.core.data.Value; import org.apache.xindice.core.FaultCodes; import org.apache.xindice.core.indexer.Indexer; import org.apache.xindice.core.indexer.IndexMatch; import org.apache.xindice.core.indexer.IndexPattern; import org.apache.xindice.core.indexer.IndexQuery; import org.apache.xindice.core.query.QueryEngine; /** * 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 ([EMAIL PROTECTED]) * @version 3/18/2003 */ public class MemValueIndexer implements Indexer { /** * 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; } /** * 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> * [EMAIL PROTECTED] Indexes all contacts by name attribute * memo Indexes the text of all memo elements * [EMAIL PROTECTED] Indexes all contact attributes * [EMAIL PROTECTED] Indexes the name attribute for all elements * * Indexes the text of all elements * [EMAIL PROTECTED] 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 String getPattern() { return itsPattern; } /** * Removes the specified value reference from the index. * * @param value The value to remove * @param key The Object ID * @param pos The offset into the stream the Element occurs at * @param len The length of the substream for the Element * @param elemID The Element ID of the value * @param attrID The Attribute ID of the value (if any, else -1) */ public synchronized void remove(String theValue, Key theKey, int thePosition, int theLength, short theElementID, short theAttributeID) throws DBException { Object aValue; if(itsValueType != STRING) { // convert from String to the primitive container object // type (e.g. Integer, Double, etc.) we store aValue = getTypedValue(theValue); } else { aValue = theValue; } // 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(theKey, thePosition, theLength, theElementID, theAttributeID))) { // removed a ValueLocator // set could now be empty // if so, remove empty set from value-locatorset map if(aLocatorSet.size() == 0) { itsValues.remove(theValue); } // keep track of how many locators we manage --itsValueLocatorCount; } } } /** * Adds a value reference to the index. * * @param theValue The value to add * @param key The Object ID * @param pos The offset into the stream the Element occurs at * @param len The length of the substream for the Element * @param elemID The Element ID of the value * @param attrID The Attribute ID of the value (if any, else -1) */ public synchronized void add(String theValue, Key theKey, int thePosition, int theLength, short theElementID, short theAttributeID) throws DBException { Object aValue; if(itsValueType != STRING) { // convert from String to the primitive container object // type (e.g. Integer, Double, etc.) we store aValue = getTypedValue(theValue); } else { aValue = theValue; } // 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(theKey, thePosition, theLength, theElementID, theAttributeID)); // keep track of how many locators we manage ++itsValueLocatorCount; } /** * 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 = null; Object aLowEndpoint = aMatchValueArray[0]; Object aHighEndpoint = aMatchValueArray[aMatchValueArray.length - 1]; Map aSubmap; Iterator aValueIterator; int aLocatorCount = 0; int anOperator = theQuery.getOperator(); // perform the requested matching type switch(anOperator) { // unsupported operator default: throw new DBException(FaultCodes.IDX_NOT_SUPPORTED, "unimplemented index query operation code (code = " + anOperator + ")"); //break; // exact match case IndexQuery.EQ: aLocatorSet = (TreeSet)itsValues.get(aLowEndpoint); if(aLocatorSet != null) { aResult = new IndexMatch[aLocatorSet.size()]; addMatches(aResult, 0, aLocatorSet); } break; // all except exact match case IndexQuery.NEQ: // see whether or not we even have the value to be excluded TreeSet anExcludedLocatorSet = (TreeSet)itsValues.get(aLowEndpoint); aValueIterator = itsValues.values().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( new Key(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( new Key(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 aLowEndpoint = aQueryValueList[0]; if(itsValueType == TRIMMED) { aLowEndpoint = (String)QueryEngine.normalizeString((String)aLowEndpoint); } else { if(itsValueType != STRING) { aLowEndpoint = ((String)aLowEndpoint).trim(); } } // get the matching submap forcing String comparisons to be used regardless of stored type aResult = getIndexMatchArray(getBWSubmap(aLowEndpoint, 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) aLowEndpoint = aQueryValueList[0]; if(itsValueType == TRIMMED) { aLowEndpoint = (String)QueryEngine.normalizeString((String)aLowEndpoint); } else { if(itsValueType != STRING) { aLowEndpoint = ((String)aLowEndpoint).trim(); } } // 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; } /** * 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 final IndexMatch[] getIndexMatchArray(Map theMap) { IndexMatch[] aResult = null; // 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 final 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 final 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.values().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 final 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.values().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... } // 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); itsPattern = theConfig.getAttribute(PATTERN); itsWildcard = itsPattern.indexOf('*') != -1; // Determine the Index Type String itsValueTypeName = theConfig.getAttribute(TYPE, STRING_VAL).toLowerCase(); if ( itsValueTypeName.equals(STRING_VAL) ) itsValueType = STRING; else if ( itsValueTypeName.equals(TRIMMED_VAL) ) itsValueType = TRIMMED; else if ( itsValueTypeName.equals(SHORT_VAL) ) itsValueType = SHORT; else if ( itsValueTypeName.equals(INT_VAL) ) itsValueType = INTEGER; else if ( itsValueTypeName.equals(LONG_VAL) ) itsValueType = LONG; else if ( itsValueTypeName.equals(FLOAT_VAL) ) itsValueType = FLOAT; else if ( itsValueTypeName.equals(DOUBLE_VAL) ) itsValueType = DOUBLE; else if ( itsValueTypeName.equals(BYTE_VAL) ) itsValueType = BYTE; else if ( itsValueTypeName.equals(CHAR_VAL) ) itsValueType = CHAR; else if ( itsValueTypeName.equals(BOOLEAN_VAL) ) itsValueType = BOOLEAN; else { if ( itsPattern.indexOf('@') != -1 ) { itsValueType = STRING; itsValueTypeName = STRING_VAL; } else { itsValueType = TRIMMED; itsValueTypeName = TRIMMED_VAL; } } } catch (Exception anException) { if(itsLog.isDebugEnabled()) { itsLog.debug("Exception while setting configuration", anException); } } } /** * 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() throws DBException { 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 final 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.toString().toLowerCase() + "]") == -1 ? new Byte((byte)0) : new Byte((byte)1); //break; } } catch(Exception anException) { if(itsLog.isDebugEnabled()) { itsLog.debug("Exception while converting value \"" + theValue + "\" from String to " + itsValueTypeName, anException); } } return new String(); } /** * 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 final 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 final Object getNextValueOf(Object theValue, int theType) { if(theValue instanceof EmptyValue) { return new String("\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 aReturn = ((String)theValue).substring(0, aLength - 1) + String.valueOf(aLastChar + 1); 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 theArray array to add matches to * @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.values().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.values().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( new Key(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( new Key(aLocator.getKey()), aLocator.getPosition(), aLocator.getLength(), aLocator.getElementID(), aLocator.getAttributeID()); } return theStartIndex; } /** * log used by this indexer */ private static Log itsLog = LogFactory.getLog("org.apache.xindice.core.indexer"); /** * 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 String itsPattern; /** * 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 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 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; /** * 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 extends Object implements Comparable { /** * Creates a new object. * * @return 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) { 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 { /** * 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 thePosition length of the value in the document stream * @aram 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.getData(); 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 byte[] 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 byte[] aCompareToKey = aCompareTo.itsKey; int aMaxLength = itsKey.length > aCompareToKey.length ? aCompareToKey.length : itsKey.length; for(int anIndex = 0; anIndex < aMaxLength; ++anIndex) { byte aByteThis = itsKey[anIndex]; byte aByteThat = aCompareToKey[anIndex]; // compare bytes if (aByteThis != aByteThat) { return aByteThis > aByteThat ? 1 : -1; } } // comparison done if keys are not of equal length but initial // substrings are equal if(itsKey.length != aCompareToKey.length) { return itsKey.length > aCompareToKey.length ? 1 : -1; } // 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; } /** * the key of the Document containing the value */ private byte[] 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; } }
package org.apache.xindice.core.filer; /* * The Apache Software License, Version 1.1 * * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Xindice" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation and was * originally based on software copyright (c) 1999-2001, The dbXML * Group, L.L.C., http://www.dbxmlgroup.com. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * $Id: SizeableMemFiler.java,v 1.5 2002/10/31 06:59:57 vladimir Exp $ */ import org.apache.xindice.core.filer.Filer; import org.apache.xindice.core.filer.FilerException; 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.Record; import org.apache.xindice.core.data.RecordSet; import org.apache.xindice.core.data.Value; import org.apache.xindice.util.SimpleConfigurable; import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * SizeableMemFiler is an In-Memory Filer implementation for Xindice. SizeableMemFiler can be * used for temporary collections and caching. It's basically a layering on * top of HashMap. */ public final class SizeableMemFiler extends SimpleConfigurable implements Filer { private Map hashTable = null; private boolean opened = false; private boolean readOnly = false; public static final String INITIAL_SIZE_KEY = "org.apache.xindice.core.filer.SizeableMemFiler.INITIAL_SIZE"; public static final int INITIAL_SIZE = 89; public SizeableMemFiler() { String aSizeString = System.getProperty(INITIAL_SIZE_KEY); int aSize = INITIAL_SIZE; try { if(aSizeString != null) { aSize = Integer.parseInt(aSizeString); } } catch(Exception anException) { aSize = INITIAL_SIZE; } hashTable = Collections.synchronizedMap(new HashMap(aSize)); } public SizeableMemFiler(Map hashTable, boolean readOnly) { this.hashTable = hashTable; this.readOnly = readOnly; } public SizeableMemFiler(Map hashTable) { this(hashTable, false); } public void setLocation(File root, String location) { } public String getName() { return "SizeableMemFiler"; } private void checkOpened() throws DBException { if ( !opened ) throw new FilerException(FaultCodes.COL_COLLECTION_CLOSED, "Filer is closed"); } private void checkReadOnly() throws DBException { if ( readOnly ) throw new FilerException(FaultCodes.COL_COLLECTION_READ_ONLY, "Filer is read-only"); } public boolean create() { hashTable.clear(); return true; } public boolean open() { opened = true; return opened; } public boolean isOpened() { return opened; } public boolean exists() { return true; } public boolean drop() { hashTable.clear(); opened = false; return !opened; } public boolean close() { opened = false; return !opened; } public void flush() { } public Record readRecord(Key key) throws DBException { if ( key == null || key.equals("") ) { return null; } checkOpened(); return (Record)hashTable.get(key); } public boolean writeRecord(Key key, Value value) throws DBException { if ( key == null || key.equals("") ) { throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Invalid key: '" + key + "'"); } if (value == null ) { throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Invalid null value"); } checkOpened(); checkReadOnly(); hashTable.put(key, new Record(key, value)); return true; } public boolean deleteRecord(Key key) throws DBException { if ( key == null || key.equals("") ) { return false; } checkOpened(); checkReadOnly(); return hashTable.remove(key) != null; } public long getRecordCount() throws DBException { checkOpened(); return hashTable.size(); } public RecordSet getRecordSet() throws DBException { checkOpened(); return new MemRecordSet(); } /** * MemRecordSet */ private class MemRecordSet implements RecordSet { private Iterator enum = hashTable.values().iterator(); public synchronized boolean hasMoreRecords() throws DBException { return enum.hasNext(); } public synchronized Record getNextRecord() throws DBException { checkOpened(); return (Record)enum.next(); } public synchronized Value getNextValue() throws DBException { checkOpened(); return ((Record)enum.next()).getValue(); } public synchronized Key getNextKey() { return ((Record)enum.next()).getKey(); } } }