/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xbird.storage.indexer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import xbird.storage.DbCollection;
import xbird.storage.DbException;
import xbird.storage.index.*;
/**
* ValueIndexer is a basic implementation of the Indexer interface.
* It is used for maintaining element and element@attribute value
* indexes.
*/
public final class ValueIndexer extends BTree implements Indexer {
private static final Log LOG = LogFactory.getLog(ValueIndexer.class);
private static final IndexMatch[] EmptyMatches = new IndexMatch[0];
private static final Value EmptyValue = new Value(new byte[0]);
private static final long MATCH_INFO = -1000;
private static final String NAME = "name";
private static final String PATTERN = "pattern";
private static final String TYPE = "type";
private static final int STRING = 0;
private static final int TRIMMED = 1;
private static final int INTEGER = 2;
private static final int FLOAT = 3;
private static final int BYTE = 4;
private static final int CHAR = 5;
private static final int BOOLEAN = 6;
private static final int[] sizes = {-1, -1, 8, 8, 1, 2, 1};
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 DbCollection collection;
private SymbolTable symbols;
private String name;
private String pattern;
private int type;
private int typeSize = 32;
private boolean wildcard = false;
public ValueIndexer() {
super();
}
/**
* Override createFileHeader - set page size to 1024
*/
public Paged.FileHeader createFileHeader() {
Paged.FileHeader header = super.createFileHeader();
header.setPageSize(1024);
return header;
}
public void setConfig(Configuration config) {
super.setConfig(config);
try {
name = config.getAttribute(NAME);
pattern = config.getAttribute(PATTERN);
wildcard = pattern.indexOf('*') != -1;
// Determine the Index Type
String tv = config.getAttribute(TYPE, STRING_VAL).toLowerCase();
if (tv.equals(STRING_VAL)) {
type = STRING;
} else if (tv.equals(TRIMMED_VAL)) {
type = TRIMMED;
} else if (tv.equals(SHORT_VAL)) {
type = INTEGER;
} else if (tv.equals(INT_VAL)) {
type = INTEGER;
} else if (tv.equals(LONG_VAL)) {
type = INTEGER;
} else if (tv.equals(FLOAT_VAL)) {
type = FLOAT;
} else if (tv.equals(DOUBLE_VAL)) {
type = FLOAT;
} else if (tv.equals(BYTE_VAL)) {
type = BYTE;
} else if (tv.equals(CHAR_VAL)) {
type = CHAR;
} else if (tv.equals(BOOLEAN_VAL)) {
type = BOOLEAN;
} else {
if (pattern.indexOf('@') != -1) {
type = STRING;
} else {
type = TRIMMED;
}
}
typeSize = sizes[type];
setLocation(name);
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
}
public String getName() {
return name;
}
public void setLocation(String location) {
setFile(new File(collection.getCollectionRoot(), location + ".idx"));
}
public void setCollection(DbCollection collection) {
try {
this.collection = collection;
symbols = collection.getSymbols();
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
}
public String getIndexStyle() {
return STYLE_NODEVALUE;
}
public String getPattern() {
return pattern;
}
public Value getTypedValue(String value) {
if (type != STRING && type != TRIMMED) {
value = value.trim();
if (value.length() == 0) {
return EmptyValue;
}
byte[] b = new byte[typeSize];
try {
switch (type) {
case INTEGER:
long l = Long.parseLong(value);
b[0] = (byte) ((l >>> 56) & 0xFF);
b[1] = (byte) ((l >>> 48) & 0xFF);
b[2] = (byte) ((l >>> 40) & 0xFF);
b[3] = (byte) ((l >>> 32) & 0xFF);
b[4] = (byte) ((l >>> 24) & 0xFF);
b[5] = (byte) ((l >>> 16) & 0xFF);
b[6] = (byte) ((l >>> 8) & 0xFF);
b[7] = (byte) ((l >>> 0) & 0xFF);
break;
case FLOAT:
double d = Double.parseDouble(value);
int i1 = (int) Math.round(d);
int i2 = (int) Math.round((d - i1) * 1000000000);
b[0] = (byte) ((i1 >>> 24) & 0xFF);
b[1] = (byte) ((i1 >>> 16) & 0xFF);
b[2] = (byte) ((i1 >>> 8) & 0xFF);
b[3] = (byte) ((i1 >>> 0) & 0xFF);
b[4] = (byte) ((i2 >>> 24) & 0xFF);
b[5] = (byte) ((i2 >>> 16) & 0xFF);
b[6] = (byte) ((i2 >>> 8) & 0xFF);
b[7] = (byte) ((i2 >>> 0) & 0xFF);
break;
case BYTE:
b[0] = Byte.parseByte(value);
break;
case CHAR:
char c = value.charAt(0);
b[0] = (byte) ((c >>> 8) & 0xFF);
b[1] = (byte) ((c >>> 0) & 0xFF);
break;
case BOOLEAN:
if ("[true][yes][1][y][on]".indexOf("[" + value.toLowerCase() + "]") != -1) {
b[0] = 1;
} else if ("[false][no][0][n][off]".indexOf("[" + value.toLowerCase() + "]") != -1) {
b[0] = 0;
} else {
return EmptyValue;
}
break;
default:
if (LOG.isWarnEnabled()) {
LOG.warn("invalid type : " + type);
}
}
return new Value(b);
} catch (Exception e) {
return EmptyValue;
}
}
if (type == TRIMMED) {
value = QueryEngine.normalizeString(value);
}
return new Value(value);
}
private Value getCombinedValue(Key key, int pos, int len, short elemID, short attrID) {
Value result;
try {
int l = key.getLength();
byte[] b = new byte[l + 13];
// Write the key
key.copyTo(b, 0, l);
b[l] = 0;
// Write the pos
b[l + 1] = (byte) ((pos >>> 24) & 0xFF);
b[l + 2] = (byte) ((pos >>> 16) & 0xFF);
b[l + 3] = (byte) ((pos >>> 8) & 0xFF);
b[l + 4] = (byte) ((pos >>> 0) & 0xFF);
// Write the len
b[l + 5] = (byte) ((len >>> 24) & 0xFF);
b[l + 6] = (byte) ((len >>> 16) & 0xFF);
b[l + 7] = (byte) ((len >>> 8) & 0xFF);
b[l + 8] = (byte) ((len >>> 0) & 0xFF);
// Write the elemID
b[l + 9] = (byte) ((elemID >>> 8) & 0xFF);
b[l + 10] = (byte) ((elemID >>> 0) & 0xFF);
// Write the attrID
b[l + 11] = (byte) ((attrID >>> 8) & 0xFF);
b[l + 12] = (byte) ((attrID >>> 0) & 0xFF);
result = new Value(b);
} catch (Exception e) {
result = null; // This will never happen
}
return result;
}
private IndexMatch getIndexMatch(Value v) {
byte[] b = v.getData();
int l = b.length - 13;
Key key = new Key(b, 0, b.length - 13);
int pos = ((b[l + 1] << 24) | (b[l + 2] << 16) | (b[l + 3] << 8) | b[l + 4]);
int len = ((b[l + 5] << 24) | (b[l + 6] << 16) | (b[l + 7] << 8) | b[l + 8]);
short elemID = (short) ((b[l + 9] << 8) | b[l + 10]);
short attrID = (short) ((b[l + 11] << 8) | b[l + 12]);
return new IndexMatch(key, pos, len, elemID, attrID);
}
public void remove(String value, Key key, int pos, int len, short elemID, short attrID) throws DBException {
Value v = getTypedValue(value);
if (type != STRING && type != TRIMMED && v.getLength() == 0) {
return;
}
try {
BTreeRootInfo root = findBTreeRoot(v);
Value cv = getCombinedValue(key, pos, len, elemID, attrID);
removeValue(root, cv);
} catch (DBException e) {
throw e;
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
}
public void add(String value, Key key, int pos, int len, short elemID, short attrID) throws DBException {
Value v = getTypedValue(value);
if (type != STRING && type != TRIMMED && v.getLength() == 0) {
return;
}
try {
BTreeRootInfo root;
try {
root = findBTreeRoot(v);
} catch (BTreeNotFoundException e) {
root = createBTreeRoot(v);
}
Value cv = getCombinedValue(key, pos, len, elemID, attrID);
addValue(root, cv, MATCH_INFO);
} catch (DBException e) {
throw e;
} catch (IOException e) {
throw new BTreeCorruptException("Corruption detected on add", e);
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
}
public void flush() throws DbException {
super.flush();
}
public IndexMatch[] matches(final IndexQuery query) throws DbException {
// Pre-process the value-set for typing and trimming
if (type != STRING) {
Value[] vals = query.getValues();
for (int i = 0; i < vals.length; i++) {
vals[i] = getTypedValue(vals[i].toString());
}
}
// Now issue the query
final List results = new ArrayList();
try {
search(query, new BTreeCallback() {
public boolean indexInfo(Value value, long pos) {
try {
if (pos == MATCH_INFO) {
IndexMatch match = getIndexMatch(value);
if (!wildcard)
results.add(match);
else {
IndexPattern pt = new IndexPattern(symbols, match.getElement(), match.getAttribute());
if (pt.getMatchLevel(query.getPattern()) > 0) {
results.add(match);
}
}
} else {
BTreeRootInfo root = new BTreeRootInfo(value, pos);
query(root, null, this);
}
return true;
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
return true;
}
});
} catch (IOException e) {
throw new BTreeCorruptException("Corruption detected on query", e);
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("ignored exception", e);
}
}
return (IndexMatch[]) results.toArray(EmptyMatches);
}
}