package rocks.inspectit.ui.rcp.editor.search.helper; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.events.SelectionAdapter; import rocks.inspectit.shared.all.util.ObjectUtils; import rocks.inspectit.ui.rcp.editor.search.ISearchExecutor; import rocks.inspectit.ui.rcp.editor.search.criteria.SearchCriteria; import rocks.inspectit.ui.rcp.editor.search.criteria.SearchResult; import rocks.inspectit.ui.rcp.editor.search.factory.SearchFactory; import rocks.inspectit.ui.rcp.repository.RepositoryDefinition; /** * Abstract search helper. Joins the search logics and delegates specific actions to the * sub-classes. * * @author Ivan Senic * */ public abstract class AbstractSearchHelper implements ISearchExecutor { /** * Current occurrence. */ private int currentOccurrence; /** * Total occurrences. */ private int totalOccurrences; /** * Last used {@link SearchCriteria}. */ private SearchCriteria lastSearchCriteria; /** * All objects that should be searched. */ private Object[] allObjects; /** * List of found objects by {@link #lastSearchCriteria}. */ private List<Object> foundObjects = Collections.emptyList(); /** * {@link RepositoryDefinition}. Needed for {@link SearchFactory}. */ private final RepositoryDefinition repositoryDefinition; /** * Caching of the viewer's input hash. When the hash changes, we need to reload all objects to * search. */ private int oldInputHash; /** * The selection adapter that will clear the search on the new sorting of columns. */ private SelectionAdapter columnSortingListener = new SelectionAdapter() { @Override public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) { clearSearch(); }; }; /** * If <code>true</code> singles that the {@link #clearSearch()} was executed and search should * start over. */ private boolean cleared; /** * @param repositoryDefinition * {@link RepositoryDefinition}. Needed for {@link SearchFactory}. */ public AbstractSearchHelper(RepositoryDefinition repositoryDefinition) { super(); this.repositoryDefinition = repositoryDefinition; } /** * Performs the selection of the element. The sub-classes need to implement this method, so that * the correct selection by viewer type is performed. * * @param element * Element to select. */ public abstract void selectElement(Object element); /** * Returns all objects that should be searched. * * @return Returns all objects that should be searched. */ public abstract Object[] getAllObjects(); /** * Returns the viewer the search is performed on. This is necessary for input change checking * and filtering. * * @return Returns the viewer the search is performed on. This is necessary for input change * checking and filtering. */ public abstract StructuredViewer getViewer(); /** * {@inheritDoc} */ @Override public SearchResult executeSearch(SearchCriteria searchCriteria) { if (!cleared && ObjectUtils.equals(lastSearchCriteria, searchCriteria)) { // we search with same criteria as last time // just execute next return this.next(); } else { // we search with new criteria if (!checkInput()) { loadAllObjects(); } updateFoundObjects(searchCriteria); if (totalOccurrences > 0) { currentOccurrence = 1; displayOccurence(currentOccurrence); } else { currentOccurrence = 0; } } lastSearchCriteria = searchCriteria; cleared = false; return getSearchResult(); } /** * {@inheritDoc} */ @Override public SearchResult next() { if (cleared) { return executeSearch(lastSearchCriteria); } if (!checkInput()) { loadAllObjects(); updateFoundObjects(lastSearchCriteria); } else { sortAsInViewer(foundObjects); } if (totalOccurrences > 1) { currentOccurrence++; if (currentOccurrence > totalOccurrences) { currentOccurrence = 1; } displayOccurence(currentOccurrence); } else { currentOccurrence = totalOccurrences; if (currentOccurrence > 0) { displayOccurence(currentOccurrence); } } return getSearchResult(); } /** * {@inheritDoc} */ @Override public SearchResult previous() { if (cleared) { return executeSearch(lastSearchCriteria); } if (!checkInput()) { loadAllObjects(); updateFoundObjects(lastSearchCriteria); } else { sortAsInViewer(foundObjects); } if (totalOccurrences > 1) { currentOccurrence--; if (currentOccurrence == 0) { currentOccurrence = totalOccurrences; } displayOccurence(currentOccurrence); } else { currentOccurrence = totalOccurrences; if (currentOccurrence > 0) { displayOccurence(currentOccurrence); } } return getSearchResult(); } /** * {@inheritDoc} */ @Override public void clearSearch() { cleared = true; } /** * * @return Returns the search result by examining the state of the {@link #currentOccurrence} * and {@link #totalOccurrences} values. */ private SearchResult getSearchResult() { if (totalOccurrences > 1) { return new SearchResult(currentOccurrence, totalOccurrences, true, true); } else { return new SearchResult(currentOccurrence, totalOccurrences, false, false); } } /** * Checks if the input of the viewer changed since the last search. If so, the {@link #input} * variable will be updated. * * @return True if the input did not change, false otherwise. */ private boolean checkInput() { int inputHash = Arrays.hashCode(getAllObjects()); if (oldInputHash != inputHash) { oldInputHash = inputHash; return false; } else { oldInputHash = inputHash; return true; } } /** * Loads all objects that need to be searched. */ private void loadAllObjects() { allObjects = getAllObjects(); } /** * Updates the {@link #foundObjects} with given {@link SearchCriteria} against * {@link #allObjects}. This method also checks if the filters of the viewer are satisfied. * * @param searchCriteria * {@link SearchCriteria}. */ private void updateFoundObjects(SearchCriteria searchCriteria) { foundObjects = new ArrayList<>(); for (Object object : allObjects) { if (SearchFactory.isSearchCompatible(object, searchCriteria, repositoryDefinition) && areFiltersPassed(object, getViewer().getFilters())) { foundObjects.add(object); } } sortAsInViewer(foundObjects); totalOccurrences = foundObjects.size(); } /** * Sorts the list of objects as they appear in the table. * * @param objects * List of objects. */ private void sortAsInViewer(List<Object> objects) { if (null != getViewer().getComparator()) { Collections.sort(objects, new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { return getViewer().getComparator().compare(getViewer(), o1, o2); } }); } } /** * Are all filers passed. * * @param object * Object to check. * @param filters * Filters. * @return True if all filters are passed or no filter was given. */ private boolean areFiltersPassed(Object object, ViewerFilter[] filters) { if (null != filters) { for (ViewerFilter filter : filters) { if (!filter.select(getViewer(), null, object)) { return false; } } } return true; } /** * Displays the wanted occurrence. * * @param occurrence * Occurrence to display. */ private void displayOccurence(int occurrence) { if ((totalOccurrences >= occurrence) && (foundObjects.size() >= occurrence)) { // select this element Object selected = foundObjects.get(occurrence - 1); this.selectElement(selected); } } /** * Gets {@link #columnSortingListener}. * * @return {@link #columnSortingListener} */ protected SelectionAdapter getColumnSortingListener() { return columnSortingListener; } }