/* 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.processor;
import java.util.*;
import com.db4o.*;
import com.db4o.config.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.marshall.*;
import com.db4o.internal.query.*;
import com.db4o.internal.query.SodaQueryComparator.*;
import com.db4o.internal.query.result.*;
import com.db4o.query.*;
import com.db4o.reflect.*;
import com.db4o.types.*;
/**
* QQuery is the users hook on our graph.
*
* A QQuery is defined by it's constraints.
*
* NOTE: This is just a 'partial' base class to allow for variant implementations
* in db4oj and db4ojdk1.2. It assumes that itself is an instance of QQuery
* and should never be used explicitly.
*
* @exclude
*/
public abstract class QQueryBase implements InternalQuery, Unversioned {
transient Transaction _trans;
@decaf.Public
private Collection4 i_constraints = new Collection4();
@decaf.Public
private QQuery i_parent;
@decaf.Public
private String i_field;
private transient QueryEvaluationMode _evaluationMode;
@decaf.Public
private int _prefetchDepth;
@decaf.Public
private int _prefetchCount;
@decaf.Public
private int _evaluationModeAsInt;
@decaf.Public
private QueryComparator _comparator;
private transient final QQuery _this;
@decaf.Public
private List<SodaQueryComparator.Ordering> _orderings;
protected QQueryBase() {
// C/S only
_this = cast(this);
}
protected QQueryBase(Transaction a_trans, QQuery a_parent, String a_field) {
_this = cast(this);
_trans = a_trans;
i_parent = a_parent;
i_field = a_field;
}
public void captureQueryResultConfig() {
final Config4Impl config = _trans.container().config();
_evaluationMode = config.evaluationMode();
_prefetchDepth = config.prefetchDepth();
_prefetchCount = config.prefetchObjectCount();
}
void addConstraint(QCon a_constraint) {
i_constraints.add(a_constraint);
}
private void addConstraint(Collection4 col, Object obj) {
if(attachToExistingConstraints(col, obj, true)){
return;
}
if(attachToExistingConstraints(col, obj, false)){
return;
}
QConObject newConstraint = new QConObject(_trans, null, null, obj);
addConstraint(newConstraint);
col.add(newConstraint);
}
private boolean attachToExistingConstraints(Collection4 newConstraintsCollector, Object obj, boolean onlyForPaths) {
boolean found = false;
final Iterator4 j = iterateConstraints();
while (j.moveNext()) {
QCon existingConstraint = (QCon)j.current();
final BooleanByRef removeExisting = new BooleanByRef(false);
if(! onlyForPaths || (existingConstraint instanceof QConPath) ){
QCon newConstraint = existingConstraint.shareParent(obj, removeExisting);
if (newConstraint != null) {
newConstraintsCollector.add(newConstraint);
addConstraint(newConstraint);
if (removeExisting.value) {
removeConstraint(existingConstraint);
}
found = true;
if(! onlyForPaths){
break;
}
}
}
}
return found;
}
/**
* Search for slot that corresponds to class. <br>If not found add it.
* <br>Constrain it. <br>
*/
public Constraint constrain(Object example) {
synchronized (streamLock()) {
ReflectClass claxx = reflectClassForClass(example);
if (claxx != null) {
return addClassConstraint(claxx);
}
QConEvaluation eval = Platform4.evaluationCreate(_trans, example);
if (eval != null) {
return addEvaluationToAllConstraints(eval);
}
Collection4 constraints = new Collection4();
addConstraint(constraints, example);
return toConstraint(constraints);
}
}
private Constraint addEvaluationToAllConstraints(QConEvaluation eval) {
if(i_constraints.size() == 0){
_trans.container().classCollection().iterateTopLevelClasses(new Visitor4() {
public void visit(Object obj) {
ClassMetadata classMetadata = (ClassMetadata) obj;
QConClass qcc = new QConClass(_trans,classMetadata.classReflector());
addConstraint(qcc);
toConstraint(i_constraints).or(qcc);
}
});
}
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
((QCon)i.current()).addConstraint(eval);
}
// FIXME: should return valid Constraint object
return null;
}
private Constraint addClassConstraint(ReflectClass claxx) {
if (isTheObjectClass(claxx)) {
return null;
}
if (claxx.isInterface()) {
return addInterfaceConstraint(claxx);
}
final Collection4 newConstraints = introduceClassConstrain(claxx);
if (newConstraints.isEmpty()) {
QConClass qcc = new QConClass(_trans,claxx);
addConstraint(qcc);
return qcc;
}
return toConstraint(newConstraints);
}
private Collection4 introduceClassConstrain(ReflectClass claxx) {
final Collection4 newConstraints = new Collection4();
final Iterator4 existingConstraints = iterateConstraints();
while (existingConstraints.moveNext()) {
final QCon existingConstraint = (QConObject)existingConstraints.current();
final BooleanByRef removeExisting = new BooleanByRef(false);
final QCon newConstraint =
existingConstraint.shareParentForClass(claxx, removeExisting);
if (newConstraint != null) {
newConstraints.add(newConstraint);
addConstraint(newConstraint);
if (removeExisting.value) {
removeConstraint(existingConstraint);
}
}
}
return newConstraints;
}
private boolean isTheObjectClass(ReflectClass claxx) {
return claxx.equals(stream()._handlers.ICLASS_OBJECT);
}
private Constraint addInterfaceConstraint(ReflectClass claxx) {
Collection4 classes = stream().classCollection().forInterface(claxx);
if (classes.size() == 0) {
QConClass qcc = new QConClass(_trans, null, null, claxx);
addConstraint(qcc);
return qcc;
}
Iterator4 i = classes.iterator();
Constraint constr = null;
while (i.moveNext()) {
ClassMetadata classMetadata = (ClassMetadata)i.current();
ReflectClass classMetadataClaxx = classMetadata.classReflector();
if(classMetadataClaxx != null){
if(! classMetadataClaxx.isInterface()){
if(constr == null){
constr = constrain(classMetadataClaxx);
}else{
constr = constr.or(constrain(classMetadata.classReflector()));
}
}
}
}
return constr;
}
private ReflectClass reflectClassForClass(Object example) {
if(example instanceof ReflectClass){
return (ReflectClass)example;
}
if(example instanceof Class) {
return _trans.reflector().forClass((Class)example);
}
return null;
}
public Constraints constraints() {
synchronized (streamLock()) {
Constraint[] constraints = new Constraint[i_constraints.size()];
i_constraints.toArray(constraints);
return new QConstraints(_trans, constraints);
}
}
public Query descend(final String a_field) {
synchronized (streamLock()) {
final QQuery query = new QQuery(_trans, _this, a_field);
IntByRef run = new IntByRef(1);
if (!descend1(query, a_field, run)) {
// try to add unparented nodes on the second run,
// if not added in the first run and a descendant
// was not found
if (run.value == 1) {
run.value = 2;
if (!descend1(query, a_field, run)) {
new QConUnconditional(_trans, false).attach(query, a_field);
}
}
}
return query;
}
}
private boolean descend1(final QQuery query, final String fieldName, IntByRef run) {
if (run.value == 2 || i_constraints.size() == 0) {
// On the second run we are really creating a second independant
// query network that is not joined to other higher level
// constraints.
// Let's see how this works out. We may need to join networks.
run.value = 0; // prevent a double run of this code
stream().classCollection().attachQueryNode(fieldName, new Visitor4() {
boolean untypedFieldConstraintCollected = false;
public void visit(Object obj) {
Object[] pair = ((Object[]) obj);
ClassMetadata containingClass = (ClassMetadata)pair[0];
FieldMetadata field = (FieldMetadata)pair[1];
if (isTyped(field)) {
addFieldConstraint(containingClass, field);
return;
}
if (untypedFieldConstraintCollected)
return;
addFieldConstraint(containingClass, field);
untypedFieldConstraintCollected = true;
}
private boolean isTyped(FieldMetadata field) {
return !Handlers4.isUntyped(field.getHandler());
}
private void addFieldConstraint(ClassMetadata containingClass,
FieldMetadata field) {
QConClass qcc =
new QConClass(
_trans,
null,
field.qField(_trans),
containingClass.classReflector());
addConstraint(qcc);
toConstraint(i_constraints).or(qcc);
}
});
}
checkConstraintsEvaluationMode();
final BooleanByRef foundClass = new BooleanByRef(false);
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
if (((QCon)i.current()).attach(query, fieldName)) {
foundClass.value = true;
}
}
return foundClass.value;
}
public ObjectSet execute() {
synchronized (streamLock()) {
return triggeringQueryEvents(new Closure4<ObjectSet>() { public ObjectSet run() {
return new ObjectSetFacade(getQueryResult());
}});
}
}
public void executeLocal(final IdListQueryResult result) {
checkConstraintsEvaluationMode();
CreateCandidateCollectionResult r = createCandidateCollection();
boolean checkDuplicates = r.checkDuplicates;
boolean topLevel = r.topLevel;
List4 candidateCollection = r.candidateCollection;
if (Debug4.queries) {
logConstraints();
}
if (candidateCollection != null) {
final Collection4 executionPath = topLevel ? null : fieldPathFromTop();
Iterator4 i = new Iterator4Impl(candidateCollection);
while (i.moveNext()) {
((QCandidates)i.current()).execute();
}
if (candidateCollection._next != null) {
checkDuplicates = true;
}
if (checkDuplicates) {
result.checkDuplicates();
}
i = new Iterator4Impl(candidateCollection);
while (i.moveNext()) {
QCandidates candidates = (QCandidates)i.current();
if (topLevel) {
candidates.traverseIds(result);
} else {
candidates.traverseIds(new AscendingQueryExecutor(_trans, result, executionPath));
}
}
}
sort(result);
}
private void triggerQueryOnFinished() {
stream().callbacks().queryOnFinished(_trans, cast(this));
}
private void triggerQueryOnStarted() {
stream().callbacks().queryOnStarted(_trans, cast(this));
}
public Iterator4 executeLazy(){
checkConstraintsEvaluationMode();
final CreateCandidateCollectionResult r = createCandidateCollection();
final Collection4 executionPath = executionPath(r);
Iterator4 candidateCollection = new Iterator4Impl(r.candidateCollection);
MappingIterator executeCandidates = new MappingIterator(candidateCollection){
protected Object map(Object current) {
return ((QCandidates)current).executeLazy(executionPath);
}
};
CompositeIterator4 resultingIDs = new CompositeIterator4(executeCandidates);
if(!r.checkDuplicates){
return resultingIDs;
}
return checkDuplicates(resultingIDs);
}
public QueryResult getQueryResult() {
synchronized (streamLock()) {
if(i_constraints.size() == 0){
return executeAllObjectsQuery();
}
QueryResult result = executeClassOnlyQuery();
if(result != null) {
return result;
}
optimizeJoins();
return executeQuery();
}
}
protected final QueryResult executeQuery() {
return stream().executeQuery(_this);
}
private QueryResult executeAllObjectsQuery() {
return stream().queryAllObjects(_trans);
}
protected ObjectContainerBase stream() {
return _trans.container();
}
public InternalObjectContainer container() {
return stream();
}
private QueryResult executeClassOnlyQuery() {
final ClassMetadata clazz = singleClassConstraint();
if (null == clazz) {
return null;
}
QueryResult queryResult = stream().classOnlyQuery(QQueryBase.this, clazz);
sort(queryResult);
return queryResult;
}
private ClassMetadata singleClassConstraint() {
if(requiresSort()) {
return null;
}
QConClass clazzconstr = classConstraint();
if (clazzconstr == null) {
return null;
}
ClassMetadata clazz=clazzconstr._classMetadata;
if(clazz==null) {
return null;
}
if(clazzconstr.hasChildren() || clazz.isArray()) {
return null;
}
return clazz;
}
private QConClass classConstraint() {
if (i_constraints.size()!=1) {
return null;
}
Constraint constr=singleConstraint();
if(constr.getClass()!=QConClass.class) {
return null;
}
return (QConClass)constr;
}
private Constraint singleConstraint() {
return (Constraint)i_constraints.singleElement();
}
private static final class AscendingQueryExecutor implements IntVisitor {
private final ObjectContainerBase _container;
private final IdListQueryResult _result;
private final Collection4 _executionPath;
private final Transaction _trans;
public AscendingQueryExecutor(Transaction trans, IdListQueryResult result, Collection4 executionPath) {
_trans = trans;
_container = _trans.container();
_result = result;
_executionPath = executionPath;
}
public void visit(int id) {
TreeInt ids = new TreeInt(id);
final ByRef<TreeInt> idsNew = new ByRef<TreeInt>();
Iterator4 itPath = _executionPath.iterator();
while (itPath.moveNext()) {
idsNew.value = null;
final String fieldName = (String) (itPath.current());
ids.traverse(new Visitor4() {
public void visit(Object treeInt) {
int id = ((TreeInt)treeInt)._key;
StatefulBuffer buffer = _container.readStatefulBufferById(_trans, id);
if (buffer != null) {
ObjectHeader oh = new ObjectHeader(_container, buffer);
CollectIdContext context = new CollectIdContext(_trans, oh, buffer);
oh.classMetadata().collectIDs(context, fieldName);
Tree.traverse(context.ids(), new Visitor4<TreeInt>() {
public void visit(TreeInt node) {
idsNew.value = TreeInt.add(idsNew.value, node._key);
}
});
}
}
});
ids = (TreeInt) idsNew.value;
if(ids == null){
return;
}
}
ids.traverse(new Visitor4() {
public void visit(Object treeInt) {
_result.addKeyCheckDuplicates(((TreeInt)treeInt)._key);
}
});
}
}
public static class CreateCandidateCollectionResult {
public final boolean checkDuplicates;
public final boolean topLevel;
public final List4 candidateCollection;
public CreateCandidateCollectionResult(List4 candidateCollection_, boolean checkDuplicates_, boolean topLevel_) {
candidateCollection = candidateCollection_;
topLevel = topLevel_;
checkDuplicates = checkDuplicates_;
}
}
public Iterator4 executeSnapshot(){
final CreateCandidateCollectionResult r = createCandidateCollection();
final Collection4 executionPath = executionPath(r);
Iterator4 candidatesIterator = new Iterator4Impl(r.candidateCollection);
Collection4 snapshots = new Collection4();
while(candidatesIterator.moveNext()){
QCandidates candidates = (QCandidates) candidatesIterator.current();
snapshots.add( candidates.executeSnapshot(executionPath));
}
Iterator4 snapshotsIterator = snapshots.iterator();
final CompositeIterator4 resultingIDs = new CompositeIterator4(snapshotsIterator);
if(!r.checkDuplicates){
return resultingIDs;
}
return checkDuplicates(resultingIDs);
}
public <T> T triggeringQueryEvents(Closure4<T> closure) {
triggerQueryOnStarted();
try {
return closure.run();
} finally {
triggerQueryOnFinished();
}
}
private Iterator4 checkDuplicates(CompositeIterator4 executeAllCandidates) {
return Iterators.filter(executeAllCandidates, new Predicate4() {
private TreeInt ids = new TreeInt(0);
public boolean match(Object current) {
int id = ((Integer)current).intValue();
if(ids.find(id) != null){
return false;
}
ids = (TreeInt)ids.add(new TreeInt(id));
return true;
}
});
}
private Collection4 executionPath(final CreateCandidateCollectionResult r) {
return r.topLevel ? null : fieldPathFromTop();
}
public void checkConstraintsEvaluationMode() {
Iterator4 constraints = iterateConstraints();
while (constraints.moveNext()) {
((QConObject)constraints.current()).setEvaluationMode();
}
}
private Collection4 fieldPathFromTop(){
QQueryBase q = this;
final Collection4 fieldPath = new Collection4();
while (q.i_parent != null) {
fieldPath.prepend(q.i_field);
q = q.i_parent;
}
return fieldPath;
}
private void logConstraints() {
if (Debug4.queries) {
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
((QCon)i.current()).log("");
}
}
}
public CreateCandidateCollectionResult createCandidateCollection() {
List4 candidatesList = createQCandidatesList();
boolean checkDuplicates = false;
boolean topLevel = true;
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
QCon constraint = (QCon)i.current();
QCon old = constraint;
constraint = constraint.getRoot();
if (constraint != old) {
checkDuplicates = true;
topLevel = false;
}
ClassMetadata classMetadata = constraint.getYapClass();
if (classMetadata == null) {
break;
}
addConstraintToCandidatesList(candidatesList, constraint);
}
return new CreateCandidateCollectionResult(candidatesList, checkDuplicates, topLevel);
}
private void addConstraintToCandidatesList(List4 candidatesList, QCon qcon) {
if (candidatesList == null) {
return;
}
Iterator4 j = new Iterator4Impl(candidatesList);
while (j.moveNext()) {
QCandidates candidates = (QCandidates)j.current();
candidates.addConstraint(qcon);
}
}
private List4 createQCandidatesList(){
List4 candidatesList = null;
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
QCon constraint = (QCon)i.current();
constraint = constraint.getRoot();
ClassMetadata classMetadata = constraint.getYapClass();
if (classMetadata == null) {
continue;
}
if(constraintCanBeAddedToExisting(candidatesList, constraint)){
continue;
}
QCandidates candidates = new QCandidates((LocalTransaction) _trans, classMetadata, null, true);
candidatesList = new List4(candidatesList, candidates);
}
return candidatesList;
}
private boolean constraintCanBeAddedToExisting(List4 candidatesList, QCon constraint){
Iterator4 j = new Iterator4Impl(candidatesList);
while (j.moveNext()) {
QCandidates candidates = (QCandidates)j.current();
if (candidates.fitsIntoExistingConstraintHierarchy(constraint)) {
return true;
}
}
return false;
}
public final Transaction transaction() {
return _trans;
}
public Iterator4 iterateConstraints(){
// clone the collection first to avoid
// InvalidIteratorException as i_constraints might be
// modified during the execution of callee
return new Collection4(i_constraints).iterator();
}
public Query orderAscending() {
if(i_parent == null) {
throw new IllegalStateException("Cannot apply ordering at top level.");
}
synchronized (streamLock()) {
addOrdering(SodaQueryComparator.Direction.ASCENDING);
return _this;
}
}
public Query orderDescending() {
if(i_parent == null) {
throw new IllegalStateException("Cannot apply ordering at top level.");
}
synchronized (streamLock()) {
addOrdering(SodaQueryComparator.Direction.DESCENDING);
return _this;
}
}
private void addOrdering(Direction direction) {
addOrdering(direction, new ArrayList<String>());
}
protected final void addOrdering(Direction direction, List<String> path) {
if (i_field != null) {
path.add(i_field);
}
if (i_parent != null) {
i_parent.addOrdering(direction, path);
return;
}
final String[] fieldPath = reverseFieldPath(path);
removeExistingOrderingFor(fieldPath);
orderings().add(new SodaQueryComparator.Ordering(direction, fieldPath));
}
private void removeExistingOrderingFor(String[] fieldPath) {
for (Ordering ordering : orderings()) {
if (Arrays.equals(ordering.fieldPath(), fieldPath)) {
orderings().remove(ordering);
break;
}
}
}
/**
* Public so it can be used by the LINQ test cases.
*/
public final List<Ordering> orderings() {
if (null == _orderings) {
_orderings = new ArrayList<SodaQueryComparator.Ordering>();
}
return _orderings;
}
private String[] reverseFieldPath(List<String> path) {
final String[] reversedPath = new String[path.size()];
for (int i = 0; i < reversedPath.length; i++) {
reversedPath[i] = path.get(path.size() - i - 1);
}
return reversedPath;
}
public void marshall() {
checkConstraintsEvaluationMode();
_evaluationModeAsInt = _evaluationMode.asInt();
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
((QCon)i.current()).getRoot().marshall();
}
}
public void unmarshall(final Transaction a_trans) {
_evaluationMode = QueryEvaluationMode.fromInt(_evaluationModeAsInt);
_trans = a_trans;
Iterator4 i = iterateConstraints();
while (i.moveNext()) {
((QCon)i.current()).unmarshall(a_trans);
}
}
void removeConstraint(QCon a_constraint) {
i_constraints.remove(a_constraint);
}
Constraint toConstraint(final Collection4 constraints) {
if (constraints.size() == 1) {
return (Constraint) constraints.singleElement();
} else if (constraints.size() > 0) {
Constraint[] constraintArray = new Constraint[constraints.size()];
constraints.toArray(constraintArray);
return new QConstraints(_trans, constraintArray);
}
return null;
}
protected Object streamLock() {
return stream().lock();
}
public Query sortBy(QueryComparator comparator) {
_comparator=comparator;
return _this;
}
private void sort(QueryResult result) {
if (_orderings != null) {
result.sortIds(newSodaQueryComparator());
}
if(_comparator!=null) {
result.sort(_comparator);
}
}
private IntComparator newSodaQueryComparator() {
return new SodaQueryComparator(
(LocalObjectContainer)this.transaction().container(),
extentType(),
_orderings.toArray(new SodaQueryComparator.Ordering[_orderings.size()]));
}
private ClassMetadata extentType() {
return classConstraint().getYapClass();
}
// cheat emulating '(QQuery)this'
private static QQuery cast(QQueryBase obj) {
return (QQuery)obj;
}
public boolean requiresSort() {
if (_comparator != null || _orderings != null){
return true;
}
return false;
}
public QueryComparator comparator() {
return _comparator;
}
public QueryEvaluationMode evaluationMode(){
return _evaluationMode;
}
public void evaluationMode(QueryEvaluationMode mode){
_evaluationMode = mode;
}
private void optimizeJoins() {
if(!hasOrJoins()) {
removeJoins();
}
}
private boolean hasOrJoins() {
return forEachConstraintRecursively(new Function4() {
public Object apply(Object obj) {
QCon constr = (QCon) obj;
Iterator4 joinIter = constr.iterateJoins();
while(joinIter.moveNext()) {
QConJoin join = (QConJoin) joinIter.current();
if(join.isOr()) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
});
}
private void removeJoins() {
forEachConstraintRecursively(new Function4() {
public Object apply(Object obj) {
QCon constr = (QCon) obj;
constr.i_joins = null;
return Boolean.FALSE;
}
});
}
private boolean forEachConstraintRecursively(Function4 block) {
Queue4 queue = new NoDuplicatesQueue(new NonblockingQueue());
Iterator4 constrIter = iterateConstraints();
while(constrIter.moveNext()) {
queue.add(constrIter.current());
}
while(queue.hasNext()) {
QCon constr = (QCon) queue.next();
Boolean cancel = (Boolean) block.apply(constr);
if(cancel.booleanValue()) {
return true;
}
Iterator4 childIter = constr.iterateChildren();
while(childIter.moveNext()) {
queue.add(childIter.current());
}
Iterator4 joinIter = constr.iterateJoins();
while(joinIter.moveNext()) {
queue.add(joinIter.current());
}
}
return false;
}
public int prefetchDepth() {
return _prefetchDepth;
}
public int prefetchCount() {
return _prefetchCount;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("QQueryBase\n");
Iterator4 i = iterateConstraints();
while(i.moveNext()){
QCon constraint = (QCon) i.current();
sb.append(constraint);
sb.append("\n");
}
return sb.toString();
}
public QQuery parent() {
return i_parent;
}
}