/* This file is part of the db4o object database http://www.db4o.com
Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com
db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.
db4o is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see http://www.gnu.org/licenses/. */
package com.db4o.internal.query;
import java.util.*;
import com.db4o.ext.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.handlers.*;
import com.db4o.internal.marshall.*;
import com.db4o.reflect.*;
import com.db4o.ta.*;
import com.db4o.typehandlers.*;
public class SodaQueryComparator implements Comparator<Integer>, IntComparator {
public static class Ordering {
@decaf.Public
private Direction _direction;
@decaf.Public
private String[] _fieldPath;
@decaf.Public
transient List<FieldMetadata> _resolvedPath;
public Ordering(Direction direction, String... fieldPath) {
_direction = direction;
_fieldPath = fieldPath;
}
public Direction direction() {
return _direction;
}
public String[] fieldPath() {
return _fieldPath;
}
}
public static class Direction {
public static final Direction ASCENDING = new Direction(0);
public static final Direction DESCENDING = new Direction(1);
@decaf.Public
private int value;
@decaf.Public
private Direction() {
}
private Direction(int value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
return ((Direction)obj).value == value;
}
@Override
public String toString() {
return this.equals(ASCENDING) ? "ASCENDING" : "DESCENDING";
}
}
private final LocalObjectContainer _container;
private final LocalTransaction _transaction;
private final ClassMetadata _extentType;
private final Ordering[] _orderings;
private final Map<Integer, ByteArrayBuffer> _bufferCache = new HashMap<Integer, ByteArrayBuffer>();
private final Map<FieldValueKey, Object> _fieldValueCache = new HashMap<FieldValueKey, Object>();
public SodaQueryComparator(
LocalObjectContainer container,
Class extentType,
Ordering... orderings) {
this(container, container.produceClassMetadata(container.reflector().forClass(extentType)), orderings);
}
public SodaQueryComparator(
LocalObjectContainer container,
final ClassMetadata extent,
Ordering... orderings) {
_container = container;
_transaction = ((LocalTransaction) _container.transaction());
_extentType = extent;
_orderings = orderings;
resolveFieldPaths(orderings);
}
private void resolveFieldPaths(Ordering[] orderings) {
for (Ordering fieldPath : orderings) {
fieldPath._resolvedPath = resolveFieldPath(fieldPath.fieldPath());
}
}
public List<Integer> sort(long[] ids) {
ArrayList<Integer> idList = listFrom(ids);
Collections.sort(idList, this);
return idList;
}
private ArrayList<Integer> listFrom(long[] ids) {
ArrayList<Integer> idList = new ArrayList<Integer>(ids.length);
for (long id : ids) {
idList.add((int)id);
}
return idList;
}
private List<FieldMetadata> resolveFieldPath(String[] fieldPath) {
List<FieldMetadata> fields = new ArrayList<FieldMetadata>(fieldPath.length);
ClassMetadata currentType = _extentType;
for (String fieldName : fieldPath) {
FieldMetadata field = currentType.fieldMetadataForName(fieldName);
if(field == null){
fields.clear();
break;
}
currentType = field.fieldType();
fields.add(field);
}
return fields;
}
public int compare(Integer x, Integer y) {
return compare(x.intValue(), y.intValue());
}
public int compare(int x, int y) {
for (Ordering ordering : _orderings) {
List<FieldMetadata> resolvedPath = ordering._resolvedPath;
if(resolvedPath.size() == 0){
continue;
}
int result = compareByField(x, y, resolvedPath);
if (result != 0) {
return ordering.direction().equals(Direction.ASCENDING)
? result
: -result;
}
}
return 0;
}
private int compareByField(int x, int y, List<FieldMetadata> path) {
final Object xFieldValue = getFieldValue(x, path);
final Object yFieldValue = getFieldValue(y, path);
ensureNoManualActivationRequired(xFieldValue);
final FieldMetadata field = path.get(path.size() - 1);
return field.prepareComparison(_transaction.context(), xFieldValue).compareTo(yFieldValue);
}
private void ensureNoManualActivationRequired(final Object obj) {
if (obj == null) return;
if (!hasValueTypeBehavior(obj)) {
if (!Activatable.class.isAssignableFrom(obj.getClass())) {
throwUnsupportedOrderingException(obj.getClass(), "make it implement Activatable interface.");
}
if (!TransparentActivationSupport.isTransparentActivationEnabledOn(_container)) {
throwUnsupportedOrderingException(obj.getClass(), "enable transparent activation support by adding TransparentActivationSupport to the configutation before opening the db.");
}
}
}
private boolean hasValueTypeBehavior(final Object obj) {
boolean isSimple = Platform4.isSimple(obj.getClass());
if (isSimple) return true;
ReflectClass reflectClass = _container.reflector().forObject(obj);
if (Platform4.isStruct(reflectClass)) return true;
boolean isEnum = Platform4.isEnum(_container.reflector(), reflectClass);
if (isEnum) return true;
TypeHandler4 typeHandler = _container.typeHandlerForClass(reflectClass);
return Handlers4.isValueType(typeHandler);
}
private void throwUnsupportedOrderingException(final Class clazz, String msg) {
throw new UnsupportedOrderingException("Cannot sort on class '" + clazz.getName() + "'. If you do want to use it as a sort criteria " + msg);
}
private Object getFieldValue(int id, List<FieldMetadata> path) {
for (int i = 0; i < path.size() - 1; ++i) {
final Object obj = getFieldValue(id, path.get(i));
if (null == obj) {
return null;
}
id = _container.getID(_transaction, obj);
}
return getFieldValue(id, path.get(path.size() - 1));
}
static class FieldValueKey {
private int _id;
private FieldMetadata _field;
public FieldValueKey(int id, FieldMetadata field) {
_id = id;
_field = field;
}
@Override
public int hashCode() {
return _field.hashCode() ^ _id;
}
@Override
public boolean equals(Object obj) {
FieldValueKey other = (FieldValueKey) obj;
return _field == other._field && _id == other._id;
}
}
private Object getFieldValue(int id, FieldMetadata field) {
final FieldValueKey key = new FieldValueKey(id, field);
Object cachedValue = _fieldValueCache.get(key);
if (null != cachedValue)
return cachedValue;
Object fieldValue = readFieldValue(id, field);
_fieldValueCache.put(key, fieldValue);
return fieldValue;
}
private Object readFieldValue(int id, FieldMetadata field) {
ByteArrayBuffer buffer = bufferFor(id);
HandlerVersion handlerVersion = field.containingClass().seekToField(_transaction, buffer, field);
if (handlerVersion == HandlerVersion.INVALID) {
return null;
}
QueryingReadContext context = new QueryingReadContext(_transaction, handlerVersion._number, buffer, id);
return field.read(context);
}
private ByteArrayBuffer bufferFor(int id) {
ByteArrayBuffer cachedBuffer = _bufferCache.get(id);
if (null != cachedBuffer)
return cachedBuffer;
ByteArrayBuffer buffer = _container.readBufferById(_transaction, id);
_bufferCache.put(id, buffer);
return buffer;
}
}