/* 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 com.db4o.*; import com.db4o.config.*; import com.db4o.foundation.*; import com.db4o.internal.*; import com.db4o.internal.handlers.*; import com.db4o.internal.handlers.array.*; import com.db4o.internal.marshall.*; import com.db4o.marshall.*; import com.db4o.reflect.*; import com.db4o.typehandlers.*; /** * Represents an actual object in the database. Forms a tree structure, indexed * by id. Can have dependents that are doNotInclude'd in the query result when * this is doNotInclude'd. * * @exclude */ public class QCandidate extends QCandidateBase implements ParentCandidate { // db4o ID is stored in _key; // db4o byte stream storing the object ByteArrayBuffer _bytes; Object _member; // the ClassMetadata of this object ClassMetadata _classMetadata; // temporary field and member for one field during evaluation FieldMetadata _fieldMetadata; // null denotes null object private int _handlerVersion; public QCandidate(QCandidates candidates, Object member, int id) { super(candidates, id); _member = member; } public Object shallowClone() { QCandidate qcan = new QCandidate(_candidates, _member, _key); qcan.setBytes(_bytes); qcan._dependants = _dependants; qcan._include = _include; qcan._member = _member; qcan._pendingJoins = _pendingJoins; qcan._root = _root; qcan._classMetadata = _classMetadata; qcan._fieldMetadata = _fieldMetadata; return super.shallowCloneInternal(qcan); } private void checkInstanceOfCompare() { if (_member instanceof Compare) { _member = ((Compare) _member).compare(); LocalObjectContainer stream = container(); _classMetadata = stream.classMetadataForReflectClass(stream.reflector().forObject(_member)); _key = stream.getID(transaction(), _member); if (_key == 0) { setBytes(null); } else { setBytes(stream.readBufferById(transaction(), _key)); } } } @Override public boolean createChild(QField field, final QCandidates candidates) { if (!_include) { return false; } useField(field); if(_fieldMetadata == null || _fieldMetadata instanceof NullFieldMetadata) { return false; } TypeHandler4 handler = _fieldMetadata.getHandler(); if (handler != null) { final QueryingReadContext queryingReadContext = new QueryingReadContext(transaction(), marshallerFamily().handlerVersion(), _bytes, _key); final TypeHandler4 arrayElementHandler = Handlers4.arrayElementHandler(handler, queryingReadContext); if (arrayElementHandler != null) { return createChildForDescendable(candidates, handler, queryingReadContext, arrayElementHandler); } // We may get simple types here too, if the YapField was null // in the higher level simple evaluation. Evaluate these // immediately. if (Handlers4.isQueryLeaf(handler)) { candidates._currentConstraint.visit(this); return true; } } _classMetadata.seekToField(transaction(), _bytes, _fieldMetadata); InternalCandidate candidate = readSubCandidate(candidates); if (candidate == null) { return false; } // fast early check for ClassMetadata if (candidates._classMetadata != null && candidates._classMetadata.isStronglyTyped()) { if (Handlers4.isUntyped(handler)){ handler = typeHandlerFor(candidate); } if(handler == null){ return false; } } addDependant(candidates.add(candidate)); return true; } private boolean createChildForDescendable(final QCandidates parentCandidates, TypeHandler4 handler, final QueryingReadContext queryingReadContext, final TypeHandler4 arrayElementHandler) { final int offset = queryingReadContext.offset(); boolean outerRes = true; // The following construct is worse than not ideal. For each constraint it completely reads the // underlying structure again. The structure could be kept fairly easy. TODO: Optimize! Iterator4 i = parentCandidates.iterateConstraints(); while (i.moveNext()) { QCon qcon = (QCon) i.current(); QField qf = qcon.getField(); if (qf != null && !qf.name().equals(_fieldMetadata.getName())) { continue; } QCon tempParent = qcon.parent(); qcon.setParent(null); final QCandidates candidates = new QCandidates(parentCandidates.i_trans, null, qf, false); candidates.addConstraint(qcon); qcon.setCandidates(candidates); readArrayCandidates(handler, queryingReadContext.buffer(), arrayElementHandler, candidates); queryingReadContext.seek(offset); final boolean isNot = qcon.isNot(); if (isNot) { qcon.removeNot(); } candidates.evaluate(); final ByRef<Tree> pending = ByRef.newInstance(); final BooleanByRef innerRes = new BooleanByRef(isNot); candidates.traverse(new CreateDescendChildTraversingVisitor(pending, innerRes, isNot)); if (isNot) { qcon.not(); } // In case we had pending subresults, we need to communicate them up to our root. if (pending.value != null) { pending.value.traverse(new Visitor4() { public void visit(Object a_object) { getRoot().evaluate((QPending) a_object); } }); } if (!innerRes.value) { if (Debug4.queries) { System.out.println(" Array evaluation false. Constraint:" + qcon.id()); } // Again this could be double triggering. // // We want to clean up the "No route" at some stage. qcon.visit(getRoot(), qcon.evaluator().not(false)); outerRes = false; } qcon.setParent(tempParent); } return outerRes; } private TypeHandler4 typeHandlerFor(InternalCandidate candidate) { ClassMetadata classMetadata = candidate.classMetadata(); if (classMetadata != null) { return classMetadata.typeHandler(); } return null; } private void readArrayCandidates(TypeHandler4 typeHandler, final ReadBuffer buffer, final TypeHandler4 arrayElementHandler, final QCandidates candidates) { if(! Handlers4.isCascading(arrayElementHandler)){ return; } final SlotFormat slotFormat = SlotFormat.forHandlerVersion(_handlerVersion); slotFormat.doWithSlotIndirection(buffer, typeHandler, new Closure4() { public Object run() { QueryingReadContext context = null; if(Handlers4.handleAsObject(arrayElementHandler)){ // TODO: Code is similar to FieldMetadata.collectIDs. Try to refactor to one place. int collectionID = buffer.readInt(); ByteArrayBuffer arrayElementBuffer = container().readBufferById(transaction(), collectionID); ObjectHeader objectHeader = ObjectHeader.scrollBufferToContent(container(), arrayElementBuffer); context = new QueryingReadContext(transaction(), candidates, _handlerVersion, arrayElementBuffer, collectionID); objectHeader.classMetadata().collectIDs(context); }else{ context = new QueryingReadContext(transaction(), candidates, _handlerVersion, buffer, 0); ((CascadingTypeHandler)arrayElementHandler).collectIDs(context); } Tree.traverse(context.ids(), new Visitor4() { public void visit(Object obj) { TreeInt idNode = (TreeInt) obj; candidates.add(new QCandidate(candidates, null, idNode._key)); } }); Iterator4 i = context.objectsWithoutId(); while(i.moveNext()){ Object obj = i.current(); candidates.add(new QCandidate(candidates, obj, 0)); } return null; } }); } ReflectClass classReflector() { classMetadata(); if (_classMetadata == null) { return null; } return _classMetadata.classReflector(); } @Override public boolean fieldIsAvailable(){ return classReflector() != null; } ReflectClass memberClass() { return transaction().reflector().forObject(_member); } private void read() { if (_include) { if (_bytes == null) { if (_key > 0) { if (DTrace.enabled) { DTrace.CANDIDATE_READ.log(_key); } setBytes(container().readBufferById(transaction(), _key)); if (_bytes == null) { include(false); } } else { include(false); } } } } private int currentOffSet(){ return _bytes._offset; } private InternalCandidate readSubCandidate(QCandidates candidateCollection) { read(); if (_bytes == null || _fieldMetadata == null) { return null; } final int offset = currentOffSet(); QueryingReadContext context = newQueryingReadContext(); TypeHandler4 handler = HandlerRegistry.correctHandlerVersion(context, _fieldMetadata.getHandler()); InternalCandidate subCandidate = candidateCollection.readSubCandidate(context, handler); seek(offset); if (subCandidate != null) { subCandidate.root(getRoot()); return subCandidate; } return null; } private void seek(int offset){ _bytes._offset = offset; } private QueryingReadContext newQueryingReadContext() { return new QueryingReadContext(transaction(), _handlerVersion, _bytes, _key); } private void readThis(boolean a_activate) { read(); final ObjectContainerBase container = transaction().container(); _member = container.tryGetByID(transaction(), _key); if (_member != null && (a_activate || _member instanceof Compare)) { container.activate(transaction(), _member); checkInstanceOfCompare(); } } @Override public ClassMetadata classMetadata() { if (_classMetadata != null) { return _classMetadata; } read(); if (_bytes == null) { return null; } seek(0); ObjectContainerBase stream = container(); ObjectHeader objectHeader = new ObjectHeader(stream, _bytes); _classMetadata = objectHeader.classMetadata(); if (_classMetadata != null) { if (stream._handlers.ICLASS_COMPARE.isAssignableFrom(_classMetadata.classReflector())) { readThis(false); } } return _classMetadata; } public String toString() { String str = "QCandidate id: " + _key; if (_classMetadata != null) { str += "\n YapClass " + _classMetadata.getName(); } if (_fieldMetadata != null) { str += "\n YapField " + _fieldMetadata.getName(); } if (_member != null) { str += "\n Member " + _member.toString(); } if (_root != null) { str += "\n rooted by:\n"; str += _root.toString(); } else { str += "\n ROOT"; } return str; } public void useField(QField a_field) { read(); if (_bytes == null) { _fieldMetadata = null; return; } classMetadata(); _member = null; if (a_field == null) { _fieldMetadata = null; return; } if (_classMetadata == null) { _fieldMetadata = null; return; } _fieldMetadata = fieldMetadataFrom(a_field, _classMetadata); if(_fieldMetadata == null){ fieldNotFound(); return; } HandlerVersion handlerVersion = _classMetadata.seekToField(transaction(), _bytes, _fieldMetadata); if (handlerVersion == HandlerVersion.INVALID ) { fieldNotFound(); return; } _handlerVersion = handlerVersion._number; } private FieldMetadata fieldMetadataFrom(QField qField, ClassMetadata type) { final FieldMetadata existingField = qField.getFieldMetadata(); if(existingField != null){ return existingField; } FieldMetadata field = type.fieldMetadataForName(qField.name()); if(field != null){ field.alive(); } return field; } private void fieldNotFound(){ if (_classMetadata.holdsAnyClass()) { // retry finding the field on reading the value _fieldMetadata = null; } else { // we can't get a value for the field, comparisons should definitely run against null _fieldMetadata = new NullFieldMetadata(); } _handlerVersion = HandlerRegistry.HANDLER_VERSION; } Object value() { return value(false); } // TODO: This is only used for Evaluations. Handling may need // to be different for collections also. Object value(boolean a_activate) { if (_member == null) { if (_fieldMetadata == null) { readThis(a_activate); } else { int offset = currentOffSet(); _member = _fieldMetadata.read(newQueryingReadContext()); seek(offset); checkInstanceOfCompare(); } } return _member; } void setBytes(ByteArrayBuffer bytes){ _bytes = bytes; } private MarshallerFamily marshallerFamily(){ return MarshallerFamily.version(_handlerVersion); } public void classMetadata(ClassMetadata classMetadata) { _classMetadata = classMetadata; } @Override public boolean evaluate(final QConObject a_constraint, final QE a_evaluator) { if (a_evaluator.identity()) { return a_evaluator.evaluate(a_constraint, this, null); } if (_member == null) { _member = value(); } return a_evaluator.evaluate(a_constraint, this, a_constraint .translate(_member)); } public Object getObject() { Object obj = value(true); if (obj instanceof ByteArrayBuffer) { ByteArrayBuffer reader = (ByteArrayBuffer) obj; int offset = reader._offset; obj = StringHandler.readString(transaction().context(), reader); reader._offset = offset; } return obj; } @Override public PreparedComparison prepareComparison(ObjectContainerBase container, Object constraint) { Context context = container.transaction().context(); if (_fieldMetadata != null) { return _fieldMetadata.prepareComparison(context, constraint); } if (_classMetadata != null) { return _classMetadata.prepareComparison(context, constraint); } Reflector reflector = container.reflector(); ClassMetadata classMetadata = null; if (_bytes != null) { classMetadata = container.produceClassMetadata(reflector.forObject(constraint)); } else { if (_member != null) { classMetadata = container.classMetadataForReflectClass(reflector.forObject(_member)); } } if (classMetadata != null) { if (_member != null && _member.getClass().isArray()) { TypeHandler4 arrayElementTypehandler = classMetadata.typeHandler(); if (reflector.array().isNDimensional(memberClass())) { MultidimensionalArrayHandler mah = new MultidimensionalArrayHandler(arrayElementTypehandler, false); return mah.prepareComparison(context, _member); } ArrayHandler ya = new ArrayHandler(arrayElementTypehandler, false); return ya.prepareComparison(context, _member); } return classMetadata.prepareComparison(context, constraint); } return null; } static final class CreateDescendChildTraversingVisitor implements Visitor4 { private final ByRef<Tree> _pending; private final BooleanByRef _innerRes; private final boolean _isNot; CreateDescendChildTraversingVisitor(ByRef<Tree> pending, BooleanByRef innerRes, boolean isNot) { _pending = pending; _innerRes = innerRes; _isNot = isNot; } public void visit(Object obj) { InternalCandidate cand = (InternalCandidate) obj; if (cand.include()) { _innerRes.value = !_isNot; } // Collect all pending subresults. if (cand.pendingJoins() == null) { return; } cand.pendingJoins().traverse(new Visitor4() { public void visit(Object a_object) { QPending newPending = ((QPending) a_object).internalClonePayload(); // We need to change the constraint here, so our pending collector // uses the right comparator. newPending.changeConstraint(); QPending oldPending = (QPending) Tree.find(_pending.value, newPending); if (oldPending != null) { // We only keep one pending result for all array elements and memorize, // whether we had a true or a false result or both. if (oldPending._result != newPending._result) { oldPending._result = QPending.BOTH; } } else { _pending.value = Tree.add(_pending.value, newPending); } } }); } } }