diff --git a/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/SearchResults.java b/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/SearchResults.java index e9ee51b17ea..19a1a54a323 100644 --- a/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/SearchResults.java +++ b/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/SearchResults.java @@ -19,6 +19,7 @@ import java.util.Iterator; import java.util.List; +import org.eclipse.help.IHelpResource; import org.eclipse.help.IToc; import org.eclipse.help.ITopic; import org.eclipse.help.base.AbstractHelpScope; @@ -297,4 +298,9 @@ private ArrayList getCriteriaScopes(WorkingSet[] wSets){ public void addQTCException(QueryTooComplexException exception) throws QueryTooComplexException { this.searchException = exception; } + + public IHelpResource[] getScopes() { + return scopes == null ? null : scopes.toArray(IHelpResource[]::new); + } + } diff --git a/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/federated/LocalHelp.java b/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/federated/LocalHelp.java index dc967c6b4bd..83f264343c0 100644 --- a/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/federated/LocalHelp.java +++ b/ua/org.eclipse.help.base/src/org/eclipse/help/internal/search/federated/LocalHelp.java @@ -37,7 +37,9 @@ public class LocalHelp implements ISearchEngine2 { public void run(String query, ISearchScope scope, final ISearchEngineResultCollector collector, IProgressMonitor monitor) throws CoreException { + String originalQuery = query; + // Pre search AbstractSearchProcessor processors[] = SearchManager.getSearchProcessors(); altList = new ArrayList<>(); for (AbstractSearchProcessor processor : processors) { @@ -56,28 +58,30 @@ public void run(String query, ISearchScope scope, } altList.sort(null); - + // Search SearchQuery searchQuery = new SearchQuery(); searchQuery.setSearchWord(query); WorkingSet[] workingSets = null; LocalHelpScope localScope = (LocalHelpScope) scope; if (localScope.getWorkingSet() != null) workingSets = new WorkingSet[] { localScope.getWorkingSet() }; - SearchResults localResults = new SearchResults(workingSets, MAX_HITS, - Platform.getNL()); + SearchResults localResults = new SearchResults(workingSets, MAX_HITS, Platform.getNL()); // If the indexer has been started and is currently running, // wait for it to finish. try { Job.getJobManager().join(IndexerJob.FAMILY, monitor); } catch (InterruptedException e) { } - BaseHelpSystem.getSearchManager().search(searchQuery, localResults, - monitor); + if (!"".equals(query)) { //$NON-NLS-1$ + BaseHelpSystem.getSearchManager().search(searchQuery, localResults, monitor); + } + // Post search ISearchResult results[] = SearchManager.convertHitsToResults(localResults.getSearchHits()); boolean reset = false; for (AbstractSearchProcessor processor : processors) { - ISearchResult tmp[] = processor.postSearch(query, results); + ISearchResult tmp[] = processor.postSearch(query, originalQuery, results, searchQuery.getLocale(), + localResults.getScopes()); if (tmp!=null) { reset = true; diff --git a/ua/org.eclipse.help.base/src/org/eclipse/help/search/AbstractSearchProcessor.java b/ua/org.eclipse.help.base/src/org/eclipse/help/search/AbstractSearchProcessor.java index 4fe8fb96b7f..f5621f03dc5 100644 --- a/ua/org.eclipse.help.base/src/org/eclipse/help/search/AbstractSearchProcessor.java +++ b/ua/org.eclipse.help.base/src/org/eclipse/help/search/AbstractSearchProcessor.java @@ -14,6 +14,8 @@ package org.eclipse.help.search; +import org.eclipse.help.IHelpResource; + /** * This class is responsible for handling any pre or post * search processing events, including query manipulation @@ -34,20 +36,52 @@ public AbstractSearchProcessor() * See {@link SearchProcessorInfo} for types of information that can be used by * the search display. * - * @return SearchProcessorInfo, or null for no changes. + * If a {@link SearchProcessorInfo} with an empty, non-{@code null} query + * ({@code ""}) is returned, no search will be executed, resulting in no search + * results. + * + * @return {@link SearchProcessorInfo}, or {@code null} for no changes. */ public abstract SearchProcessorInfo preSearch(String query); /** * This method is called after the search is performed. * - * Results are stored as an array of ISearchResult containing - * all available data. + * This method can be used to return a modified result set. For example, one can + * change the result score of an item, add new results to the top of the list, + * or remove results. * - * This method can be used to return a modified result set. For example, one can change the - * result score of an item, add new results to the top of the list, or remove results. + * This method exists for backwards compatibility. Overwrite + * {@link #postSearch(String, String, ISearchResult[], String, IHelpResource[])} + * if more information like the locale, etc. is needed instead. * - * @return {@link ISearchResult}[], or null for no changes. + * @return The modified results, or {@code null} for no changes. */ public abstract ISearchResult[] postSearch(String query, ISearchResult[] results); + + /** + * This method is called after the search is performed. + * + * This method can be used to return a modified result set. For example, one can + * change the result score of an item, add new results to the top of the list, + * or remove results. + * + * @param query The actually query that was executed (after any changes + * made by {@link #preSearch(String)}). + * @param originalQuery The original query before any changes made by + * {@link #preSearch(String)}. + * @param results The results of the executed query. + * @param results The locale. + * @param results The set scopes (might be {@code null}). + * + * @return The modified results, or {@code null} for no changes. + * + * @see #postSearch(String, ISearchResult[]) + * + * @since 4.5 + */ + public ISearchResult[] postSearch(String query, String originalQuery, ISearchResult[] results, String locale, + IHelpResource[] scopes) { + return postSearch(query, results); + } } diff --git a/ua/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/data/SearchData.java b/ua/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/data/SearchData.java index 17d6a428fd7..b43bb700de7 100644 --- a/ua/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/data/SearchData.java +++ b/ua/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/data/SearchData.java @@ -135,6 +135,7 @@ private void readDisplayFlags(HttpServletRequest request, HttpServletResponse re } public void readSearchResults() { + String originalSearchWord = searchWord; // try loading search results or get the indexing progress info. if (isSearchRequest() && !isScopeRequest()) { @@ -167,7 +168,7 @@ public void readSearchResults() { } altList.sort(null); - loadSearchResults(); + SearchResults searchResults = loadSearchResults(); if (queryException != null) { return; } @@ -183,9 +184,9 @@ public void readSearchResults() { ISearchResult results[] = SearchManager.convertHitsToResults(hits); boolean reset= false; for (AbstractSearchProcessor processor : processors) { - ISearchResult tmp[] = processor.postSearch(searchWord,results); - if (tmp!=null) - { + ISearchResult tmp[] = processor.postSearch(searchWord, originalSearchWord, results, getLocale(), + searchResults == null ? null : searchResults.getScopes()); + if (tmp != null) { reset = true; results = tmp; } @@ -438,20 +439,23 @@ private void saveWorkingSet(String workingSet) { * Call the search engine, and get results or the percentage of indexed * documents. */ - private void loadSearchResults() { + private SearchResults loadSearchResults() { try { SearchProgressMonitor pm = SearchProgressMonitor .getProgressMonitor(getLocale()); if (pm.isDone()) { this.indexCompletion = 100; SearchResults results = createHitCollector(); - BaseHelpSystem.getSearchManager().search(createSearchQuery(), - results, pm); - hits = results.getSearchHits(); + if ("".equals(searchWord)) { //$NON-NLS-1$ + hits = new SearchHit[0]; + } else { + BaseHelpSystem.getSearchManager().search(createSearchQuery(), results, pm); + hits = results.getSearchHits(); + } if (hits == null) { ILog.of(getClass()).warn("No search results returned. Help index is in use."); //$NON-NLS-1$ } - return; + return results; } // progress indexCompletion = pm.getPercentage(); @@ -459,13 +463,12 @@ private void loadSearchResults() { // 38573 We do not have results, so index cannot be 100 indexCompletion = 100 - 1; } - return; } catch (QueryTooComplexException qe) { queryException = qe; } catch (Exception e) { this.indexCompletion = 0; } - + return null; } private ISearchQuery createSearchQuery() { diff --git a/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/AllSearchTests.java b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/AllSearchTests.java index 75c7a687d94..9e683f213fb 100644 --- a/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/AllSearchTests.java +++ b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/AllSearchTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2012 IBM Corporation and others. + * Copyright (c) 2006, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -32,6 +32,7 @@ MetaKeywords.class, // SearchParticipantTest.class, // SearchParticipantXMLTest.class, // + SearchProcessorTest.class, // SearchRanking.class, // WorkingSetManagerTest.class, // InfocenterWorkingSetManagerTest.class, // diff --git a/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/SearchProcessorTest.java b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/SearchProcessorTest.java new file mode 100644 index 00000000000..264f9ce9b80 --- /dev/null +++ b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/SearchProcessorTest.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2025 Holger Voormann and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.ua.tests.help.search; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.Collections; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.help.IHelpResource; +import org.eclipse.help.internal.search.SearchQuery; +import org.eclipse.help.internal.search.SearchResult; +import org.eclipse.help.internal.search.federated.LocalHelp; +import org.eclipse.help.internal.search.federated.LocalHelpScope; +import org.eclipse.help.search.AbstractSearchProcessor; +import org.eclipse.help.search.ISearchEngineResult; +import org.eclipse.help.search.ISearchEngineResultCollector; +import org.eclipse.help.search.ISearchResult; +import org.eclipse.help.search.SearchProcessorInfo; +import org.junit.jupiter.api.Test; + +public class SearchProcessorTest { + + @Test + void testPreSearch() { + var processor = new AbstractSearchProcessor() { + + @Override + public SearchProcessorInfo preSearch(String query) { + SearchProcessorInfo info = new SearchProcessorInfo(); + info.setQuery("jkijkijkk"); + return info; + } + + @Override + public ISearchResult[] postSearch(String query, ISearchResult[] results) { + return null; + } + + }; + test(processor, new String[] { "/org.eclipse.ua.tests/participant1.xml" }); + } + + @Test + void testPostSearch() { + var processor = new AbstractSearchProcessor() { + + @Override + public SearchProcessorInfo preSearch(String query) { + SearchProcessorInfo info = new SearchProcessorInfo(); + info.setQuery("jkijkijkk"); + return info; + } + + @Override + public ISearchResult[] postSearch(String query, ISearchResult[] results) { + assertEquals("jkijkijkk", query); + assertEquals(1, results.length); + assertEquals("/org.eclipse.ua.tests/participant1.xml", withoutQueryPart(results[0].getHref())); + var addedResult = new SearchResult(); + addedResult.setHref("/org.eclipse.ua.tests/added"); + return new ISearchResult[] { addedResult }; + } + + }; + test(processor, new String[] { "/org.eclipse.ua.tests/added" }); + } + + @Test + void testExtendedPostSearch() { + var processor = new AbstractSearchProcessor() { + + @Override + public SearchProcessorInfo preSearch(String query) { + SearchProcessorInfo info = new SearchProcessorInfo(); + info.setQuery("olhoykk"); + return info; + } + + @Override + public ISearchResult[] postSearch(String query, ISearchResult[] results) { + return null; + } + + @Override + public ISearchResult[] postSearch(String query, String originalQuery, ISearchResult[] results, + String locale, IHelpResource[] scopes) { + assertEquals("olhoykk", query); + assertEquals(WrappedSearchProcessor.QUERY, originalQuery); + assertEquals(1, results.length); + assertEquals("/org.eclipse.ua.tests/participant2.xml", withoutQueryPart(results[0].getHref())); + assertEquals(new SearchQuery().getLocale(), locale); + assertNull(scopes); + var addedResult = new SearchResult(); + addedResult.setHref("/org.eclipse.ua.tests/added2"); + return new ISearchResult[] { addedResult }; + } + + }; + test(processor, new String[] { "/org.eclipse.ua.tests/added2" }); + } + + @Test + void testNullProcessor() { + var processor = new AbstractSearchProcessor() { + + @Override + public SearchProcessorInfo preSearch(String query) { + assertEquals(WrappedSearchProcessor.QUERY, query); + return null; + } + + @Override + public ISearchResult[] postSearch(String query, ISearchResult[] results) { + return null; + } + + }; + test(processor, new String[0]); + } + + private void test(AbstractSearchProcessor processor, String[] expected) { + try (var autoClosable = WrappedSearchProcessor.set(processor)) { + String[] hits = search(WrappedSearchProcessor.QUERY); + assertArrayEquals(expected, hits); + } catch (Exception e) { + e.printStackTrace(); + fail(e); + } + } + + private static String[] search(String query) throws Exception { + + var foundHrefs = new ArrayList(); + ISearchEngineResultCollector collector = new ISearchEngineResultCollector() { + + @Override + public void accept(ISearchEngineResult searchResult) { + foundHrefs.add(withoutQueryPart(searchResult.getHref())); + } + + @Override + public void accept(ISearchEngineResult[] searchResults) { + for (ISearchEngineResult searchResult : searchResults) { + foundHrefs.add(withoutQueryPart(searchResult.getHref())); + } + } + + @Override + public void error(IStatus status) { + fail(new RuntimeException(status.getMessage(), status.getException())); + } + + }; + new LocalHelp().run(query, new LocalHelpScope(null, false), collector, new NullProgressMonitor()); + Collections.sort(foundHrefs); + return foundHrefs.toArray(String[]::new); + } + + private static String withoutQueryPart(String href) { + return href.indexOf('?') < 0 ? href : href.substring(0, href.indexOf('?')); + } + +} diff --git a/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/WrappedSearchProcessor.java b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/WrappedSearchProcessor.java new file mode 100644 index 00000000000..3f7206add3e --- /dev/null +++ b/ua/org.eclipse.ua.tests/help/org/eclipse/ua/tests/help/search/WrappedSearchProcessor.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2025 Holger Voormann and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.ua.tests.help.search; + +import org.eclipse.help.IHelpResource; +import org.eclipse.help.search.AbstractSearchProcessor; +import org.eclipse.help.search.ISearchResult; +import org.eclipse.help.search.SearchProcessorInfo; + +public class WrappedSearchProcessor extends AbstractSearchProcessor { + + public static final String QUERY = "SearchProcessorTest"; + + private static volatile AbstractSearchProcessor wrapped = null; + + public static synchronized AutoCloseable set(AbstractSearchProcessor searchProcessor) { + wrapped = searchProcessor; + return () -> wrapped = null; + } + + private static boolean isNotSearchProcessorTest(String query) { + return wrapped == null || (query != null && !query.contains(QUERY)); + } + + @Override + public SearchProcessorInfo preSearch(String query) { + if (isNotSearchProcessorTest(query)) + return null; + synchronized (WrappedSearchProcessor.class) { + if (isNotSearchProcessorTest(query)) + return null; + return wrapped.preSearch(query); + } + } + + @Override + public ISearchResult[] postSearch(String query, ISearchResult[] results) { + if (wrapped == null) + return null; + synchronized (WrappedSearchProcessor.class) { + if (wrapped == null) + return null; + return wrapped.postSearch(query, results); + } + } + + @Override + public ISearchResult[] postSearch(String query, String originalQuery, ISearchResult[] results, String locale, + IHelpResource[] scopes) { + if (isNotSearchProcessorTest(originalQuery)) + return null; + synchronized (WrappedSearchProcessor.class) { + if (isNotSearchProcessorTest(originalQuery)) + return null; + return wrapped.postSearch(query, originalQuery, results, locale, scopes); + } + } + +} diff --git a/ua/org.eclipse.ua.tests/plugin.xml b/ua/org.eclipse.ua.tests/plugin.xml index b059636b51d..557495ce9df 100644 --- a/ua/org.eclipse.ua.tests/plugin.xml +++ b/ua/org.eclipse.ua.tests/plugin.xml @@ -9,7 +9,7 @@ https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 - + Contributors: IBM Corporation - initial API and implementation George Suaridze (1C-Soft LLC) - Bug 559885 @@ -17,7 +17,7 @@ - - - + - - + - + - + @@ -416,16 +416,16 @@ - + content="data/intro/search/samplesExtensionContent.xml"/> + + content="data/intro/performance/org.eclipse.jdt/overviewExtensionContent.xml"/> + content="data/intro/performance/org.eclipse.jdt/tutorialsExtensionContent.xml"/> @@ -433,8 +433,8 @@ configId="org.eclipse.ui.intro.universalConfig" content="data/intro/performance/org.eclipse.jdt/newsExtensionContent.xml"/> + configId="org.eclipse.ui.intro.universalConfig" + content="data/intro/performance/org.eclipse.pde/overviewExtensionContent.xml"/> @@ -446,7 +446,7 @@ content="data/intro/performance/org.eclipse.pde/samplesExtensionContent2.xml"/> + content="data/intro/performance/org.eclipse.pde/newsExtensionContent.xml"/> @@ -507,7 +507,7 @@ - + @@ -529,12 +529,12 @@ - - + + - - - + + + - + - + @@ -705,5 +705,8 @@ + + +