org.rssowl.core.internal.persist.search.ModelSearchQueries.java Source code

Java tutorial

Introduction

Here is the source code for org.rssowl.core.internal.persist.search.ModelSearchQueries.java

Source

/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2005-2009 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License v1.0 which accompanies this    **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl Development Team - initial API and implementation             **
 **                                                                          **
 **  **********************************************************************  */

package org.rssowl.core.internal.persist.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.document.NumberTools;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.QueryParser.Operator;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BooleanQuery.TooManyClauses;
import org.apache.lucene.search.ConstantScoreRangeQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.ISearchValueType;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.reference.BookMarkReference;
import org.rssowl.core.persist.reference.FolderReference;
import org.rssowl.core.persist.reference.NewsBinReference;
import org.rssowl.core.util.StringUtils;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;

/**
 * Helper to build the queries to search for {@link INews}.
 *
 * @author bpasero
 */
public class ModelSearchQueries {

    /* Maximum Date as String */
    private static final String MAX_DATE = DateTools.dateToString(new Date(Long.MAX_VALUE), Resolution.DAY);

    /* Minimum Date as String */
    private static final String MIN_DATE = DateTools.dateToString(new Date(0), Resolution.DAY);

    /* Maximum Number as String */
    private static final String MAX_NUMBER = NumberTools.longToString(Long.MAX_VALUE);

    /* Minimum Number as String */
    private static final String MIN_NUMBER = NumberTools.longToString(Long.MIN_VALUE);

    /* One Day in Millis */
    private static final Long DAY = 1000 * 3600 * 24L;

    /* One Minute in Millis */
    private static final Long MINUTE = 1000 * 60L;

    /* Wildcard matching any String */
    private static final char STRING_WILDCARD = '*';

    /* Wildcard matching any Char */
    private static final char CHAR_WILDCARD = '?';

    /**
     * Creates a Lucene {@link Query} from the given parameters.
     *
     * @param search the search for the query.
     * @return a {@link Query} from the given parameters.
     * @throws IOException in case of an error.
     */
    public static Query createQuery(ISearch search) throws IOException {
        return createQuery(search.getSearchConditions(), null, search.matchAllConditions());
    }

    /**
     * Creates a Lucene {@link Query} from the given parameters.
     *
     * @param conditions the search conditions for the query.
     * @param scope a specific {@link ISearchCondition} that scopes the results.
     * As such, the scope condition is a must criteria for the results.
     * @param matchAllConditions <code>true</code> to match all conditions and
     * <code>false</code> otherwise.
     * @return a {@link Query} from the given parameters.
     * @throws IOException in case of an error.
     */
    public static Query createQuery(Collection<ISearchCondition> conditions, ISearchCondition scope,
            boolean matchAllConditions) throws IOException {
        try {
            return internalCreateQuery(conditions, scope, matchAllConditions);
        }

        /* Too Many Clauses - Increase Clauses Limit */
        catch (TooManyClauses e) {

            /* Disable Clauses Limit */
            if (BooleanQuery.getMaxClauseCount() != ModelSearchImpl.MAX_CLAUSE_COUNT) {
                BooleanQuery.setMaxClauseCount(ModelSearchImpl.MAX_CLAUSE_COUNT);
                return internalCreateQuery(conditions, scope, matchAllConditions);
            }

            /* Maximum reached */
            throw e;
        }
    }

    private static Query internalCreateQuery(Collection<ISearchCondition> conditions, ISearchCondition scope,
            boolean matchAllConditions) throws IOException {
        boolean isScoped = false;
        BooleanQuery bQuery = new BooleanQuery();
        Analyzer analyzer = Indexer.createAnalyzer();

        /* Handle Location Scope Query separately */
        for (ISearchCondition condition : conditions) {
            if (isLocationScopeCondition(condition)) {
                BooleanQuery locationScopeClause = createLocationClause(condition);
                if (!locationScopeClause.clauses().isEmpty()) {
                    bQuery.add(locationScopeClause, Occur.MUST);
                    isScoped = true;
                }
            }
        }

        /* Add Condition Scope Query as necessary */
        if (scope != null) {
            addFieldClauses(Collections.singleton(scope), true, bQuery, analyzer);
            isScoped = true;
        }

        /* If scoped, the fieldQuery is a MUST-Clause to the outer Query */
        BooleanQuery fieldQuery = bQuery;
        if (isScoped)
            fieldQuery = new BooleanQuery();

        /* Add Conditions into the Boolean Query */
        addFieldClauses(conditions, matchAllConditions, fieldQuery, analyzer);

        /* Only add if not empty if scoped */
        if (isScoped && !fieldQuery.clauses().isEmpty())
            bQuery.add(fieldQuery, Occur.MUST);

        return bQuery;
    }

    private static void addFieldClauses(Collection<ISearchCondition> conditions, boolean matchAllConditions,
            BooleanQuery bQuery, Analyzer analyzer) throws IOException {

        /* Handle State-Field separately (group) */
        BooleanQuery statesQuery = null;
        for (ISearchCondition condition : conditions) {
            if (requiresStateGrouping(condition)) {

                /* Create and add new BooleanQuery for State */
                if (statesQuery == null) {
                    statesQuery = new BooleanQuery();
                    bQuery.add(statesQuery, matchAllConditions ? Occur.MUST : Occur.SHOULD);
                }

                /* Add Boolean Clause per State */
                addStateClause(statesQuery, condition);
            }
        }

        /* Create a Query for each condition */
        BooleanQuery fieldQuery = null;
        for (ISearchCondition condition : conditions) {

            /* State and Scope Queries already handled */
            if (requiresStateGrouping(condition) || isLocationScopeCondition(condition))
                continue;

            /* Create and add new BooleanQuery for other Fields */
            if (fieldQuery == null) {
                fieldQuery = new BooleanQuery();
                bQuery.add(fieldQuery, matchAllConditions ? Occur.MUST : Occur.SHOULD);
            }

            /* Create the Clause */
            BooleanClause clause = null;
            if (condition.getField().getId() == IEntity.ALL_FIELDS)
                clause = createAllNewsFieldsClause(analyzer, condition, matchAllConditions);
            else
                clause = createBooleanClause(analyzer, condition, matchAllConditions);

            /* Check if the Clause has any valid Query */
            Query query = clause.getQuery();
            if (query instanceof BooleanQuery && ((BooleanQuery) query).clauses().isEmpty())
                continue;

            /*
             * Specially treat this case where the specifier is a negation but any of
             * the supplied conditions should match in the result set.
             */
            if (condition.getSpecifier().isNegation() && !matchAllConditions) {
                BooleanQuery nestedquery = new BooleanQuery();
                nestedquery.add(clause);
                nestedquery.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
                fieldQuery.add(new BooleanClause(nestedquery, Occur.SHOULD));
            }

            /* Normal Case */
            else {
                fieldQuery.add(clause);
            }
        }

        /* Add the MatchAllDocsQuery (MUST_NOT is used, All Conditions match) */
        if (fieldQuery != null && matchAllConditions) {
            boolean requireAllDocsQuery = true;
            BooleanClause[] clauses = fieldQuery.getClauses();
            for (BooleanClause clause : clauses) {
                if (clause.getOccur() != Occur.MUST_NOT) {
                    requireAllDocsQuery = false;
                    break;
                }
            }

            /* Add if required */
            if (requireAllDocsQuery)
                fieldQuery.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
        }
    }

    @SuppressWarnings("unchecked")
    private static void addStateClause(BooleanQuery statesQuery, ISearchCondition condition) {
        String fieldName = String.valueOf(INews.STATE);
        Occur occur = condition.getSpecifier().isNegation() ? Occur.MUST_NOT : Occur.SHOULD;
        EnumSet<INews.State> newsStates = (EnumSet<State>) condition.getValue();
        for (INews.State state : newsStates) {
            String value = String.valueOf(state.ordinal());
            TermQuery stateQuery = new TermQuery(new Term(fieldName, value));
            statesQuery.add(new BooleanClause(stateQuery, occur));
        }

        /* Check if the match-all-docs query is required */
        if (condition.getSpecifier().isNegation())
            statesQuery.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
    }

    private static boolean requiresStateGrouping(ISearchCondition condition) {
        return condition.getField().getId() == INews.STATE;
    }

    private static boolean isLocationScopeCondition(ISearchCondition condition) {
        return condition.getSpecifier() == SearchSpecifier.SCOPE;
    }

    private static BooleanClause createAllNewsFieldsClause(Analyzer analyzer, ISearchCondition condition,
            boolean matchAllConditions) throws IOException {
        IModelFactory factory = Owl.getModelFactory();
        BooleanQuery allFieldsQuery = new BooleanQuery();

        /* Require all words to be contained or not contained */
        if (condition.getSpecifier() == SearchSpecifier.CONTAINS_ALL) {
            List<ISearchCondition> tokenConditions = new ArrayList<ISearchCondition>();

            List<String> tokens = StringUtils.tokenizePhraseAware((String) condition.getValue(), true);
            for (String token : tokens) {
                ISearchCondition tokenCondition = factory.createSearchCondition(condition.getField(),
                        condition.getSpecifier(), token);

                /* Rewrite Specifier */
                if (condition.getSpecifier() == SearchSpecifier.CONTAINS_ALL)
                    tokenCondition.setSpecifier(SearchSpecifier.CONTAINS);
                else
                    tokenCondition.setSpecifier(SearchSpecifier.CONTAINS_NOT);

                tokenConditions.add(tokenCondition);
            }

            /* Build custom Query out of Conditions */
            for (ISearchCondition tokenCondition : tokenConditions) {
                BooleanClause tokenClause = createAllNewsFieldsClause(analyzer, tokenCondition, matchAllConditions);

                /* Ignore empty clauses (e.g. due to Stop Words) */
                if (tokenClause.getQuery() instanceof BooleanQuery
                        && ((BooleanQuery) tokenClause.getQuery()).getClauses().length == 0)
                    continue;

                tokenClause.setOccur(Occur.MUST);
                allFieldsQuery.add(tokenClause);
            }
        }

        /* Require any word to be contained or not contained */
        else {
            List<ISearchCondition> allFieldsConditions = new ArrayList<ISearchCondition>(5);

            /* Title */
            ISearchField field = factory.createSearchField(INews.TITLE, condition.getField().getEntityName());
            allFieldsConditions
                    .add(factory.createSearchCondition(field, condition.getSpecifier(), condition.getValue()));

            /* Description */
            field = factory.createSearchField(INews.DESCRIPTION, condition.getField().getEntityName());
            allFieldsConditions
                    .add(factory.createSearchCondition(field, condition.getSpecifier(), condition.getValue()));

            /* Author */
            field = factory.createSearchField(INews.AUTHOR, condition.getField().getEntityName());
            allFieldsConditions
                    .add(factory.createSearchCondition(field, condition.getSpecifier(), condition.getValue()));

            /* Category (Phrase search stripped because unsupported */
            field = factory.createSearchField(INews.CATEGORIES, condition.getField().getEntityName());
            List<String> tokens = StringUtils.tokenizePhraseAware(condition.getValue().toString(), false);
            if (!tokens.contains(condition.getValue().toString()))
                tokens.add(condition.getValue().toString());
            for (String token : tokens) {
                if (token.length() != 0)
                    allFieldsConditions.add(factory.createSearchCondition(field,
                            condition.getSpecifier().isNegation() ? SearchSpecifier.IS_NOT : SearchSpecifier.IS,
                            token));
            }

            /* Attachment Content */
            field = factory.createSearchField(INews.ATTACHMENTS_CONTENT, condition.getField().getEntityName());
            allFieldsConditions
                    .add(factory.createSearchCondition(field, condition.getSpecifier(), condition.getValue()));

            /* Create Clauses out of Conditions */
            boolean anyClauseIsEmpty = false;
            List<BooleanClause> clauses = new ArrayList<BooleanClause>();
            for (ISearchCondition allFieldCondition : allFieldsConditions) {
                BooleanClause clause = createBooleanClause(analyzer, allFieldCondition, matchAllConditions);
                clause.setOccur(Occur.SHOULD);
                clauses.add(clause);

                /* Ignore empty clauses (e.g. due to Stop Words) */
                if (clause.getQuery() instanceof BooleanQuery
                        && ((BooleanQuery) clause.getQuery()).getClauses().length == 0)
                    anyClauseIsEmpty = true;
            }

            /* Only add if none of the clauses is empty */
            if (!anyClauseIsEmpty) {
                for (BooleanClause clause : clauses) {
                    allFieldsQuery.add(clause);
                }
            }
        }

        /* Determine Occur (MUST, SHOULD, MUST NOT) */
        Occur occur = getOccur(condition.getSpecifier(), matchAllConditions);
        return new BooleanClause(allFieldsQuery, occur);
    }

    /*
     * Will fallback to a TermQuery if the search-term is not valid for a
     * WildcardQuery
     */
    private static Query createWildcardQuery(String field, String term) {
        if (String.valueOf(INews.LABEL).equals(field) || isValidWildcardTerm(term))
            return new WildcardQuery(new Term(field, term));

        return new TermQuery(new Term(field, term));
    }

    private static boolean isValidWildcardTerm(String term) {
        for (int i = 0; i < term.length(); i++) {
            char charAtIndex = term.charAt(i);
            if (charAtIndex != STRING_WILDCARD && charAtIndex != CHAR_WILDCARD)
                return true;
        }

        return false;
    }

    private static BooleanClause createBooleanClause(Analyzer analyzer, ISearchCondition condition,
            boolean matchAllConditions) throws IOException {
        Query query = null;

        /* Separately handle this dynamic Query */
        if (condition.getField().getId() == INews.AGE_IN_DAYS
                || condition.getField().getId() == INews.AGE_IN_MINUTES)
            query = createAgeClause(condition);

        /* Separately handle this dynamic Query */
        else if (condition.getField().getId() == INews.LOCATION)
            query = createLocationClause(condition);

        /* Other Fields */
        else {
            try {
                switch (condition.getField().getSearchValueType().getId()) {

                /* Boolean: Simple Term-Query */
                case ISearchValueType.BOOLEAN:
                    query = createTermQuery(condition);
                    break;

                /* String / Link / Enum: String Query */
                case ISearchValueType.ENUM:
                case ISearchValueType.STRING:
                case ISearchValueType.LINK:
                    query = createStringQuery(analyzer, condition);
                    break;

                /* Date / Time / DateTime: Date Query (Ranged) */
                case ISearchValueType.DATE:
                case ISearchValueType.TIME:
                case ISearchValueType.DATETIME:
                    query = createDateQuery(condition);
                    break;

                /* Number / Integer: Number Query (Ranged) */
                case ISearchValueType.NUMBER:
                case ISearchValueType.INTEGER:
                    query = createNumberQuery(condition);
                }
            } catch (ParseException e) {
                Activator.getDefault().getLog().log(Activator.getDefault().createErrorStatus(e.getMessage(), e));
            }
        }

        /* In case of the Query not being created, fallback to Term-Query */
        if (query == null) {
            query = createTermQuery(condition);
        }

        /* Determine Occur (MUST, SHOULD, MUST NOT) */
        Occur occur = getOccur(condition.getSpecifier(), matchAllConditions);
        return new BooleanClause(query, occur);
    }

    /* This Clause needs to be generated dynamically */
    private static Query createAgeClause(ISearchCondition condition) {
        Integer age = (Integer) condition.getValue();
        String value;

        /* Minute Format */
        if (age < 0) {
            Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(System.currentTimeMillis() + age * MINUTE); //age is negative
            value = DateTools.dateToString(cal.getTime(), Resolution.MINUTE);

            String fieldname = String.valueOf(INews.AGE_IN_MINUTES);
            return createAgeQuery(condition, fieldname, value);
        }

        /* Day Format */
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(System.currentTimeMillis() - age * DAY);
        value = DateTools.dateToString(cal.getTime(), Resolution.DAY);

        String fieldname = String.valueOf(INews.AGE_IN_DAYS);
        return createAgeQuery(condition, fieldname, value);
    }

    private static Query createAgeQuery(ISearchCondition condition, String fieldname, String value) {
        switch (condition.getSpecifier()) {
        case IS: {
            return new TermQuery(new Term(fieldname, value));
        }

        case IS_GREATER_THAN: {
            Term lowerBound = new Term(fieldname, MIN_DATE);
            Term upperBound = new Term(fieldname, value);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }

        case IS_LESS_THAN: {
            Term lowerBound = new Term(fieldname, value);
            Term upperBound = new Term(fieldname, MAX_DATE);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }
        }

        throw new UnsupportedOperationException("Unsupported Specifier for Age Query"); //$NON-NLS-1$
    }

    /* This Clause needs to be generated dynamically */
    private static BooleanQuery createLocationClause(ISearchCondition condition) {
        BooleanQuery bQuery = new BooleanQuery();
        Long[][] value = (Long[][]) condition.getValue();
        if (value != null) {

            /* Receive Folders */
            for (int i = 0; value[0] != null && i < value[0].length; i++) {
                if (value[0][i] != null) {
                    IFolder folder = new FolderReference(value[0][i]).resolve();
                    if (folder != null)
                        addFolderLocationClause(bQuery, folder);
                }
            }

            /* Receive BookMarks */
            for (int i = 0; value[1] != null && i < value[1].length; i++) {
                if (value[1][i] != null) {
                    IBookMark bookmark = new BookMarkReference(value[1][i]).resolve();
                    if (bookmark != null)
                        addBookMarkLocationClause(bQuery, bookmark);
                }
            }

            /* Receive NewsBins */
            if (value.length == 3) {
                for (int i = 0; value[2] != null && i < value[2].length; i++) {
                    if (value[2][i] != null) {
                        INewsBin newsbin = new NewsBinReference(value[2][i]).resolve();
                        if (newsbin != null)
                            addNewsBinLocationClause(bQuery, newsbin);
                    }
                }
            }
        }

        /* The folder could be empty, make sure to add at least 1 Clause */
        if (bQuery.clauses().isEmpty())
            bQuery.add(new TermQuery(new Term(String.valueOf(INews.FEED), "")), Occur.SHOULD); //$NON-NLS-1$

        return bQuery;
    }

    private static void addFolderLocationClause(BooleanQuery bQuery, IFolder folder) {
        if (folder != null) {
            List<IFolder> folders = folder.getFolders();
            List<IMark> marks = folder.getMarks();

            /* Child Folders */
            for (IFolder childFolder : folders)
                addFolderLocationClause(bQuery, childFolder);

            /* BookMarks and Newsbins */
            for (IMark mark : marks)
                if (mark instanceof IBookMark)
                    addBookMarkLocationClause(bQuery, (IBookMark) mark);
                else if (mark instanceof INewsBin)
                    addNewsBinLocationClause(bQuery, (INewsBin) mark);
        }
    }

    private static void addBookMarkLocationClause(BooleanQuery bQuery, IBookMark bookmark) {
        if (bookmark != null) {
            BooleanQuery bookMarkLocationQuery = new BooleanQuery();

            /* Match on Feed */
            String feed = bookmark.getFeedLinkReference().getLinkAsText().toLowerCase();
            bookMarkLocationQuery.add(new TermQuery(new Term(String.valueOf(INews.FEED), feed)), Occur.MUST);

            /* But ignore News from Bins */
            bookMarkLocationQuery.add(
                    new TermQuery(new Term(String.valueOf(INews.PARENT_ID), NumberTools.longToString(0))),
                    Occur.MUST);

            bQuery.add(bookMarkLocationQuery, Occur.SHOULD);
        }
    }

    private static void addNewsBinLocationClause(BooleanQuery bQuery, INewsBin newsbin) {
        if (newsbin != null)
            bQuery.add(
                    new TermQuery(
                            new Term(String.valueOf(INews.PARENT_ID), NumberTools.longToString(newsbin.getId()))),
                    Occur.SHOULD);
    }

    @SuppressWarnings("unchecked")
    private static Query createStringQuery(Analyzer analyzer, ISearchCondition condition)
            throws ParseException, IOException {
        SearchSpecifier specifier = condition.getSpecifier();
        String fieldname = String.valueOf(condition.getField().getId());

        /* Retrieve Value */
        String value;
        if (condition.getValue() instanceof Enum)
            value = String.valueOf(((Enum<?>) condition.getValue()).ordinal());
        else
            value = String.valueOf(condition.getValue());

        switch (specifier) {

        /* Create Wildcard-Query */
        case IS:
        case IS_NOT: {
            return createWildcardQuery(fieldname, value.toLowerCase());
        }

        /* Let Query-Parser handle this */
        case CONTAINS:
        case CONTAINS_ALL:
        case CONTAINS_NOT: {
            QueryParser parser = new QueryParser(fieldname, analyzer);
            Operator operator = (specifier == SearchSpecifier.CONTAINS || specifier == SearchSpecifier.CONTAINS_NOT)
                    ? QueryParser.OR_OPERATOR
                    : QueryParser.AND_OPERATOR;
            parser.setDefaultOperator(operator);
            parser.setAllowLeadingWildcard(true);

            /* Prepare the value for parsing */
            value = prepareForParsing(value);

            /* Parse */
            return parser.parse(value);
        }

        /* Wildcard-Query with trailing '*' */
        case BEGINS_WITH: {
            value = value.toLowerCase() + "*"; //$NON-NLS-1$
            return createWildcardQuery(fieldname, value);
        }

        /* Wildcard-Query with leading '*' */
        case ENDS_WITH: {
            value = "*" + value.toLowerCase(); //$NON-NLS-1$
            return createWildcardQuery(fieldname, value);
        }

        /* Fuzzy Query */
        case SIMILIAR_TO: {
            BooleanQuery similarityQuery = new BooleanQuery();

            LowercaseWhitespaceAnalyzer similarAnalyzer = new LowercaseWhitespaceAnalyzer();
            TokenStream tokenStream = similarAnalyzer.tokenStream(String.valueOf(IEntity.ALL_FIELDS),
                    new StringReader(value));
            Token token = null;
            while ((token = tokenStream.next()) != null) {
                String termText = new String(token.termBuffer(), 0, token.termLength());
                Term term = new Term(fieldname, termText);
                similarityQuery.add(new BooleanClause(new FuzzyQuery(term), Occur.MUST));
            }

            return similarityQuery;
        }
        }

        throw new UnsupportedOperationException("Unsupported Specifier for Parsed Queries"); //$NON-NLS-1$
    }

    @SuppressWarnings("unchecked")
    private static Query createTermQuery(ISearchCondition condition) {
        String value;
        if (condition.getValue() instanceof Enum)
            value = String.valueOf(((Enum<?>) condition.getValue()).ordinal());
        else
            value = String.valueOf(condition.getValue());

        String fieldname = String.valueOf(condition.getField().getId());

        Term term = new Term(fieldname, value);
        return new TermQuery(term);
    }

    private static Query createDateQuery(ISearchCondition condition) {
        SearchSpecifier specifier = condition.getSpecifier();
        String value = DateTools.dateToString((Date) condition.getValue(), Resolution.DAY);
        String fieldname = String.valueOf(condition.getField().getId());

        switch (specifier) {
        case IS:
        case IS_NOT:
            return new TermQuery(new Term(fieldname, value));

        case IS_AFTER: {
            Term lowerBound = new Term(fieldname, value);
            Term upperBound = new Term(fieldname, MAX_DATE);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }

        case IS_BEFORE: {
            Term lowerBound = new Term(fieldname, MIN_DATE);
            Term upperBound = new Term(fieldname, value);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }
        }

        throw new UnsupportedOperationException("Unsupported Specifier for Date/Time Queries"); //$NON-NLS-1$
    }

    private static Query createNumberQuery(ISearchCondition condition) {
        SearchSpecifier specifier = condition.getSpecifier();
        String value = NumberTools.longToString((Integer) condition.getValue());
        String fieldname = String.valueOf(condition.getField().getId());

        switch (specifier) {
        case IS:
        case IS_NOT:
            return new TermQuery(new Term(fieldname, value));

        case IS_GREATER_THAN: {
            Term lowerBound = new Term(fieldname, value);
            Term upperBound = new Term(fieldname, MAX_NUMBER);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }

        case IS_LESS_THAN: {
            Term lowerBound = new Term(fieldname, MIN_NUMBER);
            Term upperBound = new Term(fieldname, value);

            return new ConstantScoreRangeQuery(fieldname, lowerBound.text(), upperBound.text(), false, false);
        }
        }

        throw new UnsupportedOperationException("Unsupported Specifier for Number Queries"); //$NON-NLS-1$
    }

    private static Occur getOccur(SearchSpecifier specifier, boolean matchAllConditions) {
        switch (specifier) {
        case IS_NOT:
        case CONTAINS_NOT:
            return Occur.MUST_NOT;

        default:
            return matchAllConditions ? Occur.MUST : Occur.SHOULD;
        }
    }

    private static String prepareForParsing(String s) {
        int doubleQuoteCount = 0;
        boolean startsWithDoubleQuote = false;
        boolean endsWithDoubleQuote = false;

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            /* Escape Special Characters being used in Lucene */
            if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':' || c == '^'
                    || c == '[' || c == ']' || c == '{' || c == '}' || c == '~')
                sb.append('\\');

            /* Keep Track of Doublequotes being used - they need special treatment */
            else if (c == '\"') {
                doubleQuoteCount++;
                if (i == 0)
                    startsWithDoubleQuote = true;
                else if (i == s.length() - 1)
                    endsWithDoubleQuote = true;
            }

            sb.append(c);
        }

        String escapedString = sb.toString();

        /*
         * This results in a parser exception from QueryParser and thus needs
         * special treatment
         */
        if (doubleQuoteCount % 2 != 0) {
            escapedString = escapedString.replace("\"", "\\\""); //$NON-NLS-1$ //$NON-NLS-2$
            if (startsWithDoubleQuote && endsWithDoubleQuote) //Restore Quotes for Phrase Search if necessary
                escapedString = escapedString.substring(1, escapedString.length() - 2) + "\""; //$NON-NLS-1$
        }

        return escapedString;
    }
}