/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.nativerdf;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.aduna.io.ByteArrayUtil;
import org.openrdf.sail.nativerdf.btree.BTree;
import org.openrdf.sail.nativerdf.btree.RecordComparator;
import org.openrdf.sail.nativerdf.btree.RecordIterator;
import org.openrdf.store.StoreException;
/**
* File-based indexed storage and retrieval of RDF statements. TripleStore
* stores statements in the form of four integer IDs. Each ID represent an RDF
* value that is stored in a {@link ValueStore}. The four IDs refer to the
* statement's subject, predicate, object and context. The ID <tt>0</tt> is used
* to represent the "null" context and doesn't map to an actual RDF value.
*
* @author Arjohn Kampman
*/
class TripleStore {
final Logger logger = LoggerFactory.getLogger(this.getClass());
/*-----------*
* Constants *
*-----------*/
/**
* The default triple indexes.
*/
private static final String DEFAULT_INDEXES = "spoc,posc";
/**
* The file name for the properties file.
*/
private static final String PROPERTIES_FILE = "triples.prop";
/**
* The key used to store the triple store version in the properties file.
*/
private static final String VERSION_KEY = "version";
/**
* The key used to store the triple indexes specification that specifies
* which triple indexes exist.
*/
private static final String INDEXES_KEY = "triple-indexes";
/**
* The version number for the current triple store.
* <ul>
* <li>version 0: The first version which used a single spo-index. This
* version did not have a properties file yet.
* <li>version 1: Introduces configurable triple indexes and the properties
* file.
* <li>version 10: Introduces a context field, essentially making this a quad
* store.
* <li>version 10a: Introduces transaction flags, this is backwards
* compatible with version 10.
* </ul>
*/
private static final int SCHEME_VERSION = 10;
// 17 bytes are used to represent a triple:
// byte 0-3 : subject
// byte 4-7 : predicate
// byte 8-11: object
// byte 12-15: context
// byte 16: additional flag(s)
static final int RECORD_LENGTH = 17;
static final int SUBJ_IDX = 0;
static final int PRED_IDX = 4;
static final int OBJ_IDX = 8;
static final int CONTEXT_IDX = 12;
static final int FLAG_IDX = 16;
/**
* Bit field indicating that a statement has been explicitly added (instead
* of being inferred).
*/
static final byte EXPLICIT_FLAG = (byte)0x1; // 0000 0001
/**
* Bit field indicating that a statement has been added in a (currently
* active) transaction.
*/
static final byte ADDED_FLAG = (byte)0x2; // 0000 0010
/**
* Bit field indicating that a statement has been removed in a (currently
* active) transaction.
*/
static final byte REMOVED_FLAG = (byte)0x4; // 0000 0100
/**
* Bit field indicating that the explicit flag has been toggled (from true to
* false, or vice versa) in a (currently active) transaction.
*/
static final byte TOGGLE_EXPLICIT_FLAG = (byte)0x8; // 0000 1000
/*-----------*
* Variables *
*-----------*/
/**
* The directory that is used to store the index files.
*/
private final File dir;
/**
* Object containing meta-data for the triple store. This includes
*/
private final Properties properties;
/**
* The array of triple indexes that are used to store and retrieve triples.
*/
private final TripleIndex[] indexes;
private final boolean forceSync;
/**
* Flag indicating whether one or more triples have been flagged as "added"
* during the current transaction.
*/
private volatile boolean txnAddedTriples = false;
/**
* Flag indicating whether one or more triples have been flagged as "removed"
* during the current transaction.
*/
private volatile boolean txnRemovedTriples = false;
private volatile RecordCache updatedTriplesCache;
/*--------------*
* Constructors *
*--------------*/
public TripleStore(File dir, String indexSpecStr)
throws IOException, StoreException
{
this(dir, indexSpecStr, false);
}
public TripleStore(File dir, String indexSpecStr, boolean forceSync)
throws IOException, StoreException
{
this.dir = dir;
this.forceSync = forceSync;
properties = new Properties();
// Read triple properties file, restore indexes, re-index
File propFile = new File(dir, PROPERTIES_FILE);
if (propFile.exists()) {
loadProperties(propFile);
// Check version number
String versionStr = properties.getProperty(VERSION_KEY);
if (versionStr == null) {
logger.warn("version missing in TripleStore's properties file");
}
else {
try {
int version = Integer.parseInt(versionStr);
if (version < 10) {
throw new StoreException("Directory contains incompatible triple data");
}
else if (version > SCHEME_VERSION) {
throw new StoreException("Directory contains data that uses a newer data format");
}
}
catch (NumberFormatException e) {
logger.warn("Malformed version number in TripleStore's properties file");
}
}
}
Set<String> indexSpecs = parseIndexSpecList(indexSpecStr);
if (indexSpecs.isEmpty()) {
// No indexes specified, use existing indexes if possible
indexSpecStr = getCurrentIndexSpecStr();
indexSpecs = parseIndexSpecList(indexSpecStr);
if (indexSpecs.size() > 0) {
logger.debug("No indexes specified, using existing indexes: {}", indexSpecStr);
}
else {
// Create default indexes
indexSpecStr = DEFAULT_INDEXES;
indexSpecs = parseIndexSpecList(indexSpecStr);
logger.debug("No indexes specified or found, defaulting to indexes: {}", indexSpecStr);
}
}
// Initialize added indexes and delete removed ones:
reindex(indexSpecs);
if (!String.valueOf(SCHEME_VERSION).equals(properties.getProperty(VERSION_KEY))
|| !indexSpecStr.equals(getCurrentIndexSpecStr()))
{
// Store up-to-date properties
properties.setProperty(VERSION_KEY, String.valueOf(SCHEME_VERSION));
properties.setProperty(INDEXES_KEY, indexSpecStr);
storeProperties(propFile);
}
// Create specified indexes
indexes = new TripleIndex[indexSpecs.size()];
int i = 0;
for (String fieldSeq : indexSpecs) {
logger.debug("Activating index '" + fieldSeq + "'...");
indexes[i++] = new TripleIndex(fieldSeq);
}
}
/*---------*
* Methods *
*---------*/
/**
* Parses a comma/whitespace-separated list of index specifications. Index
* specifications are required to consists of 4 characters: 's', 'p', 'o' and
* 'c'.
*
* @param indexSpecStr
* A string like "spoc, pocs, cosp".
* @return A Set containing the parsed index specifications.
*/
private Set<String> parseIndexSpecList(String indexSpecStr)
throws StoreException
{
Set<String> indexes = new HashSet<String>();
if (indexSpecStr != null) {
StringTokenizer tok = new StringTokenizer(indexSpecStr, ", \t");
while (tok.hasMoreTokens()) {
String index = tok.nextToken().toLowerCase();
// sanity checks
if (index.length() != 4 || index.indexOf('s') == -1 || index.indexOf('p') == -1
|| index.indexOf('o') == -1 || index.indexOf('c') == -1)
{
throw new StoreException("invalid value '" + index + "' in index specification: "
+ indexSpecStr);
}
indexes.add(index);
}
}
return indexes;
}
private void reindex(Set<String> newIndexSpecs)
throws IOException, StoreException
{
// Check if the index specification has changed and update indexes if
// necessary
String currentIndexSpecStr = getCurrentIndexSpecStr();
if (currentIndexSpecStr == null) {
return;
}
Set<String> currentIndexSpecs = parseIndexSpecList(currentIndexSpecStr);
if (currentIndexSpecs.isEmpty()) {
throw new StoreException("Invalid index specification found in index properties");
}
// Determine the set of newly added indexes
Set<String> addedIndexSpecs = new HashSet<String>(newIndexSpecs);
addedIndexSpecs.removeAll(currentIndexSpecs);
if (!addedIndexSpecs.isEmpty()) {
// Initialize new indexes using an existing index as source
String sourceIndexSpec = currentIndexSpecs.iterator().next();
TripleIndex sourceIndex = new TripleIndex(sourceIndexSpec);
try {
for (String fieldSeq : addedIndexSpecs) {
logger.debug("Initializing new index '" + fieldSeq + "'...");
TripleIndex addedIndex = new TripleIndex(fieldSeq);
BTree addedBTree = addedIndex.getBTree();
RecordIterator sourceIter = sourceIndex.getBTree().iterateAll();
try {
byte[] value = null;
while ((value = sourceIter.next()) != null) {
addedBTree.insert(value);
}
}
finally {
sourceIter.close();
}
addedBTree.close();
}
logger.debug("New index(es) initialized");
}
finally {
sourceIndex.getBTree().close();
}
}
// Determine the set of removed indexes
Set<String> removedIndexSpecs = new HashSet<String>(currentIndexSpecs);
removedIndexSpecs.removeAll(newIndexSpecs);
// Delete files for removed indexes
for (String fieldSeq : removedIndexSpecs) {
TripleIndex removedIndex = new TripleIndex(fieldSeq);
boolean deleted = removedIndex.getBTree().delete();
if (deleted) {
logger.debug("Deleted file(s) for removed {} index", fieldSeq);
}
else {
logger.warn("Unable to delete file(s) for removed {} index", fieldSeq);
}
}
}
private String getCurrentIndexSpecStr() {
return properties.getProperty(INDEXES_KEY);
}
public void close()
throws IOException
{
for (int i = 0; i < indexes.length; i++) {
indexes[i].getBTree().close();
}
}
public RecordIterator getTriples(int subj, int pred, int obj, int context)
throws IOException
{
// Return all triples except those that were added but not yet committed
return getTriples(subj, pred, obj, context, 0, ADDED_FLAG);
}
public RecordIterator getTriples(int subj, int pred, int obj, int context, boolean readTransaction)
throws IOException
{
if (readTransaction) {
// Don't read removed statements
return getTriples(subj, pred, obj, context, 0, TripleStore.REMOVED_FLAG);
}
else {
// Don't read added statements
return getTriples(subj, pred, obj, context, 0, TripleStore.ADDED_FLAG);
}
}
/**
* If an index exists by context - use it, otherwise return null.
*
* @param readTransaction
* @return All triples sorted by context or null if no context index exists
* @throws IOException
*/
public RecordIterator getAllTriplesSortedByContext(boolean readTransaction)
throws IOException
{
if (readTransaction) {
// Don't read removed statements
return getAllTriplesSortedByContext(0, TripleStore.REMOVED_FLAG);
}
else {
// Don't read added statements
return getAllTriplesSortedByContext(0, TripleStore.ADDED_FLAG);
}
}
public RecordIterator getTriples(int subj, int pred, int obj, int context, boolean explicit,
boolean readTransaction)
throws IOException
{
int flags = 0;
int flagsMask = 0;
if (readTransaction) {
flagsMask |= TripleStore.REMOVED_FLAG;
// 'explicit' is handled through an ExplicitStatementFilter
}
else {
flagsMask |= TripleStore.ADDED_FLAG;
if (explicit) {
flags |= TripleStore.EXPLICIT_FLAG;
flagsMask |= TripleStore.EXPLICIT_FLAG;
}
}
RecordIterator btreeIter = getTriples(subj, pred, obj, context, flags, flagsMask);
if (readTransaction && explicit) {
// Filter implicit statements from the result
btreeIter = new ExplicitStatementFilter(btreeIter);
}
return btreeIter;
}
/*-------------------------------------*
* Inner class ExplicitStatementFilter *
*-------------------------------------*/
private static class ExplicitStatementFilter implements RecordIterator {
private final RecordIterator wrappedIter;
public ExplicitStatementFilter(RecordIterator wrappedIter) {
this.wrappedIter = wrappedIter;
}
public byte[] next()
throws IOException
{
byte[] result;
while ((result = wrappedIter.next()) != null) {
byte flags = result[TripleStore.FLAG_IDX];
boolean explicit = (flags & TripleStore.EXPLICIT_FLAG) != 0;
boolean toggled = (flags & TripleStore.TOGGLE_EXPLICIT_FLAG) != 0;
if (explicit != toggled) {
// Statement is either explicit and hasn't been toggled, or vice
// versa
break;
}
}
return result;
}
public void set(byte[] value)
throws IOException
{
wrappedIter.set(value);
}
public void close()
throws IOException
{
wrappedIter.close();
}
@Override
public String toString() {
return "StatementFilter " + wrappedIter.toString();
}
} // end inner class ExplicitStatementFilter
private RecordIterator getTriples(int subj, int pred, int obj, int context, int flags, int flagsMask)
throws IOException
{
TripleIndex index = getBestIndex(subj, pred, obj, context);
boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0;
return getTriplesUsingIndex(subj, pred, obj, context, flags, flagsMask, index, doRangeSearch);
}
private RecordIterator getAllTriplesSortedByContext(int flags, int flagsMask)
throws IOException
{
for (TripleIndex index : indexes) {
if (index.getFieldSeq()[0] == 'c') {
// found a context-first index
return getTriplesUsingIndex(-1, -1, -1, -1, flags, flagsMask, index, false);
}
}
return null;
}
private RecordIterator getTriplesUsingIndex(int subj, int pred, int obj, int context, int flags,
int flagsMask, TripleIndex index, boolean rangeSearch)
{
byte[] searchKey = getSearchKey(subj, pred, obj, context, flags);
byte[] searchMask = getSearchMask(subj, pred, obj, context, flagsMask);
if (rangeSearch) {
// Use ranged search
byte[] minValue = getMinValue(subj, pred, obj, context);
byte[] maxValue = getMaxValue(subj, pred, obj, context);
return index.getBTree().iterateRangedValues(searchKey, searchMask, minValue, maxValue);
}
else {
// Use sequential scan
return index.getBTree().iterateValues(searchKey, searchMask);
}
}
protected double cardinality(int subj, int pred, int obj, int context)
throws IOException
{
TripleIndex index = getBestIndex(subj, pred, obj, context);
BTree btree = index.btree;
double rangeSize;
if (index.getPatternScore(subj, pred, obj, context) == 0) {
rangeSize = btree.getValueCountEstimate();
}
else {
byte[] minValue = getMinValue(subj, pred, obj, context);
byte[] maxValue = getMaxValue(subj, pred, obj, context);
rangeSize = btree.getValueCountEstimate(minValue, maxValue);
}
return rangeSize;
}
protected TripleIndex getBestIndex(int subj, int pred, int obj, int context) {
int bestScore = -1;
TripleIndex bestIndex = null;
for (TripleIndex index : indexes) {
int score = index.getPatternScore(subj, pred, obj, context);
if (score > bestScore) {
bestScore = score;
bestIndex = index;
}
}
return bestIndex;
}
public void clear()
throws IOException
{
for (int i = 0; i < indexes.length; i++) {
indexes[i].getBTree().clear();
}
}
public boolean storeTriple(int subj, int pred, int obj, int context)
throws IOException
{
return storeTriple(subj, pred, obj, context, true);
}
public boolean storeTriple(int subj, int pred, int obj, int context, boolean explicit)
throws IOException
{
boolean result = false;
byte[] data = getData(subj, pred, obj, context, 0);
byte[] storedData = indexes[0].getBTree().get(data);
if (storedData == null) {
// Statement does not yet exist
data[FLAG_IDX] |= ADDED_FLAG;
if (explicit) {
data[FLAG_IDX] |= EXPLICIT_FLAG;
}
result = true;
txnAddedTriples = true;
}
else {
// Statement already exists, only modify its flags, see txn-flags.txt
// for a description of the flag transformations
byte flags = storedData[FLAG_IDX];
boolean isExplicit = (flags & EXPLICIT_FLAG) != 0;
boolean added = (flags & ADDED_FLAG) != 0;
boolean removed = (flags & REMOVED_FLAG) != 0;
boolean toggled = (flags & TOGGLE_EXPLICIT_FLAG) != 0;
if (added) {
// Statement has been added in the current transaction and is
// invisible to other connections
data[FLAG_IDX] |= ADDED_FLAG;
if (explicit || isExplicit) {
data[FLAG_IDX] |= EXPLICIT_FLAG;
}
}
else {
// Committed statement, must keep explicit flag the same
if (isExplicit) {
data[FLAG_IDX] |= EXPLICIT_FLAG;
}
if (explicit) {
if (!isExplicit) {
// Make inferred statement explicit
data[FLAG_IDX] |= TOGGLE_EXPLICIT_FLAG;
}
}
else {
if (removed) {
if (isExplicit) {
// Re-add removed explicit statement as inferred
data[FLAG_IDX] |= TOGGLE_EXPLICIT_FLAG;
}
}
else if (toggled) {
data[FLAG_IDX] |= TOGGLE_EXPLICIT_FLAG;
}
}
}
// Statement is new when it used to be removed
result = removed;
}
if (storedData == null || !Arrays.equals(data, storedData)) {
for (TripleIndex index : indexes) {
index.getBTree().insert(data);
}
updatedTriplesCache.storeRecord(data);
}
return result;
}
public int removeTriples(int subj, int pred, int obj, int context)
throws IOException
{
RecordIterator iter = getTriples(subj, pred, obj, context, 0, 0);
return removeTriples(iter);
}
/**
* @param subj
* The subject for the pattern, or <tt>-1</tt> for a wildcard.
* @param pred
* The predicate for the pattern, or <tt>-1</tt> for a wildcard.
* @param obj
* The object for the pattern, or <tt>-1</tt> for a wildcard.
* @param context
* The context for the pattern, or <tt>-1</tt> for a wildcard.
* @param explicit
* Flag indicating whether explicit or inferred statements should be
* removed; <tt>true</tt> removes explicit statements that match the
* pattern, <tt>false</tt> removes inferred statements that match the
* pattern.
* @return The number of triples that were removed.
* @throws IOException
*/
public int removeTriples(int subj, int pred, int obj, int context, boolean explicit)
throws IOException
{
byte flags = explicit ? EXPLICIT_FLAG : 0;
RecordIterator iter = getTriples(subj, pred, obj, context, flags, EXPLICIT_FLAG);
return removeTriples(iter);
}
private int removeTriples(RecordIterator iter)
throws IOException
{
byte[] data = iter.next();
if (data == null) {
// no discarded triples
return 0;
}
int count = 0;
// Store the values that need to be removed in a tmp file and then
// iterate over this file to set the REMOVED flag
RecordCache removedTriplesCache = new SequentialRecordCache(dir, RECORD_LENGTH);
try {
while (data != null) {
if ((data[FLAG_IDX] & REMOVED_FLAG) == 0) {
data[FLAG_IDX] |= REMOVED_FLAG;
removedTriplesCache.storeRecord(data);
}
data = iter.next();
}
iter.close();
count = (int)removedTriplesCache.getRecordCount();
updatedTriplesCache.storeRecords(removedTriplesCache);
for (TripleIndex index : indexes) {
BTree btree = index.getBTree();
RecordIterator recIter = removedTriplesCache.getRecords();
try {
while ((data = recIter.next()) != null) {
btree.insert(data);
}
}
finally {
recIter.close();
}
}
}
finally {
removedTriplesCache.discard();
}
if (count > 0) {
txnRemovedTriples = true;
}
return count;
}
private void discardTriples(RecordIterator iter)
throws IOException
{
byte[] data = iter.next();
if (data == null) {
// no discarded triples
return;
}
// Store the values that need to be discarded in a tmp file and then
// iterate over this file to discard the values
RecordCache recordCache = new SequentialRecordCache(dir, RECORD_LENGTH);
try {
while (data != null) {
recordCache.storeRecord(data);
data = iter.next();
}
iter.close();
for (TripleIndex index : indexes) {
BTree btree = index.getBTree();
RecordIterator recIter = recordCache.getRecords();
try {
while ((data = recIter.next()) != null) {
btree.remove(data);
}
}
finally {
recIter.close();
}
}
}
finally {
recordCache.discard();
}
}
public void startTransaction()
throws IOException
{
// Create a record cache for storing updated triples with a maximum of
// some 10% of the number of triples
long maxRecords = indexes[0].getBTree().getValueCountEstimate() / 10L;
updatedTriplesCache = new SortedRecordCache(dir, RECORD_LENGTH, maxRecords,
new TripleComparator("spoc"));
}
public void commit()
throws IOException
{
if (txnRemovedTriples) {
RecordIterator iter = getTriples(-1, -1, -1, -1, REMOVED_FLAG, REMOVED_FLAG);
try {
discardTriples(iter);
}
finally {
txnRemovedTriples = false;
iter.close();
}
}
boolean validCache = updatedTriplesCache.isValid();
for (TripleIndex index : indexes) {
BTree btree = index.getBTree();
RecordIterator iter;
if (validCache) {
// Use the cached set of updated triples
iter = updatedTriplesCache.getRecords();
}
else {
// Cache is invalid; too much updates(?). Iterate over all triples
iter = btree.iterateAll();
}
try {
byte[] data = null;
while ((data = iter.next()) != null) {
byte flags = data[FLAG_IDX];
boolean added = (flags & ADDED_FLAG) != 0;
boolean removed = (flags & REMOVED_FLAG) != 0;
boolean toggled = (flags & TOGGLE_EXPLICIT_FLAG) != 0;
if (removed) {
// Record has been discarded earlier, do not put it back in!
continue;
}
if (added || toggled) {
if (toggled) {
data[FLAG_IDX] ^= EXPLICIT_FLAG;
}
if (added) {
data[FLAG_IDX] ^= ADDED_FLAG;
}
if (validCache) {
// We're iterating the cache
btree.insert(data);
}
else {
// We're iterating the BTree itself
iter.set(data);
}
}
}
}
finally {
iter.close();
}
}
updatedTriplesCache.discard();
updatedTriplesCache = null;
sync();
}
public void rollback()
throws IOException
{
if (txnAddedTriples) {
RecordIterator iter = getTriples(-1, -1, -1, -1, ADDED_FLAG, ADDED_FLAG);
try {
discardTriples(iter);
}
finally {
txnAddedTriples = false;
iter.close();
}
}
boolean validCache = updatedTriplesCache.isValid();
byte txnFlagsMask = ~(ADDED_FLAG | REMOVED_FLAG | TOGGLE_EXPLICIT_FLAG);
for (TripleIndex index : indexes) {
BTree btree = index.getBTree();
RecordIterator iter;
if (validCache) {
// Use the cached set of updated triples
iter = updatedTriplesCache.getRecords();
}
else {
// Cache is invalid; too much updates(?). Iterate over all triples
iter = btree.iterateAll();
}
try {
byte[] data = null;
while ((data = iter.next()) != null) {
byte flags = data[FLAG_IDX];
boolean removed = (flags & REMOVED_FLAG) != 0;
boolean toggled = (flags & TOGGLE_EXPLICIT_FLAG) != 0;
if (removed || toggled) {
data[FLAG_IDX] &= txnFlagsMask;
if (validCache) {
// We're iterating the cache
btree.insert(data);
}
else {
// We're iterating the BTree itself
iter.set(data);
}
}
}
}
finally {
iter.close();
}
}
updatedTriplesCache.discard();
updatedTriplesCache = null;
sync();
}
protected void sync()
throws IOException
{
for (int i = 0; i < indexes.length; i++) {
indexes[i].getBTree().sync();
}
}
private byte[] getData(int subj, int pred, int obj, int context, int flags) {
byte[] data = new byte[RECORD_LENGTH];
ByteArrayUtil.putInt(subj, data, SUBJ_IDX);
ByteArrayUtil.putInt(pred, data, PRED_IDX);
ByteArrayUtil.putInt(obj, data, OBJ_IDX);
ByteArrayUtil.putInt(context, data, CONTEXT_IDX);
data[FLAG_IDX] = (byte)flags;
return data;
}
private byte[] getSearchKey(int subj, int pred, int obj, int context, int flags) {
return getData(subj, pred, obj, context, flags);
}
private byte[] getSearchMask(int subj, int pred, int obj, int context, int flags) {
byte[] mask = new byte[RECORD_LENGTH];
if (subj != -1) {
ByteArrayUtil.putInt(0xffffffff, mask, SUBJ_IDX);
}
if (pred != -1) {
ByteArrayUtil.putInt(0xffffffff, mask, PRED_IDX);
}
if (obj != -1) {
ByteArrayUtil.putInt(0xffffffff, mask, OBJ_IDX);
}
if (context != -1) {
ByteArrayUtil.putInt(0xffffffff, mask, CONTEXT_IDX);
}
mask[FLAG_IDX] = (byte)flags;
return mask;
}
private byte[] getMinValue(int subj, int pred, int obj, int context) {
byte[] minValue = new byte[RECORD_LENGTH];
ByteArrayUtil.putInt((subj == -1 ? 0x00000000 : subj), minValue, SUBJ_IDX);
ByteArrayUtil.putInt((pred == -1 ? 0x00000000 : pred), minValue, PRED_IDX);
ByteArrayUtil.putInt((obj == -1 ? 0x00000000 : obj), minValue, OBJ_IDX);
ByteArrayUtil.putInt((context == -1 ? 0x00000000 : context), minValue, CONTEXT_IDX);
minValue[FLAG_IDX] = (byte)0;
return minValue;
}
private byte[] getMaxValue(int subj, int pred, int obj, int context) {
byte[] maxValue = new byte[RECORD_LENGTH];
ByteArrayUtil.putInt((subj == -1 ? 0xffffffff : subj), maxValue, SUBJ_IDX);
ByteArrayUtil.putInt((pred == -1 ? 0xffffffff : pred), maxValue, PRED_IDX);
ByteArrayUtil.putInt((obj == -1 ? 0xffffffff : obj), maxValue, OBJ_IDX);
ByteArrayUtil.putInt((context == -1 ? 0xffffffff : context), maxValue, CONTEXT_IDX);
maxValue[FLAG_IDX] = (byte)0xff;
return maxValue;
}
private void loadProperties(File propFile)
throws IOException
{
InputStream in = new FileInputStream(propFile);
try {
properties.clear();
properties.load(in);
}
finally {
in.close();
}
}
private void storeProperties(File propFile)
throws IOException
{
OutputStream out = new FileOutputStream(propFile);
try {
properties.store(out, "triple indexes meta-data, DO NOT EDIT!");
}
finally {
out.close();
}
}
/*-------------------------*
* Inner class TripleIndex *
*-------------------------*/
private class TripleIndex {
private TripleComparator tripleComparator;
private BTree btree;
public TripleIndex(String fieldSeq)
throws IOException
{
tripleComparator = new TripleComparator(fieldSeq);
btree = new BTree(dir, getFilenamePrefix(fieldSeq), 2048, RECORD_LENGTH, tripleComparator, forceSync);
}
private String getFilenamePrefix(String fieldSeq) {
return "triples-" + fieldSeq;
}
public char[] getFieldSeq() {
return tripleComparator.getFieldSeq();
}
public File getFile() {
return btree.getFile();
}
public BTree getBTree() {
return btree;
}
/**
* Determines the 'score' of this index on the supplied pattern of
* subject, predicate, object and context IDs. The higher the score, the
* better the index is suited for matching the pattern. Lowest score is 0,
* which means that the index will perform a sequential scan.
*/
public int getPatternScore(int subj, int pred, int obj, int context) {
int score = 0;
for (char field : tripleComparator.getFieldSeq()) {
switch (field) {
case 's':
if (subj >= 0) {
score++;
}
else {
return score;
}
break;
case 'p':
if (pred >= 0) {
score++;
}
else {
return score;
}
break;
case 'o':
if (obj >= 0) {
score++;
}
else {
return score;
}
break;
case 'c':
if (context >= 0) {
score++;
}
else {
return score;
}
break;
default:
throw new RuntimeException("invalid character '" + field + "' in field sequence: "
+ new String(tripleComparator.getFieldSeq()));
}
}
return score;
}
}
/*------------------------------*
* Inner class TripleComparator *
*------------------------------*/
/**
* A RecordComparator that can be used to create indexes with a configurable
* order of the subject, predicate, object and context fields.
*/
private static class TripleComparator implements RecordComparator {
private char[] fieldSeq;
public TripleComparator(String fieldSeq) {
this.fieldSeq = fieldSeq.toCharArray();
}
public char[] getFieldSeq() {
return fieldSeq;
}
public final int compareBTreeValues(byte[] key, byte[] data, int offset, int length) {
for (char field : fieldSeq) {
int fieldIdx = 0;
switch (field) {
case 's':
fieldIdx = SUBJ_IDX;
break;
case 'p':
fieldIdx = PRED_IDX;
break;
case 'o':
fieldIdx = OBJ_IDX;
break;
case 'c':
fieldIdx = CONTEXT_IDX;
break;
default:
throw new IllegalArgumentException("invalid character '" + field + "' in field sequence: "
+ new String(fieldSeq));
}
int diff = ByteArrayUtil.compareRegion(key, fieldIdx, data, offset + fieldIdx, 4);
if (diff != 0) {
return diff;
}
}
return 0;
}
}
}