UpdateNeededSearchPage.java

/*******************************************************************************
 * Copyright (c) 2020 Carsten Hammer.
 *
 * 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
 *
 * Contributors:
 *     Carsten Hammer - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.internal.ui.search;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.search.JavaSearchQuery;
import org.eclipse.jdt.internal.ui.search.JavaSearchScopeFactory;
import org.eclipse.jdt.internal.ui.search.SearchUtil;
import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.QuerySpecification;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.search.ui.ISearchPage;
import org.eclipse.search.ui.ISearchPageContainer;
import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkingSet;

public class UpdateNeededSearchPage extends DialogPage implements ISearchPage {

	private final static String PAGE_NAME = "UpdateNeededSearchPage"; //$NON-NLS-1$

	private ISearchPageContainer fContainer;

	private Map<String, Set<String>> listofClassLists;

	private IDialogSettings fDialogSettings;

	private SearchSettingsData leaveoutsearch;

	public static class SearchSettingsData extends HashMap<String, Boolean> {
		/**
		 *
		 */
		private static final long serialVersionUID = 1L;

		public SearchSettingsData(int limitTo, int includeMask, int scope) {
			this.limitTo = limitTo;
			this.includeMask = includeMask;
			this.scope = scope;
		}

		public int getLimitTo() {
			return limitTo;
		}

		public void setLimitTo(int limitTo) {
			this.limitTo = limitTo;
		}

		public int getIncludeMask() {
			return includeMask;
		}

		public void setIncludeMask(int includeMask) {
			this.includeMask = includeMask;
		}

		public int getScope() {
			return scope;
		}

		public void setScope(int scope) {
			this.scope = scope;
		}

		int limitTo;

		int includeMask;

		int scope;

		public static SearchSettingsData create(IDialogSettings settings, Map<String, Set<String>> listofClassLists) {
			if (settings == null) {
				// Use defaults if settings are not available
				return new SearchSettingsData(IJavaSearchConstants.REFERENCES, 
						JavaSearchScopeFactory.NO_JRE, 
						ISearchPageContainer.WORKSPACE_SCOPE);
			}
			
			int limitto;
			try {
				limitto = settings.getInt("limitTo"); //$NON-NLS-1$
			} catch (NumberFormatException e) {
				limitto = IJavaSearchConstants.REFERENCES;
			}
			int includemask;
			try {
				includemask = settings.getInt("includeMask"); //$NON-NLS-1$
			} catch (NumberFormatException e) {
				includemask = JavaSearchScopeFactory.NO_JRE;
			}
			int myscope;
			try {
				myscope = settings.getInt("scope"); //$NON-NLS-1$
			} catch (NumberFormatException e) {
				myscope = ISearchPageContainer.WORKSPACE_SCOPE;
			}
			SearchSettingsData ssd = new SearchSettingsData(limitto, includemask, myscope);
			if (listofClassLists != null) {
				listofClassLists.keySet().forEach(lc -> {
					ssd.put(lc, settings.getBoolean(lc));
				});
			}
			return ssd;
		}

		public void store(IDialogSettings settings, Map<String, Set<String>> listofClassLists) {
			if (settings == null || listofClassLists == null) {
				return;
			}
			settings.put("scope", scope); //$NON-NLS-1$
			settings.put("limitTo", limitTo); //$NON-NLS-1$
			settings.put("includeMask", includeMask); //$NON-NLS-1$
			listofClassLists.keySet().forEach(lc -> {
				Boolean value = this.get(lc);
				settings.put(lc, value != null ? value.booleanValue() : false);
			});
		}

	}

	@Override
	public void createControl(Composite parent) {
		initializeDialogUnits(parent);
		listofClassLists = readClassList();
		if (listofClassLists.isEmpty()) {
			JavaPlugin.logErrorMessage(Messages.UpdateNeededSearchPage_ClassListLoadFailed);
		}
		readConfiguration();

		ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.BORDER);
		scrolledComposite.setExpandHorizontal(true);
		scrolledComposite.setExpandVertical(true);

		Composite composite = new Composite(scrolledComposite, SWT.NONE);
		composite.setLayout(new RowLayout(SWT.VERTICAL));

		Label lblNewLabel = new Label(composite, SWT.NONE);
		lblNewLabel.setText("Select legacy classes to search for"); //$NON-NLS-1$
		if (listofClassLists != null && !listofClassLists.isEmpty()) {
			listofClassLists.keySet().forEach(lc -> {
				Button btnCheckButton = new Button(composite, SWT.CHECK);
				Boolean selection = leaveoutsearch.get(lc);
				btnCheckButton.setSelection(selection == null || !selection.booleanValue());
				btnCheckButton.setText(lc);
				btnCheckButton.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						Button button = (Button) e.widget;
						if (button.getSelection()) {
							leaveoutsearch.put(lc, Boolean.FALSE);
						} else {
							leaveoutsearch.put(lc, Boolean.TRUE);
						}
					}
				});
			});
		}
		scrolledComposite.setContent(composite);
		scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
		setControl(scrolledComposite);
	}

	@Override
	public void dispose() {
		writeConfiguration();
		super.dispose();
	}

	private IDialogSettings getDialogSettings() {
		if (fDialogSettings == null) {
			fDialogSettings = JavaPlugin.getDefault().getDialogSettingsSection(PAGE_NAME);
		}
		return fDialogSettings;
	}

	/**
	 * Initializes itself from the stored page settings.
	 */
	private void readConfiguration() {
		leaveoutsearch = SearchSettingsData.create(getDialogSettings(), listofClassLists);
	}

	/**
	 * Stores the current configuration in the dialog store.
	 */
	private void writeConfiguration() {
		leaveoutsearch.store(getDialogSettings(), listofClassLists);
	}

	@Override
	public boolean performAction() {
		return performNewSearch();
	}

	/**
	 * Returns the search page's container.
	 *
	 * @return the search page container
	 */
	private ISearchPageContainer getContainer() {
		return fContainer;
	}

	private boolean performNewSearch() {
		// Setup search scope
		IJavaSearchScope scope = null;
		String scopeDescription = ""; //$NON-NLS-1$
		int limitTo = IJavaSearchConstants.ALL_OCCURRENCES;
		int includeMask = IJavaSearchScope.SOURCES;
		JavaSearchScopeFactory factory = JavaSearchScopeFactory.getInstance();

		switch (getContainer().getSelectedScope()) {
		case ISearchPageContainer.WORKSPACE_SCOPE:
			scopeDescription = factory.getWorkspaceScopeDescription(includeMask);
			scope = factory.createWorkspaceScope(includeMask);
			break;

		case ISearchPageContainer.SELECTION_SCOPE:
			IJavaElement[] javaElements = {};

			if (getContainer().getActiveEditorInput() != null) {
				IFile file = getContainer().getActiveEditorInput().getAdapter(IFile.class);

				if (file != null && file.exists()) {
					IJavaElement javaElement = JavaCore.create(file);

					if (javaElement != null) {
						javaElements = new IJavaElement[] { javaElement };
					}
				}
			} else {
				javaElements = factory.getJavaElements(getContainer().getSelection());
			}

			scope = factory.createJavaSearchScope(javaElements, includeMask);
			scopeDescription = factory.getSelectionScopeDescription(javaElements, includeMask);
			break;

		case ISearchPageContainer.SELECTED_PROJECTS_SCOPE:
			String[] projectNames = getContainer().getSelectedProjectNames();
			scope = factory.createJavaProjectSearchScope(projectNames, includeMask);
			scopeDescription = factory.getProjectScopeDescription(projectNames, includeMask);
			break;

		case ISearchPageContainer.WORKING_SET_SCOPE:
			IWorkingSet[] workingSets = getContainer().getSelectedWorkingSets();

			// Should not happen - just to be sure
			if (workingSets == null || workingSets.length < 1) {
				return false;
			}

			scopeDescription = factory.getWorkingSetScopeDescription(workingSets, includeMask);
			scope = factory.createJavaSearchScope(workingSets, includeMask);
			SearchUtil.updateLRUWorkingSets(workingSets);
			break;

		default:
			break;
		}

		IJavaElement element = null;
		IWorkspaceRoot wspace = ResourcesPlugin.getWorkspace().getRoot();
		if (wspace == null) {
			JavaPlugin.logErrorMessage(Messages.UpdateNeededSearchPage_WorkspaceNotAvailable);
			return false;
		}

		List<QuerySpecification> arl = new ArrayList<>();

		for (Entry<String, Set<String>> lc : listofClassLists.entrySet()) {
			if (leaveoutsearch.get(lc.getKey())) {
				continue;
			}

			for (String checkclass : lc.getValue()) {
				IProject[] projects = wspace.getProjects();
				if (projects == null || projects.length == 0) {
					JavaPlugin.logErrorMessage(Messages.UpdateNeededSearchPage_NoProjectsFound);
					break;
				}
				for (IProject pr : projects) {
					try {
						if (pr.isNatureEnabled("org.eclipse.jdt.core.javanature")) { //$NON-NLS-1$
							IJavaProject project = JavaCore.create(pr);
							element = JavaModelUtil.findTypeContainer(project, checkclass);

							if (element instanceof IType) {
								break;
							}
						}
					} catch (CoreException e) {
						JavaPlugin.log(e);
					}
				}

				if (element != null) {
					QuerySpecification querySpec = new ElementQuerySpecification(element, limitTo, scope,
							scopeDescription);
					arl.add(querySpec);
				}
			}
		}

		if (arl.isEmpty()) {
			return false;
		}

		JavaSearchQuery textSearchJob = new JavaSearchQuery(arl);
		NewSearchUI.runQueryInBackground(textSearchJob);
		return true;
	}

	/*
	 * Implements method from ISearchPage
	 */
	@Override
	public void setContainer(ISearchPageContainer container) {
		fContainer = container;
	}

	/**
	 * Reads the list of deprecated/legacy classes from classlist.properties.
	 * 
	 * @return a map of category names to sets of fully-qualified class names,
	 *         or an empty map if loading fails
	 */
	private Map<String, Set<String>> readClassList() {
		Map<String, Set<String>> result = new HashMap<>();
		String filename = "/org/sandbox/jdt/internal/ui/search/classlist.properties"; //$NON-NLS-1$
		
		try (InputStream input = getClass().getClassLoader().getResourceAsStream(filename)) {
			if (input == null) {
				JavaPlugin.logErrorMessage(Messages.UpdateNeededSearchPage_ClassListNotFound);
				return Collections.emptyMap();
			}

			Properties prop = new Properties();
			prop.load(input);
			
			// Build category -> class set map
			Map<String, String> sprop = new HashMap<>();
			for (String key : prop.stringPropertyNames()) {
				sprop.put(key, prop.getProperty(key));
			}
			
			// Initialize category sets
			sprop.forEach((key, value) -> result.putIfAbsent(value, new HashSet<>()));
			// Populate class names into category sets
			sprop.forEach((key, value) -> result.get(value).add(key));

		} catch (IOException e) {
			JavaPlugin.log(e);
			return Collections.emptyMap();
		}
		return result;
	}
}