/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.store.stringpool.memory;
// Java 2 standard packages
import java.net.URI;
import java.util.*;
// Third-party packages
import org.apache.log4j.*;
// Locally written packages
// Use the XA implementation of SPObjectFactoryImpl.
import org.mulgara.query.Constraint;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.store.nodepool.NodePool;
import org.mulgara.store.nodepool.NodePoolException;
import org.mulgara.store.nodepool.memory.MemoryNodePoolImpl;
import org.mulgara.store.statement.StatementStore;
import org.mulgara.store.stringpool.SPLimit;
import org.mulgara.store.stringpool.SPObject;
import org.mulgara.store.stringpool.SPObjectFactory;
import org.mulgara.store.stringpool.StringPoolException;
import org.mulgara.store.stringpool.xa.SPObjectFactoryImpl;
import org.mulgara.store.tuples.Annotation;
import org.mulgara.store.tuples.RowComparator;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.store.tuples.TuplesOperations;
import org.mulgara.store.xa.XANodePool;
import org.mulgara.store.xa.XAStringPool;
import gnu.trove.TLongObjectHashMap;
import gnu.trove.TObjectLongHashMap;
/**
* A memory based StringPool implementation.
*
* @created 2003-09-11
*
* @author Andrew Newman
*
* @version $Revision: 1.2 $
*
* @modified $Date: 2005/05/16 11:07:09 $
*
* @maintenanceAuthor $Author: amuys $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2003 <A href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</A>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public final class MemoryStringPoolImpl implements XAStringPool {
/**
* Logger.
*/
private final static Logger logger =
Logger.getLogger(MemoryStringPoolImpl.class.getName());
final static SPObjectFactory SPO_FACTORY = SPObjectFactoryImpl.getInstance();
/**
* The hash map containing a long to an object.
*/
private TLongObjectHashMap<SPObject> nodeToStringPool;
/**
* The hash map containing an object to a long.
*/
private TObjectLongHashMap<SPObject> stringToNodePool;
/**
* An index for sorting the SPObjects.
*/
private SortedSet<SPObject> stringIndex;
/**
* An array of smallest SPObject values. Ordered by Type ID.
*/
private SPObject[] smallestSPObjects;
/**
* An array of largest SPObject values. Ordered by Type ID.
*/
private SPObject[] largestSPObjects;
/**
* The node pool to allocate gNodes from.
*/
private XANodePool xaNodePool;
/**
* Variables for when data is returned as a Tuples.
*/
static final Variable[] VARIABLES = new Variable[] {
StatementStore.VARIABLES[0]
};
/**
* Create a new in memory string pool with 10 items by default.
*/
public MemoryStringPoolImpl() {
nodeToStringPool = new TLongObjectHashMap<SPObject>(10);
stringToNodePool = new TObjectLongHashMap<SPObject>(10);
stringIndex = new TreeSet<SPObject>();
// intialise the SPObject arrays
smallestSPObjects = new SPObject[SPObject.TypeCategory.TCID_TYPED_LITERAL + 1];
largestSPObjects = new SPObject[SPObject.TypeCategory.TCID_TYPED_LITERAL + 1];
for (int s = 1; s <= SPObject.TypeCategory.TCID_TYPED_LITERAL; s++) {
smallestSPObjects[s] = new SPLimit(s, true);
largestSPObjects[s] = new SPLimit(s, false);
}
}
//
// Methods from StringPool.
//
/**
* Gets the SPObjectFactory associated with this StringPool implementation.
*/
public SPObjectFactory getSPObjectFactory() {
return SPO_FACTORY;
}
/**
* Add a graph node:SPObject pair to the string pool. If the pair already
* exists, do nothing. If neither the SPObject nor the graph node exists,
* create them. If either the graph node or the SPObject exists, but the other
* doesn't, throw an exception.
* @deprecated
* @param gNode the graph node half of the graph node:SPObject pair
* @param spObject the SPObject half of the graph node:SPObject pair
* @throws StringPoolException if only one of the graph node and SPObject
* exists in the pool
*/
public void put(long gNode, SPObject spObject) throws StringPoolException {
putInternal(gNode, spObject);
}
/**
* Add a graph node:SPObject pair to the string pool. If the pair already
* exists, do nothing. If neither the SPObject nor the graph node exists,
* create them. If either the graph node or the SPObject exists, but the other
* doesn't, throw an exception.
*
* @param gNode the graph node half of the graph node:SPObject pair
* @param spObject the SPObject half of the graph node:SPObject pair
* @throws StringPoolException if only one of the graph node and SPObject
* exists in the pool
*/
private void putInternal(long gNode, SPObject spObject) throws StringPoolException {
// Stop adding of nodes below a certain threshold.
if (gNode < MemoryNodePoolImpl.MIN_NODE) {
throw new IllegalArgumentException("gNode < MIN_NODE");
}
// Check to see if it already exists.
if (nodeToStringPool.containsKey(gNode)) {
throw new StringPoolException("Graph node already present in string pool");
} else if (stringToNodePool.containsKey(spObject)) {
throw new StringPoolException("SPObject already present in string pool");
} else {
// Add to both ways of indexing objects and nodes.
nodeToStringPool.put(gNode, spObject);
stringToNodePool.put(spObject, gNode);
try {
stringIndex.add(spObject);
} catch (RuntimeException e) {
throw new StringPoolException("Unable to add object: " + spObject + " for " + gNode, e);
}
}
}
public long put(SPObject spObject) throws StringPoolException, NodePoolException {
if (xaNodePool == null) throw new IllegalStateException("No node pool set for the string pool.");
long gNode = xaNodePool.newNode();
putInternal(gNode, spObject);
return gNode;
}
public void setNodePool(XANodePool nodePool) {
this.xaNodePool = nodePool;
}
/**
* Remove a graph node:SPObject pair from the string pool.
*
* @param gNode the node half of the graph node:SPObject pair
* @return <code>true</code> if the node existed and was removed.
* @throws StringPoolException if an internal error occurs
*/
public boolean remove(long gNode) throws StringPoolException {
boolean successful = false;
if (nodeToStringPool.contains(gNode)) {
SPObject obj = nodeToStringPool.remove(gNode);
long node = stringToNodePool.remove(obj);
stringIndex.remove(obj);
if (node == gNode) {
successful = true;
}
else {
if (logger.isEnabledFor(Level.ERROR)) {
logger.error("The retrieved node and the given node were unequal " +
"when removing node: " + gNode);
}
throw new StringPoolException("The retrieved node and the given " +
"node were unequal when removing node: " + gNode);
}
}
return successful;
}
/**
* @param spObject an SPObject to search for within the pool
* @return the graph node corresponding to <var>spObject</var> , or
* <code>null</code> if no such SPObject is in the pool.
* @throws StringPoolException EXCEPTION TO DO
*/
public long findGNode(SPObject spObject) throws
StringPoolException {
// Default result is zero
long result = NodePool.NONE;
if (stringToNodePool.containsKey(spObject)) {
result = stringToNodePool.get(spObject);
}
return result;
}
public long findGNode(SPObject spObject, boolean create) throws StringPoolException {
return findGNodeInternal(spObject, xaNodePool);
}
@Deprecated
public long findGNode(SPObject spObject, NodePool nodePool) throws StringPoolException {
return findGNodeInternal(spObject, nodePool);
}
public long findGNodeInternal(SPObject spObject, NodePool nodePool) throws StringPoolException {
if (nodePool == null) {
throw new IllegalArgumentException("nodePool parameter is null");
}
long result;
if (stringToNodePool.containsKey(spObject)) {
result = stringToNodePool.get(spObject);
} else {
try {
result = nodePool.newNode();
} catch (NodePoolException ex) {
throw new StringPoolException("Could not allocate new node.", ex);
}
assert !nodeToStringPool.containsKey(result);
nodeToStringPool.put(result, spObject);
stringToNodePool.put(spObject, result);
}
return result;
}
/**
* @param gNode a graph node to search for within the pool
* @return the SPObject corresponding to <var>gNode</var> , or
* <code>null</code> if no such graph node is in the pool
* @throws StringPoolException EXCEPTION TO DO
*/
public SPObject findSPObject(long gNode) throws StringPoolException {
return (SPObject) nodeToStringPool.get(gNode);
}
public Tuples findGNodes(
SPObject lowValue, boolean inclLowValue,
SPObject highValue, boolean inclHighValue
) throws StringPoolException {
SortedSet<SPObject> subset;
// check if the low value should be excluded
if (lowValue != null && !inclLowValue && stringIndex.contains(lowValue)) {
// it is there, so move the lovValue on by one
// find the tailSet including the low value
subset = stringIndex.tailSet(lowValue);
// find the second element
Iterator<SPObject> it = subset.iterator();
assert it.hasNext();
// move to the element that needs to be dropped
it.next();
// see if there is more than just this element
if (!it.hasNext()) {
// no other items, so just exit with empty data
return new SetWrapperTuples(new TreeSet<SPObject>());
}
// move the low value
lowValue = it.next();
}
// check if the high value should be appended
if (highValue != null && inclHighValue && stringIndex.contains(highValue)) {
// need to add the high value
// get the subset following the high value
subset = stringIndex.tailSet(highValue);
// go to the second element of this set
Iterator<SPObject> it = subset.iterator();
assert it.hasNext();
// move to the highValue
it.next();
// see if there is more than just this element
if (!it.hasNext()) {
// no other items, so make the high value open ended
highValue = null;
} else {
// move the high value
highValue = it.next();
}
}
// slice out the required data
if (lowValue == null) {
// get from the start of stringIndex
if (highValue == null) {
// get the whole set
subset = stringIndex;
} else {
// get up to the high value
subset = stringIndex.headSet(highValue);
}
} else {
// get from after the low value
if (highValue == null) {
// get to the end
subset = stringIndex.tailSet(lowValue);
} else {
// get between the two limits
subset = stringIndex.subSet(lowValue, highValue);
}
}
// return a tuples based on this set
return new SetWrapperTuples(subset);
}
public Tuples findGNodes(
SPObject.TypeCategory typeCategory, URI typeURI
) throws StringPoolException {
if (typeURI != null) {
throw new UnsupportedOperationException(
"Finding typed literal nodes on the in memory string pool is not supported"
);
}
SortedSet<SPObject> subset = typeCategory != null ?
stringIndex.subSet(smallestSPObjects[typeCategory.ID], largestSPObjects[typeCategory.ID]) : stringIndex;
return new SetWrapperTuples(subset);
}
public XAStringPool newReadOnlyStringPool() {
throw new UnsupportedOperationException();
}
public XAStringPool newWritableStringPool() {
return this;
}
/**
* Releases the snapshot of the StringPool held by a read-only view.
*/
public void release() {
// Does nothing.
}
/**
* When applied to a read-only view of a StringPool, brings the view up to
* date with the current state of the StringPool.
*/
public void refresh() {
// Does nothing.
}
/**
* Advise that this string pool is no longer needed.
*
* @throws StringPoolException EXCEPTION TO DO
*/
public void close() throws StringPoolException {
nodeToStringPool.clear();
stringToNodePool.clear();
}
/**
* Close this string pool, if it is currently open, and remove all files
* associated with it.
*
* @throws StringPoolException EXCEPTION TO DO
*/
public void delete() throws StringPoolException {
nodeToStringPool = null;
stringToNodePool = null;
}
public void newNode(long gNode) {
throw new UnsupportedOperationException();
}
public void releaseNode(long gNode) {
throw new UnsupportedOperationException();
}
public void prepare() {}
public void commit() {}
public int[] recover() { return new int[0]; }
public void selectPhase(int phaseNumber) {}
public void rollback() {}
public void clear() {}
public void clear(int phaseNumber) {}
public int getPhaseNumber() { return 0; }
/**
* Used to wrap a set of SPObjects and return the associated GNodes as a Tuples.
*/
private class SetWrapperTuples implements Tuples {
/** The internal set data */
private SortedSet<SPObject> set;
/** The current row of data for this set */
private SPObject currentRow;
/** The iterator for the internal set */
private Iterator<SPObject> internalIterator;
/** The single column of variables for this object */
private Variable[] variables;
/**
* Constructor.
*
* @param set The set to wrap.
*/
public SetWrapperTuples(SortedSet<SPObject> set) {
this.set = set;
internalIterator = null;
currentRow = null;
variables = (Variable[])VARIABLES.clone();
}
/**
* {@inheritDoc}
*/
public Variable[] getVariables() {
return variables;
}
/**
* {@inheritDoc}
*/
public long getRowCount() throws TuplesException {
return set.size();
}
/**
* {@inheritDoc}
*/
public long getRowUpperBound() throws TuplesException {
return set.size();
}
/**
* {@inheritDoc}
*/
public long getRowExpectedCount() throws TuplesException {
return set.size();
}
/**
* {@inheritDoc}
*/
public int getRowCardinality() throws TuplesException {
int size = set.size();
return size == 0 ? ZERO :
size == 1 ? ONE :
MANY;
}
/**
* {@inheritDoc}
*/
public boolean isEmpty() throws TuplesException {
return set.isEmpty();
}
/**
* {@inheritDoc}
*/
public int getColumnIndex(Variable variable) throws TuplesException {
if (variable == null) {
throw new IllegalArgumentException("variable is null");
}
if (variable.equals(variables[0])) {
// The variable matches the one and only column.
return 0;
}
throw new TuplesException("variable doesn't match any column: " + variable);
}
/**
* {@inheritDoc}
*/
public boolean isColumnEverUnbound(int column) throws TuplesException {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isMaterialized() {
return true;
}
/**
* {@inheritDoc}
*/
public boolean isUnconstrained() throws TuplesException {
return false;
}
/**
* {@inheritDoc}
*/
public boolean hasNoDuplicates() throws TuplesException {
return true;
}
/**
* {@inheritDoc}
* data is unsorted, so return null.
*/
public RowComparator getComparator() {
return null;
}
/**
* {@inheritDoc}
*/
public java.util.List<Tuples> getOperands() {
return java.util.Collections.emptyList();
}
/**
* {@inheritDoc}
*/
public void beforeFirst() throws TuplesException {
internalIterator = set.iterator();
}
/**
* {@inheritDoc}
*/
public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
if (prefix.length > 1) {
throw new TuplesException("prefix is out of range");
}
if (prefix == NO_PREFIX) {
// no prefix, so just do a before first
internalIterator = set.iterator();
} else {
// check if the object exists
SPObject start = (SPObject)nodeToStringPool.get(prefix[0]);
if (start == null) {
Set<SPObject> empty = Collections.emptySet();
internalIterator = empty.iterator();
} else {
internalIterator = Collections.singleton(start).iterator();
}
}
}
/** {@inheritDoc} */
public long getColumnValue(int column) throws TuplesException {
if (column != 0) {
throw new TuplesException("Column does not exist");
}
return -stringToNodePool.get(currentRow);
}
/** {@inheritDoc} */
public long getRawColumnValue(int column) throws TuplesException {
return getColumnValue(column);
}
/**
* {@inheritDoc}
*/
public int getNumberOfVariables() {
return 1;
}
/**
* {@inheritDoc}
*/
public void renameVariables(Constraint constraint) {
variables[0] = (Variable)constraint.getElement(0);
}
/**
* {@inheritDoc}
*/
public boolean next() throws TuplesException {
if (internalIterator.hasNext()) {
currentRow = (SPObject)internalIterator.next();
return true;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public void close() throws TuplesException {
// no op
}
/**
* {@inheritDoc}
*/
public Object clone() {
try {
SetWrapperTuples s = (SetWrapperTuples)super.clone();
s.currentRow = null;
s.internalIterator = null;
return s;
} catch (CloneNotSupportedException e) {
throw new AssertionError("Unable to clone tuples");
}
}
/**
* {@inheritDoc}
*/
public boolean equals(Object object) {
// check if this is a tuples
if (!(object instanceof Tuples)) {
return false;
}
// SetWrapperTuples are the same if they contain the same sets
if (object instanceof SetWrapperTuples) {
return set.equals(((SetWrapperTuples)object).set);
}
// differing types, so ask the other tuples to do all the work
return ((Tuples)object).equals(this);
}
/**
* Added to match {@link #equals(Object)}.
*/
public int hashCode() {
return TuplesOperations.hashCode(this);
}
/**
* {@inheritDoc}
*/
public String toString() {
return set.toString();
}
/**
* Copied from AbstractTuples
*/
public Annotation getAnnotation(Class<? extends Annotation> annotationClass) throws TuplesException {
return null;
}
}
}