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