| /*=============================================================================# |
| # Copyright (c) 2014, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.rhelp.core.index; |
| |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.lucene.analysis.Analyzer; |
| import org.apache.lucene.queryparser.flexible.core.QueryNodeException; |
| import org.apache.lucene.queryparser.flexible.core.QueryNodeParseException; |
| import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; |
| import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.TermInSetQuery; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| |
| import org.eclipse.statet.internal.rhelp.core.RHelpCoreInternals; |
| import org.eclipse.statet.rhelp.core.RHelpCore; |
| import org.eclipse.statet.rhelp.core.RHelpSearchQuery; |
| |
| |
| @NonNullByDefault |
| public final class REnvIndexSearchQuery implements REnvIndexSchema { |
| |
| |
| private static final ImList<String> TOPIC_SEARCH_FIELDS= ImCollections.newList( |
| ALIAS_FIELD_NAME ); |
| |
| private static final ImList<String> DOC_SEARCH_FIELDS= ImCollections.newList( |
| TITLE_TXT_FIELD_NAME, |
| DESCRIPTION_TXT_FIELD_NAME, |
| DOC_TXT_FIELD_NAME, |
| EXAMPLES_TXT_FIELD_NAME ); |
| |
| private static final Map<String, Float> FIELD_BOOSTS= new HashMap<>(); |
| static { // see BoostQuery |
| FIELD_BOOSTS.put(ALIAS_TXT_FIELD_NAME, 2.0f); |
| FIELD_BOOSTS.put(TITLE_TXT_FIELD_NAME, 2.0f); |
| FIELD_BOOSTS.put(DESCRIPTION_TXT_FIELD_NAME, 1.5f); |
| FIELD_BOOSTS.put(EXAMPLES_TXT_FIELD_NAME, 0.5f); |
| } |
| |
| |
| private final static Analyzer QUERY_ANALYZER= new DefaultAnalyzer(); |
| |
| |
| public static REnvIndexSearchQuery compile(final RHelpSearchQuery searchQuery) throws StatusException { |
| try { |
| Query query= null; |
| ImList<String> fields= ImCollections.emptyList(); |
| if (searchQuery.getSearchString().length() > 0) { |
| switch (searchQuery.getSearchType()) { |
| case RHelpSearchQuery.TOPIC_SEARCH: |
| fields= TOPIC_SEARCH_FIELDS; |
| query= createMainQuery(searchQuery.getSearchString(), fields, null); |
| break; |
| case RHelpSearchQuery.FIELD_SEARCH: |
| fields= sortFields(searchQuery.getEnabledFields()); |
| if (fields.isEmpty()) { |
| break; |
| } |
| query= createMainQuery(searchQuery.getSearchString(), fields, FIELD_BOOSTS); |
| break; |
| case RHelpSearchQuery.DOC_SEARCH: |
| fields= DOC_SEARCH_FIELDS; |
| query= createMainQuery(searchQuery.getSearchString(), fields, FIELD_BOOSTS); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| final List<Query> filters= new ArrayList<>(4); |
| { final List<String> packages= searchQuery.getPackages(); |
| if (!packages.isEmpty()) { |
| filters.add(new TermInSetQuery(PACKAGE_FIELD_NAME, |
| REnvIndexUtils.toByteRefTerms(packages) )); |
| } |
| } |
| { final List<String> keywords= searchQuery.getKeywords(); |
| if (!keywords.isEmpty()) { |
| filters.add(new TermInSetQuery(KEYWORD_FIELD_NAME, |
| REnvIndexUtils.toByteRefTerms(keywords) )); |
| } |
| } |
| filters.add(REnvIndexReader.DOCTYPE_PAGE_FILTER); |
| |
| return new REnvIndexSearchQuery(query, fields, filters); |
| } |
| catch (final QueryNodeParseException e) { |
| RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID, |
| String.format("An error occurred when creating the Lucene query for: %1$s.", //$NON-NLS-1$ |
| searchQuery.toString() ), |
| e )); |
| throw new StatusException(new ErrorStatus(RHelpCore.BUNDLE_ID, |
| "The search string is invalid: " + e.getLocalizedMessage() )); |
| } |
| catch (final Exception e) { |
| RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID, |
| String.format("An error occurred when creating the Lucene query for %1$s.", //$NON-NLS-1$ |
| searchQuery.toString() ), |
| e )); |
| throw new StatusException(new ErrorStatus(RHelpCore.BUNDLE_ID, |
| "An error occurred when preparing the R help query." )); |
| } |
| } |
| |
| private static ImList<String> sortFields(final List<String> fields) { |
| return ImCollections.toList(fields, new Comparator<String>() { |
| @Override |
| public int compare(final String o1, final String o2) { |
| return getRank(o1) - getRank(o2); |
| } |
| }); |
| } |
| |
| |
| static Query createMainQuery(final String queryText, |
| final ImList<String> fields, final @Nullable Map<String, Float> fieldBoosts) throws QueryNodeException { |
| final StandardQueryParser p= new StandardQueryParser(QUERY_ANALYZER); |
| p.setDefaultOperator(Operator.AND); |
| p.setAllowLeadingWildcard(true); |
| p.setMultiFields(fields.toArray(new String[fields.size()])); |
| if (fieldBoosts != null) { |
| p.setFieldsBoost(fieldBoosts); |
| } |
| return p.parse(queryText, null); |
| } |
| |
| |
| private static int getRank(final String o) { |
| if (o == PAGE_FIELD_NAME) { |
| return 1; |
| } |
| if (o == ALIAS_FIELD_NAME || o == ALIAS_TXT_FIELD_NAME) { |
| return 2; |
| } |
| if (o == TITLE_TXT_FIELD_NAME) { |
| return 3; |
| } |
| if (o == CONCEPT_TXT_FIELD_NAME) { |
| return 4; |
| } |
| if (o == DESCRIPTION_TXT_FIELD_NAME) { |
| return 5; |
| } |
| if (o == DOC_TXT_FIELD_NAME || o == DOC_HTML_FIELD_NAME) { |
| return 6; |
| } |
| if (o == EXAMPLES_TXT_FIELD_NAME) { |
| return 15; |
| } |
| return 10; |
| } |
| |
| |
| public final ImList<String> fieldNames; |
| |
| private final @Nullable Query query; |
| private final ImList<Query> filters; |
| |
| |
| private REnvIndexSearchQuery(final @Nullable Query mainQuery, |
| final List<String> fieldNames, final List<Query> filters) { |
| this.fieldNames= ImCollections.toList(fieldNames); |
| |
| this.query= mainQuery; |
| this.filters= ImCollections.toList(filters); |
| } |
| |
| |
| public @Nullable Query getQuery() { |
| return this.query; |
| } |
| |
| public ImList<Query> getFilters() { |
| return this.filters; |
| } |
| |
| } |