org.alfresco.solr.query.Solr4QueryParser.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.solr.query.Solr4QueryParser.java

Source

/*
 * #%L
 * Alfresco Solr 4
 * %%
 * 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.solr.query;

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 java.util.TimeZone;

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.MLTokenDuplicator;
import org.alfresco.repo.search.impl.parsers.FTSQueryException;
import org.alfresco.repo.search.impl.parsers.FTSQueryParser;
import org.alfresco.repo.search.impl.parsers.FTSQueryParser.RerankPhase;
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.solr.AlfrescoAnalyzerWrapper;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.AlfrescoSolrDataModel.ContentFieldType;
import org.alfresco.solr.AlfrescoSolrDataModel.FieldInstance;
import org.alfresco.solr.AlfrescoSolrDataModel.FieldUse;
import org.alfresco.solr.AlfrescoSolrDataModel.IndexedField;
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.analysis.commongrams.CommonGramsFilter;
import org.apache.lucene.analysis.shingle.ShingleFilterFactory;
import org.apache.lucene.analysis.synonym.SynonymFilter;
import org.apache.lucene.analysis.synonym.SynonymFilterFactory;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.apache.lucene.analysis.util.TokenFilterFactory;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MultiPhraseQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanOrQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper.TopTermsSpanBooleanQueryRewrite;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.Version;
import org.apache.solr.analysis.TokenizerChain;
import org.apache.solr.common.SolrException;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.base.XPathReader;
import org.springframework.extensions.surf.util.I18NUtil;

/**
 * @author Andy
 *
 */
public class Solr4QueryParser extends QueryParser implements QueryConstants {

    /**
      * @param schema IndexSchema
      * @param matchVersion
      * @param f
      * @param a
     */
    public Solr4QueryParser(IndexSchema schema, Version matchVersion, String f, Analyzer a,
            FTSQueryParser.RerankPhase rerankPhase) {
        super(matchVersion, f, a);
        this.schema = schema;
        setAllowLeadingWildcard(true);
        setAnalyzeRangeTerms(true);
        this.rerankPhase = rerankPhase;
    }

    private RerankPhase rerankPhase;

    IndexSchema schema;

    @SuppressWarnings("unused")
    private static Log s_logger = LogFactory.getLog(Solr4QueryParser.class);

    protected NamespacePrefixResolver namespacePrefixResolver;

    protected DictionaryService dictionaryService;

    private TenantService tenantService;

    private SearchParameters searchParameters;

    private MLAnalysisMode mlAnalysisMode = MLAnalysisMode.EXACT_LANGUAGE_AND_ALL;

    private int internalSlop = 0;

    int topTermSpanRewriteLimit = 1000;

    /**
     * @param topTermSpanRewriteLimit the topTermSpanRewriteLimit to set
     */
    public void setTopTermSpanRewriteLimit(int topTermSpanRewriteLimit) {
        this.topTermSpanRewriteLimit = topTermSpanRewriteLimit;
    }

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

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

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

    public SearchParameters getSearchParameters() {
        return searchParameters;
    }

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

    }

    /**
      * @param field
      * @param queryText
      * @param analysisMode
      * @param slop
      * @param luceneFunction
     * @return the query
     * @throws ParseException
      * @throws IOException 
     */
    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
      * @param sqlLikeClause
      * @param 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
      * @param queryText
      * @param analysisMode
      * @param luceneFunction
     * @return the query
     * @throws ParseException
      * @throws IOException 
     */
    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
      * @param first
      * @param last
      * @param slop
      * @param inOrder
     * @return the query
     * @throws ParseException 
     */
    public Query getSpanQuery(String field, String first, String last, int slop, boolean inOrder)
            throws ParseException {
        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 (isPropertyField(field)) {
            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 (field.equals(FIELD_EXISTS)) {
            throw new UnsupportedOperationException("Span is not supported for " + FIELD_EXISTS);
        } else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                            namespacePrefixResolver, dictionaryService, 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 if (isPropertyField(field)) {
            return spanQueryBuilder(field, first, last, slop, inOrder);
        } else {
            BytesRef firstBytes = analyzeMultitermTerm(field, first, getAnalyzer());
            BytesRef lastBytes = analyzeMultitermTerm(field, last, getAnalyzer());
            SpanQuery firstTerm = new SpanTermQuery(new Term(field, firstBytes));
            SpanQuery lastTerm = new SpanTermQuery(new Term(field, lastBytes));
            return new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
        }
    }

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

    /**
      * @param field
      * @param queryText
      * @param analysisMode
      * @param luceneFunction
     * @return the query
     * @throws ParseException
      * @throws IOException 
     */
    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_SOLR4_ID)) {
                return createSolr4IdQuery(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_DENIED)) {
                return createDeniedQuery(queryText);
            } else if (field.equals(FIELD_DENYSET)) {
                return createDenySetQuery(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) {
                    return new TermQuery(new Term(FIELD_TYPE, "_unknown_"));
                }
                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 (isPropertyField(field)) {
                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 (field.equals(FIELD_EXISTS)) {
                return createExistsQuery(queryText, analysisMode, luceneFunction);
            } else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                    namespacePrefixResolver, dictionaryService, 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_PNAME)) {
                return createPNameQuery(queryText);
            } else if (field.equals(FIELD_NPATH)) {
                return createNPathQuery(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());
        } catch (IOException e) {
            throw new ParseException("IO: " + e.getMessage());
        }

    }

    /**
      * @param queryText
      * @return
     */
    private org.apache.lucene.search.Query createNPathQuery(String queryText) {
        return createTermQuery(FIELD_NPATH, queryText);
    }

    /**
      * @param queryText
      * @return
     */
    private Query createPNameQuery(String queryText) {
        return createTermQuery(FIELD_PNAME, queryText);
    }

    /**
      * @param queryText
      * @return
     */
    private Query createSiteQuery(String queryText) {
        if (queryText.equals("_EVERYTHING_")) {
            return createTermQuery(FIELD_ISNODE, "T");
        } else if (queryText.equals("_ALL_SITES_")) {
            BooleanQuery invertedRepositoryQuery = new BooleanQuery();
            invertedRepositoryQuery.add(createTermQuery(FIELD_ISNODE, "T"), Occur.MUST);
            invertedRepositoryQuery.add(createTermQuery(FIELD_SITE, "_REPOSITORY_"), Occur.MUST_NOT);
            return invertedRepositoryQuery;
        } else {
            return createTermQuery(FIELD_SITE, queryText);
        }
    }

    private boolean isPropertyField(String field) {
        if (field.startsWith(PROPERTY_FIELD_PREFIX)) {
            return true;
        }
        int index = field.lastIndexOf('@');
        if (index > -1) {
            PropertyDefinition pDef = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                    namespacePrefixResolver, dictionaryService, field.substring(index + 1));
            if (pDef != null) {
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                        .getIndexedFieldNamesForProperty(pDef.getName());
                for (FieldInstance instance : indexedField.getFields()) {
                    if (instance.getField().equals(field)) {
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    protected Query createTenantQuery(String queryText) throws ParseException {

        if (queryText.length() > 0) {

            return getFieldQueryImplWithIOExceptionWrapped(FIELD_TENANT, queryText, AnalysisMode.DEFAULT,
                    LuceneFunction.FIELD);

        } else {
            return getFieldQueryImplWithIOExceptionWrapped(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
     * @throws ParseException
     */
    protected Query createTagQuery(String tag) throws ParseException {
        return createTermQuery(FIELD_TAG, tag.toLowerCase());
    }

    private Query getFieldQueryImplWithIOExceptionWrapped(String field, String queryText, AnalysisMode analysisMode,
            LuceneFunction luceneFunction) throws ParseException {
        try {
            return getFieldQueryImpl(field, queryText, analysisMode, luceneFunction);
        } catch (IOException e) {
            throw new ParseException("IO: " + e.getMessage());
        }
    }

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

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

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

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

    protected Query createAclTxCommitTimeQuery(String queryText) throws ParseException {
        return getFieldQueryImplWithIOExceptionWrapped(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(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                        namespacePrefixResolver, dictionaryService, 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 = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (pd != null) {
            BooleanQuery query = new BooleanQuery();
            query.add(createTermQuery(FIELD_PROPERTIES, pd.getName().toString()), Occur.MUST);
            query.add(createTermQuery(FIELD_NULLPROPERTIES, pd.getName().toString()), Occur.MUST_NOT);
            ;
            return query;
        } else {
            BooleanQuery query = new BooleanQuery();

            Query presenceQuery = getWildcardQuery(queryText, "*");
            if (presenceQuery != null) {
                query.add(presenceQuery, Occur.MUST);
            }
            return query;
        }
    }

    protected Query createIsNullQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (pd != null) {
            Query q1 = createTermQuery(FIELD_NULLPROPERTIES, pd.getName().toString());
            Query q2 = createTermQuery(FIELD_PROPERTIES, pd.getName().toString());
            BooleanQuery query = new BooleanQuery();
            query.add(q1, Occur.SHOULD);
            BooleanQuery wrapped = new BooleanQuery();
            wrapped.add(q2, Occur.MUST_NOT);
            wrapped.add(createTermQuery(FIELD_ISNODE, "T"), Occur.MUST);
            query.add(wrapped, Occur.SHOULD);
            return query;
        } else {
            BooleanQuery query = new BooleanQuery();
            Query presenceQuery = getWildcardQuery(queryText, "*");
            if (presenceQuery != null) {
                query.add(createIsNodeQuery("T"), Occur.MUST);
                query.add(presenceQuery, Occur.MUST_NOT);
            }
            return query;
        }
    }

    protected Query createIsUnsetQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (pd != null) {
            ClassDefinition containerClass = pd.getContainerClass();
            QName container = containerClass.getName();
            String classType = containerClass.isAspect() ? FIELD_ASPECT : FIELD_TYPE;
            Query typeQuery = getFieldQuery(classType, container.toString(), analysisMode, luceneFunction);

            BooleanQuery query = new BooleanQuery();
            Query presenceQuery = createTermQuery(FIELD_PROPERTIES, pd.getName().toString());
            if (presenceQuery != null) {
                query.add(typeQuery, Occur.MUST);
                query.add(presenceQuery, Occur.MUST_NOT);
            }

            return query;
        } else {
            BooleanQuery query = new BooleanQuery();

            Query presenceQuery = getWildcardQuery(queryText, "*");
            if (presenceQuery != null) {
                query.add(presenceQuery, Occur.MUST_NOT);
            }
            return query;
        }
    }

    protected Query createExistsQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction)
            throws ParseException {
        PropertyDefinition pd = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (pd != null) {
            return createTermQuery(FIELD_PROPERTIES, pd.getName().toString());
        } else {
            BooleanQuery query = new BooleanQuery();

            Query presenceQuery = getWildcardQuery(queryText, "*");
            if (presenceQuery != null) {
                query.add(createIsNodeQuery("T"), Occur.MUST);
                query.add(presenceQuery, Occur.MUST_NOT);
            }
            return query;
        }
    }

    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;
        //        }
        throw new UnsupportedOperationException();
    }

    protected Query createAspectQuery(String queryText, boolean exactOnly) {
        AspectDefinition target = QueryParserUtils.matchAspectDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (target == null) {
            return new TermQuery(new Term(FIELD_ASPECT, "_unknown_"));
        }

        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) throws ParseException {
        TypeDefinition target = QueryParserUtils.matchTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, queryText);
        if (target == null) {
            return new TermQuery(new Term(FIELD_TYPE, "_unknown_"));
        }
        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 Query createInTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImplWithIOExceptionWrapped(FIELD_INTXID, queryText, AnalysisMode.DEFAULT,
                LuceneFunction.FIELD);
    }

    protected Query createInAclTxIdQuery(String queryText) throws ParseException {
        return getFieldQueryImplWithIOExceptionWrapped(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 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);
    }

    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, IOException {
        // make sure the field exists or return a dummy query so we have no error ....ACE-3231
        SchemaField schemaField = schema.getFieldOrNull(field);
        boolean isNumeric = false;
        if (schemaField == null) {
            return new TermQuery(new Term("_dummy_", "_miss_"));
        } else {
            isNumeric = (schemaField.getType().getNumericType() != null);
        }

        // 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("}");
            if (position > 0) {
                String language = queryText.substring(0, position + 1);
                Locale locale = new Locale(queryText.substring(1, position));
                String token = queryText.substring(position + 1);
                boolean found = false;
                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 (isPropertyField(field) && (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 = null;
        ArrayList<org.apache.lucene.analysis.Token> list = new ArrayList<org.apache.lucene.analysis.Token>();
        boolean severalTokensAtSamePosition = false;
        org.apache.lucene.analysis.Token nextToken;
        int positionCount = 0;

        try {
            org.apache.lucene.analysis.Token reusableToken = new org.apache.lucene.analysis.Token();

            source = getAnalyzer().tokenStream(field, new StringReader(toTokenise));
            source.reset();
            while (source.incrementToken()) {
                CharTermAttribute cta = source.getAttribute(CharTermAttribute.class);
                OffsetAttribute offsetAtt = source.getAttribute(OffsetAttribute.class);
                TypeAttribute typeAtt = null;
                if (source.hasAttribute(TypeAttribute.class)) {
                    typeAtt = source.getAttribute(TypeAttribute.class);
                }
                PositionIncrementAttribute posIncAtt = null;
                if (source.hasAttribute(PositionIncrementAttribute.class)) {
                    posIncAtt = source.getAttribute(PositionIncrementAttribute.class);
                }
                nextToken = new Token(cta.buffer(), 0, cta.length(), offsetAtt.startOffset(),
                        offsetAtt.endOffset());
                if (typeAtt != null) {
                    nextToken.setType(typeAtt.type());
                }
                if (posIncAtt != null) {
                    nextToken.setPositionIncrement(posIncAtt.getPositionIncrement());
                }

                list.add(nextToken);
                if (nextToken.getPositionIncrement() != 0)
                    positionCount += nextToken.getPositionIncrement();
                else
                    severalTokensAtSamePosition = true;
            }
        } catch (SolrException e) {
            // MNT-15336
            // Text against a numeric field should fail silently rather then tell you it is not possible.
            if (isNumeric && e.getMessage() != null && e.getMessage().startsWith("Invalid Number:")) {
                // Generate a query that does not match any document - rather than nothing
                return createNoMatchQuery();
            } else {
                throw e;
            }
        } finally {
            try {
                if (source != null) {
                    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 && (list.size() == 0)) {
                        // 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.setType("ALPHANUM");
                        if (requiresMLTokenDuplication) {
                            Locale locale = I18NUtil.parseLocale(localeString);
                            MLTokenDuplicator duplicator = new MLTokenDuplicator(locale,
                                    MLAnalysisMode.EXACT_LANGUAGE);
                            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 = test.toString();
                                int position = termText.indexOf("}");
                                String language = termText.substring(0, position + 1);
                                String token = termText.substring(position + 1);
                                if (index >= test.startOffset() + token.length()) {
                                    test.setEmpty();
                                    test.append(language + token + current);
                                }
                            } else {
                                if (index >= test.startOffset() + test.length()) {
                                    test.setEmpty();
                                    test.append(test.toString() + 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(
                                    pre.toString(), index - pre.length(), index);
                            newToken.setType("ALPHANUM");
                            if (requiresMLTokenDuplication) {
                                Locale locale = I18NUtil.parseLocale(localeString);
                                MLTokenDuplicator duplicator = new MLTokenDuplicator(locale,
                                        MLAnalysisMode.EXACT_LANGUAGE);
                                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(
                                post.toString(), index + 1, index + 1 + post.length());
                        newToken.setType("ALPHANUM");
                        if (requiresMLTokenDuplication) {
                            Locale locale = I18NUtil.parseLocale(localeString);
                            MLTokenDuplicator duplicator = new MLTokenDuplicator(locale,
                                    MLAnalysisMode.EXACT_LANGUAGE);
                            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);
                        }
                    }
                }

            }
        }

        // Put in real position increments as we treat them correctly

        int curentIncrement = -1;
        for (org.apache.lucene.analysis.Token c : list) {
            if (curentIncrement == -1) {
                curentIncrement = c.getPositionIncrement();
            } else if (c.getPositionIncrement() > 0) {
                curentIncrement = c.getPositionIncrement();
            } else {
                c.setPositionIncrement(curentIncrement);
            }
        }

        // Remove small bits already covered in larger fragments 
        list = getNonContained(list);

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

            public int compare(Token o1, Token o2) {
                int dif = o1.startOffset() - o2.startOffset();
                return dif;

            }
        });

        // 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;
        int lastStart = 0;
        for (org.apache.lucene.analysis.Token c : list) {
            if (c.startOffset() == lastStart) {
                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);
            }
            lastStart = c.startOffset();
        }

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

        OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>> allTokenSequencesSet = new OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>>();
        for (LinkedList<org.apache.lucene.analysis.Token> tokensAtPosition : tokensByPosition) {
            OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>> positionalSynonymSequencesSet = new OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>>();

            OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>> newAllTokenSequencesSet = new OrderedHashSet<LinkedList<org.apache.lucene.analysis.Token>>();

            FOR_FIRST_TOKEN_AT_POSITION_ONLY: for (org.apache.lucene.analysis.Token t : tokensAtPosition) {
                org.apache.lucene.analysis.Token replace = new org.apache.lucene.analysis.Token(t, t.startOffset(),
                        t.endOffset());
                replace.setType(t.type());
                replace.setPositionIncrement(t.getPositionIncrement());

                boolean tokenFoundSequence = false;
                for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : allTokenSequencesSet) {
                    LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                    newEntry.addAll(tokenSequence);
                    if ((newEntry.getLast().endOffset() == replace.endOffset())
                            && replace.type().equals(SynonymFilter.TYPE_SYNONYM)) {
                        if ((newEntry.getLast().startOffset() == replace.startOffset())
                                && newEntry.getLast().type().equals(SynonymFilter.TYPE_SYNONYM)) {
                            positionalSynonymSequencesSet.add(tokenSequence);
                            newEntry.add(replace);
                            tokenFoundSequence = true;
                        } else if (newEntry.getLast().type().equals(CommonGramsFilter.GRAM_TYPE)) {
                            if (newEntry.toString().endsWith(replace.toString())) {
                                // already in the gram
                                positionalSynonymSequencesSet.add(tokenSequence);
                                tokenFoundSequence = true;
                            } else {
                                // need to replace the synonym in the current gram
                                tokenFoundSequence = true;
                                StringBuffer old = new StringBuffer(newEntry.getLast().toString());
                                old.replace(replace.startOffset() - newEntry.getLast().startOffset(),
                                        replace.endOffset() - newEntry.getLast().startOffset(), replace.toString());
                                Token newToken = new org.apache.lucene.analysis.Token(old.toString(),
                                        newEntry.getLast().startOffset(), newEntry.getLast().endOffset());
                                newEntry.removeLast();
                                newEntry.add(newToken);
                            }
                        }
                    } else if ((newEntry.getLast().startOffset() < replace.startOffset())
                            && (newEntry.getLast().endOffset() < replace.endOffset())) {
                        if (newEntry.getLast().type().equals(SynonymFilter.TYPE_SYNONYM)
                                && replace.type().equals(SynonymFilter.TYPE_SYNONYM)) {
                            positionalSynonymSequencesSet.add(tokenSequence);
                        }
                        newEntry.add(replace);
                        tokenFoundSequence = true;
                    }
                    newAllTokenSequencesSet.add(newEntry);
                }
                if (false == tokenFoundSequence) {
                    for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : newAllTokenSequencesSet) {
                        LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                        newEntry.addAll(tokenSequence);
                        if ((newEntry.getLast().endOffset() == replace.endOffset())
                                && replace.type().equals(SynonymFilter.TYPE_SYNONYM)) {
                            if ((newEntry.getLast().startOffset() == replace.startOffset())
                                    && newEntry.getLast().type().equals(SynonymFilter.TYPE_SYNONYM)) {
                                positionalSynonymSequencesSet.add(tokenSequence);
                                newEntry.add(replace);
                                tokenFoundSequence = true;
                            } else if (newEntry.getLast().type().equals(CommonGramsFilter.GRAM_TYPE)) {
                                if (newEntry.toString().endsWith(replace.toString())) {
                                    // already in the gram
                                    positionalSynonymSequencesSet.add(tokenSequence);
                                    tokenFoundSequence = true;
                                } else {
                                    // need to replace the synonym in the current gram
                                    tokenFoundSequence = true;
                                    StringBuffer old = new StringBuffer(newEntry.getLast().toString());
                                    old.replace(replace.startOffset() - newEntry.getLast().startOffset(),
                                            replace.endOffset() - newEntry.getLast().startOffset(),
                                            replace.toString());
                                    Token newToken = new org.apache.lucene.analysis.Token(old.toString(),
                                            newEntry.getLast().startOffset(), newEntry.getLast().endOffset());
                                    newEntry.removeLast();
                                    newEntry.add(newToken);
                                    positionalSynonymSequencesSet.add(newEntry);
                                }
                            }
                        } else if ((newEntry.getLast().startOffset() < replace.startOffset())
                                && (newEntry.getLast().endOffset() < replace.endOffset())) {
                            if (newEntry.getLast().type().equals(SynonymFilter.TYPE_SYNONYM)
                                    && replace.type().equals(SynonymFilter.TYPE_SYNONYM)) {
                                positionalSynonymSequencesSet.add(tokenSequence);
                                newEntry.add(replace);
                                tokenFoundSequence = true;
                            }
                        }
                    }
                }
                if (false == tokenFoundSequence) {
                    LinkedList<org.apache.lucene.analysis.Token> newEntry = new LinkedList<org.apache.lucene.analysis.Token>();
                    newEntry.add(replace);
                    newAllTokenSequencesSet.add(newEntry);
                }
                // Limit the max number of permutations we consider
                if (newAllTokenSequencesSet.size() > 64) {
                    break FOR_FIRST_TOKEN_AT_POSITION_ONLY;
                }
            }
            allTokenSequencesSet = newAllTokenSequencesSet;
            allTokenSequencesSet.addAll(positionalSynonymSequencesSet);

        }

        LinkedList<LinkedList<org.apache.lucene.analysis.Token>> allTokenSequences = new LinkedList<LinkedList<org.apache.lucene.analysis.Token>>(
                allTokenSequencesSet);

        // build the unique

        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 = c.toString();
                        int position = termText.indexOf("}");
                        String language = termText.substring(0, position + 1);
                        String token = termText.substring(position + 1);
                        replace = new org.apache.lucene.analysis.Token(language + pre + token,
                                c.startOffset() - pre.length(), c.endOffset());
                        replace.setType(c.type());
                        replace.setPositionIncrement(c.getPositionIncrement());
                    } else {
                        String termText = c.toString();
                        replace = new org.apache.lucene.analysis.Token(pre + termText,
                                c.startOffset() - pre.length(), c.endOffset());
                        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 = c.toString();
                        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 = replace.toString();
                            replace = new org.apache.lucene.analysis.Token(replaceTermText + pre + token,
                                    replace.startOffset(), c.endOffset());
                            replace.setType(replace.type());
                            replace.setPositionIncrement(oldPositionIncrement);
                        } else {
                            int oldPositionIncrement = replace.getPositionIncrement();
                            String replaceTermText = replace.toString();
                            replace = new org.apache.lucene.analysis.Token(replaceTermText + pre + termText,
                                    replace.startOffset(), c.endOffset());
                            replace.setType(replace.type());
                            replace.setPositionIncrement(oldPositionIncrement);
                        }
                    } else {
                        String termText = c.toString();
                        if (requiresMLTokenDuplication) {
                            int position = termText.indexOf("}");
                            String language = termText.substring(0, position + 1);
                            String token = termText.substring(position + 1);
                            String replaceTermText = replace.toString();
                            org.apache.lucene.analysis.Token last = new org.apache.lucene.analysis.Token(
                                    replaceTermText + post, replace.startOffset(),
                                    replace.endOffset() + post.length());
                            last.setType(replace.type());
                            last.setPositionIncrement(replace.getPositionIncrement());
                            fixedTokenSequence.add(last);
                            replace = new org.apache.lucene.analysis.Token(language + pre + token,
                                    c.startOffset() - pre.length(), c.endOffset());
                            replace.setType(c.type());
                            replace.setPositionIncrement(c.getPositionIncrement());
                        } else {
                            String replaceTermText = replace.toString();
                            org.apache.lucene.analysis.Token last = new org.apache.lucene.analysis.Token(
                                    replaceTermText + post, replace.startOffset(),
                                    replace.endOffset() + post.length());
                            last.setType(replace.type());
                            last.setPositionIncrement(replace.getPositionIncrement());
                            fixedTokenSequence.add(last);
                            replace = new org.apache.lucene.analysis.Token(pre + termText,
                                    c.startOffset() - pre.length(), c.endOffset());
                            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 = replace.toString();
                replace = new org.apache.lucene.analysis.Token(replaceTermText + post, replace.startOffset(),
                        replace.endOffset() + post.length());
                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 = currentToken.toString();
                currentToken.setEmpty();
                currentToken.append(localePrefix + termText);
            }
        }

        SchemaField sf = schema.getField(field);
        TokenizerChain tokenizerChain = (sf.getType().getQueryAnalyzer() instanceof TokenizerChain)
                ? ((TokenizerChain) sf.getType().getQueryAnalyzer())
                : null;
        boolean isShingled = false;
        if (tokenizerChain != null) {
            for (TokenFilterFactory factory : tokenizerChain.getTokenFilterFactories()) {
                if (factory instanceof ShingleFilterFactory) {
                    isShingled = true;
                    break;
                }
            }
        }
        AlfrescoAnalyzerWrapper analyzerWrapper = (sf.getType()
                .getQueryAnalyzer() instanceof AlfrescoAnalyzerWrapper)
                        ? ((AlfrescoAnalyzerWrapper) sf.getType().getQueryAnalyzer())
                        : null;
        if (analyzerWrapper != null) {
            // assume if there are no term positions it is shingled ....
            isShingled = true;
        }

        boolean forceConjuncion = rerankPhase == RerankPhase.QUERY_PHASE;

        if (list.size() == 0)
            return null;
        else if (list.size() == 1) {
            nextToken = list.get(0);
            String termText = nextToken.toString();
            if (!isNumeric && (termText.contains("*") || termText.contains("?"))) {
                return newWildcardQuery(new Term(field, 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 = nextToken.toString();
                        if (termText.contains("*") || termText.contains("?")) {
                            currentQuery = newWildcardQuery(new Term(field, termText));
                        } else {
                            currentQuery = newTermQuery(new Term(field, termText));
                        }
                        q.add(currentQuery, BooleanClause.Occur.SHOULD);
                    }
                    return q;
                } else if (forceConjuncion) {
                    BooleanQuery or = new BooleanQuery();

                    for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : fixedTokenSequences) {
                        BooleanQuery and = new BooleanQuery();
                        for (int i = 0; i < tokenSequence.size(); i++) {
                            nextToken = (org.apache.lucene.analysis.Token) tokenSequence.get(i);
                            String termText = nextToken.toString();

                            Term term = new Term(field, termText);
                            if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(
                                        term);
                                and.add(wildQuery, Occur.MUST);
                            } else {
                                TermQuery termQuery = new TermQuery(term);
                                and.add(termQuery, Occur.MUST);
                            }
                        }
                        if (and.clauses().size() > 0) {
                            or.add(and, Occur.SHOULD);
                        }
                    }
                    return or;
                }
                // shingle
                else if (sf.omitPositions() && isShingled) {

                    ArrayList<org.apache.lucene.analysis.Token> nonContained = getNonContained(list);
                    Query currentQuery;

                    BooleanQuery weakPhrase = new BooleanQuery();
                    for (org.apache.lucene.analysis.Token shingleToken : nonContained) {
                        String termText = shingleToken.toString();
                        Term term = new Term(field, termText);

                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            currentQuery = new org.apache.lucene.search.WildcardQuery(term);
                        } else {
                            currentQuery = new TermQuery(term);
                        }
                        weakPhrase.add(currentQuery, Occur.MUST);
                    }

                    return weakPhrase;

                }
                // 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 = nextToken.toString();

                        Term term = new Term(field, termText);
                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            throw new IllegalStateException("Wildcards are not allowed in multi phrase anymore");
                        } 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 {

                    return generateSpanOrQuery(field, fixedTokenSequences);

                }
            } else {
                if (forceConjuncion) {
                    BooleanQuery or = new BooleanQuery();

                    for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : fixedTokenSequences) {
                        BooleanQuery and = new BooleanQuery();
                        for (int i = 0; i < tokenSequence.size(); i++) {
                            nextToken = (org.apache.lucene.analysis.Token) tokenSequence.get(i);
                            String termText = nextToken.toString();

                            Term term = new Term(field, termText);
                            if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(
                                        term);
                                and.add(wildQuery, Occur.MUST);
                            } else {
                                TermQuery termQuery = new TermQuery(term);
                                and.add(termQuery, Occur.MUST);
                            }
                        }
                        if (and.clauses().size() > 0) {
                            or.add(and, Occur.SHOULD);
                        }
                    }
                    return or;
                } else {
                    SpanQuery spanQuery = null;
                    SpanOrQuery atSamePosition = new SpanOrQuery();
                    int gap = 0;
                    for (int i = 0; i < list.size(); i++) {
                        nextToken = list.get(i);
                        String termText = nextToken.toString();
                        Term term = new Term(field, termText);
                        if (getEnablePositionIncrements()) {
                            SpanQuery nextSpanQuery;
                            if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(
                                        term);
                                SpanMultiTermQueryWrapper wrapper = new SpanMultiTermQueryWrapper<>(wildQuery);
                                wrapper.setRewriteMethod(
                                        new TopTermsSpanBooleanQueryRewrite(topTermSpanRewriteLimit));
                                nextSpanQuery = wrapper;
                            } else {
                                nextSpanQuery = new SpanTermQuery(term);
                            }
                            if (gap == 0) {
                                atSamePosition.addClause(nextSpanQuery);
                            } else {
                                if (atSamePosition.getClauses().length == 0) {
                                    if (spanQuery == null) {
                                        spanQuery = nextSpanQuery;
                                    } else {
                                        spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, nextSpanQuery },
                                                (gap - 1) + internalSlop, internalSlop < 2);
                                    }
                                    atSamePosition = new SpanOrQuery();
                                } else if (atSamePosition.getClauses().length == 1) {
                                    if (spanQuery == null) {
                                        spanQuery = atSamePosition.getClauses()[0];
                                    } else {
                                        spanQuery = new SpanNearQuery(
                                                new SpanQuery[] { spanQuery, atSamePosition.getClauses()[0] },
                                                (gap - 1) + internalSlop, internalSlop < 2);
                                    }
                                    atSamePosition = new SpanOrQuery();
                                    atSamePosition.addClause(nextSpanQuery);
                                } else {
                                    if (spanQuery == null) {
                                        spanQuery = atSamePosition;
                                    } else {
                                        spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, atSamePosition },
                                                (gap - 1) + internalSlop, internalSlop < 2);
                                    }
                                    atSamePosition = new SpanOrQuery();
                                    atSamePosition.addClause(nextSpanQuery);
                                }
                            }
                            gap = nextToken.getPositionIncrement();
                        } else {
                            SpanQuery nextSpanQuery;
                            if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                                org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(
                                        term);
                                SpanMultiTermQueryWrapper wrapper = new SpanMultiTermQueryWrapper<>(wildQuery);
                                wrapper.setRewriteMethod(
                                        new TopTermsSpanBooleanQueryRewrite(topTermSpanRewriteLimit));
                                nextSpanQuery = wrapper;
                            } else {
                                nextSpanQuery = new SpanTermQuery(term);
                            }
                            if (spanQuery == null) {
                                spanQuery = new SpanOrQuery();
                                ((SpanOrQuery) spanQuery).addClause(nextSpanQuery);
                            } else {
                                ((SpanOrQuery) spanQuery).addClause(nextSpanQuery);
                            }
                        }
                    }
                    if (atSamePosition.getClauses().length == 0) {
                        return spanQuery;
                    } else if (atSamePosition.getClauses().length == 1) {
                        if (spanQuery == null) {
                            spanQuery = atSamePosition.getClauses()[0];
                        } else {
                            spanQuery = new SpanNearQuery(
                                    new SpanQuery[] { spanQuery, atSamePosition.getClauses()[0] },
                                    (gap - 1) + internalSlop, internalSlop < 2);
                        }
                        return spanQuery;
                    } else {
                        if (spanQuery == null) {
                            spanQuery = atSamePosition;
                        } else {
                            spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, atSamePosition },
                                    (gap - 1) + internalSlop, internalSlop < 2);
                        }
                        return spanQuery;
                    }
                }
            }
        }
    }

    /**
     * @param list
     * @return
     */
    private ArrayList<Token> getNonContained(ArrayList<Token> list) {
        ArrayList<Token> nonContained = new ArrayList<Token>();
        NEXT_CANDIDATE: for (Token candidate : list) {
            NEXT_TEST: for (Token test : list) {
                if (candidate == test) {
                    continue NEXT_TEST;
                } else if ((test.startOffset() == candidate.startOffset())
                        && (candidate.endOffset() == test.endOffset())
                        && (test.toString().equals(candidate.toString()))) {
                    continue NEXT_TEST;
                } else if ((test.startOffset() <= candidate.startOffset())
                        && (candidate.endOffset() <= test.endOffset())
                        && (test.toString().contains(candidate.toString()))) {
                    continue NEXT_CANDIDATE;
                }
            }
            nonContained.add(candidate);
        }
        return nonContained;
    }

    /**
      * @param field
      * @return Query
      */
    protected SpanOrQuery generateSpanOrQuery(String field,
            LinkedList<LinkedList<org.apache.lucene.analysis.Token>> fixedTokenSequences) {
        org.apache.lucene.analysis.Token nextToken;
        SpanOrQuery spanOr = new SpanOrQuery();

        for (LinkedList<org.apache.lucene.analysis.Token> tokenSequence : fixedTokenSequences) {
            int gap = 1;
            SpanQuery spanQuery = null;
            SpanOrQuery atSamePosition = new SpanOrQuery();

            // MNT-13239: if all tokens's positions are incremented by one then create flat nearQuery 
            if (getEnablePositionIncrements() && isAllTokensSequentiallyShifted(tokenSequence)) {
                // there will be no tokens at same position
                List<SpanQuery> wildWrappedList = new ArrayList<SpanQuery>(tokenSequence.size());
                for (org.apache.lucene.analysis.Token token : tokenSequence) {
                    String termText = token.toString();
                    Term term = new Term(field, termText);
                    SpanQuery nextSpanQuery = wrapWildcardTerms(term);
                    wildWrappedList.add(nextSpanQuery);
                }
                spanQuery = new SpanNearQuery(wildWrappedList.toArray(new SpanQuery[wildWrappedList.size()]), 0,
                        true);
            } else {
                for (int i = 0; i < tokenSequence.size(); i++) {
                    nextToken = (org.apache.lucene.analysis.Token) tokenSequence.get(i);
                    String termText = nextToken.toString();

                    Term term = new Term(field, termText);

                    if (getEnablePositionIncrements()) {
                        SpanQuery nextSpanQuery = wrapWildcardTerms(term);
                        if (gap == 0) {
                            atSamePosition.addClause(nextSpanQuery);
                        } else {
                            if (atSamePosition.getClauses().length == 0) {
                                if (spanQuery == null) {
                                    spanQuery = nextSpanQuery;
                                } else {
                                    spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, nextSpanQuery },
                                            (gap - 1) + internalSlop, internalSlop < 2);
                                }
                                atSamePosition = new SpanOrQuery();
                            } else if (atSamePosition.getClauses().length == 1) {
                                if (spanQuery == null) {
                                    spanQuery = atSamePosition.getClauses()[0];
                                } else {
                                    spanQuery = new SpanNearQuery(
                                            new SpanQuery[] { spanQuery, atSamePosition.getClauses()[0] },
                                            (gap - 1) + internalSlop, internalSlop < 2);
                                }
                                atSamePosition = new SpanOrQuery();
                                atSamePosition.addClause(nextSpanQuery);
                            } else {
                                if (spanQuery == null) {
                                    spanQuery = atSamePosition;
                                } else {
                                    spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, atSamePosition },
                                            (gap - 1) + internalSlop, internalSlop < 2);
                                }
                                atSamePosition = new SpanOrQuery();
                                atSamePosition.addClause(nextSpanQuery);
                            }
                        }
                        gap = nextToken.getPositionIncrement();

                    } else {
                        SpanQuery nextSpanQuery;
                        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                            org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(
                                    term);
                            SpanMultiTermQueryWrapper wrapper = new SpanMultiTermQueryWrapper<>(wildQuery);
                            wrapper.setRewriteMethod(new TopTermsSpanBooleanQueryRewrite(topTermSpanRewriteLimit));
                            nextSpanQuery = wrapper;
                        } else {
                            nextSpanQuery = new SpanTermQuery(term);
                        }
                        if (spanQuery == null) {
                            spanQuery = new SpanOrQuery();
                            ((SpanOrQuery) spanQuery).addClause(nextSpanQuery);
                        } else {
                            ((SpanOrQuery) spanQuery).addClause(nextSpanQuery);
                        }
                    }
                }
            }

            if (atSamePosition.getClauses().length == 0) {
                spanOr.addClause(spanQuery);
            } else if (atSamePosition.getClauses().length == 1) {
                if (spanQuery == null) {
                    spanQuery = atSamePosition.getClauses()[0];
                } else {
                    spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, atSamePosition.getClauses()[0] },
                            (gap - 1) + internalSlop, internalSlop < 2);
                }
                atSamePosition = new SpanOrQuery();
                spanOr.addClause(spanQuery);
            } else {
                if (spanQuery == null) {
                    spanQuery = atSamePosition;
                } else {
                    spanQuery = new SpanNearQuery(new SpanQuery[] { spanQuery, atSamePosition },
                            (gap - 1) + internalSlop, internalSlop < 2);
                }
                atSamePosition = new SpanOrQuery();
                spanOr.addClause(spanQuery);
            }
        }
        return spanOr;
    }

    private SpanQuery wrapWildcardTerms(org.apache.lucene.index.Term term) {
        String termText = term.text();
        SpanQuery nextSpanQuery;
        if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
            org.apache.lucene.search.WildcardQuery wildQuery = new org.apache.lucene.search.WildcardQuery(term);
            SpanMultiTermQueryWrapper wrapper = new SpanMultiTermQueryWrapper<>(wildQuery);
            wrapper.setRewriteMethod(new TopTermsSpanBooleanQueryRewrite(topTermSpanRewriteLimit));
            nextSpanQuery = wrapper;
        } else {
            nextSpanQuery = new SpanTermQuery(term);
        }
        return nextSpanQuery;
    }

    protected boolean isAllTokensSequentiallyShifted(List<Token> tokenSequence) {
        if (0 == internalSlop) {
            // check if all tokens have gap = 1
            for (org.apache.lucene.analysis.Token tokenToCheck : tokenSequence) {
                if (1 != tokenToCheck.getPositionIncrement()) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * @param field String
     * @param queryText
     * @param mpq
     * @return
     * @throws ParseException 
    */
    private void checkTermCount(String field, String queryText, MultiPhraseQuery mpq) throws ParseException {
        if (exceedsTermCount(mpq)) {
            throw new ParseException("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) {
        LinkedList<Token> first = fixedTokenSequences.get(0);
        for (int i = 0; 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;
                }
                String termText = fromCurrent.toString();
                if ((termText != null) && (termText.contains("*") || termText.contains("?"))) {
                    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]);
        }

    }

    // TODO - resort to span if we have to ...
    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());
        //        }
        throw new UnsupportedOperationException();
    }

    /**
     * @exception ParseException
     *                throw in overridden method to disallow
     */

    protected Query getRangeQuery(String field, String part1, String part2, boolean startInclusive,
            boolean endInclusive) throws ParseException {
        return getRangeQuery(field, part1, part2, startInclusive, endInclusive, AnalysisMode.IDENTIFIER,
                LuceneFunction.FIELD);
    }

    protected Query getRangeQuery(String field, String part1, String part2, boolean inclusive)
            throws ParseException {
        return getRangeQuery(field, part1, part2, inclusive, inclusive, AnalysisMode.IDENTIFIER,
                LuceneFunction.FIELD);
    }

    /**
      * @param field
      * @param part1
      * @param part2
      * @param includeLower
      * @param includeUpper
      * @param analysisMode
      * @param 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;
            }

        } else if (field.equals(FIELD_CASCADETX)) {
            SchemaField sf = schema.getField(FIELD_CASCADETX);
            if (sf != null) {
                String start = null;
                try {
                    analyzeMultitermTerm(FIELD_CASCADETX, part1, null);
                    start = part1;
                } catch (Exception e) {

                }
                String end = null;
                try {
                    analyzeMultitermTerm(FIELD_CASCADETX, part2, null);
                    end = part2;
                } catch (Exception e) {

                }
                return sf.getType().getRangeQuery(null, sf, start, end, includeLower, includeUpper);
            } else {
                throw new UnsupportedOperationException();
            }
        }
        // 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 (isPropertyField(field)) {
            Pair<String, String> fieldNameAndEnding = QueryParserUtils.extractFieldNameAndEnding(field);

            String expandedFieldName = null;
            QName propertyQName;
            PropertyDefinition propertyDef = QueryParserUtils.matchPropertyDefinition(
                    searchParameters.getNamespace(), namespacePrefixResolver, dictionaryService,
                    fieldNameAndEnding.getFirst());
            IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
            if (propertyDef != null) {
                tokenisationMode = propertyDef.getIndexTokenisationMode();
                if (tokenisationMode == null) {
                    tokenisationMode = IndexTokenisationMode.TRUE;
                }
                propertyQName = propertyDef.getName();
            } else {
                expandedFieldName = expandAttributeFieldName(field);
                propertyQName = QName.createQName(fieldNameAndEnding.getFirst());
            }

            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();
                        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(expandedFieldName, propertyDef, part1,
                                    part2, includeLower, includeUpper, luceneFunction, booleanQuery, locale,
                                    tokenisationMode);
                        }
                        return booleanQuery;
                    } else {
                        throw new UnsupportedOperationException("Lucene Function");
                    }
                }

                if (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) {
                    return buildTextMLTextOrContentRange(field, part1, part2, includeLower, includeUpper,
                            analysisMode, expandedFieldName, propertyDef, tokenisationMode);
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) {
                    String solrField = null;
                    switch (fieldNameAndEnding.getSecond()) {
                    case FIELD_SIZE_SUFFIX:
                        solrField = AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyDef.getName(), ContentFieldType.SIZE, FieldUse.ID)
                                .getFields().get(0).getField();
                        break;
                    case FIELD_MIMETYPE_SUFFIX:
                        solrField = AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyDef.getName(), ContentFieldType.MIMETYPE, FieldUse.ID)
                                .getFields().get(0).getField();
                        break;
                    case FIELD_ENCODING_SUFFIX:
                        solrField = AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyDef.getName(), ContentFieldType.ENCODING, FieldUse.ID)
                                .getFields().get(0).getField();
                        break;
                    case FIELD_LOCALE_SUFFIX:
                        solrField = AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyDef.getName(), ContentFieldType.LOCALE, FieldUse.ID)
                                .getFields().get(0).getField();
                        break;
                    }
                    if (solrField != null) {
                        String start = null;
                        try {
                            analyzeMultitermTerm(solrField, part1, null);
                            start = part1;
                        } catch (Exception e) {

                        }
                        String end = null;
                        try {
                            analyzeMultitermTerm(solrField, part2, null);
                            end = part2;
                        } catch (Exception e) {

                        }

                        SchemaField sf = schema.getField(solrField);
                        return sf.getType().getRangeQuery(null, sf, start, end, includeLower, includeUpper);
                    } else {
                        return buildTextMLTextOrContentRange(field, part1, part2, includeLower, includeUpper,
                                analysisMode, expandedFieldName, propertyDef, tokenisationMode);
                    }
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)) {
                    return buildTextMLTextOrContentRange(field, part1, part2, includeLower, includeUpper,
                            analysisMode, expandedFieldName, propertyDef, tokenisationMode);
                } else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)
                        || propertyDef.getDataType().getName().equals(DataTypeDefinition.DATE)) {
                    Pair<Date, Integer> dateAndResolution1 = parseDateString(part1);
                    Pair<Date, Integer> dateAndResolution2 = parseDateString(part2);

                    BooleanQuery bQuery = new BooleanQuery();
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(propertyDef.getName(), null, FieldUse.ID);
                    for (FieldInstance instance : indexedField.getFields()) {
                        String start = dateAndResolution1 == null ? part1
                                : (includeLower ? getDateStart(dateAndResolution1)
                                        : getDateEnd(dateAndResolution1));
                        String end = dateAndResolution2 == null ? part2
                                : (includeUpper ? getDateEnd(dateAndResolution2)
                                        : getDateStart(dateAndResolution2));
                        if (start.equals("*")) {
                            start = null;
                        }
                        if (end.equals("*")) {
                            end = null;
                        }

                        SchemaField sf = schema.getField(instance.getField());

                        Query query = sf.getType().getRangeQuery(null, sf, start, end, includeLower, includeUpper);
                        if (query != null) {
                            bQuery.add(query, Occur.SHOULD);
                        }
                    }
                    return bQuery;
                } else {
                    String solrField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(propertyDef.getName(), null, FieldUse.ID).getFields().get(0)
                            .getField();

                    String start = null;
                    try {
                        analyzeMultitermTerm(solrField, part1, null);
                        start = part1;
                    } catch (Exception e) {

                    }
                    String end = null;
                    try {
                        analyzeMultitermTerm(solrField, part2, null);
                        end = part2;
                    } catch (Exception e) {

                    }

                    SchemaField sf = schema.getField(solrField);
                    return sf.getType().getRangeQuery(null, sf, start, end, includeLower, includeUpper);
                }
            } else {
                throw new UnsupportedOperationException();
            }
        } 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
        // FIELD_EXISTS uses the default
        else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(), namespacePrefixResolver,
                dictionaryService, field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                            namespacePrefixResolver, dictionaryService, 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
            throw new UnsupportedOperationException();
        }
    }

    /**
     * @param field
     * @param part1
     * @param part2
     * @param includeLower
     * @param includeUpper
     * @param analysisMode
     * @param expandedFieldName
     * @param propertyDef
     * @param tokenisationMode
     * @return
     * @throws ParseException
     */
    private Query buildTextMLTextOrContentRange(String field, String part1, String part2, boolean includeLower,
            boolean includeUpper, AnalysisMode analysisMode, String expandedFieldName,
            PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode) throws ParseException {
        BooleanQuery booleanQuery = new BooleanQuery();
        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)) {

            try {
                addTextRange(field, propertyDef, part1, part2, includeLower, includeUpper, analysisMode,
                        expandedFieldName, propertyDef, tokenisationMode, booleanQuery, locale);
            } catch (IOException e) {
                throw new ParseException(
                        "Failed to tokenise: <" + part1 + "> or <" + part2 + ">   for " + propertyDef.getName());
            }

        }
        return booleanQuery;
    }

    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 {
        try (TokenStream source = getAnalyzer().tokenStream(field, new StringReader(value))) {
            org.apache.lucene.analysis.Token reusableToken = new org.apache.lucene.analysis.Token();
            org.apache.lucene.analysis.Token nextToken;
            String tokenised = null;

            while (source.incrementToken()) {
                CharTermAttribute cta = source.getAttribute(CharTermAttribute.class);
                OffsetAttribute offsetAtt = source.getAttribute(OffsetAttribute.class);
                TypeAttribute typeAtt = null;
                if (source.hasAttribute(TypeAttribute.class)) {
                    typeAtt = source.getAttribute(TypeAttribute.class);
                }
                PositionIncrementAttribute posIncAtt = null;
                if (source.hasAttribute(PositionIncrementAttribute.class)) {
                    posIncAtt = source.getAttribute(PositionIncrementAttribute.class);
                }
                Token token = new Token(cta.buffer(), 0, cta.length(), offsetAtt.startOffset(),
                        offsetAtt.endOffset());
                if (typeAtt != null) {
                    token.setType(typeAtt.type());
                }
                if (posIncAtt != null) {
                    token.setPositionIncrement(posIncAtt.getPositionIncrement());
                }

                tokenised = token.toString();
            }
            return tokenised;
        } catch (IOException e) {
            throw new ParseException("IO" + e.getMessage());
        }

    }

    @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)) {
            boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
            try {
                setLowercaseExpandedTerms(false);
                return super.getPrefixQuery(FIELD_LID, termStr);
            } finally {
                setLowercaseExpandedTerms(lowercaseExpandedTerms);
            }
        } else if (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 (isPropertyField(field)) {
            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 (field.equals(FIELD_EXISTS)) {
            throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_EXISTS);
        } else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                            namespacePrefixResolver, dictionaryService, 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 super.getPrefixQuery(field, termStr);
        } else if (field.equals(FIELD_SITE)) {
            return super.getPrefixQuery(field, termStr);
        } else if (field.equals(FIELD_NPATH)) {
            return super.getPrefixQuery(field, termStr);
        } else if (field.equals(FIELD_PNAME)) {
            return super.getPrefixQuery(field, termStr);
        } 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)) {
            boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
            try {
                setLowercaseExpandedTerms(false);
                return super.getWildcardQuery(FIELD_LID, termStr);
            } finally {
                setLowercaseExpandedTerms(lowercaseExpandedTerms);
            }
        } else if (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 (isPropertyField(field)) {
            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 (field.equals(FIELD_EXISTS)) {
            throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_EXISTS);
        } else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                            namespacePrefixResolver, dictionaryService, 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 super.getWildcardQuery(field, termStr);
        } else if (field.equals(FIELD_SITE)) {
            return super.getWildcardQuery(field, termStr);
        } else if (field.equals(FIELD_PNAME)) {
            return super.getWildcardQuery(field, termStr);
        } else if (field.equals(FIELD_NPATH)) {
            return super.getWildcardQuery(field, termStr);
        } 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 (isPropertyField(field)) {
            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 (field.equals(FIELD_EXISTS)) {
            throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_EXISTS);
        } else if (QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, field) != null) {
            Collection<QName> contentAttributes = dictionaryService
                    .getAllProperties(QueryParserUtils.matchDataTypeDefinition(searchParameters.getNamespace(),
                            namespacePrefixResolver, dictionaryService, 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)) {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        } else if (field.equals(FIELD_SITE)) {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        } else if (field.equals(FIELD_PNAME)) {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        } else if (field.equals(FIELD_NPATH)) {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        } else {
            return super.getFuzzyQuery(field, termStr, minSimilarity);
        }
    }

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

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

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

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

    /**
      * @param field
      * @param termStr
     * @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().equals("*")) {
            BooleanQuery bQuery = new BooleanQuery();
            bQuery.add(createTermQuery(FIELD_FIELDS, t.field()), Occur.SHOULD);
            bQuery.add(createTermQuery(FIELD_PROPERTIES, t.field()), Occur.SHOULD);
            return bQuery;
        } else if (t.text().contains("\\")) {
            String regexp = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE,
                    SearchLanguageConversion.DEF_REGEX, t.text());
            return new RegexpQuery(new Term(t.field(), regexp));
        } else {
            org.apache.lucene.search.WildcardQuery query = new org.apache.lucene.search.WildcardQuery(t);
            query.setRewriteMethod(new MultiTermQuery.TopTermsScoringBooleanQueryRewrite(topTermSpanRewriteLimit));
            return query;
        }
    }

    /*
     * (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 RegexpQuery(new Term(prefix.field(), regexp));
        } else {
            return super.newPrefixQuery(prefix);
        }

    }

    public interface SubQuery {
        /**
           * @param field
           * @param queryText
           * @param analysisMode
           * @param 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)
            throws ParseException {
        String propertyFieldName = field.substring(1);
        String expandedFieldName = null;

        PropertyDefinition propertyDef = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, propertyFieldName);
        IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
        if (propertyDef != null) {
            tokenisationMode = propertyDef.getIndexTokenisationMode();
            if (tokenisationMode == null) {
                tokenisationMode = IndexTokenisationMode.TRUE;
            }
            QName propertyQName = propertyDef.getName();
        } 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();
            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, propertyDef, first, last, slop, inOrder, expandedFieldName, propertyDef,
                        tokenisationMode, booleanQuery, locale);
            }
            return booleanQuery;
        }
        // Content
        else if ((propertyDef != null)
                && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {

            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, propertyDef, first, last, slop, inOrder, expandedFieldName,
                    expandedLocales);

        } else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT))) {
            BooleanQuery booleanQuery = new BooleanQuery();
            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, propertyDef, first, last, slop, inOrder, expandedFieldName,
                        tokenisationMode, booleanQuery, locale);

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

    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

        Pair<String, String> fieldNameAndEnding = QueryParserUtils.extractFieldNameAndEnding(field);

        String expandedFieldName = null;
        QName propertyQName;
        PropertyDefinition propertyDef = QueryParserUtils.matchPropertyDefinition(searchParameters.getNamespace(),
                namespacePrefixResolver, dictionaryService, fieldNameAndEnding.getFirst());
        IndexTokenisationMode tokenisationMode = IndexTokenisationMode.TRUE;
        if (propertyDef != null) {
            tokenisationMode = propertyDef.getIndexTokenisationMode();
            if (tokenisationMode == null) {
                tokenisationMode = IndexTokenisationMode.TRUE;
            }
            propertyQName = propertyDef.getName();
        } else {
            expandedFieldName = expandAttributeFieldName(field);
            propertyQName = QName.createQName(fieldNameAndEnding.getFirst());
        }

        if (isAllStar(queryText)) {
            return createTermQuery(FIELD_PROPERTIES, propertyQName.toString());
        }

        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, fieldNameAndEnding.getSecond(), propertyQName,
                        propertyDef, tokenisationMode, queryText, luceneFunction);
            }
        }

        // Mime type
        if (fieldNameAndEnding.getSecond().equals(FIELD_MIMETYPE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyQName, ContentFieldType.MIMETYPE, FieldUse.ID).getFields()
                        .get(0).getField(), queryText, analysisMode, luceneFunction);
            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_SIZE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyQName, ContentFieldType.SIZE, FieldUse.ID).getFields().get(0)
                        .getField(), queryText, analysisMode, luceneFunction);

            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_LOCALE_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyQName, ContentFieldType.LOCALE, FieldUse.ID).getFields().get(0)
                        .getField(), queryText, analysisMode, luceneFunction);

            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_ENCODING_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyQName, ContentFieldType.ENCODING, FieldUse.ID).getFields()
                        .get(0).getField(), queryText, analysisMode, luceneFunction);

            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_TRANSFORMATION_STATUS_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(
                        AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyQName, ContentFieldType.TRANSFORMATION_STATUS,
                                        FieldUse.ID)
                                .getFields().get(0).getField(),
                        queryText, analysisMode, luceneFunction);

            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_TRANSFORMATION_TIME_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(
                        AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyQName, ContentFieldType.TRANSFORMATION_TIME,
                                        FieldUse.ID)
                                .getFields().get(0).getField(),
                        queryText, analysisMode, luceneFunction);

            }

        } else if (fieldNameAndEnding.getSecond().equals(FIELD_TRANSFORMATION_EXCEPTION_SUFFIX)) {
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))) {
                return subQueryBuilder.getQuery(
                        AlfrescoSolrDataModel.getInstance()
                                .getQueryableFields(propertyQName, ContentFieldType.TRANSFORMATION_EXCEPTION,
                                        FieldUse.ID)
                                .getFields().get(0).getField(),
                        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();
            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, propertyDef, queryText, subQueryBuilder, analysisMode,
                        luceneFunction, expandedFieldName, propertyDef, tokenisationMode, booleanQuery, 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

            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(propertyDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                    expandedFieldName, expandedLocales);

        } 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();
            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)) {
                Locale fixedLocale = locale;
                if (fixedLocale.getLanguage().equals("*")) {
                    fixedLocale = new Locale("??");
                }
                addTextAttributeQuery(field, propertyDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                        expandedFieldName, tokenisationMode, booleanQuery, fixedLocale);

            }
            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");
                }
            }

            // expand date for loose date parsing 
            if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)
                    || propertyDef.getDataType().getName().equals(DataTypeDefinition.DATE))) {
                Pair<Date, Integer> dateAndResolution = parseDateString(queryText);

                BooleanQuery bQuery = new BooleanQuery();
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyDef.getName(), null, FieldUse.FTS);
                for (FieldInstance instance : indexedField.getFields()) {
                    if (dateAndResolution != null) {
                        Query query = newRangeQuery(instance.getField(), getDateStart(dateAndResolution),
                                getDateEnd(dateAndResolution), true, true);
                        if (query != null) {
                            bQuery.add(query, Occur.SHOULD);
                        }
                    } else {
                        Query query = subQueryBuilder.getQuery(instance.getField(), queryText, AnalysisMode.DEFAULT,
                                luceneFunction);
                        if (query != null) {
                            bQuery.add(query, Occur.SHOULD);
                        }
                    }
                }
                if (bQuery.getClauses().length > 0) {
                    return bQuery;
                } else {
                    return createNoMatchQuery();
                }
            }

            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
            if (propertyDef != null) {
                BooleanQuery bQuery = new BooleanQuery();
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                        .getQueryableFields(propertyDef.getName(), null, FieldUse.FTS);
                for (FieldInstance instance : indexedField.getFields()) {
                    Query query = subQueryBuilder.getQuery(instance.getField(), queryText, AnalysisMode.DEFAULT,
                            luceneFunction);
                    if (query != null) {
                        bQuery.add(query, Occur.SHOULD);
                    }
                }
                if (bQuery.getClauses().length > 0) {
                    return bQuery;
                } else {
                    return createNoMatchQuery();
                }
            } else {
                Query query = subQueryBuilder.getQuery(expandedFieldName, queryText, AnalysisMode.DEFAULT,
                        luceneFunction);
                if (query != null) {
                    return query;
                } else {
                    return createNoMatchQuery();
                }
            }
        }
    }

    /**
      * @param queryText
      * @return
     */
    private boolean isAllStar(String queryText) {
        if ((queryText == null) || (queryText.length() == 0)) {
            return false;
        }
        for (char c : queryText.toCharArray()) {
            if (c != '*') {
                return false;
            }
        }
        return true;

    }

    /**
      * @param dateAndResolution
      * @return
     */
    private String getDateEnd(Pair<Date, Integer> dateAndResolution) {
        Calendar cal = Calendar.getInstance(I18NUtil.getLocale());
        cal.setTime(dateAndResolution.getFirst());
        switch (dateAndResolution.getSecond()) {
        case Calendar.YEAR:
            cal.set(Calendar.MONTH, cal.getActualMaximum(Calendar.MONTH));
        case Calendar.MONTH:
            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        case Calendar.DAY_OF_MONTH:
            cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
        case Calendar.HOUR_OF_DAY:
            cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        case Calendar.MINUTE:
            cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        case Calendar.SECOND:
            cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));
        case Calendar.MILLISECOND:
        default:
        }
        SimpleDateFormat formatter = CachingDateFormat.getSolrDatetimeFormat();
        formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        return formatter.format(cal.getTime());
    }

    /**
      * @param dateAndResolution
      * @return
     */
    private String getDateStart(Pair<Date, Integer> dateAndResolution) {
        Calendar cal = Calendar.getInstance(I18NUtil.getLocale());
        cal.setTime(dateAndResolution.getFirst());
        switch (dateAndResolution.getSecond()) {
        case Calendar.YEAR:
            cal.set(Calendar.MONTH, cal.getActualMinimum(Calendar.MONTH));
        case Calendar.MONTH:
            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
        case Calendar.DAY_OF_MONTH:
            cal.set(Calendar.HOUR_OF_DAY, cal.getActualMinimum(Calendar.HOUR_OF_DAY));
        case Calendar.HOUR_OF_DAY:
            cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
        case Calendar.MINUTE:
            cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
        case Calendar.SECOND:
            cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));
        case Calendar.MILLISECOND:
        default:
        }
        SimpleDateFormat formatter = CachingDateFormat.getSolrDatetimeFormat();
        formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        return formatter.format(cal.getTime());
    }

    private Pair<Date, Integer> parseDateString(String dateString) {
        try {
            Pair<Date, Integer> result = CachingDateFormat.lenientParse(dateString, Calendar.YEAR);
            return result;
        } catch (java.text.ParseException e) {
            SimpleDateFormat oldDf = CachingDateFormat.getDateFormat();
            try {
                Date date = oldDf.parse(dateString);
                return new Pair<Date, Integer>(date, Calendar.SECOND);
            } catch (java.text.ParseException ee) {
                if (dateString.equalsIgnoreCase("min")) {
                    Calendar cal = Calendar.getInstance(I18NUtil.getLocale());
                    cal.set(Calendar.YEAR, cal.getMinimum(Calendar.YEAR));
                    cal.set(Calendar.DAY_OF_YEAR, cal.getMinimum(Calendar.DAY_OF_YEAR));
                    cal.set(Calendar.HOUR_OF_DAY, cal.getMinimum(Calendar.HOUR_OF_DAY));
                    cal.set(Calendar.MINUTE, cal.getMinimum(Calendar.MINUTE));
                    cal.set(Calendar.SECOND, cal.getMinimum(Calendar.SECOND));
                    cal.set(Calendar.MILLISECOND, cal.getMinimum(Calendar.MILLISECOND));
                    return new Pair<Date, Integer>(cal.getTime(), Calendar.MILLISECOND);
                } else if (dateString.equalsIgnoreCase("now")) {
                    return new Pair<Date, Integer>(new Date(), Calendar.MILLISECOND);
                } else if (dateString.equalsIgnoreCase("today")) {
                    Calendar cal = Calendar.getInstance(I18NUtil.getLocale());
                    cal.setTime(new Date());
                    cal.set(Calendar.HOUR_OF_DAY, cal.getMinimum(Calendar.HOUR_OF_DAY));
                    cal.set(Calendar.MINUTE, cal.getMinimum(Calendar.MINUTE));
                    cal.set(Calendar.SECOND, cal.getMinimum(Calendar.SECOND));
                    cal.set(Calendar.MILLISECOND, cal.getMinimum(Calendar.MILLISECOND));
                    return new Pair<Date, Integer>(cal.getTime(), Calendar.DAY_OF_MONTH);
                } else if (dateString.equalsIgnoreCase("max")) {
                    Calendar cal = Calendar.getInstance(I18NUtil.getLocale());
                    cal.set(Calendar.YEAR, cal.getMaximum(Calendar.YEAR));
                    cal.set(Calendar.DAY_OF_YEAR, cal.getMaximum(Calendar.DAY_OF_YEAR));
                    cal.set(Calendar.HOUR_OF_DAY, cal.getMaximum(Calendar.HOUR_OF_DAY));
                    cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE));
                    cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND));
                    cal.set(Calendar.MILLISECOND, cal.getMaximum(Calendar.MILLISECOND));
                    return new Pair<Date, Integer>(cal.getTime(), Calendar.MILLISECOND);
                } else {
                    return null; // delegate to SOLR date parsing
                }
            }
        }
    }

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

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

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

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

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

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

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

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

        } else if (ending.equals(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();
            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, propertyDef, queryText,
                        luceneFunction, booleanQuery, 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();
            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, propertyDef, queryText,
                        luceneFunction, booleanQuery, locale, tokenisationMode);

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

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

    /**
     * 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;
        }
    }

    protected Query createSolr4IdQuery(String queryText) {
        return createTermQuery(FIELD_SOLR4_ID, queryText);
    }

    // Previous SOLR

    protected Query createIdQuery(String queryText) {
        if (NodeRef.isNodeRef(queryText)) {
            return createNodeRefQuery(FIELD_LID, queryText);
        } else {
            return createNodeRefQuery(FIELD_ID, queryText);
        }
    }

    protected Query createPathQuery(String queryText, boolean withRepeats) throws SAXPathException {
        XPathReader reader = new XPathReader();
        SolrXPathHandler handler = new SolrXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse(queryText);
        SolrPathQuery pathQuery = handler.getQuery();
        pathQuery.setRepeats(withRepeats);
        return new SolrCachingPathQuery(pathQuery);
    }

    protected Query createQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        SolrXPathHandler handler = new SolrXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        SolrPathQuery pathQuery = handler.getQuery();
        return new SolrCachingPathQuery(pathQuery);
    }

    protected Query createPrimaryAssocQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        SolrXPathHandler handler = new SolrXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        SolrPathQuery pathQuery = handler.getQuery();
        pathQuery.setPathField(FIELD_PRIMARYASSOCQNAME);
        return new SolrCachingPathQuery(pathQuery);
    }

    protected Query createPrimaryAssocTypeQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        SolrXPathHandler handler = new SolrXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        SolrPathQuery pathQuery = handler.getQuery();
        pathQuery.setPathField(FIELD_PRIMARYASSOCTYPEQNAME);
        return new SolrCachingPathQuery(pathQuery);
    }

    protected Query createAssocTypeQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        SolrXPathHandler handler = new SolrXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        SolrPathQuery pathQuery = handler.getQuery();
        pathQuery.setPathField(FIELD_ASSOCTYPEQNAME);
        return new SolrCachingPathQuery(pathQuery);
    }

    /**
      * @param queryText
      * @return
     */
    protected Query createAclIdQuery(String queryText) throws ParseException {
        return getFieldQueryImplWithIOExceptionWrapped(FIELD_ACLID, queryText, AnalysisMode.DEFAULT,
                LuceneFunction.FIELD);
    }

    /**
      * @param queryText
      * @return
     */
    protected Query createOwnerQuery(String queryText) throws ParseException {
        return new SolrOwnerQuery(queryText);
    }

    /**
      * @param queryText
      * @return
     */
    protected Query createReaderQuery(String queryText) throws ParseException {
        return new SolrReaderQuery(queryText);
    }

    /**
      * @param queryText
      * @return
     */
    protected Query createAuthorityQuery(String queryText) throws ParseException {
        return new SolrAuthorityQuery(queryText);
    }

    // TODO: correct field names
    protected Query addContentAttributeQuery(PropertyDefinition pDef, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            List<Locale> expandedLocales) throws ParseException {
        BooleanQuery booleanQuery = new BooleanQuery();
        for (Locale locale : expandedLocales) {
            if (locale.toString().length() == 0) {
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance().getQueryableFields(pDef.getName(),
                        null, FieldUse.FTS);
                for (FieldInstance field : indexedField.getFields()) {
                    if (!field.isLocalised()) {
                        Query subQuery = subQueryBuilder.getQuery(field.getField(), queryText, analysisMode,
                                luceneFunction);
                        if (subQuery != null) {
                            booleanQuery.add(subQuery, Occur.SHOULD);
                        }
                    }
                }
            } else {
                StringBuilder builder = new StringBuilder(queryText.length() + 10);
                builder.append("\u0000").append(locale.toString()).append("\u0000").append(queryText);
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance().getQueryableFields(pDef.getName(),
                        null, FieldUse.FTS);
                for (FieldInstance field : indexedField.getFields()) {
                    if (field.isLocalised()) {
                        Query subQuery = subQueryBuilder.getQuery(field.getField(), builder.toString(),
                                analysisMode, luceneFunction);
                        if (subQuery != null) {
                            booleanQuery.add(subQuery, Occur.SHOULD);
                        }
                    }
                }
            }
        }
        return getNonEmptyBooleanQuery(booleanQuery);
    }

    protected void addLocaleSpecificUntokenisedMLOrTextFunction(String expandedFieldName, PropertyDefinition pDef,
            String queryText, LuceneFunction luceneFunction, BooleanQuery booleanQuery, Locale locale,
            IndexTokenisationMode tokenisationMode) {
        //        Query subQuery = new CaseInsensitiveFieldQuery(new Term(getFieldName(expandedFieldName, locale, tokenisationMode, IndexTokenisationMode.FALSE), getFixedFunctionQueryText(
        //                queryText, locale, tokenisationMode, IndexTokenisationMode.FALSE)));
        //        booleanQuery.add(subQuery, Occur.SHOULD);
        //
        //        if (booleanQuery.getClauses().length == 0)
        //        {
        //            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
        //        }
        throw new UnsupportedOperationException();
    }

    private String getFixedFunctionQueryText(String queryText, Locale locale,
            IndexTokenisationMode actualIndexTokenisationMode,
            IndexTokenisationMode preferredIndexTokenisationMode) {
        StringBuilder builder = new StringBuilder(queryText.length() + 10);
        if (locale.toString().length() > 0) {
            builder.append("{").append(locale.toString()).append("}");
        }
        builder.append(queryText);

        return builder.toString();
    }

    private FieldInstance getFieldInstance(String baseFieldName, PropertyDefinition pDef, Locale locale,
            IndexTokenisationMode preferredIndexTokenisationMode) {
        if (pDef != null) {

            switch (preferredIndexTokenisationMode) {
            case BOTH:
                throw new IllegalStateException("Preferred mode can not be BOTH");
            case FALSE:
                if (locale.toString().length() == 0) {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.ID);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (!field.isLocalised()) {
                            return field;
                        }
                    }
                } else {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.ID);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (field.isLocalised()) {
                            return field;
                        }
                    }
                }
                break;
            case TRUE:
                if (locale.toString().length() == 0) {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.FTS);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (!field.isLocalised()) {
                            return field;
                        }
                    }
                } else {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.FTS);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (field.isLocalised()) {
                            return field;
                        }
                    }
                }
                break;
            }
            return new FieldInstance("_dummy_", false, false);

        }

        return new FieldInstance(baseFieldName, false, false);

    }

    protected void addLocaleSpecificUntokenisedTextRangeFunction(String expandedFieldName, PropertyDefinition pDef,
            String lower, String upper, boolean includeLower, boolean includeUpper, LuceneFunction luceneFunction,
            BooleanQuery booleanQuery, Locale locale, IndexTokenisationMode tokenisationMode)
            throws ParseException {
        //        String field = getFieldName(expandedFieldName, locale, tokenisationMode, IndexTokenisationMode.FALSE);
        //
        //        StringBuilder builder = new StringBuilder();
        //        builder.append("\u0000").append(locale.toString()).append("\u0000").append(lower);
        //        String first = getToken(field, builder.toString(), AnalysisMode.IDENTIFIER);
        //
        //        builder = new StringBuilder();
        //        builder.append("\u0000").append(locale.toString()).append("\u0000").append(upper);
        //        String last = getToken(field, builder.toString(), AnalysisMode.IDENTIFIER);
        //
        //        Query query = new CaseInsensitiveFieldRangeQuery(field, first, last, includeLower, includeUpper);
        //        booleanQuery.add(query, Occur.SHOULD);

        throw new UnsupportedOperationException();

    }

    protected void addMLTextAttributeQuery(String field, PropertyDefinition pDef, String queryText,
            SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
            String expandedFieldName, PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode,
            BooleanQuery booleanQuery, Locale locale) throws ParseException {

        addMLTextOrTextAttributeQuery(field, pDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                expandedFieldName, tokenisationMode, booleanQuery, locale);
    }

    private void addMLTextOrTextAttributeQuery(String field, PropertyDefinition pDef, String queryText,
            SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
            String expandedFieldName, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            Locale locale) throws ParseException {

        boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
        try {
            switch (tokenisationMode) {
            case BOTH:
                switch (analysisMode) {
                default:
                case DEFAULT:
                case TOKENISE:
                    addLocaleSpecificMLOrTextAttribute(pDef, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, expandedFieldName, tokenisationMode,
                            IndexTokenisationMode.TRUE);
                    break;
                case IDENTIFIER:
                case FUZZY:
                case PREFIX:
                case WILD:
                case LIKE:
                    setLowercaseExpandedTerms(false);
                    addLocaleSpecificMLOrTextAttribute(pDef, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, expandedFieldName, tokenisationMode,
                            IndexTokenisationMode.FALSE);

                    break;
                }
                break;
            case FALSE:
                setLowercaseExpandedTerms(false);
                addLocaleSpecificMLOrTextAttribute(pDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                        booleanQuery, locale, expandedFieldName, tokenisationMode, IndexTokenisationMode.FALSE);
                break;
            case TRUE:
            default:
                addLocaleSpecificMLOrTextAttribute(pDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                        booleanQuery, locale, expandedFieldName, tokenisationMode, IndexTokenisationMode.TRUE);
                break;
            }
        } finally {
            setLowercaseExpandedTerms(lowercaseExpandedTerms);
        }

    }

    protected void addTextAttributeQuery(String field, PropertyDefinition pDef, String queryText,
            SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
            String expandedFieldName, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            Locale locale) throws ParseException {

        addMLTextOrTextAttributeQuery(field, pDef, queryText, subQueryBuilder, analysisMode, luceneFunction,
                expandedFieldName, tokenisationMode, booleanQuery, locale);
    }

    private void addLocaleSpecificMLOrTextAttribute(PropertyDefinition pDef, String queryText,
            SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
            BooleanQuery booleanQuery, Locale locale, String textFieldName, IndexTokenisationMode tokenisationMode,
            IndexTokenisationMode preferredTokenisationMode) throws ParseException {

        FieldInstance fieldInstance = getFieldInstance(textFieldName, pDef, locale, preferredTokenisationMode);
        StringBuilder builder = new StringBuilder(queryText.length() + 10);
        if (fieldInstance.isLocalised()) {
            builder.append("\u0000").append(locale.toString()).append("\u0000");
        }
        builder.append(queryText);
        Query subQuery = subQueryBuilder.getQuery(fieldInstance.getField(), builder.toString(), analysisMode,
                luceneFunction);
        if (subQuery != null) {
            booleanQuery.add(subQuery, Occur.SHOULD);
        }
    }

    protected void addTextRange(String field, PropertyDefinition pDef, String part1, String part2,
            boolean includeLower, boolean includeUpper, AnalysisMode analysisMode, String fieldName,
            PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            Locale locale) throws ParseException, IOException {
        switch (tokenisationMode) {
        case BOTH:
            switch (analysisMode) {
            case DEFAULT:
            case TOKENISE:
                addLocaleSpecificTextRange(fieldName, pDef, part1, part2, includeLower, includeUpper, booleanQuery,
                        locale, analysisMode, tokenisationMode, IndexTokenisationMode.TRUE);
                break;
            case IDENTIFIER:
                addLocaleSpecificTextRange(fieldName, pDef, part1, part2, includeLower, includeUpper, booleanQuery,
                        locale, analysisMode, tokenisationMode, IndexTokenisationMode.FALSE);
                break;
            case WILD:
            case LIKE:
            case PREFIX:
            case FUZZY:
            default:
                throw new UnsupportedOperationException();
            }
            break;
        case FALSE:
            addLocaleSpecificTextRange(fieldName, pDef, part1, part2, includeLower, includeUpper, booleanQuery,
                    locale, analysisMode, tokenisationMode, IndexTokenisationMode.FALSE);
            break;
        case TRUE:
            addLocaleSpecificTextRange(fieldName, pDef, part1, part2, includeLower, includeUpper, booleanQuery,
                    locale, analysisMode, tokenisationMode, IndexTokenisationMode.TRUE);
            break;
        default:
        }

    }

    private void addLocaleSpecificTextRange(String expandedFieldName, PropertyDefinition pDef, String part1,
            String part2, boolean includeLower, boolean includeUpper, BooleanQuery booleanQuery, Locale locale,
            AnalysisMode analysisMode, IndexTokenisationMode tokenisationMode,
            IndexTokenisationMode preferredTokenisationMode) throws ParseException, IOException {
        FieldInstance fieldInstance = getFieldInstance(expandedFieldName, pDef, locale, preferredTokenisationMode);

        String firstString = null;
        if ((part1 != null) && !part1.equals("\u0000")) {
            if (fieldInstance.isLocalised()) {
                firstString = getFirstTokenForRange(getLocalePrefixedText(part1, locale), fieldInstance);
                if (firstString == null) {
                    firstString = "{" + locale.getLanguage() + "}";
                }
            } else {
                firstString = getFirstTokenForRange(part1, fieldInstance);
            }
        } else {
            if (fieldInstance.isLocalised()) {
                firstString = "{" + locale.getLanguage() + "}";
            } else {
                firstString = null;
            }
        }

        String lastString = null;
        if ((part2 != null) && !part2.equals("\uffff")) {
            if (fieldInstance.isLocalised()) {
                lastString = getFirstTokenForRange(getLocalePrefixedText(part2, locale), fieldInstance);
                if (lastString == null) {
                    lastString = "{" + locale.getLanguage() + "}\uffff";
                }
            } else {
                lastString = getFirstTokenForRange(part2, fieldInstance);
            }
        } else {
            if (fieldInstance.isLocalised()) {
                lastString = "{" + locale.getLanguage() + "}\uffff";
            } else {
                lastString = null;
            }
        }

        TermRangeQuery query = new TermRangeQuery(fieldInstance.getField(),
                firstString == null ? null : new BytesRef(firstString),
                lastString == null ? null : new BytesRef(lastString), includeLower, includeUpper);
        booleanQuery.add(query, Occur.SHOULD);
    }

    private String getFirstTokenForRange(String string, FieldInstance field) throws IOException {
        org.apache.lucene.analysis.Token nextToken;
        TokenStream source = null;
        ;

        try {
            source = getAnalyzer().tokenStream(field.getField(), new StringReader(string));
            source.reset();
            while (source.incrementToken()) {
                CharTermAttribute cta = source.getAttribute(CharTermAttribute.class);
                OffsetAttribute offsetAtt = source.getAttribute(OffsetAttribute.class);
                TypeAttribute typeAtt = null;
                if (source.hasAttribute(TypeAttribute.class)) {
                    typeAtt = source.getAttribute(TypeAttribute.class);
                }
                PositionIncrementAttribute posIncAtt = null;
                if (source.hasAttribute(PositionIncrementAttribute.class)) {
                    posIncAtt = source.getAttribute(PositionIncrementAttribute.class);
                }
                nextToken = new Token(cta.buffer(), 0, cta.length(), offsetAtt.startOffset(),
                        offsetAtt.endOffset());
                if (typeAtt != null) {
                    nextToken.setType(typeAtt.type());
                }
                if (posIncAtt != null) {
                    nextToken.setPositionIncrement(posIncAtt.getPositionIncrement());
                }

                return nextToken.toString();
            }
        } finally {
            try {
                if (source != null) {
                    source.close();
                }
            } catch (IOException e) {
                // ignore
            }
        }
        return null;
    }

    protected void addTextSpanQuery(String field, PropertyDefinition pDef, String first, String last, int slop,
            boolean inOrder, String expandedFieldName, IndexTokenisationMode tokenisationMode,
            BooleanQuery booleanQuery, Locale locale) {
        addMLTextOrTextSpanQuery(field, pDef, first, last, slop, inOrder, expandedFieldName, tokenisationMode,
                booleanQuery, locale);
    }

    protected org.apache.lucene.search.Query addContentSpanQuery(String afield, PropertyDefinition pDef,
            String first, String last, int slop, boolean inOrder, String expandedFieldName,
            List<Locale> expandedLocales) {
        try {
            BooleanQuery booleanQuery = new BooleanQuery();
            for (Locale locale : expandedLocales) {
                if (locale.toString().length() == 0) {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.FTS);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (!field.isLocalised()) {
                            SpanOrQuery firstQuery = buildSpanOrQuery(first, field);
                            SpanOrQuery lastQuery = buildSpanOrQuery(last, field);
                            SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstQuery, lastQuery },
                                    slop, inOrder);
                            booleanQuery.add(result, Occur.SHOULD);
                        }
                    }
                } else {
                    IndexedField indexedField = AlfrescoSolrDataModel.getInstance()
                            .getQueryableFields(pDef.getName(), null, FieldUse.FTS);
                    for (FieldInstance field : indexedField.getFields()) {
                        if (field.isLocalised()) {
                            SpanOrQuery firstQuery = buildSpanOrQuery(getLocalePrefixedText(first, locale), field);
                            SpanOrQuery lastQuery = buildSpanOrQuery(getLocalePrefixedText(last, locale), field);
                            SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstQuery, lastQuery },
                                    slop, inOrder);
                            booleanQuery.add(result, Occur.SHOULD);
                        }
                    }
                }
            }
            return getNonEmptyBooleanQuery(booleanQuery);
        } catch (IOException ioe) {
            return createNoMatchQuery();
        }

    }

    private String getLocalePrefixedText(String text, Locale locale) {
        StringBuilder builder = new StringBuilder(text.length() + 10);
        builder.append("\u0000").append(locale.toString()).append("\u0000").append(text);
        return builder.toString();
    }

    /**
      * @param first
      * @param field
     * @return SpanOrQuery
     * @throws IOException
     */
    private SpanOrQuery buildSpanOrQuery(String first, FieldInstance field) throws IOException {
        SpanOrQuery spanOrQuery = new SpanOrQuery();

        org.apache.lucene.analysis.Token nextToken;
        TokenStream source = null;

        try {
            source = getAnalyzer().tokenStream(field.getField(), new StringReader(first));
            source.reset();
            while (source.incrementToken()) {
                CharTermAttribute cta = source.getAttribute(CharTermAttribute.class);
                OffsetAttribute offsetAtt = source.getAttribute(OffsetAttribute.class);
                TypeAttribute typeAtt = null;
                if (source.hasAttribute(TypeAttribute.class)) {
                    typeAtt = source.getAttribute(TypeAttribute.class);
                }
                PositionIncrementAttribute posIncAtt = null;
                if (source.hasAttribute(PositionIncrementAttribute.class)) {
                    posIncAtt = source.getAttribute(PositionIncrementAttribute.class);
                }
                nextToken = new Token(cta.buffer(), 0, cta.length(), offsetAtt.startOffset(),
                        offsetAtt.endOffset());
                if (typeAtt != null) {
                    nextToken.setType(typeAtt.type());
                }
                if (posIncAtt != null) {
                    nextToken.setPositionIncrement(posIncAtt.getPositionIncrement());
                }

                SpanQuery termQuery = new SpanTermQuery(new Term(field.getField(), nextToken.toString()));
                spanOrQuery.addClause(termQuery);
            }
        } finally {
            try {
                if (source != null) {
                    source.close();
                }
            } catch (IOException e) {
                // ignore
            }
        }

        return spanOrQuery;
    }

    protected void addMLTextSpanQuery(String field, PropertyDefinition pDef, String first, String last, int slop,
            boolean inOrder, String expandedFieldName, PropertyDefinition propertyDef,
            IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery, Locale locale) {
        addMLTextOrTextSpanQuery(field, pDef, first, last, slop, inOrder, expandedFieldName, tokenisationMode,
                booleanQuery, locale);
    }

    private void addMLTextOrTextSpanQuery(String afield, PropertyDefinition pDef, String first, String last,
            int slop, boolean inOrder, String expandedFieldName, IndexTokenisationMode tokenisationMode,
            BooleanQuery booleanQuery, Locale locale) {
        try {

            if (locale.toString().length() == 0) {
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance().getQueryableFields(pDef.getName(),
                        null, FieldUse.FTS);
                for (FieldInstance field : indexedField.getFields()) {
                    if (!field.isLocalised()) {
                        SpanOrQuery firstQuery = buildSpanOrQuery(first, field);
                        SpanOrQuery lastQuery = buildSpanOrQuery(last, field);
                        SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstQuery, lastQuery }, slop,
                                inOrder);
                        booleanQuery.add(result, Occur.SHOULD);
                    }
                }
            } else {
                IndexedField indexedField = AlfrescoSolrDataModel.getInstance().getQueryableFields(pDef.getName(),
                        null, FieldUse.FTS);
                for (FieldInstance field : indexedField.getFields()) {
                    if (field.isLocalised()) {
                        SpanOrQuery firstQuery = buildSpanOrQuery(getLocalePrefixedText(first, locale), field);
                        SpanOrQuery lastQuery = buildSpanOrQuery(getLocalePrefixedText(last, locale), field);
                        SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstQuery, lastQuery }, slop,
                                inOrder);
                        booleanQuery.add(result, Occur.SHOULD);
                    }
                }
            }

        } catch (IOException ioe) {

        }
    }

    public boolean addContentCrossLocaleWildcards() {
        return false;
    }

    protected Query createOwnerSetQuery(String queryText) throws ParseException {
        return new SolrOwnerSetQuery(queryText);
    }

    protected Query createReaderSetQuery(String queryText) throws ParseException {
        return new SolrReaderSetQuery(queryText);
    }

    protected Query createAuthoritySetQuery(String queryText) throws ParseException {
        return new SolrAuthoritySetQuery(queryText);
    }

    protected Query createDeniedQuery(String queryText) throws ParseException {
        return new SolrDeniedQuery(queryText);
    }

    protected Query createDenySetQuery(String queryText) throws ParseException {
        return new SolrDenySetQuery(queryText);
    }
}