org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser.java

Source

/*
 * #%L
 * Alfresco Legacy Lucene
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.search.impl.lucene;

import java.io.IOException;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.dictionary.IndexTokenisationMode;
import org.alfresco.repo.search.MLAnalysisMode;
import org.alfresco.repo.search.adaptor.lucene.AnalysisMode;
import org.alfresco.repo.search.adaptor.lucene.LuceneFunction;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.repo.search.impl.QueryParserUtils;
import org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser;
import org.alfresco.repo.search.impl.lucene.analysis.MLTokenDuplicator;
import org.alfresco.repo.search.impl.parsers.FTSQueryException;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.CachingDateFormat;
import org.alfresco.util.ISO9075;
import org.alfresco.util.Pair;
import org.alfresco.util.SearchLanguageConversion;
import org.antlr.misc.OrderedHashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.CharStream;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.QueryParserTokenManager;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreRangeQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MultiPhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardTermEnum;
import org.apache.lucene.search.regex.RegexQuery;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.jaxen.saxpath.SAXPathException;
import org.springframework.extensions.surf.util.I18NUtil;

/**
 * Extensions to the standard lucene query parser.
 * <p>
 * Covers:
 * <ul>
 * <li>special fields;
 * <li>range expansion;
 * <li>adds wild card support for phrases;
 * <li>exposes more helper methods to build lucene queries and request tokneisation bahviour.
 * </ul>
 * TODO: Locale loop should not include tokenisation expansion
 * 
 * @author andyh
 */
public abstract class AbstractLuceneQueryParser extends QueryParser implements QueryConstants {
    @SuppressWarnings("unused")
    private static Log s_logger = LogFactory.getLog(AbstractLuceneQueryParser.class);

    protected NamespacePrefixResolver namespacePrefixResolver;

    protected DictionaryService dictionaryService;

    private TenantService tenantService;

    private SearchParameters searchParameters;

    private MLAnalysisMode defaultSearchMLAnalysisMode;

    private IndexReader indexReader;

    private int internalSlop = 0;

    private AbstractAnalyzer luceneAnalyser;

    /**
     * @param defaultSearchMLAnalysisMode MLAnalysisMode
     */
    public void setDefaultSearchMLAnalysisMode(MLAnalysisMode defaultSearchMLAnalysisMode) {
        this.defaultSearchMLAnalysisMode = defaultSearchMLAnalysisMode;
    }

    /**
     * @param indexReader IndexReader
     */
    public void setIndexReader(IndexReader indexReader) {
        this.indexReader = indexReader;
    }

    /**
     * @param searchParameters SearchParameters
     */
    public void setSearchParameters(SearchParameters searchParameters) {
        this.searchParameters = searchParameters;
    }

    /**
     * @param namespacePrefixResolver NamespacePrefixResolver
     */
    public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) {
        this.namespacePrefixResolver = namespacePrefixResolver;
    }

    /**
     * @param tenantService TenantService
     */
    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    public SearchParameters getSearchParameters() {
        return searchParameters;
    }

    public IndexReader getIndexReader() {
        return indexReader;
    }

    public MLAnalysisMode getDefaultSearchMLAnalysisMode() {
        return defaultSearchMLAnalysisMode;
    }

    public abstract boolean addContentCrossLocaleWildcards();

    /**
     * Lucene default constructor
     * 
     * @param arg0 String
     * @param arg1 Analyzer
     */
    public AbstractLuceneQueryParser(String arg0, Analyzer arg1) {
        super(arg0, arg1);
        if (arg1 instanceof AbstractAnalyzer) {
            luceneAnalyser = (AbstractAnalyzer) arg1;
        }
    }

    /**
     * Lucene default constructor
     * 
     * @param arg0 CharStream
     */
    public AbstractLuceneQueryParser(CharStream arg0) {
        super(arg0);
    }

    /**
     * Lucene default constructor
     * 
     * @param arg0 QueryParserTokenManager
     */
    public AbstractLuceneQueryParser(QueryParserTokenManager arg0) {
        super(arg0);
    }

    protected Query getFieldQuery(String field, String queryText, int slop) throws ParseException {
        try {
            internalSlop = slop;
            Query query = getFieldQuery(field, queryText);
            return query;
        } finally {
            internalSlop = 0;
        }

    }

    /**
     * @param field String
     * @param queryText String
     * @param analysisMode AnalysisMode
     * @param slop int
     * @param luceneFunction LuceneFunction
     * @return the query
     * @throws ParseException
     */
    public Query getFieldQuery(String field, String queryText, AnalysisMode analysisMode, int slop,
            LuceneFunction luceneFunction) throws ParseException {
        try {
            internalSlop = slop;
            Query query = getFieldQuery(field, queryText, analysisMode, luceneFunction);
            return query;
        } finally {
            internalSlop = 0;
        }

    }

    /**
     * @param field String
     * @param sqlLikeClause String
     * @param analysisMode AnalysisMode
     * @return the query
     * @throws ParseException
     */
    public Query getLikeQuery(String field, String sqlLikeClause, AnalysisMode analysisMode) throws ParseException {
        String luceneWildCardExpression = translate(sqlLikeClause);
        return getWildcardQuery(field, luceneWildCardExpression, AnalysisMode.LIKE);
    }

    private String translate(String string) {
        StringBuilder builder = new StringBuilder(string.length());

        boolean lastWasEscape = false;

        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (lastWasEscape) {
                builder.append(c);
                lastWasEscape = false;
            } else {
                if (c == '\\') {
                    lastWasEscape = true;
                } else if (c == '%') {
                    builder.append('*');
                } else if (c == '_') {
                    builder.append('?');
                } else if (c == '*') {
                    builder.append('\\');
                    builder.append(c);
                } else if (c == '?') {
                    builder.append('\\');
                    builder.append(c);
                } else {
                    builder.append(c);
                }
            }
        }
        if (lastWasEscape) {
            throw new FTSQueryException("Escape character at end of string " + string);
        }

        return builder.toString();
    }

    /**
     * @param field String
     * @param queryText String
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @return the query
     * @throws ParseException
     */
    public Query getDoesNotMatchFieldQuery(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        BooleanQuery query = new BooleanQuery();
        Query allQuery = new MatchAllDocsQuery();
        Query matchQuery = getFieldQuery(field, queryText, analysisMode, luceneFunction);
        if ((matchQuery != null)) {
            query.add(allQuery, Occur.MUST);
            query.add(matchQuery, Occur.MUST_NOT);
        } else {
            throw new UnsupportedOperationException();
        }
        return query;
    }

    public Query getFieldQuery(String field, String queryText) throws ParseException {
        return getFieldQuery(field, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    /**
     * @param field String
     * @param first String
     * @param last String
     * @param slop int
     * @param inOrder boolean
     * @return the query
     */
    public Query getSpanQuery(String field, String first, String last, int slop, boolean inOrder) {
        if (field.equals(FIELD_PATH)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_PATH);
        } else if (field.equals(FIELD_PATHWITHREPEATS)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_PATHWITHREPEATS);
        } else if (field.equals(FIELD_TEXT)) {
            Set<String> text = searchParameters.getTextAttributes();
            if ((text == null) || (text.size() == 0)) {
                Query query = getSpanQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), first,
                        last, slop, inOrder);
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : text) {
                    Query part = getSpanQuery(fieldName, first, last, slop, inOrder);
                    query.add(part, Occur.SHOULD);
                }
                return query;
            }
        } else if (field.equals(FIELD_CLASS)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_CLASS);
        } else if (field.equals(FIELD_TYPE)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_TYPE);
        } else if (field.equals(FIELD_EXACTTYPE)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_EXACTTYPE);
        } else if (field.equals(FIELD_ASPECT)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_ASPECT);
        } else if (field.equals(FIELD_EXACTASPECT)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_EXACTASPECT);
        } else if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            return spanQueryBuilder(field, first, last, slop, inOrder);
        } else if (field.equals(FIELD_ALL)) {
            Set<String> all = searchParameters.getAllAttributes();
            if ((all == null) || (all.size() == 0)) {
                Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
                BooleanQuery query = new BooleanQuery();
                for (QName qname : contentAttributes) {
                    Query part = getSpanQuery(PROPERTY_FIELD_PREFIX + qname.toString(), first, last, slop, inOrder);
                    query.add(part, Occur.SHOULD);
                }
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : all) {
                    Query part = getSpanQuery(fieldName, first, last, slop, inOrder);
                    query.add(part, Occur.SHOULD);
                }
                return query;
            }

        } else if (field.equals(FIELD_ISUNSET)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_ISUNSET);
        } else if (field.equals(FIELD_ISNULL)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_ISNULL);
        } else if (field.equals(FIELD_ISNOTNULL)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_ISNOTNULL);
        } else if (matchDataTypeDefinition(field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(matchDataTypeDefinition(field).getName());
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                Query part = getSpanQuery(PROPERTY_FIELD_PREFIX + qname.toString(), first, last, slop, inOrder);
                query.add(part, Occur.SHOULD);
            }
            return query;
        } else if (field.equals(FIELD_FTSSTATUS)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_FTSSTATUS);
        } else if (field.equals(FIELD_TAG)) {
            return null;
        } else {
            // Default behaviour for the following fields

            // FIELD_ID
            // FIELD_DBID
            // FIELD_ISROOT
            // FIELD_ISCONTAINER
            // FIELD_ISNODE
            // FIELD_TX
            // FIELD_PARENT
            // FIELD_PRIMARYPARENT
            // FIELD_QNAME
            // FIELD_PRIMARYASSOCTYPEQNAME
            // FIELD_ASSOCTYPEQNAME
            // 
            // 

            SpanQuery firstTerm = new SpanTermQuery(new Term(field, first));
            SpanQuery lastTerm = new SpanTermQuery(new Term(field, last));
            return new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
        }

    }

    private DataTypeDefinition matchDataTypeDefinition(String string) {
        QName search = QName.createQName(
                QueryParserUtils.expandQName(searchParameters.getNamespace(), namespacePrefixResolver, string));
        DataTypeDefinition dataTypeDefinition = dictionaryService.getDataType(QName.createQName(
                QueryParserUtils.expandQName(searchParameters.getNamespace(), namespacePrefixResolver, string)));
        QName match = null;
        if (dataTypeDefinition == null) {
            for (QName definition : dictionaryService.getAllDataTypes()) {
                if (definition.getNamespaceURI().equalsIgnoreCase(search.getNamespaceURI())) {
                    if (definition.getLocalName().equalsIgnoreCase(search.getLocalName())) {
                        if (match == null) {
                            match = definition;
                        } else {
                            throw new LuceneQueryParserException("Ambiguous data datype " + string);
                        }
                    }
                }

            }
        } else {
            return dataTypeDefinition;
        }
        if (match == null) {
            return null;
        } else {
            return dictionaryService.getDataType(match);
        }
    }

    private PropertyDefinition matchPropertyDefinition(String string) {
        QName search = QName.createQName(
                QueryParserUtils.expandQName(searchParameters.getNamespace(), namespacePrefixResolver, string));
        PropertyDefinition propertyDefinition = dictionaryService.getProperty(QName.createQName(
                QueryParserUtils.expandQName(searchParameters.getNamespace(), namespacePrefixResolver, string)));
        QName match = null;
        if (propertyDefinition == null) {
            for (QName definition : dictionaryService.getAllProperties(null)) {
                if (definition.getNamespaceURI().equalsIgnoreCase(search.getNamespaceURI())) {
                    if (definition.getLocalName().equalsIgnoreCase(search.getLocalName())) {
                        if (match == null) {
                            match = definition;
                        } else {
                            throw new LuceneQueryParserException("Ambiguous data datype " + string);
                        }
                    }
                }

            }
        } else {
            return propertyDefinition;
        }
        if (match == null) {
            return null;
        } else {
            return dictionaryService.getProperty(match);
        }
    }

    /**
     * @param field String
     * @param queryText String
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @return the query
     * @throws ParseException
     */
    public Query getFieldQuery(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        try {
            if (field.equals(FIELD_PATH)) {
                return createPathQuery(queryText, false);
            } else if (field.equals(FIELD_PATHWITHREPEATS)) {
                return createPathQuery(queryText, true);
            } else if (field.equals(FIELD_TEXT)) {
                return createTextQuery(queryText, analysisMode, luceneFunction);
            } else if (field.equals(FIELD_ID)) {
                return createIdQuery(queryText);
            } else if (field.equals(FIELD_DBID)) {
                return createDbidQuery(queryText);
            } else if (field.equals(FIELD_ACLID)) {
                return createAclIdQuery(queryText);
            } else if (field.equals(FIELD_OWNER)) {
                return createOwnerQuery(queryText);
            } else if (field.equals(FIELD_OWNERSET)) {
                return createOwnerSetQuery(queryText);
            } else if (field.equals(FIELD_READER)) {
                return createReaderQuery(queryText);
            } else if (field.equals(FIELD_READERSET)) {
                return createReaderSetQuery(queryText);
            } else if (field.equals(FIELD_AUTHORITY)) {
                return createAuthorityQuery(queryText);
            } else if (field.equals(FIELD_AUTHORITYSET)) {
                return createAuthoritySetQuery(queryText);
            } else if (field.equals(FIELD_ISROOT)) {
                return createIsRootQuery(queryText);
            } else if (field.equals(FIELD_ISCONTAINER)) {
                return createIsContainerQuery(queryText);
            } else if (field.equals(FIELD_ISNODE)) {
                return createIsNodeQuery(queryText);
            } else if (field.equals(FIELD_TX)) {
                return createTransactionQuery(queryText);
            } else if (field.equals(FIELD_INTXID)) {
                return createInTxIdQuery(queryText);
            } else if (field.equals(FIELD_INACLTXID)) {
                return createInAclTxIdQuery(queryText);
            } else if (field.equals(FIELD_PARENT)) {
                return createParentQuery(queryText);
            } else if (field.equals(FIELD_PRIMARYPARENT)) {
                return createPrimaryParentQuery(queryText);
            } else if (field.equals(FIELD_QNAME)) {
                return createQNameQuery(queryText);
            } else if (field.equals(FIELD_PRIMARYASSOCQNAME)) {
                return createPrimaryAssocQNameQuery(queryText);
            } else if (field.equals(FIELD_PRIMARYASSOCTYPEQNAME)) {
                return createPrimaryAssocTypeQNameQuery(queryText);
            } else if (field.equals(FIELD_ASSOCTYPEQNAME)) {
                return createAssocTypeQNameQuery(queryText);
            } else if (field.equals(FIELD_CLASS)) {
                ClassDefinition target = QueryParserUtils.matchClassDefinition(searchParameters.getNamespace(),
                        namespacePrefixResolver, dictionaryService, queryText);
                if (target == null) {
                    throw new LuceneQueryParserException("Invalid type: " + queryText);
                }
                return getFieldQuery(target.isAspect() ? FIELD_ASPECT : FIELD_TYPE, queryText, analysisMode,
                        luceneFunction);
            } else if (field.equals(FIELD_TYPE)) {
                return createTypeQuery(queryText, false);
            } else if (field.equals(FIELD_EXACTTYPE)) {
                return createTypeQuery(queryText, true);
            } else if (field.equals(FIELD_ASPECT)) {
                return createAspectQuery(queryText, false);
            } else if (field.equals(FIELD_EXACTASPECT)) {
                return createAspectQuery(queryText, true);
            } else if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
                Query query = attributeQueryBuilder(field, queryText, new FieldQuery(), analysisMode,
                        luceneFunction);
                return query;
            } else if (field.equals(FIELD_ALL)) {
                return createAllQuery(queryText, analysisMode, luceneFunction);
            } else if (field.equals(FIELD_ISUNSET)) {
                return createIsUnsetQuery(queryText, analysisMode, luceneFunction);
            } else if (field.equals(FIELD_ISNULL)) {
                return createIsNullQuery(queryText, analysisMode, luceneFunction);
            } else if (field.equals(FIELD_ISNOTNULL)) {
                return createIsNotNull(queryText, analysisMode, luceneFunction);
            } else if (matchDataTypeDefinition(field) != null) {
                return createDataTypeDefinitionQuery(field, queryText, analysisMode, luceneFunction);
            } else if (field.equals(FIELD_FTSSTATUS)) {
                return createTermQuery(field, queryText);
            } else if (field.equals(FIELD_TXID)) {
                return createTxIdQuery(queryText);
            } else if (field.equals(FIELD_ACLTXID)) {
                return createAclTxIdQuery(queryText);
            } else if (field.equals(FIELD_TXCOMMITTIME)) {
                return createTxCommitTimeQuery(queryText);
            } else if (field.equals(FIELD_ACLTXCOMMITTIME)) {
                return createAclTxCommitTimeQuery(queryText);
            } else if (field.equals(FIELD_TAG)) {
                return createTagQuery(queryText);
            } else if (field.equals(FIELD_SITE)) {
                return createSiteQuery(queryText);
            } else if (field.equals(FIELD_TENANT)) {
                return createTenantQuery(queryText);
            } else if (field.equals(FIELD_ANCESTOR)) {
                return createAncestorQuery(queryText);
            } else {
                return getFieldQueryImpl(field, queryText, analysisMode, luceneFunction);
            }

        } catch (SAXPathException e) {
            throw new ParseException("Failed to parse XPath...\n" + e.getMessage());
        }

    }

    protected Query createTenantQuery(String queryText) throws ParseException {
        if (queryText.length() > 0) {
            return getFieldQueryImpl(FIELD_TENANT, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
        } else {
            return getFieldQueryImpl(FIELD_TENANT, "_DEFAULT_", AnalysisMode.DEFAULT, LuceneFunction.FIELD);
        }
    }

    protected Query createAncestorQuery(String queryText) throws ParseException {
        return createNodeRefQuery(FIELD_ANCESTOR, queryText);
    }

    /**
     * @param tag (which will then be ISO9075 encoded)
     * @return Query
     * @throws ParseException
     */
    protected Query createTagQuery(String tag) throws ParseException {
        return getFieldQuery(FIELD_PATH, "/cm:taggable/cm:" + ISO9075.encode(tag.toLowerCase()) + "/member");
    }

    protected Query createSiteQuery(String site) throws ParseException {
        if (site.equals("_EVERYTHING_")) {
            return createTermQuery(FIELD_ISNODE, "T");
        } else if (site.equals("_ALL_SITES_")) {
            return getFieldQuery(FIELD_PATH, "/app:company_home/st:sites/*//*");
        } else if (site.equals("_REPOSITORY_")) {
            return createTermQuery(FIELD_ISNODE, "T");
        } else {
            return getFieldQuery(FIELD_PATH, "/app:company_home/st:sites/cm:" + ISO9075.encode(site) + "//*");
        }

    }

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createAclIdQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createOwnerQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createReaderQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createAuthorityQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createOwnerSetQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createReaderSetQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected abstract Query createAuthoritySetQuery(String queryText) throws ParseException;

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createDbidQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_DBID, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_TXID, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createAclTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_ACLTXID, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createTxCommitTimeQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_TXCOMMITTIME, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createAclTxCommitTimeQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_ACLTXCOMMITTIME, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createDataTypeDefinitionQuery(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        Collection<QName> contentAttributes = dictionaryService
                .getAllProperties(matchDataTypeDefinition(field).getName());
        BooleanQuery query = new BooleanQuery();
        for (QName qname : contentAttributes) {
            // The super implementation will create phrase queries etc if required
            Query part = getFieldQuery(PROPERTY_FIELD_PREFIX + qname.toString(), queryText, analysisMode,
                    luceneFunction);
            if (part != null) {
                query.add(part, Occur.SHOULD);
            } else {
                query.add(createNoMatchQuery(), Occur.SHOULD);
            }
        }
        return query;
    }

    protected Query createIsNotNull(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = matchPropertyDefinition(queryText);
        if (pd != null) {
            ClassDefinition containerClass = pd.getContainerClass();
            QName container = containerClass.getName();
            BooleanQuery query = new BooleanQuery();
            String classType = containerClass.isAspect() ? FIELD_ASPECT : FIELD_TYPE;
            Query typeQuery = getFieldQuery(classType, container.toString(), analysisMode, luceneFunction);
            Query presenceQuery = getWildcardQuery(PROPERTY_FIELD_PREFIX + pd.getName().toString(), "*");
            if ((typeQuery != null) && (presenceQuery != null)) {
                // query.add(typeQuery, Occur.MUST);
                query.add(presenceQuery, Occur.MUST);
            }
            return query;
        } else {
            return getFieldQueryImpl(FIELD_ISNOTNULL, queryText, analysisMode, luceneFunction);
        }
    }

    protected Query createIsNullQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = matchPropertyDefinition(queryText);
        if (pd != null) {
            BooleanQuery query = new BooleanQuery();
            Query presenceQuery = getWildcardQuery(PROPERTY_FIELD_PREFIX + pd.getName().toString(), "*");
            if (presenceQuery != null) {
                query.add(new MatchAllDocsQuery(), Occur.MUST);
                query.add(presenceQuery, Occur.MUST_NOT);
            }
            return query;
        } else {
            return getFieldQueryImpl(FIELD_ISNULL, queryText, analysisMode, luceneFunction);
        }
    }

    protected Query createIsUnsetQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = matchPropertyDefinition(queryText);
        if (pd != null) {
            ClassDefinition containerClass = pd.getContainerClass();
            QName container = containerClass.getName();
            BooleanQuery query = new BooleanQuery();
            String classType = containerClass.isAspect() ? FIELD_ASPECT : FIELD_TYPE;
            Query typeQuery = getFieldQuery(classType, container.toString(), analysisMode, luceneFunction);
            Query presenceQuery = getWildcardQuery(PROPERTY_FIELD_PREFIX + pd.getName().toString(), "*");
            if ((typeQuery != null) && (presenceQuery != null)) {
                query.add(typeQuery, Occur.MUST);
                query.add(presenceQuery, Occur.MUST_NOT);
            }
            return query;
        } else {
            return getFieldQueryImpl(FIELD_ISUNSET, queryText, analysisMode, luceneFunction);
        }
    }

    protected Query createAllQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        Set<String> all = searchParameters.getAllAttributes();
        if ((all == null) || (all.size() == 0)) {
            Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                // The super implementation will create phrase queries etc if required
                Query part = getFieldQuery(PROPERTY_FIELD_PREFIX + qname.toString(), queryText, analysisMode,
                        luceneFunction);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        } else {
            BooleanQuery query = new BooleanQuery();
            for (String fieldName : all) {
                Query part = getFieldQuery(fieldName, queryText, analysisMode, luceneFunction);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        }
    }

    protected Query createAspectQuery(String queryText, boolean exactOnly) {
        AspectDefinition target = QueryParserUtils.matchAspectDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (target == null) {
            // failed to find the aspect in the dictionary
            throw new AlfrescoRuntimeException("Unknown aspect specified in query: " + queryText);
        }

        if (exactOnly) {
            QName targetQName = target.getName();
            TermQuery termQuery = new TermQuery(new Term(FIELD_ASPECT, targetQName.toString()));

            return termQuery;
        } else {
            Collection<QName> subclasses = dictionaryService.getSubAspects(target.getName(), true);

            BooleanQuery booleanQuery = new BooleanQuery();
            for (QName qname : subclasses) {
                AspectDefinition current = dictionaryService.getAspect(qname);
                if (target.getName().equals(current.getName()) || current.getIncludedInSuperTypeQuery()) {
                    TermQuery termQuery = new TermQuery(new Term(FIELD_ASPECT, qname.toString()));
                    if (termQuery != null) {
                        booleanQuery.add(termQuery, Occur.SHOULD);
                    }
                }
            }
            return booleanQuery;
        }

    }

    protected Query createTypeQuery(String queryText, boolean exactOnly) {
        TypeDefinition target = QueryParserUtils.matchTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (target == null) {
            throw new LuceneQueryParserException("Invalid type: " + queryText);
        }
        if (exactOnly) {
            QName targetQName = target.getName();
            TermQuery termQuery = new TermQuery(new Term(FIELD_TYPE, targetQName.toString()));
            return termQuery;
        } else {
            Collection<QName> subclasses = dictionaryService.getSubTypes(target.getName(), true);
            BooleanQuery booleanQuery = new BooleanQuery();
            for (QName qname : subclasses) {
                TypeDefinition current = dictionaryService.getType(qname);
                if (target.getName().equals(current.getName()) || current.getIncludedInSuperTypeQuery()) {
                    TermQuery termQuery = new TermQuery(new Term(FIELD_TYPE, qname.toString()));
                    if (termQuery != null) {
                        booleanQuery.add(termQuery, Occur.SHOULD);
                    }
                }
            }
            return booleanQuery;
        }
    }

    protected abstract Query createAssocTypeQNameQuery(String queryText) throws SAXPathException;

    protected abstract Query createPrimaryAssocTypeQNameQuery(String queryText) throws SAXPathException;

    protected abstract Query createPrimaryAssocQNameQuery(String queryText) throws SAXPathException;

    protected abstract Query createQNameQuery(String queryText) throws SAXPathException;

    protected Query createInTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_INTXID, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createInAclTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImpl(FIELD_INACLTXID, queryText, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    protected Query createTransactionQuery(String queryText) {
        return createTermQuery(FIELD_TX, queryText);
    }

    protected Query createIsNodeQuery(String queryText) {
        return createTermQuery(FIELD_ISNODE, queryText);
    }

    protected Query createIsContainerQuery(String queryText) {
        return createTermQuery(FIELD_ISCONTAINER, queryText);
    }

    protected Query createIsRootQuery(String queryText) {
        return createTermQuery(FIELD_ISROOT, queryText);
    }

    protected Query createTermQuery(String field, String queryText) {
        TermQuery termQuery = new TermQuery(new Term(field, queryText));
        return termQuery;
    }

    protected Query createPrimaryParentQuery(String queryText) {
        return createNodeRefQuery(FIELD_PRIMARYPARENT, queryText);
    }

    protected Query createParentQuery(String queryText) {
        return createNodeRefQuery(FIELD_PARENT, queryText);
    }

    protected Query createIdQuery(String queryText) {
        return createNodeRefQuery(FIELD_ID, queryText);
    }

    protected Query createNodeRefQuery(String field, String queryText) {
        if (tenantService.isTenantUser() && (queryText.contains(StoreRef.URI_FILLER))) {
            // assume NodeRef, since it contains StorRef URI filler
            queryText = tenantService.getName(new NodeRef(queryText)).toString();
        }
        return createTermQuery(field, queryText);
    }

    abstract protected Query createPathQuery(String queryText, boolean withRepeats) throws SAXPathException;

    protected Query createTextQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        Set<String> text = searchParameters.getTextAttributes();
        if ((text == null) || (text.size() == 0)) {
            Query query = getFieldQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), queryText,
                    analysisMode, luceneFunction);
            if (query == null) {
                return createNoMatchQuery();
            }
            return query;
        } else {
            BooleanQuery query = new BooleanQuery();
            for (String fieldName : text) {
                Query part = getFieldQuery(fieldName, queryText, analysisMode, luceneFunction);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        }
    }

    @SuppressWarnings("unchecked")
    protected Query getFieldQueryImpl(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        // Use the analyzer to get all the tokens, and then build a TermQuery,
        // PhraseQuery, or noth

        // TODO: Untokenised columns with functions require special handling

        if (luceneFunction != LuceneFunction.FIELD) {
            throw new UnsupportedOperationException(
                    "Field queries are not supported on lucene functions (UPPER, LOWER, etc)");
        }

        // if the incoming string already has a language identifier we strip it iff and addit back on again

        String localePrefix = "";

        String toTokenise = queryText;

        if (queryText.startsWith("{")) {
            int position = queryText.indexOf("}");
            String language = queryText.substring(0, position + 1);
            Locale locale = new Locale(queryText.substring(1, position));
            String token = queryText.substring(position + 1);
            boolean found = false;
            if (!locale.toString().isEmpty()) {
                for (Locale current : Locale.getAvailableLocales()) {
                    if (current.toString().equalsIgnoreCase(locale.toString())) {
                        found = true;
                        break;
                    }
                }
            }
            if (found) {
                localePrefix = language;
                toTokenise = token;
            } else {
                toTokenise = token;
            }
        }

        String testText = toTokenise;
        boolean requiresMLTokenDuplication = false;
        String localeString = null;
        if (field.startsWith(PROPERTY_FIELD_PREFIX) && (localePrefix.length() == 0)) {
            if ((queryText.length() > 0) && (queryText.charAt(0) == '\u0000')) {
                int position = queryText.indexOf("\u0000", 1);
                testText = queryText.substring(position + 1);
                requiresMLTokenDuplication = true;
                localeString = queryText.substring(1, position);
            }
        }

        // find the positions of any escaped * and ? and ignore them

        Set<Integer> wildcardPoistions = getWildcardPositions(testText);

        TokenStream source;
        if ((localePrefix.length() == 0) || (wildcardPoistions.size() > 0)
                || (analysisMode == AnalysisMode.IDENTIFIER)) {
            source = getAnalyzer().tokenStream(field, new StringReader(toTokenise), analysisMode);
        } else {
            source = getAnalyzer().tokenStream(field, new StringReader(
                    "\u0000" + localePrefix.substring(1, localePrefix.length() - 1) + "\u0000" + toTokenise),
                    analysisMode);
            localePrefix = "";
        }

        ArrayList<org.apache.lucene.analysis.Token> list = new ArrayList<org.apache.lucene.analysis.Token>();
        org.apache.lucene.analysis.Token reusableToken = new org.apache.lucene.analysis.Token();
        org.apache.lucene.analysis.Token nextToken;
        int positionCount = 0;
        boolean severalTokensAtSamePosition = false;

        while (true) {
            try {
                nextToken = source.next(reusableToken);
            } catch (IOException e) {
                nextToken = null;
            }
            if (nextToken == null)
                break;
            list.add((org.apache.lucene.analysis.Token) nextToken.clone());
            if (nextToken.getPositionIncrement() != 0)
                positionCount += nextToken.getPositionIncrement();
            else
                severalTokensAtSamePosition = true;
        }
        try {
            source.close();
        } catch (IOException e) {
            // ignore
        }

        // add any alpha numeric wildcards that have been missed
        // Fixes most stop word and wild card issues

        for (int index = 0; index < testText.length(); index++) {
            char current = testText.charAt(index);
            if (((current == '*') || (current == '?')) && wildcardPoistions.contains(index)) {
                StringBuilder pre = new StringBuilder(10);
                if (index == 0) {
                    // "*" and "?" at the start

                    boolean found = false;
                    for (int j = 0; j < list.size(); j++) {
                        org.apache.lucene.analysis.Token test = list.get(j);
                        if ((test.startOffset() <= 0) && (0 < test.endOffset())) {
                            found = true;
                            break;
                        }
                    }
                    if (!found && (testText.length() == 1)) {
                        // Add new token followed by * not given by the tokeniser
                        org.apache.lucene.analysis.Token newToken = new org.apache.lucene.analysis.Token(0, 0);
                        newToken.setTermBuffer("");
                        newToken.setType("ALPHANUM");
                        if (requiresMLTokenDuplication) {
                            Locale locale = I18NUtil.parseLocale(localeString);
                            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                                    ? defaultSearchMLAnalysisMode
                                    : searchParameters.getMlAnalaysisMode();
                            MLTokenDuplicator duplicator = new MLTokenDuplicator(locale, mlAnalysisMode);
                            Iterator<org.apache.lucene.analysis.Token> it = duplicator.buildIterator(newToken);
                            if (it != null) {
                                int count = 0;
                                while (it.hasNext()) {
                                    list.add(it.next());
                                    count++;
                                    if (count > 1) {
                                        severalTokensAtSamePosition = true;
                                    }
                                }
                            }
                        }
                        // content
                        else {
                            list.add(newToken);
                        }
                    }
                } else if (index > 0) {
                    // Add * and ? back into any tokens from which it has been removed

                    boolean tokenFound = false;
                    for (int j = 0; j < list.size(); j++) {
                        org.apache.lucene.analysis.Token test = list.get(j);
                        if ((test.startOffset() <= index) && (index < test.endOffset())) {
                            if (requiresMLTokenDuplication) {
                                String termText = new String(test.termBuffer(), 0, test.termLength());
                                int position = termText.indexOf("}");
                                String language = termText.substring(0, position + 1);
                                String token = termText.substring(position + 1);
                                if (index >= test.startOffset() + token.length()) {
                                    test.setTermBuffer(language + token + current);
                                }
                            } else {
                                if (index >= test.startOffset() + test.termLength()) {
                                    test.setTermBuffer(test.term() + current);
                                }
                            }
                            tokenFound = true;
                            break;
                        }
                    }

                    if (!tokenFound) {
                        for (int i = index - 1; i >= 0; i--) {
                            char c = testText.charAt(i);
                            if (Character.isLetterOrDigit(c)) {
                                boolean found = false;
                                for (int j = 0; j < list.size(); j++) {
                                    org.apache.lucene.analysis.Token test = list.get(j);
                                    if ((test.startOffset() <= i) && (i < test.endOffset())) {
                                        found = true;
                                        break;
                                    }
                                }
                                if (found) {
                                    break;
                                } else {
                                    pre.insert(0, c);
                                }
                            } else {
                                break;
                            }
                        }
                        if (pre.length() > 0) {
                            // Add new token followed by * not given by the tokeniser
                            org.apache.lucene.analysis.Token newToken = new org.apache.lucene.analysis.Token(
                                    index - pre.length(), index);
                            newToken.setTermBuffer(pre.toString());
                            newToken.setType("ALPHANUM");
                            if (requiresMLTokenDuplication) {
                                Locale locale = I18NUtil.parseLocale(localeString);
                                MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                                        ? defaultSearchMLAnalysisMode
                                        : searchParameters.getMlAnalaysisMode();
                                MLTokenDuplicator duplicator = new MLTokenDuplicator(locale, mlAnalysisMode);
                                Iterator<org.apache.lucene.analysis.Token> it = duplicator.buildIterator(newToken);
                                if (it != null) {
                                    int count = 0;
                                    while (it.hasNext()) {
                                        list.add(it.next());
                                        count++;
                                        if (count > 1) {
                                            severalTokensAtSamePosition = true;
                                        }
                                    }
                                }
                            }
                            // content
                            else {
                                list.add(newToken);
                            }
                        }
                    }
                }

                StringBuilder post = new StringBuilder(10);
                if (index > 0) {
                    for (int i = index + 1; i < testText.length(); i++) {
                        char c = testText.charAt(i);
                        if (Character.isLetterOrDigit(c)) {
                            boolean found = false;
                            for (int j = 0; j < list.size(); j++) {
                                org.apache.lucene.analysis.Token test = list.get(j);
                                if ((test.startOffset() <= i) && (i < test.endOffset())) {
                                    found = true;
                                    break;
                                }
                            }
                            if (found) {
                                break;
                            } else {
                                post.append(c);
                            }
                        } else {
                            break;
                        }
                    }
                    if (post.length() > 0) {
                        // Add new token followed by * not given by the tokeniser
                        org.apache.lucene.analysis.Token newToken = new org.apache.lucene.analysis.Token(index + 1,
                                index + 1 + post.length());
                        newToken.setTermBuffer(post.toString());
                        newToken.setType("ALPHANUM");
                        if (requiresMLTokenDuplication) {
                            Locale locale = I18NUtil.parseLocale(localeString);
                            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                                    ? defaultSearchMLAnalysisMode
                                    : searchParameters.getMlAnalaysisMode();
                            MLTokenDuplicator duplicator = new MLTokenDuplicator(locale, mlAnalysisMode);
                            Iterator<org.apache.lucene.analysis.Token> it = duplicator.buildIterator(newToken);
                            if (it != null) {
                                int count = 0;
                                while (it.hasNext()) {
                                    list.add(it.next());
                                    count++;
                                    if (count > 1) {
                                        severalTokensAtSamePosition = true;
                                    }
                                }
                            }
                        }
                        // content
                        else {
                            list.add(newToken);
                        }
                    }
                }

            }
        }

        Collections.sort(list, new Comparator<org.apache.lucene.analysis.Token>() {

            public int compare(Token o1, Token o2) {
                int dif = o1.startOffset() - o2.startOffset();
                if (dif != 0) {
                    return dif;
                } else {
                    return o2.getPositionIncrement() - o1.getPositionIncrement();
                }
            }
        });

        // Combined * and ? based strings - should redo the tokeniser

        // Build tokens by position

        LinkedList<LinkedList<org.apache.lucene.analysis.Token>> tokensByPosition = new LinkedList<LinkedList<org.apache.lucene.analysis.Token>>();
        LinkedList<org.apache.lucene.analysis.Token> currentList = null;
        for (org.apache.lucene.analysis.Token c : list) {
            if (c.getPositionIncrement() == 0) {
                if (currentList == null) {
                    currentList = new LinkedList<org.apache.lucene.analysis.Token>();
                    tokensByPosition.add(currentList);
                }
                currentList.add(c);
            } else {
                currentList = new LinkedList<org.apache.lucene.analysis.Token>();
                tokensByPosition.add(currentList);
                currentList.add(c);
            }
        }

        // Build all the token sequences and see which ones get strung together

        LinkedList<LinkedList<org.apache.lucene.analysis.Token>> allTokenSequences = new LinkedList<LinkedList<org.apache.lucene.analysis.Token>>();
        for (LinkedList<org.apache.lucene.analysis.Token> tokensAtPosition : tokensByPosition) {
            if (allTokenSequences.size() == 0) {
                for (org.apache.lucene.analysis.Token t : tokensAtPosition) {
                    LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                    newEntry.add(t);
                    allTokenSequences.add(newEntry);
                }
            } else {
                LinkedList<LinkedList<org.apache.lucene.analysis.Token>> newAllTokeSequences = new LinkedList<LinkedList<org.apache.lucene.analysis.Token>>();

                FOR_FIRST_TOKEN_AT_POSITION_ONLY: for (org.apache.lucene.analysis.Token t : tokensAtPosition) {
                    boolean tokenFoundSequence = false;
                    for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : allTokenSequences) {
                        LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                        newEntry.addAll(tokenSequence);
                        if (newEntry.getLast().endOffset() <= t.startOffset()) {
                            newEntry.add(t);
                            tokenFoundSequence = true;
                        }
                        newAllTokeSequences.add(newEntry);
                    }
                    if (false == tokenFoundSequence) {
                        LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                        newEntry.add(t);
                        newAllTokeSequences.add(newEntry);
                    }
                    // Limit the max number of permutations we consider
                    if (newAllTokeSequences.size() > 64) {
                        break FOR_FIRST_TOKEN_AT_POSITION_ONLY;
                    }
                }
                allTokenSequences = newAllTokeSequences;
            }
        }

        // build the uniquie

        LinkedList<LinkedList<org.apache.lucene.analysis.Token>> fixedTokenSequences = new LinkedList<LinkedList<org.apache.lucene.analysis.Token>>();
        for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : allTokenSequences) {
            LinkedList<org.apache.lucene.analysis.Token> fixedTokenSequence = new LinkedList<org.apache.lucene.analysis.Token>();
            fixedTokenSequences.add(fixedTokenSequence);
            org.apache.lucene.analysis.Token replace = null;
            for (org.apache.lucene.analysis.Token c : tokenSequence) {
                if (replace == null) {
                    StringBuilder prefix = new StringBuilder();
                    for (int i = c.startOffset() - 1; i >= 0; i--) {
                        char test = testText.charAt(i);
                        if (((test == '*') || (test == '?')) && wildcardPoistions.contains(i)) {
                            prefix.insert(0, test);
                        } else {
                            break;
                        }
                    }
                    String pre = prefix.toString();
                    if (requiresMLTokenDuplication) {
                        String termText = new String(c.termBuffer(), 0, c.termLength());
                        int position = termText.indexOf("}");
                        String language = termText.substring(0, position + 1);
                        String token = termText.substring(position + 1);
                        replace = new org.apache.lucene.analysis.Token(c.startOffset() - pre.length(),
                                c.endOffset());
                        replace.setTermBuffer(language + pre + token);
                        replace.setType(c.type());
                        replace.setPositionIncrement(c.getPositionIncrement());
                    } else {
                        String termText = new String(c.termBuffer(), 0, c.termLength());
                        replace = new org.apache.lucene.analysis.Token(c.startOffset() - pre.length(),
                                c.endOffset());
                        replace.setTermBuffer(pre + termText);
                        replace.setType(c.type());
                        replace.setPositionIncrement(c.getPositionIncrement());
                    }
                } else {
                    StringBuilder prefix = new StringBuilder();
                    StringBuilder postfix = new StringBuilder();
                    StringBuilder builder = prefix;
                    for (int i = c.startOffset() - 1; i >= replace.endOffset(); i--) {
                        char test = testText.charAt(i);
                        if (((test == '*') || (test == '?')) && wildcardPoistions.contains(i)) {
                            builder.insert(0, test);
                        } else {
                            builder = postfix;
                            postfix.setLength(0);
                        }
                    }
                    String pre = prefix.toString();
                    String post = postfix.toString();

                    // Does it bridge?
                    if ((pre.length() > 0) && (replace.endOffset() + pre.length()) == c.startOffset()) {
                        String termText = new String(c.termBuffer(), 0, c.termLength());
                        if (requiresMLTokenDuplication) {
                            int position = termText.indexOf("}");
                            @SuppressWarnings("unused")
                            String language = termText.substring(0, position + 1);
                            String token = termText.substring(position + 1);
                            int oldPositionIncrement = replace.getPositionIncrement();
                            String replaceTermText = new String(replace.termBuffer(), 0, replace.termLength());
                            replace = new org.apache.lucene.analysis.Token(replace.startOffset(), c.endOffset());
                            replace.setTermBuffer(replaceTermText + pre + token);
                            replace.setType(replace.type());
                            replace.setPositionIncrement(oldPositionIncrement);
                        } else {
                            int oldPositionIncrement = replace.getPositionIncrement();
                            String replaceTermText = new String(replace.termBuffer(), 0, replace.termLength());
                            replace = new org.apache.lucene.analysis.Token(replace.startOffset(), c.endOffset());
                            replace.setTermBuffer(replaceTermText + pre + termText);
                            replace.setType(replace.type());
                            replace.setPositionIncrement(oldPositionIncrement);
                        }
                    } else {
                        String termText = new String(c.termBuffer(), 0, c.termLength());
                        if (requiresMLTokenDuplication) {
                            int position = termText.indexOf("}");
                            String language = termText.substring(0, position + 1);
                            String token = termText.substring(position + 1);
                            String replaceTermText = new String(replace.termBuffer(), 0, replace.termLength());
                            org.apache.lucene.analysis.Token last = new org.apache.lucene.analysis.Token(
                                    replace.startOffset(), replace.endOffset() + post.length());
                            last.setTermBuffer(replaceTermText + post);
                            last.setType(replace.type());
                            last.setPositionIncrement(replace.getPositionIncrement());
                            fixedTokenSequence.add(last);
                            replace = new org.apache.lucene.analysis.Token(c.startOffset() - pre.length(),
                                    c.endOffset());
                            replace.setTermBuffer(language + pre + token);
                            replace.setType(c.type());
                            replace.setPositionIncrement(c.getPositionIncrement());
                        } else {
                            String replaceTermText = new String(replace.termBuffer(), 0, replace.termLength());
                            org.apache.lucene.analysis.Token last = new org.apache.lucene.analysis.Token(
                                    replace.startOffset(), replace.endOffset() + post.length());
                            last.setTermBuffer(replaceTermText + post);
                            last.setType(replace.type());
                            last.setPositionIncrement(replace.getPositionIncrement());
                            fixedTokenSequence.add(last);
                            replace = new org.apache.lucene.analysis.Token(c.startOffset() - pre.length(),
                                    c.endOffset());
                            replace.setTermBuffer(pre + termText);
                            replace.setType(c.type());
                            replace.setPositionIncrement(c.getPositionIncrement());
                        }
                    }
                }
            }
            // finish last
            if (replace != null) {
                StringBuilder postfix = new StringBuilder();
                if ((replace.endOffset() >= 0) && (replace.endOffset() < testText.length())) {
                    for (int i = replace.endOffset(); i < testText.length(); i++) {
                        char test = testText.charAt(i);
                        if (((test == '*') || (test == '?')) && wildcardPoistions.contains(i)) {
                            postfix.append(test);
                        } else {
                            break;
                        }
                    }
                }
                String post = postfix.toString();
                int oldPositionIncrement = replace.getPositionIncrement();
                String replaceTermText = new String(replace.termBuffer(), 0, replace.termLength());
                replace = new org.apache.lucene.analysis.Token(replace.startOffset(),
                        replace.endOffset() + post.length());
                replace.setTermBuffer(replaceTermText + post);
                replace.setType(replace.type());
                replace.setPositionIncrement(oldPositionIncrement);
                fixedTokenSequence.add(replace);
            }
        }

        // rebuild fixed list

        ArrayList<org.apache.lucene.analysis.Token> fixed = new ArrayList<org.apache.lucene.analysis.Token>();
        for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : fixedTokenSequences) {
            for (org.apache.lucene.analysis.Token token : tokenSequence) {
                fixed.add(token);
            }
        }

        // reorder by start position and increment

        Collections.sort(fixed, new Comparator<org.apache.lucene.analysis.Token>() {

            public int compare(Token o1, Token o2) {
                int dif = o1.startOffset() - o2.startOffset();
                if (dif != 0) {
                    return dif;
                } else {
                    return o1.getPositionIncrement() - o2.getPositionIncrement();
                }
            }
        });

        // make sure we remove any tokens we have duplicated

        @SuppressWarnings("rawtypes")
        OrderedHashSet unique = new OrderedHashSet();
        unique.addAll(fixed);
        fixed = new ArrayList<org.apache.lucene.analysis.Token>(unique);

        list = fixed;

        // add any missing locales back to the tokens

        if (localePrefix.length() > 0) {
            for (int j = 0; j < list.size(); j++) {
                org.apache.lucene.analysis.Token currentToken = list.get(j);
                String termText = new String(currentToken.termBuffer(), 0, currentToken.termLength());
                currentToken.setTermBuffer(localePrefix + termText);
            }
        }

        if (list.size() == 0)
            return null;
        else if (list.size() == 1) {
            nextToken = list.get(0);
            String termText = new String(nextToken.termBuffer(), 0, nextToken.termLength());
            if (termText.contains("*") || termText.contains("?")) {
                return newWildcardQuery(
                        new Term(field, getLowercaseExpandedTerms() ? termText.toLowerCase() : termText));
            } else {
                return newTermQuery(new Term(field, termText));
            }
        } else {
            if (severalTokensAtSamePosition) {
                if (positionCount == 1) {
                    // no phrase query:
                    BooleanQuery q = newBooleanQuery(true);
                    for (int i = 0; i < list.size(); i++) {
                        Query currentQuery;
                        nextToken = list.get(i);
                        String termText = new String(nextToken.termBuffer(), 0, nextToken.termLength());
                        if (termText.contains("*") || termText.contains("?")) {
                            currentQuery = newWildcardQuery(new Term(field,
                                    getLowercaseExpandedTerms() ? termText.toLowerCase() : termText));
                        } else {
                            currentQuery = newTermQuery(new Term(field, termText));
                        }
                        q.add(currentQuery, BooleanClause.Occur.SHOULD);
                    }
                    return q;
                }
                // Consider if we can use a multi-phrase query (e.g for synonym use rather then WordDelimiterFilterFactory)
                else if (canUseMultiPhraseQuery(fixedTokenSequences)) {
                    // phrase query:
                    MultiPhraseQuery mpq = newMultiPhraseQuery();
                    mpq.setSlop(internalSlop);
                    ArrayList<Term> multiTerms = new ArrayList<Term>();
                    int position = 0;
                    for (int i = 0; i < list.size(); i++) {
                        nextToken = list.get(i);
                        String termText = new String(nextToken.termBuffer(), 0, nextToken.termLength());

                        Term term = new Term(field, termText);
                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            addWildcardTerms(multiTerms, term);
                        } else {
                            multiTerms.add(term);
                        }

                        if (nextToken.getPositionIncrement() > 0 && multiTerms.size() > 0) {
                            if (getEnablePositionIncrements()) {
                                mpq.add(multiTerms.toArray(new Term[0]), position);
                            } else {
                                mpq.add(multiTerms.toArray(new Term[0]));
                            }
                            checkTermCount(field, queryText, mpq);
                            multiTerms.clear();
                        }
                        position += nextToken.getPositionIncrement();

                    }
                    if (getEnablePositionIncrements()) {
                        if (multiTerms.size() > 0) {
                            mpq.add(multiTerms.toArray(new Term[0]), position);
                        }
                        //                        else
                        //                        {
                        //                            mpq.add(new Term[] { new Term(field, "\u0000") }, position);
                        //                        }
                    } else {
                        if (multiTerms.size() > 0) {
                            mpq.add(multiTerms.toArray(new Term[0]));
                        }
                        //                        else
                        //                        {
                        //                            mpq.add(new Term[] { new Term(field, "\u0000") });
                        //                        }
                    }
                    checkTermCount(field, queryText, mpq);
                    return mpq;

                }
                // Word delimiter factory and other odd things generate complex token patterns
                // Smart skip token  sequences with small tokens that generate toomany wildcards
                // Fall back to the larger pattern
                // e.g Site1* will not do (S ite 1*) or (Site 1*)  if 1* matches too much (S ite1*)  and (Site1*) will still be OK 
                // If we skip all (for just 1* in the input) this is still an issue.
                else {
                    boolean skippedTokens = false;
                    BooleanQuery q = newBooleanQuery(true);
                    TOKEN_SEQUENCE: for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : fixedTokenSequences) {
                        // phrase query:
                        MultiPhraseQuery mpq = newMultiPhraseQuery();
                        mpq.setSlop(internalSlop);
                        int position = 0;
                        for (int i = 0; i < tokenSequence.size(); i++) {
                            nextToken = (org.apache.lucene.analysis.Token) tokenSequence.get(i);
                            String termText = new String(nextToken.termBuffer(), 0, nextToken.termLength());

                            Term term = new Term(field, termText);

                            if (getEnablePositionIncrements()) {
                                if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                    mpq.add(getMatchingTerms(field, term), position);
                                } else {
                                    mpq.add(new Term[] { term }, position);
                                }
                                if (exceedsTermCount(mpq)) {
                                    // We could duplicate the token sequence without the failing wildcard expansion and try again ??
                                    skippedTokens = true;
                                    continue TOKEN_SEQUENCE;
                                }
                                if (nextToken.getPositionIncrement() > 0) {
                                    position += nextToken.getPositionIncrement();
                                } else {
                                    position++;
                                }

                            } else {
                                if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                    mpq.add(getMatchingTerms(field, term));
                                } else {
                                    mpq.add(term);
                                }
                                if (exceedsTermCount(mpq)) {
                                    skippedTokens = true;
                                    continue TOKEN_SEQUENCE;
                                }
                            }
                        }
                        q.add(mpq, BooleanClause.Occur.SHOULD);
                    }
                    if (skippedTokens && (q.clauses().size() == 0)) {
                        throw new LuceneQueryParserException(
                                "Query skipped all token sequences as wildcards generated too many clauses: "
                                        + field + " " + queryText);
                    }
                    return q;
                }
            } else {
                MultiPhraseQuery q = new MultiPhraseQuery();
                q.setSlop(internalSlop);
                int position = 0;
                for (int i = 0; i < list.size(); i++) {
                    nextToken = list.get(i);
                    String termText = new String(nextToken.termBuffer(), 0, nextToken.termLength());
                    Term term = new Term(field, termText);
                    if (getEnablePositionIncrements()) {
                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            q.add(getMatchingTerms(field, term), position);
                        } else {
                            q.add(new Term[] { term }, position);
                        }
                        checkTermCount(field, queryText, q);
                        if (nextToken.getPositionIncrement() > 0) {
                            position += nextToken.getPositionIncrement();
                        } else {
                            position++;
                        }
                    } else {
                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            q.add(getMatchingTerms(field, term));
                        } else {
                            q.add(term);
                        }
                        checkTermCount(field, queryText, q);
                    }
                }
                return q;
            }
        }
    }

    /**
     * @param field String
     * @param queryText String
     * @param mpq MultiPhraseQuery
     */
    private void checkTermCount(String field, String queryText, MultiPhraseQuery mpq) {
        if (exceedsTermCount(mpq)) {
            throw new LuceneQueryParserException(
                    "Wildcard has generated too many clauses: " + field + " " + queryText);
        }
    }

    /**
     * 
     * @param mpq MultiPhraseQuery
     * @return boolean
     */
    private boolean exceedsTermCount(MultiPhraseQuery mpq) {
        int termCount = 0;
        for (Iterator<?> iter = mpq.getTermArrays().iterator(); iter.hasNext(); /**/) {
            Term[] arr = (Term[]) iter.next();
            termCount += arr.length;
            if (termCount > BooleanQuery.getMaxClauseCount()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param fixedTokenSequences LinkedList<LinkedList<Token>>
     * @return boolean
     */
    private boolean canUseMultiPhraseQuery(LinkedList<LinkedList<Token>> fixedTokenSequences) {
        if (fixedTokenSequences.size() <= 1) {
            return true;
        }
        LinkedList<Token> first = fixedTokenSequences.get(0);
        for (int i = 1; i < fixedTokenSequences.size(); i++) {
            LinkedList<Token> current = fixedTokenSequences.get(i);
            if (first.size() != current.size()) {
                return false;
            }
            for (int j = 0; j < first.size(); j++) {
                Token fromFirst = first.get(j);
                Token fromCurrent = current.get(j);
                if (fromFirst.startOffset() != fromCurrent.startOffset()) {
                    return false;
                }
            }
        }
        return true;
    }

    private Set<Integer> getWildcardPositions(String string) {
        HashSet<Integer> wildcardPositions = new HashSet<Integer>();

        boolean lastWasEscape = false;

        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (lastWasEscape) {
                lastWasEscape = false;
            } else {
                if (c == '\\') {
                    lastWasEscape = true;
                } else if (c == '*') {
                    wildcardPositions.add(i);
                } else if (c == '?') {
                    wildcardPositions.add(i);
                }
            }
        }

        return wildcardPositions;
    }

    private Term[] getMatchingTerms(String field, Term term) throws ParseException {
        ArrayList<Term> terms = new ArrayList<Term>();
        addWildcardTerms(terms, term);
        if (terms.size() == 0) {
            return new Term[] { new Term(field, "\u0000") };
        } else {
            return terms.toArray(new Term[0]);
        }

    }

    private void addWildcardTerms(ArrayList<Term> terms, Term term) throws ParseException {
        try {
            Term searchTerm = term;
            if (getLowercaseExpandedTerms()) {
                searchTerm = new Term(term.field(), term.text().toLowerCase());
            }
            WildcardTermEnum wcte = new WildcardTermEnum(indexReader, searchTerm);

            do {
                Term current = wcte.term();
                if (current != null) {
                    if ((current.text() != null) && (current.text().length() > 0)
                            && (current.text().charAt(0) == '{')) {
                        if ((searchTerm != null) && (searchTerm.text().length() > 0)
                                && (searchTerm.text().charAt(0) == '{')) {
                            terms.add(current);
                        }
                        // If not, we cod not add so wildcards do not match the locale prefix
                    } else {
                        terms.add(current);
                    }
                }
            } while (wcte.next());

        } catch (IOException e) {
            throw new ParseException("IO error generating phares wildcards " + e.getMessage());
        }
    }

    /**
     * @exception ParseException
     *                throw in overridden method to disallow
     */
    protected Query getRangeQuery(String field, String part1, String part2, boolean inclusive)
            throws ParseException {
        return getRangeQuery(field, part1, part2, inclusive, inclusive, AnalysisMode.DEFAULT, LuceneFunction.FIELD);
    }

    /**
     * @param field String
     * @param part1 String
     * @param part2 String
     * @param includeLower boolean
     * @param includeUpper boolean
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @return the query
     * @exception ParseException
     *                throw in overridden method to disallow
     */
    public Query getRangeQuery(String field, String part1, String part2, boolean includeLower, boolean includeUpper,
            AnalysisMode analysisMode, LuceneFunction luceneFunction) throws ParseException {
        if (field.equals(FIELD_PATH)) {
            throw new UnsupportedOperationException("Range Queries are not support for " + FIELD_PATH);
        } else if (field.equals(FIELD_PATHWITHREPEATS)) {
            throw new UnsupportedOperationException("Range Queries are not support for " + FIELD_PATHWITHREPEATS);
        } else if (field.equals(FIELD_TEXT)) {
            Set<String> text = searchParameters.getTextAttributes();
            if ((text == null) || (text.size() == 0)) {
                Query query = getRangeQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), part1,
                        part2, includeLower, includeUpper, analysisMode, luceneFunction);
                if (query == null) {
                    return createNoMatchQuery();
                }

                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : text) {
                    Query part = getRangeQuery(fieldName, part1, part2, includeLower, includeUpper, analysisMode,
                            luceneFunction);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }

        }
        // FIELD_ID uses the default
        // FIELD_DBID uses the default
        // FIELD_ISROOT uses the default
        // FIELD_ISCONTAINER uses the default
        // FIELD_ISNODE uses the default
        // FIELD_TX uses the default
        // FIELD_PARENT uses the default
        // FIELD_PRIMARYPARENT uses the default
        // FIELD_QNAME uses the default
        // FIELD_PRIMARYASSOCTYPEQNAME uses the default
        // FIELD_ASSOCTYPEQNAME uses the default
        // FIELD_CLASS uses the default
        // FIELD_TYPE uses the default
        // FIELD_EXACTTYPE uses the default
        // FIELD_ASPECT uses the default
        // FIELD_EXACTASPECT uses the default
        // FIELD_TYPE uses the default
        // FIELD_TYPE uses the default
        if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            String fieldName;
            PropertyDefinition propertyDef = matchPropertyDefinition(field.substring(1));
            if (propertyDef != null) {
                fieldName = PROPERTY_FIELD_PREFIX + propertyDef.getName();
            } else {
                fieldName = expandAttributeFieldName(field);
            }

            IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
            if (propertyDef != null) {
                tokenisationMode = propertyDef.getIndexTokenisationMode();
                if (tokenisationMode == null) {
                    tokenisationMode = IndexTokenisationMode.TRUE;
                }
            }

            if (propertyDef != null) {
                // LOWER AND UPPER
                if (luceneFunction != LuceneFunction.FIELD) {
                    if (luceneFunction == LuceneFunction.LOWER) {
                        if ((false == part1.toLowerCase().equals(part1))
                                || (false == part2.toLowerCase().equals(part2))) {
                            return createNoMatchQuery();
                        }
                    }

                    if (luceneFunction == LuceneFunction.UPPER) {
                        if ((false == part1.toUpperCase().equals(part1))
                                || (false == part2.toUpperCase().equals(part2))) {
                            return createNoMatchQuery();
                        }
                    }

                    if (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)) {
                        BooleanQuery booleanQuery = new BooleanQuery();
                        MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                                ? defaultSearchMLAnalysisMode
                                : searchParameters.getMlAnalaysisMode();
                        List<Locale> locales = searchParameters.getLocales();
                        List<Locale> expandedLocales = new ArrayList<Locale>();
                        for (Locale locale : (((locales == null) || (locales.size() == 0))
                                ? Collections.singletonList(I18NUtil.getLocale())
                                : locales)) {
                            expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
                        }
                        for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                                ? Collections.singletonList(I18NUtil.getLocale())
                                : expandedLocales)) {
                            addLocaleSpecificUntokenisedTextRangeFunction(fieldName, part1, part2, includeLower,
                                    includeUpper, luceneFunction, booleanQuery, mlAnalysisMode, locale,
                                    tokenisationMode);
                        }
                        return booleanQuery;
                    } else {
                        throw new UnsupportedOperationException("Lucene Function");
                    }
                }

                if (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) {
                    throw new UnsupportedOperationException("Range is not supported against ml-text");
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) {
                    throw new UnsupportedOperationException("Range is not supported against content");
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)) {
                    BooleanQuery booleanQuery = new BooleanQuery();
                    MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                            ? defaultSearchMLAnalysisMode
                            : searchParameters.getMlAnalaysisMode();
                    List<Locale> locales = searchParameters.getLocales();
                    List<Locale> expandedLocales = new ArrayList<Locale>();
                    for (Locale locale : (((locales == null) || (locales.size() == 0))
                            ? Collections.singletonList(I18NUtil.getLocale())
                            : locales)) {
                        expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
                    }
                    for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                            ? Collections.singletonList(I18NUtil.getLocale())
                            : expandedLocales)) {

                        addTextRange(field, part1, part2, includeLower, includeUpper, analysisMode, fieldName,
                                propertyDef, tokenisationMode, booleanQuery, mlAnalysisMode, locale);

                    }
                    return booleanQuery;
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)) {
                    String analyserClassName = propertyDef.resolveAnalyserClassName();
                    boolean usesDateTimeAnalyser = analyserClassName
                            .equals(DateTimeAnalyser.class.getCanonicalName());
                    // Expand query for internal date time format

                    if (usesDateTimeAnalyser) {
                        Calendar start = Calendar.getInstance();
                        int startResolution;
                        Calendar end = Calendar.getInstance();
                        int endResolution;
                        try {
                            Pair<Date, Integer> result = CachingDateFormat.lenientParse(part1, Calendar.YEAR);
                            start.setTime(result.getFirst());
                            startResolution = result.getSecond();
                        } catch (java.text.ParseException e) {
                            SimpleDateFormat oldDf = CachingDateFormat.getDateFormat();
                            try {
                                Date date = oldDf.parse(part1);
                                start.setTime(date);
                                start.set(Calendar.MILLISECOND, 0);
                                startResolution = Calendar.SECOND;
                            } catch (java.text.ParseException ee) {
                                if (part1.equalsIgnoreCase("min")) {
                                    start.set(Calendar.YEAR, start.getMinimum(Calendar.YEAR));
                                    start.set(Calendar.DAY_OF_YEAR, start.getMinimum(Calendar.DAY_OF_YEAR));
                                    start.set(Calendar.HOUR_OF_DAY, start.getMinimum(Calendar.HOUR_OF_DAY));
                                    start.set(Calendar.MINUTE, start.getMinimum(Calendar.MINUTE));
                                    start.set(Calendar.SECOND, start.getMinimum(Calendar.SECOND));
                                    start.set(Calendar.MILLISECOND, start.getMinimum(Calendar.MILLISECOND));
                                    startResolution = Calendar.MILLISECOND;
                                } else if (part1.equalsIgnoreCase("now")) {
                                    start.setTime(new Date());
                                    startResolution = Calendar.MILLISECOND;
                                } else if (part1.equalsIgnoreCase("today")) {
                                    start.setTime(new Date());
                                    start.set(Calendar.HOUR_OF_DAY, start.getMinimum(Calendar.HOUR_OF_DAY));
                                    start.set(Calendar.MINUTE, start.getMinimum(Calendar.MINUTE));
                                    start.set(Calendar.SECOND, start.getMinimum(Calendar.SECOND));
                                    start.set(Calendar.MILLISECOND, start.getMinimum(Calendar.MILLISECOND));
                                    startResolution = Calendar.DAY_OF_MONTH;

                                } else {
                                    return createNoMatchQuery();
                                }
                            }
                        }
                        try {
                            Pair<Date, Integer> result = CachingDateFormat.lenientParse(part2, Calendar.YEAR);
                            end.setTime(result.getFirst());
                            endResolution = result.getSecond();
                        } catch (java.text.ParseException e) {
                            SimpleDateFormat oldDf = CachingDateFormat.getDateFormat();
                            try {
                                Date date = oldDf.parse(part2);
                                end.setTime(date);
                                end.set(Calendar.MILLISECOND, 0);
                                endResolution = Calendar.SECOND;
                            } catch (java.text.ParseException ee) {
                                if (part2.equalsIgnoreCase("max")) {
                                    end.set(Calendar.YEAR, end.getMaximum(Calendar.YEAR));
                                    end.set(Calendar.DAY_OF_YEAR, end.getMaximum(Calendar.DAY_OF_YEAR));
                                    end.set(Calendar.HOUR_OF_DAY, end.getMaximum(Calendar.HOUR_OF_DAY));
                                    end.set(Calendar.MINUTE, end.getMaximum(Calendar.MINUTE));
                                    end.set(Calendar.SECOND, end.getMaximum(Calendar.SECOND));
                                    end.set(Calendar.MILLISECOND, end.getMaximum(Calendar.MILLISECOND));
                                    endResolution = Calendar.MILLISECOND;
                                } else if (part2.equalsIgnoreCase("now")) {
                                    end.setTime(new Date());
                                    endResolution = Calendar.MILLISECOND;
                                } else if (part2.equalsIgnoreCase("today")) {
                                    end.setTime(new Date());
                                    end.set(Calendar.HOUR_OF_DAY, end.getMinimum(Calendar.HOUR_OF_DAY));
                                    end.set(Calendar.MINUTE, end.getMinimum(Calendar.MINUTE));
                                    end.set(Calendar.SECOND, end.getMinimum(Calendar.SECOND));
                                    end.set(Calendar.MILLISECOND, end.getMinimum(Calendar.MILLISECOND));
                                    endResolution = Calendar.DAY_OF_MONTH;
                                } else {
                                    return createNoMatchQuery();
                                }
                            }
                        }

                        // Build a composite query for all the bits
                        Query rq = buildDateTimeRange(fieldName, start, startResolution, end, endResolution,
                                includeLower, includeUpper);
                        return rq;
                    } else {
                        // Old Date time
                        String first = getToken(fieldName, part1, AnalysisMode.DEFAULT);
                        String last = getToken(fieldName, part2, AnalysisMode.DEFAULT);
                        return new ConstantScoreRangeQuery(fieldName, first, last, includeLower, includeUpper);
                    }
                } else {
                    // Default property behaviour
                    String first = getToken(fieldName, part1, AnalysisMode.DEFAULT);
                    String last = getToken(fieldName, part2, AnalysisMode.DEFAULT);
                    return new ConstantScoreRangeQuery(fieldName, first, last, includeLower, includeUpper);
                }
            } else {
                // No DD def
                String first = getToken(fieldName, part1, AnalysisMode.DEFAULT);
                String last = getToken(fieldName, part2, AnalysisMode.DEFAULT);
                return new ConstantScoreRangeQuery(fieldName, first, last, includeLower, includeUpper);
            }
        } else if (field.equals(FIELD_ALL)) {
            Set<String> all = searchParameters.getAllAttributes();
            if ((all == null) || (all.size() == 0)) {
                Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
                BooleanQuery query = new BooleanQuery();
                for (QName qname : contentAttributes) {
                    Query part = getRangeQuery(PROPERTY_FIELD_PREFIX + qname.toString(), part1, part2, includeLower,
                            includeUpper, analysisMode, luceneFunction);
                    query.add(part, Occur.SHOULD);
                }
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : all) {
                    Query part = getRangeQuery(fieldName, part1, part2, includeLower, includeUpper, analysisMode,
                            luceneFunction);
                    query.add(part, Occur.SHOULD);
                }
                return query;
            }

        }
        // FIELD_ISUNSET uses the default
        // FIELD_ISNULL uses the default
        // FIELD_ISNOTNULL uses the default
        else if (matchDataTypeDefinition(field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(matchDataTypeDefinition(field).getName());
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                Query part = getRangeQuery(PROPERTY_FIELD_PREFIX + qname.toString(), part1, part2, includeLower,
                        includeUpper, analysisMode, luceneFunction);
                query.add(part, Occur.SHOULD);
            }
            return query;
        }
        // FIELD_FTSSTATUS uses the default
        if (field.equals(FIELD_TAG)) {
            throw new UnsupportedOperationException("Range Queries are not support for " + FIELD_TAG);
        } else {
            // None property - leave alone
            if (getLowercaseExpandedTerms()) {
                part1 = part1.toLowerCase();
                part2 = part2.toLowerCase();
            }
            return new ConstantScoreRangeQuery(field, part1, part2, includeLower, includeUpper);
        }
    }

    /**
     * @param field String
     * @param part1 String
     * @param part2 String
     * @param includeLower boolean
     * @param includeUpper boolean
     * @param analysisMode AnalysisMode
     * @param fieldName String
     * @param propertyDef PropertyDefinition
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     * @throws ParseException
     */
    protected abstract void addTextRange(String field, String part1, String part2, boolean includeLower,
            boolean includeUpper, AnalysisMode analysisMode, String fieldName, PropertyDefinition propertyDef,
            IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode,
            Locale locale) throws ParseException;

    protected abstract void addLocaleSpecificUntokenisedTextRangeFunction(String expandedFieldName, String lower,
            String upper, boolean includeLower, boolean includeUpper, LuceneFunction luceneFunction,
            BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale,
            IndexTokenisationMode tokenisationMode) throws ParseException;

    // private Query buildRangeFunctionQuery(String expandedFieldName, String lowerTermText, String upperTermText,
    // boolean includeLower, boolean includeUpper,
    // LuceneFunction luceneFunction)
    // {
    // String testLowerTermText = lowerTermText;
    // if (testLowerTermText.startsWith("{"))
    // {
    // int index = lowerTermText.indexOf("}");
    // testLowerTermText = lowerTermText.substring(index + 1);
    // }
    //
    // String testUpperTermText = upperTermText;
    // if (testUpperTermText.startsWith("{"))
    // {
    // int index = upperTermText.indexOf("}");
    // testUpperTermText = upperTermText.substring(index + 1);
    // }
    //
    // switch (luceneFunction)
    // {
    // case LOWER:
    // if (testLowerTermText.equals(testLowerTermText.toLowerCase()) &&
    // testUpperTermText.equals(testUpperTermText.toLowerCase()))
    // {
    // return new CaseInsensitiveFieldRangeQuery(expandedFieldName, lowerTermText, upperTermText, includeLower,
    // includeUpper);
    // }
    // else
    // {
    // // No match
    // return createNoMatchQuery();
    // }
    // case UPPER:
    // if (testLowerTermText.equals(testLowerTermText.toUpperCase()) &&
    // testUpperTermText.equals(testUpperTermText.toUpperCase()))
    // {
    // return new CaseInsensitiveFieldRangeQuery(expandedFieldName, lowerTermText, upperTermText, includeLower,
    // includeUpper);
    // }
    // else
    // {
    // // No match
    // return createNoMatchQuery();
    // }
    // default:
    // throw new UnsupportedOperationException("Unsupported Lucene Function " + luceneFunction);
    //
    // }
    // }

    // private void addLocaleSpecificTokenisedTextRange(String part1, String part2, boolean includeLower, boolean
    // includeUpper, AnalysisMode analysisMode, String fieldName,
    // BooleanQuery booleanQuery, Locale locale, String textFieldName) throws ParseException
    // {
    // StringBuilder builder = new StringBuilder();
    // builder.append("\u0000").append(locale.toString()).append("\u0000").append(part1);
    // String first = getToken(fieldName, builder.toString(), analysisMode);
    //
    // builder = new StringBuilder();
    // builder.append("\u0000").append(locale.toString()).append("\u0000").append(part2);
    // String last = getToken(fieldName, builder.toString(), analysisMode);
    //
    // Query query = new ConstantScoreRangeQuery(textFieldName, first, last, includeLower, includeUpper);
    // booleanQuery.add(query, Occur.SHOULD);
    // }
    //
    // private void addLocaleSpecificUntokenisedTextRange(String field, String part1, String part2, boolean
    // includeLower, boolean includeUpper, BooleanQuery booleanQuery,
    // MLAnalysisMode mlAnalysisMode, Locale locale, String textFieldName)
    // {
    // String lower = part1;
    // String upper = part2;
    // if (locale.toString().length() > 0)
    // {
    // lower = "{" + locale + "}" + part1;
    // upper = "{" + locale + "}" + part2;
    // }
    //
    // Query subQuery = new ConstantScoreRangeQuery(textFieldName, lower, upper, includeLower, includeUpper);
    // booleanQuery.add(subQuery, Occur.SHOULD);
    //
    // if (booleanQuery.getClauses().length == 0)
    // {
    // booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
    // }
    // }

    protected Query buildDateTimeRange(String field, Calendar startIn, int startResolution, Calendar endIn,
            int endResolution, boolean includeLower, boolean includeUpper) throws ParseException {
        int minResolution = (startResolution <= endResolution) ? startResolution : endResolution;

        // fix start and end dates and treat all as inclusive ranges

        Calendar start = Calendar.getInstance();
        start.setTime(startIn.getTime());
        if (!includeLower) {
            start.add(startResolution, 1);
        }

        Calendar end = Calendar.getInstance();
        end.setTime(endIn.getTime());
        if (!includeUpper) {
            end.add(endResolution, -1);
        }

        // Calendar comparison does not work for MAX .... joy
        if (start.get(Calendar.YEAR) > end.get(Calendar.YEAR)) {
            return createNoMatchQuery();
        } else if (start.get(Calendar.YEAR) == end.get(Calendar.YEAR)) {
            if (start.get(Calendar.MONTH) > end.get(Calendar.MONTH)) {
                return createNoMatchQuery();
            } else if (start.get(Calendar.MONTH) == end.get(Calendar.MONTH)) {
                if (start.get(Calendar.DAY_OF_MONTH) > end.get(Calendar.DAY_OF_MONTH)) {
                    return createNoMatchQuery();
                } else if (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH)) {
                    if (start.get(Calendar.HOUR_OF_DAY) > end.get(Calendar.HOUR_OF_DAY)) {
                        return createNoMatchQuery();
                    } else if (start.get(Calendar.HOUR_OF_DAY) == end.get(Calendar.HOUR_OF_DAY)) {
                        if (start.get(Calendar.MINUTE) > end.get(Calendar.MINUTE)) {
                            return createNoMatchQuery();
                        } else if (start.get(Calendar.MINUTE) == end.get(Calendar.MINUTE)) {
                            if (start.get(Calendar.SECOND) > end.get(Calendar.SECOND)) {
                                return createNoMatchQuery();
                            } else if (start.get(Calendar.SECOND) == end.get(Calendar.SECOND)) {
                                if (start.get(Calendar.MILLISECOND) > end.get(Calendar.MILLISECOND)) {
                                    return createNoMatchQuery();
                                } else if (start.get(Calendar.MILLISECOND) == end.get(Calendar.MILLISECOND)) {
                                    // continue
                                }
                            }
                        }
                    }
                }
            }
        }

        BooleanQuery query = new BooleanQuery();
        Query part;
        if ((minResolution > Calendar.YEAR) && (start.get(Calendar.YEAR) == end.get(Calendar.YEAR))) {
            part = new TermQuery(new Term(field, "YE" + start.get(Calendar.YEAR)));
            query.add(part, Occur.MUST);
            if ((minResolution > Calendar.MONTH) && (start.get(Calendar.MONTH) == end.get(Calendar.MONTH))) {
                part = new TermQuery(new Term(field, build2SF("MO", start.get(Calendar.MONTH))));
                query.add(part, Occur.MUST);
                if ((minResolution > Calendar.DAY_OF_MONTH)
                        && (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH))) {
                    part = new TermQuery(new Term(field, build2SF("DA", start.get(Calendar.DAY_OF_MONTH))));
                    query.add(part, Occur.MUST);
                    if ((minResolution > Calendar.HOUR_OF_DAY)
                            && (start.get(Calendar.HOUR_OF_DAY) == end.get(Calendar.HOUR_OF_DAY))) {
                        part = new TermQuery(new Term(field, build2SF("HO", start.get(Calendar.HOUR_OF_DAY))));
                        query.add(part, Occur.MUST);
                        if ((minResolution > Calendar.MINUTE)
                                && (start.get(Calendar.MINUTE) == end.get(Calendar.MINUTE))) {
                            part = new TermQuery(new Term(field, build2SF("MI", start.get(Calendar.MINUTE))));
                            query.add(part, Occur.MUST);
                            if ((minResolution > Calendar.SECOND)
                                    && (start.get(Calendar.SECOND) == end.get(Calendar.SECOND))) {
                                part = new TermQuery(new Term(field, build2SF("SE", start.get(Calendar.SECOND))));
                                query.add(part, Occur.MUST);
                                if (minResolution >= Calendar.MILLISECOND) {
                                    if (start.get(Calendar.MILLISECOND) == end.get(Calendar.MILLISECOND)) {

                                        part = new TermQuery(
                                                new Term(field, build3SF("MS", start.get(Calendar.MILLISECOND))));
                                        query.add(part, Occur.MUST);

                                    } else {
                                        part = new ConstantScoreRangeQuery(field,
                                                build3SF("MS", start.get(Calendar.MILLISECOND)),
                                                build3SF("MS", end.get(Calendar.MILLISECOND)), true, true);
                                        query.add(part, Occur.MUST);
                                    }
                                } else {
                                    return createNoMatchQuery();
                                }
                            } else {
                                // s + ms

                                BooleanQuery subQuery = new BooleanQuery();
                                Query subPart;

                                for (int i : new int[] { Calendar.MILLISECOND }) {
                                    subPart = buildStart(field, start, Calendar.SECOND, i, startResolution);
                                    if (subPart != null) {
                                        subQuery.add(subPart, Occur.SHOULD);
                                    }
                                }

                                if (Calendar.SECOND < minResolution) {
                                    if ((end.get(Calendar.SECOND) - start.get(Calendar.SECOND)) > 1) {
                                        subPart = new ConstantScoreRangeQuery(field,
                                                build2SF("SE", start.get(Calendar.SECOND)),
                                                build2SF("SE", end.get(Calendar.SECOND)), false, false);
                                        subQuery.add(subPart, Occur.SHOULD);
                                    }
                                }
                                if (Calendar.SECOND == minResolution) {
                                    if (start.get(Calendar.SECOND) == end.get(Calendar.SECOND)) {
                                        if (includeLower && includeUpper) {
                                            part = new TermQuery(
                                                    new Term(field, build2SF("SE", start.get(Calendar.SECOND))));
                                            query.add(part, Occur.MUST);
                                        }

                                        else {
                                            return createNoMatchQuery();
                                        }
                                    } else {
                                        subPart = new ConstantScoreRangeQuery(field,
                                                build2SF("SE", start.get(Calendar.SECOND)),
                                                build2SF("SE", end.get(Calendar.SECOND)), includeLower,
                                                includeUpper);
                                        subQuery.add(subPart, Occur.SHOULD);
                                    }
                                }

                                for (int i : new int[] { Calendar.MILLISECOND }) {

                                    subPart = buildEnd(field, end, Calendar.SECOND, i, endResolution);
                                    if (subPart != null) {
                                        subQuery.add(subPart, Occur.SHOULD);
                                    }

                                }

                                if (subQuery.clauses().size() > 0) {
                                    query.add(subQuery, Occur.MUST);
                                }

                            }
                        } else {
                            // min + s + ms

                            BooleanQuery subQuery = new BooleanQuery();
                            Query subPart;

                            for (int i : new int[] { Calendar.MILLISECOND, Calendar.SECOND }) {

                                subPart = buildStart(field, start, Calendar.MINUTE, i, startResolution);
                                if (subPart != null) {
                                    subQuery.add(subPart, Occur.SHOULD);
                                }

                            }

                            if (Calendar.MINUTE < minResolution) {
                                if ((end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE)) > 1) {
                                    subPart = new ConstantScoreRangeQuery(field,
                                            build2SF("MI", start.get(Calendar.MINUTE)),
                                            build2SF("MI", end.get(Calendar.MINUTE)), false, false);
                                    subQuery.add(subPart, Occur.SHOULD);
                                }
                            }
                            if (Calendar.MINUTE == minResolution) {
                                if (start.get(Calendar.MINUTE) == end.get(Calendar.MINUTE)) {
                                    if (includeLower && includeUpper) {
                                        part = new TermQuery(
                                                new Term(field, build2SF("MI", start.get(Calendar.MINUTE))));
                                        query.add(part, Occur.MUST);
                                    }

                                    else {
                                        return createNoMatchQuery();
                                    }
                                } else {
                                    subPart = new ConstantScoreRangeQuery(field,
                                            build2SF("MI", start.get(Calendar.MINUTE)),
                                            build2SF("MI", end.get(Calendar.MINUTE)), includeLower, includeUpper);
                                    subQuery.add(subPart, Occur.SHOULD);
                                }
                            }

                            for (int i : new int[] { Calendar.SECOND, Calendar.MILLISECOND }) {

                                subPart = buildEnd(field, end, Calendar.MINUTE, i, endResolution);
                                if (subPart != null) {
                                    subQuery.add(subPart, Occur.SHOULD);
                                }

                            }

                            if (subQuery.clauses().size() > 0) {
                                query.add(subQuery, Occur.MUST);
                            }
                        }
                    } else {
                        // hr + min + s + ms

                        BooleanQuery subQuery = new BooleanQuery();
                        Query subPart;

                        for (int i : new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE }) {

                            subPart = buildStart(field, start, Calendar.HOUR_OF_DAY, i, startResolution);
                            if (subPart != null) {
                                subQuery.add(subPart, Occur.SHOULD);
                            }

                        }

                        if (Calendar.HOUR_OF_DAY < minResolution) {
                            if ((end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY)) > 1) {
                                subPart = new ConstantScoreRangeQuery(field,
                                        build2SF("HO", start.get(Calendar.HOUR_OF_DAY)),
                                        build2SF("HO", end.get(Calendar.HOUR_OF_DAY)), false, false);
                                subQuery.add(subPart, Occur.SHOULD);
                            }
                        }
                        if (Calendar.HOUR_OF_DAY == minResolution) {
                            if (start.get(Calendar.HOUR_OF_DAY) == end.get(Calendar.HOUR_OF_DAY)) {
                                if (includeLower && includeUpper) {
                                    part = new TermQuery(
                                            new Term(field, build2SF("HO", start.get(Calendar.HOUR_OF_DAY))));
                                    query.add(part, Occur.MUST);
                                }

                                else {
                                    return createNoMatchQuery();
                                }
                            } else {
                                subPart = new ConstantScoreRangeQuery(field,
                                        build2SF("HO", start.get(Calendar.HOUR_OF_DAY)),
                                        build2SF("HO", end.get(Calendar.HOUR_OF_DAY)), includeLower, includeUpper);
                                subQuery.add(subPart, Occur.SHOULD);
                            }
                        }
                        for (int i : new int[] { Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND }) {

                            subPart = buildEnd(field, end, Calendar.HOUR_OF_DAY, i, endResolution);
                            if (subPart != null) {
                                subQuery.add(subPart, Occur.SHOULD);
                            }

                        }

                        if (subQuery.clauses().size() > 0) {
                            query.add(subQuery, Occur.MUST);
                        }
                    }
                } else {
                    // day + hr + min + s + ms

                    BooleanQuery subQuery = new BooleanQuery();
                    Query subPart;

                    for (int i : new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE,
                            Calendar.HOUR_OF_DAY }) {

                        subPart = buildStart(field, start, Calendar.DAY_OF_MONTH, i, startResolution);
                        if (subPart != null) {
                            subQuery.add(subPart, Occur.SHOULD);
                        }

                    }

                    if (Calendar.DAY_OF_MONTH < minResolution) {
                        if ((end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH)) > 1) {
                            subPart = new ConstantScoreRangeQuery(field,
                                    build2SF("DA", start.get(Calendar.DAY_OF_MONTH)),
                                    build2SF("DA", end.get(Calendar.DAY_OF_MONTH)), false, false);
                            subQuery.add(subPart, Occur.SHOULD);
                        }
                    }
                    if (Calendar.DAY_OF_MONTH == minResolution) {
                        if (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH)) {
                            if (includeLower && includeUpper) {
                                part = new TermQuery(
                                        new Term(field, build2SF("DA", start.get(Calendar.DAY_OF_MONTH))));
                                query.add(part, Occur.MUST);
                            }

                            else {
                                return createNoMatchQuery();
                            }
                        } else {
                            subPart = new ConstantScoreRangeQuery(field,
                                    build2SF("DA", start.get(Calendar.DAY_OF_MONTH)),
                                    build2SF("DA", end.get(Calendar.DAY_OF_MONTH)), includeLower, includeUpper);
                            subQuery.add(subPart, Occur.SHOULD);
                        }
                    }

                    for (int i : new int[] { Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND,
                            Calendar.MILLISECOND }) {

                        subPart = buildEnd(field, end, Calendar.DAY_OF_MONTH, i, endResolution);
                        if (subPart != null) {
                            subQuery.add(subPart, Occur.SHOULD);
                        }

                    }

                    if (subQuery.clauses().size() > 0) {
                        query.add(subQuery, Occur.MUST);
                    }

                }
            } else {
                // month + day + hr + min + s + ms

                BooleanQuery subQuery = new BooleanQuery();
                Query subPart;

                for (int i : new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE,
                        Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH }) {

                    subPart = buildStart(field, start, Calendar.MONTH, i, startResolution);
                    if (subPart != null) {
                        subQuery.add(subPart, Occur.SHOULD);
                    }

                }

                if (Calendar.MONTH < minResolution) {
                    if ((end.get(Calendar.MONTH) - start.get(Calendar.MONTH)) > 1) {
                        subPart = new ConstantScoreRangeQuery(field, build2SF("MO", start.get(Calendar.MONTH)),
                                build2SF("MO", end.get(Calendar.MONTH)), false, false);
                        subQuery.add(subPart, Occur.SHOULD);
                    }
                }
                if (Calendar.MONTH == minResolution) {
                    if (start.get(Calendar.MONTH) == end.get(Calendar.MONTH)) {
                        if (includeLower && includeUpper) {
                            part = new TermQuery(new Term(field, build2SF("MO", start.get(Calendar.MONTH))));
                            query.add(part, Occur.MUST);
                        }

                        else {
                            return createNoMatchQuery();
                        }
                    } else {
                        subPart = new ConstantScoreRangeQuery(field, build2SF("MO", start.get(Calendar.MONTH)),
                                build2SF("MO", end.get(Calendar.MONTH)), includeLower, includeUpper);
                        subQuery.add(subPart, Occur.SHOULD);
                    }
                }

                for (int i : new int[] { Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
                        Calendar.SECOND, Calendar.MILLISECOND }) {

                    subPart = buildEnd(field, end, Calendar.MONTH, i, endResolution);
                    if (subPart != null) {
                        subQuery.add(subPart, Occur.SHOULD);
                    }

                }

                if (subQuery.clauses().size() > 0) {
                    query.add(subQuery, Occur.MUST);
                }
            }
        } else {
            // year + month + day + hr + min + s + ms

            BooleanQuery subQuery = new BooleanQuery();
            Query subPart;

            for (int i : new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY,
                    Calendar.DAY_OF_MONTH, Calendar.MONTH }) {

                subPart = buildStart(field, start, Calendar.YEAR, i, startResolution);
                if (subPart != null) {
                    subQuery.add(subPart, Occur.SHOULD);
                }

            }

            if (Calendar.YEAR < minResolution) {
                if ((end.get(Calendar.YEAR) - start.get(Calendar.YEAR)) > 1) {
                    subPart = new ConstantScoreRangeQuery(field, "YE" + start.get(Calendar.YEAR),
                            "YE" + end.get(Calendar.YEAR), false, false);
                    subQuery.add(subPart, Occur.SHOULD);
                }
            }
            if (Calendar.YEAR == minResolution) {
                if (start.get(Calendar.YEAR) == end.get(Calendar.YEAR)) {
                    if (includeLower && includeUpper) {
                        part = new TermQuery(new Term(field, "YE" + start.get(Calendar.YEAR)));
                        query.add(part, Occur.MUST);
                    }

                    else {
                        return createNoMatchQuery();
                    }
                } else {
                    subPart = new ConstantScoreRangeQuery(field, "YE" + start.get(Calendar.YEAR),
                            "YE" + end.get(Calendar.YEAR), includeLower, includeUpper);
                    subQuery.add(subPart, Occur.SHOULD);
                }
            }

            for (int i : new int[] { Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
                    Calendar.SECOND, Calendar.MILLISECOND }) {

                subPart = buildEnd(field, end, Calendar.YEAR, i, endResolution);
                if (subPart != null) {
                    subQuery.add(subPart, Occur.SHOULD);
                }

            }

            if (subQuery.clauses().size() > 0) {
                query.add(subQuery, Occur.MUST);
            }
        }

        return query;
    }

    private Query buildStart(String field, Calendar cal, int startField, int padField, int resolutionField) {
        BooleanQuery range = new BooleanQuery();
        // only ms difference
        Query part;

        switch (startField) {
        case Calendar.YEAR:
            if ((cal.get(Calendar.YEAR) == 1) && (cal.get(Calendar.MONTH) == 0)
                    && (cal.get(Calendar.DAY_OF_MONTH) == 1) && (cal.get(Calendar.HOUR_OF_DAY) == 0)
                    && (cal.get(Calendar.MINUTE) == 0) && (cal.get(Calendar.SECOND) == 0)
                    && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.MONTH) {
                    if (Calendar.YEAR <= resolutionField) {
                        part = new TermQuery(new Term(field, "YE" + cal.get(Calendar.YEAR)));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.YEAR) {
                return null;
            } else {
                if (Calendar.YEAR <= resolutionField) {
                    part = new TermQuery(new Term(field, "YE" + cal.get(Calendar.YEAR)));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.MONTH:
            if ((cal.get(Calendar.MONTH) == 0) && (cal.get(Calendar.DAY_OF_MONTH) == 1)
                    && (cal.get(Calendar.HOUR_OF_DAY) == 0) && (cal.get(Calendar.MINUTE) == 0)
                    && (cal.get(Calendar.SECOND) == 0) && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.DAY_OF_MONTH) {
                    if (Calendar.MONTH <= resolutionField) {
                        part = new TermQuery(new Term(field, build2SF("MO", cal.get(Calendar.MONTH))));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.MONTH) {
                if (Calendar.MONTH < resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MO", (cal.get(Calendar.MONTH) + 1)),
                            "MO" + cal.getMaximum(Calendar.MONTH), true, true);
                    range.add(part, Occur.MUST);
                } else if (Calendar.MONTH == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MO", (cal.get(Calendar.MONTH))),
                            "MO" + cal.getMaximum(Calendar.MONTH), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.MONTH <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("MO", cal.get(Calendar.MONTH))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }

            }
        case Calendar.DAY_OF_MONTH:
            if ((cal.get(Calendar.DAY_OF_MONTH) == 1) && (cal.get(Calendar.HOUR_OF_DAY) == 0)
                    && (cal.get(Calendar.MINUTE) == 0) && (cal.get(Calendar.SECOND) == 0)
                    && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.HOUR_OF_DAY) {
                    if (Calendar.DAY_OF_MONTH <= resolutionField) {
                        part = new TermQuery(new Term(field, build2SF("DA", cal.get(Calendar.DAY_OF_MONTH))));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.DAY_OF_MONTH) {
                if (Calendar.DAY_OF_MONTH < resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("DA", (cal.get(Calendar.DAY_OF_MONTH) + 1)),
                            "DA" + cal.getMaximum(Calendar.DAY_OF_MONTH), true, true);
                    range.add(part, Occur.MUST);

                } else if (Calendar.DAY_OF_MONTH == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("DA", (cal.get(Calendar.DAY_OF_MONTH))),
                            "DA" + cal.getMaximum(Calendar.DAY_OF_MONTH), true, true);
                    range.add(part, Occur.MUST);

                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.DAY_OF_MONTH <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("DA", cal.get(Calendar.DAY_OF_MONTH))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }

            }
        case Calendar.HOUR_OF_DAY:
            if ((cal.get(Calendar.HOUR_OF_DAY) == 0) && (cal.get(Calendar.MINUTE) == 0)
                    && (cal.get(Calendar.SECOND) == 0) && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.MINUTE) {
                    if (Calendar.HOUR_OF_DAY <= resolutionField) {
                        part = new TermQuery(new Term(field, build2SF("HO", cal.get(Calendar.HOUR_OF_DAY))));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.HOUR_OF_DAY) {
                if (Calendar.HOUR_OF_DAY < resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("HO", (cal.get(Calendar.HOUR_OF_DAY) + 1)),
                            "HO" + cal.getMaximum(Calendar.HOUR_OF_DAY), true, true);
                    range.add(part, Occur.MUST);

                } else if (Calendar.HOUR_OF_DAY == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("HO", (cal.get(Calendar.HOUR_OF_DAY))),
                            "HO" + cal.getMaximum(Calendar.HOUR_OF_DAY), true, true);
                    range.add(part, Occur.MUST);

                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.HOUR_OF_DAY <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("HO", cal.get(Calendar.HOUR_OF_DAY))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }

            }
        case Calendar.MINUTE:
            if ((cal.get(Calendar.MINUTE) == 0) && (cal.get(Calendar.SECOND) == 0)
                    && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.SECOND) {
                    if (Calendar.MINUTE <= resolutionField) {
                        part = new TermQuery(new Term(field, build2SF("MI", cal.get(Calendar.MINUTE))));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.MINUTE) {
                if (Calendar.MINUTE < resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MI", (cal.get(Calendar.MINUTE) + 1)),
                            "MI" + cal.getMaximum(Calendar.MINUTE), true, true);
                    range.add(part, Occur.MUST);

                } else if (Calendar.MINUTE == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MI", (cal.get(Calendar.MINUTE))),
                            "MI" + cal.getMaximum(Calendar.MINUTE), true, true);
                    range.add(part, Occur.MUST);

                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.MINUTE <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("MI", cal.get(Calendar.MINUTE))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }

            }
        case Calendar.SECOND:
            if ((cal.get(Calendar.SECOND) == 0) && (cal.get(Calendar.MILLISECOND) == 0)) {
                if (padField == Calendar.MILLISECOND) {
                    if (Calendar.SECOND <= resolutionField) {
                        part = new TermQuery(new Term(field, build2SF("SE", cal.get(Calendar.SECOND))));
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                }
                break;
            } else if (padField == Calendar.SECOND) {
                if (Calendar.SECOND < resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("SE", (cal.get(Calendar.SECOND) + 1)),
                            "SE" + cal.getMaximum(Calendar.SECOND), true, true);
                    range.add(part, Occur.MUST);

                } else if (Calendar.SECOND == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("SE", (cal.get(Calendar.SECOND))),
                            "SE" + cal.getMaximum(Calendar.SECOND), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.SECOND <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("SE", cal.get(Calendar.SECOND))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }

            }
        case Calendar.MILLISECOND:
            if ((cal.get(Calendar.MILLISECOND) > 0)
                    && (cal.get(Calendar.MILLISECOND) <= cal.getMaximum(Calendar.MILLISECOND))) {
                if (Calendar.MILLISECOND <= resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build3SF("MS", cal.get(Calendar.MILLISECOND)),
                            "MS" + cal.getMaximum(Calendar.MILLISECOND), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    break;
                }

            }
        default:
        }

        return getNonEmptyBooleanQuery(range);
    }

    private Query buildEnd(String field, Calendar cal, int startField, int padField, int resolutionField) {
        BooleanQuery range = new BooleanQuery();
        Query part;

        switch (startField) {
        case Calendar.YEAR:
            if (padField == Calendar.YEAR) {
                if (Calendar.YEAR < resolutionField) {
                    if (cal.get(Calendar.YEAR) > cal.getMinimum(Calendar.YEAR)) {
                        part = new ConstantScoreRangeQuery(field, "YE" + cal.getMinimum(Calendar.YEAR),
                                "YE" + (cal.get(Calendar.YEAR) - 1), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }
                } else if (Calendar.YEAR == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, "YE" + cal.getMinimum(Calendar.YEAR),
                            "YE" + (cal.get(Calendar.YEAR)), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.YEAR <= resolutionField) {
                    part = new TermQuery(new Term(field, "YE" + cal.get(Calendar.YEAR)));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.MONTH:
            if (padField == Calendar.MONTH) {
                if (Calendar.MONTH < resolutionField) {
                    if (cal.get(Calendar.MONTH) > cal.getMinimum(Calendar.MONTH)) {
                        part = new ConstantScoreRangeQuery(field, build2SF("MO", cal.getMinimum(Calendar.MONTH)),
                                build2SF("MO", (cal.get(Calendar.MONTH) - 1)), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }
                } else if (Calendar.MONTH == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MO", cal.getMinimum(Calendar.MONTH)),
                            build2SF("MO", (cal.get(Calendar.MONTH))), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.MONTH <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("MO", cal.get(Calendar.MONTH))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.DAY_OF_MONTH:
            if (padField == Calendar.DAY_OF_MONTH) {
                if (Calendar.DAY_OF_MONTH < resolutionField) {
                    if (cal.get(Calendar.DAY_OF_MONTH) > cal.getMinimum(Calendar.DAY_OF_MONTH)) {
                        part = new ConstantScoreRangeQuery(field,
                                build2SF("DA", cal.getMinimum(Calendar.DAY_OF_MONTH)),
                                build2SF("DA", (cal.get(Calendar.DAY_OF_MONTH) - 1)), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }
                } else if (Calendar.DAY_OF_MONTH == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("DA", cal.getMinimum(Calendar.DAY_OF_MONTH)),
                            build2SF("DA", (cal.get(Calendar.DAY_OF_MONTH))), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.DAY_OF_MONTH <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("DA", cal.get(Calendar.DAY_OF_MONTH))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.HOUR_OF_DAY:
            if (padField == Calendar.HOUR_OF_DAY) {
                if (Calendar.HOUR_OF_DAY < resolutionField) {
                    if (cal.get(Calendar.HOUR_OF_DAY) > cal.getMinimum(Calendar.HOUR_OF_DAY)) {
                        part = new ConstantScoreRangeQuery(field,
                                build2SF("HO", cal.getMinimum(Calendar.HOUR_OF_DAY)),
                                build2SF("HO", (cal.get(Calendar.HOUR_OF_DAY) - 1)), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }

                } else if (Calendar.HOUR_OF_DAY == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("HO", cal.getMinimum(Calendar.HOUR_OF_DAY)),
                            build2SF("HO", (cal.get(Calendar.HOUR_OF_DAY))), true, true);
                    range.add(part, Occur.MUST);

                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.HOUR_OF_DAY <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("HO", cal.get(Calendar.HOUR_OF_DAY))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.MINUTE:
            if (padField == Calendar.MINUTE) {
                if (Calendar.MINUTE < resolutionField) {
                    if (cal.get(Calendar.MINUTE) > cal.getMinimum(Calendar.MINUTE)) {
                        part = new ConstantScoreRangeQuery(field, build2SF("MI", cal.getMinimum(Calendar.MINUTE)),
                                build2SF("MI", (cal.get(Calendar.MINUTE) - 1)), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }
                } else if (Calendar.MINUTE == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("MI", cal.getMinimum(Calendar.MINUTE)),
                            build2SF("MI", (cal.get(Calendar.MINUTE))), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            } else {
                if (Calendar.MINUTE <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("MI", cal.get(Calendar.MINUTE))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.SECOND:
            if (padField == Calendar.SECOND) {
                if (Calendar.SECOND < resolutionField) {
                    if (cal.get(Calendar.SECOND) > cal.getMinimum(Calendar.SECOND)) {
                        part = new ConstantScoreRangeQuery(field, build2SF("SE", cal.getMinimum(Calendar.SECOND)),
                                build2SF("SE", (cal.get(Calendar.SECOND) - 1)), true, true);
                        range.add(part, Occur.MUST);
                    } else {
                        return null;
                    }
                } else if (Calendar.SECOND == resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build2SF("SE", cal.getMinimum(Calendar.SECOND)),
                            build2SF("SE", (cal.get(Calendar.SECOND))), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
                break;
            }

            else {
                if (Calendar.SECOND <= resolutionField) {
                    part = new TermQuery(new Term(field, build2SF("SE", cal.get(Calendar.SECOND))));
                    range.add(part, Occur.MUST);
                } else {
                    return null;
                }
            }
        case Calendar.MILLISECOND:
            if ((cal.get(Calendar.MILLISECOND) >= cal.getMinimum(Calendar.MILLISECOND))
                    && (cal.get(Calendar.MILLISECOND) < cal.getMaximum(Calendar.MILLISECOND))) {
                if (Calendar.MILLISECOND <= resolutionField) {
                    part = new ConstantScoreRangeQuery(field, build3SF("MS", cal.getMinimum(Calendar.MILLISECOND)),
                            build3SF("MS", cal.get(Calendar.MILLISECOND)), true, true);
                    range.add(part, Occur.MUST);
                } else {
                    break;
                }
            }
        default:
        }

        return getNonEmptyBooleanQuery(range);
    }

    private String build2SF(String prefix, int value) {
        if (value < 10) {
            return prefix + "0" + value;
        } else {
            return prefix + value;
        }
    }

    private String build3SF(String prefix, int value) {
        if (value < 10) {
            return prefix + "00" + value;
        } else if (value < 100) {
            return prefix + "0" + value;
        } else {
            return prefix + value;
        }
    }

    private String expandAttributeFieldName(String field) {
        return PROPERTY_FIELD_PREFIX + QueryParserUtils.expandQName(searchParameters.getNamespace(),
                namespacePrefixResolver, field.substring(1));
    }

    protected String getToken(String field, String value, AnalysisMode analysisMode) throws ParseException {
        TokenStream source = getAnalyzer().tokenStream(field, new StringReader(value), analysisMode);
        org.apache.lucene.analysis.Token reusableToken = new org.apache.lucene.analysis.Token();
        org.apache.lucene.analysis.Token nextToken;
        String tokenised = null;

        while (true) {
            try {
                nextToken = source.next(reusableToken);
            } catch (IOException e) {
                nextToken = null;
            }
            if (nextToken == null)
                break;
            tokenised = new String(nextToken.termBuffer(), 0, nextToken.termLength());
        }
        try {
            source.close();
        } catch (IOException e) {

        }

        return tokenised;
    }

    @Override
    public Query getPrefixQuery(String field, String termStr) throws ParseException {
        return getPrefixQuery(field, termStr, AnalysisMode.PREFIX);
    }

    public Query getPrefixQuery(String field, String termStr, AnalysisMode analysisMode) throws ParseException {
        if (field.equals(FIELD_PATH)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_PATH);
        } else if (field.equals(FIELD_PATHWITHREPEATS)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_PATHWITHREPEATS);
        } else if (field.equals(FIELD_TEXT)) {
            Set<String> text = searchParameters.getTextAttributes();
            if ((text == null) || (text.size() == 0)) {
                Query query = getPrefixQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), termStr,
                        analysisMode);
                if (query == null) {
                    return createNoMatchQuery();
                }

                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : text) {
                    Query part = getPrefixQuery(fieldName, termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ID) || field.equals(FIELD_DBID) || field.equals(FIELD_ISROOT)
                || field.equals(FIELD_ISCONTAINER) || field.equals(FIELD_ISNODE) || field.equals(FIELD_TX)
                || field.equals(FIELD_PARENT) || field.equals(FIELD_PRIMARYPARENT) || field.equals(FIELD_QNAME)
                || field.equals(FIELD_PRIMARYASSOCTYPEQNAME) || field.equals(FIELD_ASSOCTYPEQNAME)) {
            boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
            try {
                setLowercaseExpandedTerms(false);
                return super.getPrefixQuery(field, termStr);
            } finally {
                setLowercaseExpandedTerms(lowercaseExpandedTerms);
            }
        } else if (field.equals(FIELD_CLASS)) {
            return super.getPrefixQuery(field, termStr);
            // throw new UnsupportedOperationException("Prefix Queries are not support for "+FIELD_CLASS);
        } else if (field.equals(FIELD_TYPE)) {
            return super.getPrefixQuery(field, termStr);
            // throw new UnsupportedOperationException("Prefix Queries are not support for "+FIELD_TYPE);
        } else if (field.equals(FIELD_EXACTTYPE)) {
            return super.getPrefixQuery(field, termStr);
            // throw new UnsupportedOperationException("Prefix Queries are not support for "+FIELD_EXACTTYPE);
        } else if (field.equals(FIELD_ASPECT)) {
            return super.getPrefixQuery(field, termStr);
            // throw new UnsupportedOperationException("Prefix Queries are not support for "+FIELD_ASPECT);
        } else if (field.equals(FIELD_EXACTASPECT)) {
            return super.getPrefixQuery(field, termStr);
            // throw new UnsupportedOperationException("Prefix Queries are not support for "+FIELD_EXACTASPECT);
        } else if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            return attributeQueryBuilder(field, termStr, new PrefixQuery(), analysisMode, LuceneFunction.FIELD);
        } else if (field.equals(FIELD_ALL)) {
            Set<String> all = searchParameters.getAllAttributes();
            if ((all == null) || (all.size() == 0)) {
                Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
                BooleanQuery query = new BooleanQuery();
                for (QName qname : contentAttributes) {
                    // The super implementation will create phrase queries etc if required
                    Query part = getPrefixQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : all) {
                    Query part = getPrefixQuery(fieldName, termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ISUNSET)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_ISUNSET);
        } else if (field.equals(FIELD_ISNULL)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_ISNULL);
        } else if (field.equals(FIELD_ISNOTNULL)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_ISNOTNULL);
        } else if (matchDataTypeDefinition(field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(matchDataTypeDefinition(field).getName());
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                // The super implementation will create phrase queries etc if required
                Query part = getPrefixQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, analysisMode);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        } else if (field.equals(FIELD_FTSSTATUS)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_FTSSTATUS);
        } else if (field.equals(FIELD_TAG)) {
            return null;
        } else {
            return super.getPrefixQuery(field, termStr);
        }
    }

    @Override
    public Query getWildcardQuery(String field, String termStr) throws ParseException {
        return getWildcardQuery(field, termStr, AnalysisMode.WILD);
    }

    public Query getWildcardQuery(String field, String termStr, AnalysisMode analysisMode) throws ParseException {
        if (field.equals(FIELD_PATH)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_PATH);
        } else if (field.equals(FIELD_PATHWITHREPEATS)) {
            throw new UnsupportedOperationException(
                    "Wildcard Queries are not support for " + FIELD_PATHWITHREPEATS);
        } else if (field.equals(FIELD_TEXT)) {
            Set<String> text = searchParameters.getTextAttributes();
            if ((text == null) || (text.size() == 0)) {
                Query query = getWildcardQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(),
                        termStr, analysisMode);
                if (query == null) {
                    return createNoMatchQuery();
                }

                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : text) {
                    Query part = getWildcardQuery(fieldName, termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ID) || field.equals(FIELD_DBID) || field.equals(FIELD_ISROOT)
                || field.equals(FIELD_ISCONTAINER) || field.equals(FIELD_ISNODE) || field.equals(FIELD_TX)
                || field.equals(FIELD_PARENT) || field.equals(FIELD_PRIMARYPARENT) || field.equals(FIELD_QNAME)
                || field.equals(FIELD_PRIMARYASSOCTYPEQNAME) || field.equals(FIELD_ASSOCTYPEQNAME)) {
            boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
            try {
                setLowercaseExpandedTerms(false);
                return super.getWildcardQuery(field, termStr);
            } finally {
                setLowercaseExpandedTerms(lowercaseExpandedTerms);
            }
        } else if (field.equals(FIELD_CLASS)) {
            return super.getWildcardQuery(field, termStr);
            // throw new UnsupportedOperationException("Wildcard Queries are not support for "+FIELD_CLASS);
        } else if (field.equals(FIELD_TYPE)) {
            return super.getWildcardQuery(field, termStr);
            // throw new UnsupportedOperationException("Wildcard Queries are not support for "+FIELD_TYPE);
        } else if (field.equals(FIELD_EXACTTYPE)) {
            return super.getWildcardQuery(field, termStr);
            // throw new UnsupportedOperationException("Wildcard Queries are not support for "+FIELD_EXACTTYPE);
        } else if (field.equals(FIELD_ASPECT)) {
            return super.getWildcardQuery(field, termStr);
            // throw new UnsupportedOperationException("Wildcard Queries are not support for "+FIELD_ASPECT);
        } else if (field.equals(FIELD_EXACTASPECT)) {
            return super.getWildcardQuery(field, termStr);
            // throw new UnsupportedOperationException("Wildcard Queries are not support for "+FIELD_EXACTASPECT);
        } else if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            return attributeQueryBuilder(field, termStr, new WildcardQuery(), analysisMode, LuceneFunction.FIELD);
        } else if (field.equals(FIELD_ALL)) {
            Set<String> all = searchParameters.getAllAttributes();
            if ((all == null) || (all.size() == 0)) {
                Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
                BooleanQuery query = new BooleanQuery();
                for (QName qname : contentAttributes) {
                    // The super implementation will create phrase queries etc if required
                    Query part = getWildcardQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : all) {
                    Query part = getWildcardQuery(fieldName, termStr, analysisMode);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ISUNSET)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_ISUNSET);
        } else if (field.equals(FIELD_ISNULL)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_ISNULL);
        } else if (field.equals(FIELD_ISNOTNULL)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_ISNOTNULL);
        } else if (matchDataTypeDefinition(field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(matchDataTypeDefinition(field).getName());
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                // The super implementation will create phrase queries etc if required
                Query part = getWildcardQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, analysisMode);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        } else if (field.equals(FIELD_FTSSTATUS)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_FTSSTATUS);
        } else if (field.equals(FIELD_TAG)) {
            return null;
        } else {
            return super.getWildcardQuery(field, termStr);
        }
    }

    @Override
    public Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException {
        if (field.equals(FIELD_PATH)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_PATH);
        } else if (field.equals(FIELD_PATHWITHREPEATS)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_PATHWITHREPEATS);
        } else if (field.equals(FIELD_TEXT)) {
            Set<String> text = searchParameters.getTextAttributes();
            if ((text == null) || (text.size() == 0)) {
                Query query = getFuzzyQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), termStr,
                        minSimilarity);
                if (query == null) {
                    return createNoMatchQuery();
                }

                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : text) {
                    Query part = getFuzzyQuery(fieldName, termStr, minSimilarity);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ID) || field.equals(FIELD_DBID) || field.equals(FIELD_ISROOT)
                || field.equals(FIELD_ISCONTAINER) || field.equals(FIELD_ISNODE) || field.equals(FIELD_TX)
                || field.equals(FIELD_PARENT) || field.equals(FIELD_PRIMARYPARENT) || field.equals(FIELD_QNAME)
                || field.equals(FIELD_PRIMARYASSOCTYPEQNAME) || field.equals(FIELD_ASSOCTYPEQNAME)) {
            boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
            try {
                setLowercaseExpandedTerms(false);
                return super.getFuzzyQuery(field, termStr, minSimilarity);
            } finally {
                setLowercaseExpandedTerms(lowercaseExpandedTerms);
            }
        } else if (field.equals(FIELD_CLASS)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_CLASS);
        } else if (field.equals(FIELD_TYPE)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_TYPE);
        } else if (field.equals(FIELD_EXACTTYPE)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_EXACTTYPE);
        } else if (field.equals(FIELD_ASPECT)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_ASPECT);
        } else if (field.equals(FIELD_EXACTASPECT)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_EXACTASPECT);
        } else if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            return attributeQueryBuilder(field, termStr, new FuzzyQuery(minSimilarity), AnalysisMode.FUZZY,
                    LuceneFunction.FIELD);
        } else if (field.equals(FIELD_ALL)) {
            Set<String> all = searchParameters.getAllAttributes();
            if ((all == null) || (all.size() == 0)) {
                Collection<QName> contentAttributes = dictionaryService.getAllProperties(null);
                BooleanQuery query = new BooleanQuery();
                for (QName qname : contentAttributes) {
                    // The super implementation will create phrase queries etc if required
                    Query part = getFuzzyQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, minSimilarity);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            } else {
                BooleanQuery query = new BooleanQuery();
                for (String fieldName : all) {
                    Query part = getFuzzyQuery(fieldName, termStr, minSimilarity);
                    if (part != null) {
                        query.add(part, Occur.SHOULD);
                    } else {
                        query.add(createNoMatchQuery(), Occur.SHOULD);
                    }
                }
                return query;
            }
        } else if (field.equals(FIELD_ISUNSET)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_ISUNSET);
        } else if (field.equals(FIELD_ISNULL)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_ISNULL);
        } else if (field.equals(FIELD_ISNOTNULL)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_ISNOTNULL);
        } else if (matchDataTypeDefinition(field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(matchDataTypeDefinition(field).getName());
            BooleanQuery query = new BooleanQuery();
            for (QName qname : contentAttributes) {
                // The super implementation will create phrase queries etc if required
                Query part = getFuzzyQuery(PROPERTY_FIELD_PREFIX + qname.toString(), termStr, minSimilarity);
                if (part != null) {
                    query.add(part, Occur.SHOULD);
                } else {
                    query.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
            return query;
        } else if (field.equals(FIELD_FTSSTATUS)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_FTSSTATUS);
        } else if (field.equals(FIELD_TAG)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_TAG);
        } else {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        }
    }

    /**
     * @param dictionaryService DictionaryService
     */
    public void setDictionaryService(DictionaryService dictionaryService) {
        this.dictionaryService = dictionaryService;
    }

    /**
     * @param field String
     * @param queryText String
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @return the query
     * @throws ParseException
     */
    public Query getSuperFieldQuery(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        return getFieldQueryImpl(field, queryText, analysisMode, luceneFunction);
    }

    /**
     * @param field String
     * @param termStr String
     * @param minSimilarity float
     * @return the query
     * @throws ParseException
     */
    public Query getSuperFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException {
        return super.getFuzzyQuery(field, termStr, minSimilarity);
    }

    /**
     * @param field String
     * @param termStr String
     * @return the query
     * @throws ParseException
     */
    public Query getSuperPrefixQuery(String field, String termStr) throws ParseException {
        return super.getPrefixQuery(field, termStr);
    }

    /**
     * @param field String
     * @param termStr String
     * @return the query
     * @throws ParseException
     */
    public Query getSuperWildcardQuery(String field, String termStr) throws ParseException {
        return super.getWildcardQuery(field, termStr);
    }

    /*
     * (non-Javadoc)
     * @see org.apache.lucene.queryParser.QueryParser#newWildcardQuery(org.apache.lucene.index.Term)
     */
    @Override
    protected Query newWildcardQuery(Term t) {
        if (t.text().contains("\\")) {
            String regexp = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE,
                    SearchLanguageConversion.DEF_REGEX, t.text());
            return new RegexQuery(new Term(t.field(), regexp));
        } else {
            return super.newWildcardQuery(t);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.lucene.queryParser.QueryParser#newPrefixQuery(org.apache.lucene.index.Term)
     */
    @Override
    protected Query newPrefixQuery(Term prefix) {
        if (prefix.text().contains("\\")) {
            String regexp = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE,
                    SearchLanguageConversion.DEF_REGEX, prefix.text());
            return new RegexQuery(new Term(prefix.field(), regexp));
        } else {
            return super.newPrefixQuery(prefix);
        }

    }

    public interface SubQuery {
        /**
         * @param field String
         * @param queryText String
         * @param analysisMode AnalysisMode
         * @param luceneFunction LuceneFunction
         * @return the query
         * @throws ParseException
         */
        Query getQuery(String field, String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
                throws ParseException;
    }

    class FieldQuery implements SubQuery {
        public Query getQuery(String field, String queryText, AnalysisMode analysisMode,
                LuceneFunction luceneFunction) throws ParseException {
            return getSuperFieldQuery(field, queryText, analysisMode, luceneFunction);
        }
    }

    class FuzzyQuery implements SubQuery {
        float minSimilarity;

        FuzzyQuery(float minSimilarity) {
            this.minSimilarity = minSimilarity;
        }

        public Query getQuery(String field, String termStr, AnalysisMode analysisMode,
                LuceneFunction luceneFunction) throws ParseException {
            return getSuperFuzzyQuery(field, translateLocale(termStr), minSimilarity);
        }
    }

    private String translateLocale(String localised) {
        if (localised.startsWith("\u0000")) {
            if (localised.startsWith("\u0000\u0000")) {
                if (localised.length() < 3) {
                    return "";
                } else {
                    return localised.substring(2);
                }
            } else {
                int end = localised.indexOf('\u0000', 1);
                if (end == -1) {
                    return localised;
                } else {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("{");
                    buffer.append(localised.substring(1, end));
                    buffer.append("}");
                    buffer.append(localised.substring(end + 1));
                    return buffer.toString();
                }
            }
        } else {
            return localised;
        }
    }

    class PrefixQuery implements SubQuery {
        public Query getQuery(String field, String termStr, AnalysisMode analysisMode,
                LuceneFunction luceneFunction) throws ParseException {
            StringBuilder builder = new StringBuilder(termStr.length() + 1);
            builder.append(termStr);
            builder.append("*");
            return getSuperFieldQuery(field, builder.toString(), analysisMode, luceneFunction);
            //return getSuperPrefixQuery(field, termStr);
        }
    }

    class WildcardQuery implements SubQuery {
        public Query getQuery(String field, String termStr, AnalysisMode analysisMode,
                LuceneFunction luceneFunction) throws ParseException {
            return getSuperFieldQuery(field, termStr, analysisMode, luceneFunction);
            //return getSuperWildcardQuery(field, termStr);
        }
    }

    private Query spanQueryBuilder(String field, String first, String last, int slop, boolean inOrder) {
        String propertyFieldName = field.substring(1);

        String expandedFieldName;
        PropertyDefinition propertyDef = matchPropertyDefinition(propertyFieldName);
        IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
        if (propertyDef != null) {
            tokenisationMode = propertyDef.getIndexTokenisationMode();
            if (tokenisationMode == null) {
                tokenisationMode = IndexTokenisationMode.TRUE;
            }
            QName propertyQName = propertyDef.getName();
            expandedFieldName = PROPERTY_FIELD_PREFIX + propertyQName;

        } else {
            expandedFieldName = expandAttributeFieldName(field);
        }

        if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT))) {
            // Build a sub query for each locale and or the results together - the analysis will take care of
            // cross language matching for each entry
            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {
                addMLTextSpanQuery(field, first, last, slop, inOrder, expandedFieldName, propertyDef,
                        tokenisationMode, booleanQuery, mlAnalysisMode, locale);
            }
            return booleanQuery;
        }
        // Content
        else if ((propertyDef != null)
                && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {

            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();

            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(
                        MLAnalysisMode.getLocales(mlAnalysisMode, locale, addContentCrossLocaleWildcards()));
            }

            return addContentSpanQuery(field, first, last, slop, inOrder, expandedFieldName, expandedLocales,
                    mlAnalysisMode);

        } else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT))) {
            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {

                addTextSpanQuery(field, first, last, slop, inOrder, expandedFieldName, tokenisationMode,
                        booleanQuery, mlAnalysisMode, locale);

            }
            return booleanQuery;
        } else {
            throw new UnsupportedOperationException(
                    "Span queries are only supported for d:text, d:mltext and d:content data types");
        }

        // need to build each term for the span
        //SpanQuery firstTerm = new SpanTermQuery(new Term(field, first));
        //SpanQuery lastTerm = new SpanTermQuery(new Term(field, last));
        //return new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
    }

    /**
     * @param field String
     * @param first String
     * @param last String
     * @param slop int
     * @param inOrder boolean
     * @param expandedFieldName String
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     */
    protected abstract void addTextSpanQuery(String field, String first, String last, int slop, boolean inOrder,
            String expandedFieldName, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            MLAnalysisMode mlAnalysisMode, Locale locale);

    /**
     * @param field String
     * @param first String
     * @param last String
     * @param slop int
     * @param inOrder boolean
     * @param expandedFieldName String
     * @param mlAnalysisMode MLAnalysisMode
     * @return org.apache.lucene.search.Query
     */
    protected abstract org.apache.lucene.search.Query addContentSpanQuery(String field, String first, String last,
            int slop, boolean inOrder, String expandedFieldName, List<Locale> expandedLocales,
            MLAnalysisMode mlAnalysisMode);

    /**
     * @param field String
     * @param first String
     * @param last String
     * @param slop int
     * @param inOrder boolean
     * @param expandedFieldName String
     * @param propertyDef PropertyDefinition
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     */
    protected abstract void addMLTextSpanQuery(String field, String first, String last, int slop, boolean inOrder,
            String expandedFieldName, PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode,
            BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale);

    private Query attributeQueryBuilder(String field, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction) throws ParseException {
        // TODO: Fix duplicate token generation for mltext, content and text.
        // -locale expansion here and in tokeisation -> duplicates

        // Get type info etc

        // TODO: additional suffixes
        String propertyFieldName = null;
        String ending = "";
        if (field.endsWith(FIELD_MIMETYPE_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_MIMETYPE_SUFFIX.length());
            ending = FIELD_MIMETYPE_SUFFIX;
        } else if (field.endsWith(FIELD_SIZE_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_SIZE_SUFFIX.length());
            ending = FIELD_SIZE_SUFFIX;
        } else if (field.endsWith(FIELD_LOCALE_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_LOCALE_SUFFIX.length());
            ending = FIELD_LOCALE_SUFFIX;
        } else if (field.endsWith(FIELD_ENCODING_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_ENCODING_SUFFIX.length());
            ending = FIELD_ENCODING_SUFFIX;
        } else if (field.endsWith(FIELD_CONTENT_DOC_ID_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_CONTENT_DOC_ID_SUFFIX.length());
            ending = FIELD_CONTENT_DOC_ID_SUFFIX;
        } else if (field.endsWith(FIELD_TRANSFORMATION_EXCEPTION_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_TRANSFORMATION_EXCEPTION_SUFFIX.length());
            ending = FIELD_TRANSFORMATION_EXCEPTION_SUFFIX;
        } else if (field.endsWith(FIELD_TRANSFORMATION_TIME_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_TRANSFORMATION_TIME_SUFFIX.length());
            ending = FIELD_TRANSFORMATION_TIME_SUFFIX;
        } else if (field.endsWith(FIELD_TRANSFORMATION_STATUS_SUFFIX)) {
            propertyFieldName = field.substring(1, field.length() - FIELD_TRANSFORMATION_STATUS_SUFFIX.length());
            ending = FIELD_TRANSFORMATION_STATUS_SUFFIX;
        } else {
            propertyFieldName = field.substring(1);
        }

        String expandedFieldName;
        QName propertyQName;
        PropertyDefinition propertyDef = matchPropertyDefinition(propertyFieldName);
        IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
        if (propertyDef != null) {
            tokenisationMode = propertyDef.getIndexTokenisationMode();
            if (tokenisationMode == null) {
                tokenisationMode = IndexTokenisationMode.TRUE;
            }
            expandedFieldName = PROPERTY_FIELD_PREFIX + propertyDef.getName() + ending;
            propertyQName = propertyDef.getName();
        } else {
            expandedFieldName = expandAttributeFieldName(field);
            propertyQName = QName.createQName(propertyFieldName);
        }

        if (luceneFunction != LuceneFunction.FIELD) {
            if ((tokenisationMode == IndexTokenisationMode.FALSE)
                    || (tokenisationMode == IndexTokenisationMode.BOTH)) {
                if (luceneFunction == LuceneFunction.LOWER) {
                    if (false == queryText.toLowerCase().equals(queryText)) {
                        return createNoMatchQuery();
                    }
                }
                if (luceneFunction == LuceneFunction.UPPER) {
                    if (false == queryText.toUpperCase().equals(queryText)) {
                        return createNoMatchQuery();
                    }
                }

                return functionQueryBuilder(expandedFieldName, propertyQName, propertyDef, tokenisationMode,
                        queryText, luceneFunction);
            }
        }

        // Mime type
        if (expandedFieldName.endsWith(FIELD_MIMETYPE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_SIZE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_LOCALE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_ENCODING_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_STATUS_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_TIME_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_EXCEPTION_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            }

        }

        // Already in expanded form

        // ML

        if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT))) {
            // Build a sub query for each locale and or the results together - the analysis will take care of
            // cross language matching for each entry
            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {
                addMLTextAttributeQuery(field, queryText, subQueryBuilder, analysisMode, luceneFunction,
                        expandedFieldName, propertyDef, tokenisationMode, booleanQuery, mlAnalysisMode, locale);
            }
            return getNonEmptyBooleanQuery(booleanQuery);
        }
        // Content
        else if ((propertyDef != null)
                && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
            // Identifier request are ignored for content

            // Build a sub query for each locale and or the results together -
            // - add an explicit condition for the locale

            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();

            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(
                        MLAnalysisMode.getLocales(mlAnalysisMode, locale, addContentCrossLocaleWildcards()));
            }

            return addContentAttributeQuery(queryText, subQueryBuilder, analysisMode, luceneFunction,
                    expandedFieldName, expandedLocales, mlAnalysisMode);

        } else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT))) {
            if (propertyQName.equals(ContentModel.PROP_USER_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME)) {
                // nasty work around for solr support for user and group look up as we can not support lowercased identifiers in the model
                if (isLucene()) {
                    return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
                }
            }

            boolean withWildCards = propertyQName.equals(ContentModel.PROP_USER_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME);

            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, withWildCards));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {

                addTextAttributeQuery(field, queryText, subQueryBuilder, analysisMode, luceneFunction,
                        expandedFieldName, tokenisationMode, booleanQuery, mlAnalysisMode, locale);

            }
            return getNonEmptyBooleanQuery(booleanQuery);
        } else {
            // Date does not support like
            if ((propertyDef != null)
                    && (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME))) {
                if (analysisMode == AnalysisMode.LIKE) {
                    throw new UnsupportedOperationException("Wild cards are not supported for the datetime type");
                }
            }

            if ((propertyDef != null) && (tenantService.isTenantUser())
                    && (propertyDef.getDataType().getName().equals(DataTypeDefinition.NODE_REF))
                    && (queryText.contains(StoreRef.URI_FILLER))) {
                // ALF-6202
                queryText = tenantService.getName(new NodeRef(queryText)).toString();
            }

            // Sort and id is only special for MLText, text, and content
            // Dates are not special in this case
            Query query = subQueryBuilder.getQuery(expandedFieldName, queryText, AnalysisMode.DEFAULT,
                    luceneFunction);
            if (query != null) {
                return query;
            } else {
                return createNoMatchQuery();
            }
        }
    }

    /**
     * @return boolean
     */
    protected abstract boolean isLucene();

    /**
     * @param field String
     * @param queryText String
     * @param subQueryBuilder SubQuery
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @param expandedFieldName String
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     * @throws ParseException
     */
    protected abstract void addTextAttributeQuery(String field, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode,
            Locale locale) throws ParseException;

    /**
     * @param queryText String
     * @param subQueryBuilder SubQuery
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @param expandedFieldName String
     * @return Query
     * @throws ParseException
     */
    protected abstract Query addContentAttributeQuery(String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            List<Locale> expandedLocales, MLAnalysisMode mlAnalysisMode) throws ParseException;

    /**
     * @param field String
     * @param queryText String
     * @param subQueryBuilder SubQuery
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @param expandedFieldName String
     * @param propertyDef PropertyDefinition
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     * @throws ParseException
     */
    protected abstract void addMLTextAttributeQuery(String field, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            MLAnalysisMode mlAnalysisMode, Locale locale) throws ParseException;

    //    private void addLocaleSpecificUntokenisedMLOrTextAttribute(String sourceField, String queryText, SubQuery subQueryBuilder, AnalysisMode analysisMode,
    //            LuceneFunction luceneFunction, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale, String actualField) throws ParseException
    //    {
    //
    //        String termText = queryText;
    //        if (locale.toString().length() > 0)
    //        {
    //            termText = "{" + locale + "}" + queryText;
    //        }
    //        Query subQuery = subQueryBuilder.getQuery(actualField, termText, analysisMode, luceneFunction);
    //        booleanQuery.add(subQuery, Occur.SHOULD);
    //
    //        if (booleanQuery.getClauses().length == 0)
    //        {
    //            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
    //        }
    //    }

    //    private void addLocaleSpecificTokenisedMLOrTextAttribute(String queryText, SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
    //            BooleanQuery booleanQuery, Locale locale, String actualField) throws ParseException
    //    {
    //        StringBuilder builder = new StringBuilder(queryText.length() + 10);
    //        builder.append("\u0000").append(locale.toString()).append("\u0000").append(queryText);
    //        Query subQuery = subQueryBuilder.getQuery(actualField, builder.toString(), analysisMode, luceneFunction);
    //        if (subQuery != null)
    //        {
    //            booleanQuery.add(subQuery, Occur.SHOULD);
    //        }
    //        else
    //        {
    //            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
    //        }
    //    }

    protected Query functionQueryBuilder(String expandedFieldName, QName propertyQName,
            PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode, String queryText,
            LuceneFunction luceneFunction) throws ParseException {

        // Mime type
        if (expandedFieldName.endsWith(FIELD_MIMETYPE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_SIZE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_LOCALE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_ENCODING_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_CONTENT_DOC_ID_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_EXCEPTION_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_TIME_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        } else if (expandedFieldName.endsWith(FIELD_TRANSFORMATION_STATUS_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                throw new UnsupportedOperationException("Lucene Function");
            }

        }

        // Already in expanded form

        // ML

        if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT))) {
            // Build a sub query for each locale and or the results together - the analysis will take care of
            // cross language matching for each entry
            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, false));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {

                addLocaleSpecificUntokenisedMLOrTextFunction(expandedFieldName, queryText, luceneFunction,
                        booleanQuery, mlAnalysisMode, locale, tokenisationMode);

            }
            return booleanQuery;
        }
        // Content
        else if ((propertyDef != null)
                && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
            throw new UnsupportedOperationException("Lucene functions not supported for content");
        } else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT))) {
            if (propertyQName.equals(ContentModel.PROP_USER_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME)) {
                throw new UnsupportedOperationException("Functions are not supported agaisnt special text fields");
            }

            boolean withWildCards = propertyQName.equals(ContentModel.PROP_USER_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_USERNAME)
                    || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME);

            BooleanQuery booleanQuery = new BooleanQuery();
            MLAnalysisMode mlAnalysisMode = searchParameters.getMlAnalaysisMode() == null
                    ? defaultSearchMLAnalysisMode
                    : searchParameters.getMlAnalaysisMode();
            List<Locale> locales = searchParameters.getLocales();
            List<Locale> expandedLocales = new ArrayList<Locale>();
            for (Locale locale : (((locales == null) || (locales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : locales)) {
                expandedLocales.addAll(MLAnalysisMode.getLocales(mlAnalysisMode, locale, withWildCards));
            }
            for (Locale locale : (((expandedLocales == null) || (expandedLocales.size() == 0))
                    ? Collections.singletonList(I18NUtil.getLocale())
                    : expandedLocales)) {
                addLocaleSpecificUntokenisedMLOrTextFunction(expandedFieldName, queryText, luceneFunction,
                        booleanQuery, mlAnalysisMode, locale, tokenisationMode);

            }
            return booleanQuery;
        } else {
            throw new UnsupportedOperationException("Lucene Function");
        }
    }

    protected abstract void addLocaleSpecificUntokenisedMLOrTextFunction(String expandedFieldName, String queryText,
            LuceneFunction luceneFunction, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale,
            IndexTokenisationMode tokenisationMode);

    // private Query buildFunctionQuery(String expandedFieldName, String termText, LuceneFunction luceneFunction)
    // {
    // String testText = termText;
    // if (termText.startsWith("{"))
    // {
    // int index = termText.indexOf("}");
    // testText = termText.substring(index + 1);
    // }
    // switch (luceneFunction)
    // {
    // case LOWER:
    // if (testText.equals(testText.toLowerCase()))
    // {
    // return new CaseInsensitiveFieldQuery(new Term(expandedFieldName, termText));
    // }
    // else
    // {
    // // No match
    // return createNoMatchQuery();
    // }
    // case UPPER:
    // if (testText.equals(testText.toUpperCase()))
    // {
    // return new CaseInsensitiveFieldQuery(new Term(expandedFieldName, termText));
    // }
    // else
    // {
    // // No match
    // return createNoMatchQuery();
    // }
    // default:
    // throw new UnsupportedOperationException("Unsupported Lucene Function " + luceneFunction);
    //
    // }
    // }

    protected TermQuery createNoMatchQuery() {
        return new TermQuery(new Term("NO_TOKENS", "__"));
    }

    public static void main(String[] args) throws ParseException, java.text.ParseException {
        //Query query;

        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        SimpleDateFormat df = CachingDateFormat.getDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", false);

        Date date = df.parse("2011-09-31T00:00:00.000");
        System.out.println(date);
        start.setTime(date);
        System.out.println(start);

        date = df.parse("2011-10-28T00:00:00.000");
        System.out.println(date);
        end.setTime(date);
        System.out.println(end);

        LuceneQueryParser lqp = new LuceneQueryParser(null, null);
        Query query = lqp.buildDateTimeRange("WOOF", start, Calendar.DAY_OF_MONTH, end, Calendar.DAY_OF_MONTH, true,
                true);
        System.out.println("Query is " + query);
    }

    @Override
    public AbstractAnalyzer getAnalyzer() {
        return luceneAnalyser;
    }

    /**
     * Returns null if all clause words were filtered away by the analyzer
     * @param booleanQuery - initial BooleanQuery
     * @return BooleanQuery or <code>null</code> if booleanQuery has no clauses 
     */
    protected BooleanQuery getNonEmptyBooleanQuery(BooleanQuery booleanQuery) {
        if (booleanQuery.clauses().size() > 0) {
            return booleanQuery;
        } else {
            return null;
        }
    }

}