/*
* Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved.
*
* 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 com.hazelcast.query;
import com.hazelcast.core.MapEntry;
import com.hazelcast.impl.Record;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class Index {
// recordId -- indexValue
private final ConcurrentMap<Long, Long> recordValues = new ConcurrentHashMap<Long, Long>(100, 0.75f, 1);
// indexValue -- Map<recordId, Record>
private final IndexStore indexStore;
private final Expression expression;
private final boolean ordered;
private final int attributeIndex;
private volatile byte returnType = -1;
volatile boolean strong = false;
volatile boolean checkedStrength = false;
private static final int TYPE_STRING = 101;
private static final int TYPE_INT = 102;
private static final int TYPE_LONG = 103;
private static final int TYPE_BOOLEAN = 104;
private static final int TYPE_DOUBLE = 105;
private static final int TYPE_FLOAT = 106;
private static final int TYPE_BYTE = 107;
private static final int TYPE_CLASS = 108;
private static final int TYPE_UNKNOWN = 109;
Index(Expression expression, boolean ordered, int attributeIndex) {
this.expression = expression;
this.ordered = ordered;
this.attributeIndex = attributeIndex;
if (ordered) {
indexStore = new SortedIndexStore();
} else {
indexStore = new UnsortedIndexStore();
}
}
public void index(Long newValue, Record record) {
if (expression != null && returnType == -1) {
returnType = record.getIndexTypes()[attributeIndex];
}
final Long recordId = record.getId();
Long oldValue = recordValues.get(recordId);
if (record.isActive()) {
// add or update
if (oldValue == null) {
// record is new
newRecordIndex(newValue, record);
} else if (!oldValue.equals(newValue)) {
// record is updated
removeRecordIndex(oldValue, recordId);
newRecordIndex(newValue, record);
}
} else {
// remove the index
if (oldValue != null) {
removeRecordIndex(oldValue, recordId);
}
recordValues.remove(recordId);
}
}
public Long extractLongValue(Object value) {
Object extractedValue = expression.getValue(value);
setIndexType(extractedValue);
if (extractedValue == null) {
return Long.MIN_VALUE;
} else {
returnType = getIndexType(extractedValue.getClass());
if (!checkedStrength) {
if (extractedValue instanceof Boolean || extractedValue instanceof Number) {
strong = true;
}
checkedStrength = true;
}
return getLongValueByType(extractedValue);
}
}
private void newRecordIndex(Long newValue, Record record) {
Long recordId = record.getId();
indexStore.newRecordIndex(newValue, record);
recordValues.put(recordId, newValue);
}
private void removeRecordIndex(Long oldValue, Long recordId) {
recordValues.remove(recordId);
indexStore.removeRecordIndex(oldValue, recordId);
}
public void appendState(StringBuffer sbState) {
sbState.append("\nexp:" + expression + ", recordValues:" + recordValues.size() + ", " + indexStore);
}
public Set<MapEntry> getRecords(Set<Long> uniqueValues) {
if (uniqueValues.size() == 1) {
return indexStore.getRecords(uniqueValues.iterator().next());
} else {
MultiResultSet results = new MultiResultSet(recordValues);
indexStore.getRecords(results, uniqueValues);
return results;
}
}
public Set<MapEntry> getRecords(Long value) {
return indexStore.getRecords(value);
}
public Set<MapEntry> getSubRecordsBetween(Long from, Long to) {
MultiResultSet results = new MultiResultSet(recordValues);
indexStore.getSubRecordsBetween(results, from, to);
return results;
}
public Set<MapEntry> getSubRecords(boolean equal, boolean lessThan, Long searchedValue) {
MultiResultSet results = new MultiResultSet(recordValues);
indexStore.getSubRecords(results, equal, lessThan, searchedValue);
return results;
}
void setIndexType(Object extractedValue) {
if (returnType == -1) {
if (expression instanceof Predicates.GetExpressionImpl) {
Predicates.GetExpressionImpl ex = (Predicates.GetExpressionImpl) expression;
returnType = getIndexType(ex.getter.getReturnType());
} else {
if (extractedValue == null) throw new RuntimeException("Indexed value cannot be null!");
returnType = getIndexType(extractedValue.getClass());
}
}
}
public byte getIndexType() {
return returnType;
}
public static byte getIndexType(Class klass) {
if (klass == String.class) {
return TYPE_STRING;
} else if (klass == int.class || klass == Integer.class) {
return TYPE_INT;
} else if (klass == long.class || klass == Long.class) {
return TYPE_LONG;
} else if (klass == boolean.class || klass == Boolean.class) {
return TYPE_BOOLEAN;
} else if (klass == double.class || klass == Double.class) {
return TYPE_DOUBLE;
} else if (klass == float.class || klass == Float.class) {
return TYPE_FLOAT;
} else if (klass == byte.class || klass == Byte.class) {
return TYPE_BYTE;
} else if (klass == char.class || klass == Character.class) {
return TYPE_CLASS;
} else {
return TYPE_UNKNOWN;
}
}
private static long getLongValueByType(Object value) {
if (value == null) return Long.MIN_VALUE;
if (value instanceof Double) {
return Double.doubleToLongBits((Double) value);
} else if (value instanceof Float) {
return Float.floatToIntBits((Float) value);
} else if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof Boolean) {
return (Boolean.TRUE.equals(value)) ? 1 : -1;
} else if (value instanceof String) {
return value.hashCode();
} else {
return value.hashCode();
}
}
long getLongValue(Object value) {
if (value == null) return Long.MIN_VALUE;
int valueType = getIndexType(value.getClass());
if (valueType != returnType) {
if (value instanceof String) {
String str = (String) value;
if (returnType == TYPE_INT) {
value = Integer.valueOf(str);
} else if (returnType == TYPE_LONG) {
value = Long.valueOf(str);
} else if (returnType == TYPE_BOOLEAN) {
value = Boolean.valueOf(str);
} else if (returnType == TYPE_DOUBLE) {
value = Double.valueOf(str);
} else if (returnType == TYPE_FLOAT) {
value = Float.valueOf(str);
} else if (returnType == TYPE_BYTE) {
value = Byte.valueOf(str);
} else if (returnType == TYPE_CLASS) {
value = str.hashCode();
}
}
}
return getLongValueByType(value);
}
public int getAttributeIndex() {
return attributeIndex;
}
ConcurrentMap<Long, Long> getRecordValues() {
return recordValues;
}
ConcurrentMap<Long, ConcurrentMap<Long, Record>> getMapRecords() {
return indexStore.getMapRecords();
}
public Expression getExpression() {
return expression;
}
public boolean isOrdered() {
return ordered;
}
public boolean isStrong() {
return strong;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Index index = (Index) o;
return expression.equals(index.expression);
}
@Override
public int hashCode() {
return expression.hashCode();
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append("Index{");
sb.append("recordValues=").append(recordValues.size());
sb.append(", ").append(indexStore);
sb.append(", ordered=").append(ordered);
sb.append(", strong=").append(strong);
sb.append(", expression=").append(expression);
sb.append('}');
return sb.toString();
}
}