/*
Copyright (C) 2007 Mobixess Inc. http://www.java-objects-database.com
This file is part of the JODB (Java Objects Database) open source project.
JODB is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation.
JODB 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, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.mobixess.jodb.core.query;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.Iterator;
import java.util.WeakHashMap;
import com.mobixess.jodb.core.JODBConfig;
import com.mobixess.jodb.core.JodbIOException;
import com.mobixess.jodb.core.index.IndexDataIterator;
import com.mobixess.jodb.core.index.JODBIndexingAgent;
import com.mobixess.jodb.core.index.JODBIndexingRootAgent;
import com.mobixess.jodb.core.io.IOBase;
import com.mobixess.jodb.core.io.IOTicket;
import com.mobixess.jodb.core.io.JODBOperationContext;
import com.mobixess.jodb.core.query.INqLoader.QueryBytecode;
import com.mobixess.jodb.core.transaction.JODBSession;
import com.mobixess.jodb.core.transaction.JODBSession.ClassDescriptor;
import com.mobixess.jodb.query.api.ObjectSet;
import com.mobixess.jodb.query.api.Predicate;
public class NQExecutor {
private static NQExecutor _instance = new NQExecutor();
private WeakHashMap<LoaderCacheKey, INqLoader> _loadersCache = new WeakHashMap<LoaderCacheKey, INqLoader>();
private enum COMPARISON_STATE{UNKNOWN,ASCENDING,DESCENDING,MIXED};
private static Class<? extends INqLoader> _nqLoaderClass;
private NQExecutor() {
}
public static void registerLoader(Class<? extends INqLoader> nqLoaderClass){
_nqLoaderClass = nqLoaderClass;
}
public static NQExecutor getInstance() {
return _instance;
}
@SuppressWarnings("unchecked")
private Object instantiateSyntheticPredicate(JODBSession session, Object originalPredicate, Class syntheticPredicate) throws Exception{
ClassDescriptor originalDescriptor = session.getDescriptorForClass(originalPredicate.getClass(), false);
Object outerInstance = null;
if(originalDescriptor.isInnerClass()){
Field outerInstanceAccessField = originalDescriptor.getOuterRefField();
outerInstance = outerInstanceAccessField!=null ? outerInstanceAccessField.get(originalPredicate) : null;
}
ClassDescriptor synthetiClassDescriptor = session.getDescriptorForClass(syntheticPredicate, false);
return synthetiClassDescriptor.newInstance(outerInstance);
//Constructor<>
}
@SuppressWarnings("unchecked")
public boolean isOptimizedQuery(JODBSession session, Predicate predicate, Comparator comparator) throws IOException{
JODBOperationContext context = new JODBOperationContext(session, null, null, null,null);
INqLoader loader = getLoader(context, predicate, comparator);
return loader.isPredicateOptimized() && loader.isComparatorOptimized();
}
public QueryBytecode getTransformationForQuery(JODBSession session, Predicate predicate, Comparator comparator) throws IOException{
if(!isOptimizedQuery(session, predicate, comparator)){
return null;
}
JODBOperationContext context = new JODBOperationContext(session, null, null, null,null);
INqLoader loader = getLoader(context, predicate, comparator);
return loader.getTransformedQueryBytecode();
}
@SuppressWarnings("unchecked")
public ObjectSet execute(JODBSession session, Predicate predicate, Comparator comparator) throws IOException
{
IOBase base = session.getBase();
IOTicket ticket = base.getIOTicket(true, false);
try {
JODBOperationContext context = new JODBOperationContext(session, ticket, null, null,null);
INqLoader loader = getLoader(context, predicate, comparator);
if(!loader.isPredicateOptimized() || !loader.isComparatorOptimized()){
return executeUnoptimized(session, predicate, comparator, loader);
}
Class predicateSubjectClass = loader.getPredicateSubjectClass();
//ClassDescriptor predicateSubjectClassDescriptor = session.getDescriptorForClass(predicateSubjectClass);
//Method setProxyMethod = loader.getSyntheticProxySetMethod();
//Class syntheticPredicate;
Class syntheticPredicateSubjectClass = loader.loadClass(predicateSubjectClass.getName());
SyntheticSubject syntheticSubject = new SyntheticSubject(context, loader,syntheticPredicateSubjectClass);
Class syntheticPredicateClass = loader.loadClass(predicate.getClass().getName());
Predicate syntheticPredicate = (Predicate) instantiateSyntheticPredicate(session, predicate, syntheticPredicateClass);// (Predicate)syntheticPredicateClass.newInstance();
Comparator syntheticComparator = null;
if(comparator!=null){
Class syntheticComparatorClass = loader.loadClass(comparator.getClass().getName());
syntheticComparator = (Comparator) instantiateSyntheticPredicate(session, comparator, syntheticComparatorClass);
//(Comparator) session.getDescriptorForClass(syntheticComparatorClass).newInstance();
}
//int[] predicateSubjectFields = loader.getPredicateSubjectFields();
JODBIndexingRootAgent indexingRootAgent = session.getIndexingRootAgent();
JODBIndexingAgent indexingAgent = null;
Iterator<Integer> predicateSubjectAccessedFields = loader.getAccessedPredicateSubjectFields();
while (predicateSubjectAccessedFields.hasNext() && indexingAgent == null) {
indexingAgent = indexingRootAgent.getIndexingAgent(predicateSubjectAccessedFields.next(), session.getBase());
}
ByteBuffer indexDataBuffer = null;
int indexField = -1;
IndexDataIterator indexIterator = null;
if(indexingAgent!=null){
indexField = indexingAgent.getFieldId();
indexDataBuffer = ByteBuffer.allocate(16);
indexIterator = indexingAgent.getIndexIterator(true);
}
if(indexIterator==null){
indexIterator = new LArrayIndexIterator(base.getForAllObjects(ticket));
}
SyntheticSubject syntheticSubjectPrev = null;
COMPARISON_STATE comparison_state= COMPARISON_STATE.UNKNOWN;
LArrayChunkedBuffer acceptedIdsBuffer = new LArrayChunkedBuffer();
while (indexIterator.hasNext()) {
if(indexDataBuffer != null){
indexDataBuffer.clear();
}
long nextObjectId = indexIterator.next(indexDataBuffer);
if(!syntheticSubject.setObjectData(nextObjectId, indexField, indexDataBuffer)){
continue;
}
if(!syntheticPredicate.match(syntheticSubject._syntheticSubject)){
continue;
}
acceptedIdsBuffer.add(nextObjectId);
if(syntheticComparator!=null && comparison_state!=COMPARISON_STATE.MIXED){
if(syntheticSubjectPrev!=null ){
int compareResult = syntheticComparator.compare(syntheticSubjectPrev._syntheticSubject, syntheticSubject._syntheticSubject);
if ((compareResult < 0 && comparison_state == COMPARISON_STATE.DESCENDING)
|| (compareResult > 0 && comparison_state == COMPARISON_STATE.ASCENDING))
{
comparison_state = COMPARISON_STATE.MIXED;
continue;
}else if(compareResult!=0){
comparison_state = compareResult < 0? COMPARISON_STATE.ASCENDING : COMPARISON_STATE.DESCENDING;
}
SyntheticSubject swapHolder = syntheticSubjectPrev;
syntheticSubjectPrev = syntheticSubject;
syntheticSubject = swapHolder;
}else{
syntheticSubjectPrev = syntheticSubject;
syntheticSubject = new SyntheticSubject(context, loader,syntheticPredicateSubjectClass);
continue;
}
}
}
if(comparator!=null){
if(comparison_state == COMPARISON_STATE.MIXED){
long[] acceptedIds = acceptedIdsBuffer.toArray();
acceptedIdsBuffer = null;//for GC
NQueryUtils.quickQuerySort(acceptedIds, syntheticComparator, syntheticSubject, syntheticSubjectPrev);
return new SimpleArrayQueryList(acceptedIds,session);
}else if (comparison_state == COMPARISON_STATE.DESCENDING){
acceptedIdsBuffer.setIteratorDirection(false);
}
}
return new ChunkedBufferQueryList(acceptedIdsBuffer, session);
} catch (Exception e) {
// TODO log
throw new JodbIOException(e);
} finally {
ticket.close();
}
//return null;
}
@SuppressWarnings("unchecked")
private ObjectSet executeUnoptimized(JODBSession session, Predicate predicate, Comparator comparator, INqLoader loader) throws IOException{
IOBase base = session.getBase();
IOTicket ticket = base.getIOTicket(true, false);
try {
Class predicateSubjectClass = loader.getPredicateSubjectClass();
JODBIndexingRootAgent indexingRootAgent = session.getIndexingRootAgent();
JODBIndexingAgent indexingAgent = null;
Iterator<Integer> predicateSubjectAccessedFields = loader.getAccessedPredicateSubjectFields();
if(predicateSubjectAccessedFields != null){
while (predicateSubjectAccessedFields.hasNext() && indexingAgent == null) {//TODO may be useless if analysis incomplete
Integer fieldId = predicateSubjectAccessedFields.next();
if(fieldId!=null){
indexingAgent = indexingRootAgent.getIndexingAgent(fieldId, session.getBase());
}
}
}
IndexDataIterator indexIterator = null;
if(indexingAgent!=null){
indexIterator = indexingAgent.getIndexIterator(true);
}
if(indexIterator==null){
indexIterator = new LArrayIndexIterator(base.getForAllObjects(ticket));
}
COMPARISON_STATE comparison_state= COMPARISON_STATE.UNKNOWN;
LArrayChunkedBuffer acceptedIdsBuffer = new LArrayChunkedBuffer();
Object previous = null;
while (indexIterator.hasNext()) {
long nextObjectId = indexIterator.next(null);
Object obj = session.getObjectForOffset(nextObjectId, JODBConfig.getDefaultActivationDepth());
//TODO make configurable upon analysis
if(!obj.getClass().isAssignableFrom(predicateSubjectClass)){
continue;
}
if(!predicate.match(obj)){
continue;
}
acceptedIdsBuffer.add(nextObjectId);
if(comparator!=null ){
if(previous!=null && comparison_state != COMPARISON_STATE.MIXED){
int compareResult = comparator.compare(previous, obj);
if ((compareResult < 0 && comparison_state == COMPARISON_STATE.DESCENDING)
|| (compareResult > 0 && comparison_state == COMPARISON_STATE.ASCENDING))
{
comparison_state = COMPARISON_STATE.MIXED;
continue;
}else if(compareResult!=0){
comparison_state = compareResult < 0? COMPARISON_STATE.ASCENDING : COMPARISON_STATE.DESCENDING;
}
}
previous = obj;
}
}
if(comparator!=null){
if(comparison_state == COMPARISON_STATE.MIXED){
long[] acceptedIds = acceptedIdsBuffer.toArray();
acceptedIdsBuffer = null;//for GC
NQueryUtils.quickQuerySort(acceptedIds, comparator,session);
return new SimpleArrayQueryList(acceptedIds,session);
}else if (comparison_state == COMPARISON_STATE.DESCENDING){
acceptedIdsBuffer.setIteratorDirection(false);
}
}
return new ChunkedBufferQueryList(acceptedIdsBuffer, session);
} catch (Exception e) {
// TODO log
throw new JodbIOException(e);
} finally {
ticket.close();
}
}
private INqLoader getLoader(JODBOperationContext operationContext, Predicate predicate, Comparator comparator) throws IOException{
LoaderCacheKey cacheKey = new LoaderCacheKey(predicate.getClass(), comparator!=null ? comparator.getClass() : null);//TODO threadlocal for key
INqLoader loader = _loadersCache.get(cacheKey);
if(loader == null){
synchronized (_loadersCache) {
loader = _loadersCache.get(cacheKey);
if(loader == null){
try {
loader = _nqLoaderClass.newInstance();// new NQLoader();
} catch (Exception e) {
throw new JodbIOException(e);
}
loader.registerPredicate(operationContext, predicate, comparator);
_loadersCache.put(cacheKey, loader);
}
}
}
return loader;
}
static class SyntheticSubject{
private Object _syntheticSubject;
private NQueryDataObjectProxy _dataObjectProxy;
public SyntheticSubject(JODBOperationContext context, INqLoader loader, Class syntheticPredicateSubjectClass) throws Exception {
JODBSession session = context.getSession();
Field proxyField = loader.getSyntheticProxySetMethod();
ClassDescriptor syntheticPredicateSubjectClassDescriptor = session.getDescriptorForClass(syntheticPredicateSubjectClass, true, proxyField);
_syntheticSubject = syntheticPredicateSubjectClassDescriptor.newInstance();
_dataObjectProxy = new NQueryDataObjectProxy();
proxyField.set(_syntheticSubject, _dataObjectProxy);
//setProxyMethod.invoke(_syntheticSubject, _dataObjectProxy);
_dataObjectProxy.setTargetClass(context, syntheticPredicateSubjectClassDescriptor);
}
boolean setObjectData(long objectOffset, int indexingField, ByteBuffer indexData) throws IOException {
return _dataObjectProxy.setObjectData(objectOffset, indexingField, indexData);
}
Object getSyntheticSubject() {
return _syntheticSubject;
}
}
private static class LoaderCacheKey{
Class _predicate;
Class _comparator;
int _hash;
public LoaderCacheKey(Class predicate, Class comparator) {
set(predicate, comparator);
}
public void set(Class predicate, Class comparator){
_predicate = predicate;
_comparator = comparator;
calcHashCode();
}
private void calcHashCode(){
_hash = _predicate.hashCode();
if(_comparator!=null){
_hash+=_comparator.hashCode();
}
}
@Override
public int hashCode() {
return _hash;
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
LoaderCacheKey objKey = (LoaderCacheKey) obj;
return objKey._predicate == _predicate && objKey._comparator == _comparator;
}
}
}