/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* 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.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.erlide.ui.dialogs;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.Collator;
import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IPathVariableManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ResourceWorkingSetFilter;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.actions.WorkingSetFilterActionGroup;
import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog;
import org.eclipse.ui.dialogs.SearchPattern;
import org.eclipse.ui.statushandlers.StatusManager;
import org.erlide.debug.ui.utils.ModuleItemLabelProvider;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.model.SourcePathUtils;
import org.erlide.engine.model.root.ErlangProjectProperties;
import org.erlide.engine.model.root.IErlElementLocator;
import org.erlide.engine.model.root.IErlProject;
import org.erlide.engine.util.CommonUtils;
import org.erlide.engine.util.ResourceUtil;
import org.erlide.ui.internal.ErlideUIPlugin;
import org.erlide.util.ErlLogger;
import org.erlide.util.PreferencesUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Shows a list of resources to the user with a text entry field for a string
* pattern used to filter the list of resources.
*
*/
public class FilteredModulesSelectionDialog extends FilteredItemsSelectionDialog {
public class DuplicateModuleItemLabelProvider extends ModuleItemLabelProvider {
@Override
protected boolean showFullPath(final Object item) {
return isDuplicateElement(item);
}
}
private static final String DIALOG_SETTINGS = "org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog"; //$NON-NLS-1$
private static final String WORKINGS_SET_SETTINGS = "WorkingSet"; //$NON-NLS-1$
private final ModuleItemLabelProvider moduleItemLabelProvider;
private final ModuleItemDetailsLabelProvider moduleItemDetailsLabelProvider;
private WorkingSetFilterActionGroup workingSetFilterActionGroup;
final CustomWorkingSetFilter workingSetFilter = new CustomWorkingSetFilter();
private String title;
final IContainer container;
final int typeMask;
private Comparator<Object> fComparator = null;
private Collator fCollator = null;
private final boolean allowHrl;
/**
* Creates a new instance of the class
*
* @param shell
* the parent shell
* @param multi
* the multi selection flag
* @param container
* the container
* @param typesMask
* the types mask
*/
public FilteredModulesSelectionDialog(final Shell shell, final boolean multi,
final IContainer container, final int typeMask, final boolean allowHrl) {
super(shell, multi);
setSelectionHistory(new ModuleSelectionHistory());
this.container = container;
this.typeMask = typeMask;
this.allowHrl = allowHrl;
moduleItemLabelProvider = new DuplicateModuleItemLabelProvider();
moduleItemDetailsLabelProvider = new ModuleItemDetailsLabelProvider();
setListLabelProvider(moduleItemLabelProvider);
setDetailsLabelProvider(moduleItemDetailsLabelProvider);
}
@Override
public void setTitle(final String title) {
super.setTitle(title);
this.title = title;
}
/**
* Adds or replaces subtitle of the dialog
*
* @param text
* the new subtitle
*/
void setSubtitle(final String text) {
if (text == null || text.length() == 0) {
getShell().setText(title);
} else {
getShell().setText(title + " - " + text); //$NON-NLS-1$
}
}
@Override
protected IDialogSettings getDialogSettings() {
IDialogSettings settings = ErlideUIPlugin.getDefault().getDialogSettings()
.getSection(DIALOG_SETTINGS);
if (settings == null) {
settings = ErlideUIPlugin.getDefault().getDialogSettings()
.addNewSection(DIALOG_SETTINGS);
}
return settings;
}
@Override
protected void storeDialog(final IDialogSettings settings) {
super.storeDialog(settings);
final XMLMemento memento = XMLMemento.createWriteRoot("workingSet"); //$NON-NLS-1$
workingSetFilterActionGroup.saveState(memento);
workingSetFilterActionGroup.dispose();
final StringWriter writer = new StringWriter();
try {
memento.save(writer);
settings.put(WORKINGS_SET_SETTINGS, writer.getBuffer().toString());
} catch (final IOException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, ErlideUIPlugin.PLUGIN_ID, IStatus.ERROR,
"", e)); //$NON-NLS-1$
// don't do anything. Simply don't store the settings
}
}
@Override
protected void restoreDialog(final IDialogSettings settings) {
super.restoreDialog(settings);
final String setting = settings.get(WORKINGS_SET_SETTINGS);
if (setting != null) {
try {
final IMemento memento = XMLMemento.createReadRoot(new StringReader(
setting));
workingSetFilterActionGroup.restoreState(memento);
} catch (final WorkbenchException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, ErlideUIPlugin.PLUGIN_ID,
IStatus.ERROR, "", e)); //$NON-NLS-1$
// don't do anything. Simply don't restore the settings
}
}
addListFilter(workingSetFilter);
applyFilter();
}
@Override
protected void fillViewMenu(final IMenuManager menuManager) {
super.fillViewMenu(menuManager);
workingSetFilterActionGroup = new WorkingSetFilterActionGroup(getShell(),
new IPropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String property = event.getProperty();
if (WorkingSetFilterActionGroup.CHANGE_WORKING_SET
.equals(property)) {
IWorkingSet workingSet = (IWorkingSet) event.getNewValue();
if (workingSet != null
&& !(workingSet.isAggregateWorkingSet() && workingSet
.isEmpty())) {
workingSetFilter.setWorkingSet(workingSet);
setSubtitle(workingSet.getLabel());
} else {
final IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
if (window != null) {
final IWorkbenchPage page = window.getActivePage();
workingSet = page.getAggregateWorkingSet();
if (workingSet.isAggregateWorkingSet()
&& workingSet.isEmpty()) {
workingSet = null;
}
}
workingSetFilter.setWorkingSet(workingSet);
setSubtitle(null);
}
scheduleRefresh();
}
}
});
menuManager.add(new Separator());
workingSetFilterActionGroup.fillContextMenu(menuManager);
}
@Override
protected Control createExtendedContentArea(final Composite parent) {
return null;
}
private boolean isLostFound(final IPath projectRelativePath) {
for (final String s : projectRelativePath.segments()) {
if ("lost+found".equals(s)) {
return true;
}
}
return false;
}
@Override
public int open() {
if (getInitialPattern() == null) {
final IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
if (window != null) {
final ISelection selection = window.getSelectionService().getSelection();
if (selection instanceof ITextSelection) {
String text = ((ITextSelection) selection).getText();
if (text != null) {
text = text.trim();
if (text.length() > 0) {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IStatus result = workspace.validateName(text,
IResource.FILE);
if (result.isOK()) {
setInitialPattern(text);
}
}
}
}
}
}
return super.open();
}
@Override
public String getElementName(final Object item) {
if (item instanceof String) {
return (String) item;
}
final IResource resource = (IResource) item;
return resource.getName();
}
@Override
protected IStatus validateItem(final Object item) {
return new Status(IStatus.OK, ErlideUIPlugin.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
}
@Override
protected ItemsFilter createFilter() {
return new ModuleFilter(container, typeMask, allowHrl,
new MatchAnySearchPattern());
}
private String patternText;
@Override
protected Comparator<Object> getItemsComparator() {
if (fComparator == null) {
final Collator collator = Collator.getInstance();
if (collator instanceof RuleBasedCollator) {
final RuleBasedCollator rbc = (RuleBasedCollator) collator;
final String rules = rbc.getRules();
final String newRules = rules.replaceFirst("<\'.\'<", "<").replaceFirst(
"<\'_\'<", "<\'.\'<\'_\'<");
try {
fCollator = new RuleBasedCollator(newRules);
} catch (final ParseException e) {
ErlLogger.error(e);
fCollator = collator;
}
}
fComparator = new Comparator<Object>() {
@Override
public int compare(final Object o1, final Object o2) {
final String s1 = o1 instanceof IResource ? ((IResource) o1)
.getName() : (String) o1;
final String s2 = o2 instanceof IResource ? ((IResource) o2)
.getName() : (String) o2;
if (s1.startsWith(patternText)) {
return -1;
}
if (s2.startsWith(patternText)) {
return 1;
}
final int result = fCollator.compare(s1, s2);
return result;
}
};
}
return fComparator;
}
@Override
protected void fillContentProvider(final AbstractContentProvider contentProvider,
final ItemsFilter itemsFilter, final IProgressMonitor progressMonitor)
throws CoreException {
if (itemsFilter instanceof ModuleFilter) {
container.accept(new ModuleProxyVisitor(contentProvider,
(ModuleFilter) itemsFilter, progressMonitor), IResource.NONE);
}
if (progressMonitor != null) {
progressMonitor.done();
}
}
/**
* A label provider for details of ResourceItem objects.
*/
class ModuleItemDetailsLabelProvider extends ModuleItemLabelProvider {
@Override
public Image getImage(final Object element) {
if (!(element instanceof IResource)) {
return super.getImage(element);
}
final IResource parent = ((IResource) element).getParent();
return provider.getImage(parent);
}
@Override
public String getText(final Object element) {
if (!(element instanceof IResource)) {
return super.getText(element);
}
final IResource parent = ((IResource) element).getParent();
if (parent.getType() == IResource.ROOT) {
// Get readable name for workspace root ("Workspace"), without
// duplicating language-specific string here.
return null;
}
return parent.getProjectRelativePath().makeRelative().toString() + " - "
+ parent.getProject().getName();
}
@Override
public void labelProviderChanged(final LabelProviderChangedEvent event) {
final Object[] l = super.listeners.getListeners();
for (int i = 0; i < super.listeners.size(); i++) {
((ILabelProviderListener) l[i]).labelProviderChanged(event);
}
}
}
/**
* Viewer filter which filters resources due to current working set
*/
static class CustomWorkingSetFilter extends ViewerFilter {
private final ResourceWorkingSetFilter resourceWorkingSetFilter = new ResourceWorkingSetFilter();
/**
* Returns the active working set the filter is working with.
*
* @return the active working set
*/
public IWorkingSet getWorkingSet() {
return resourceWorkingSetFilter.getWorkingSet();
}
/**
* Sets the active working set.
*
* @param workingSet
* the working set the filter should work with
*/
public void setWorkingSet(final IWorkingSet workingSet) {
resourceWorkingSetFilter.setWorkingSet(workingSet);
}
@Override
public boolean select(final Viewer viewer, final Object parentElement,
final Object element) {
return resourceWorkingSetFilter.select(viewer, parentElement, element);
}
}
/**
* ResourceProxyVisitor to visit resource tree and get matched resources.
* During visit resources it updates progress monitor and adds matched
* resources to ContentProvider instance.
*/
private class ModuleProxyVisitor implements IResourceProxyVisitor {
private final AbstractContentProvider proxyContentProvider;
private final ModuleFilter moduleFilter;
private final IProgressMonitor progressMonitor;
private final List<IResource> projects;
private final Set<IPath> validPaths = new HashSet<IPath>();
private final Set<IPath> extraLocations = Sets.newHashSet();
/**
* Creates new ResourceProxyVisitor instance.
*
* @param contentProvider
* @param moduleFilter
* @param progressMonitor
* @throws CoreException
*/
public ModuleProxyVisitor(final AbstractContentProvider contentProvider,
final ModuleFilter moduleFilter, final IProgressMonitor progressMonitor)
throws CoreException {
super();
proxyContentProvider = contentProvider;
this.moduleFilter = moduleFilter;
this.progressMonitor = progressMonitor;
final IResource[] resources = container.members();
projects = new ArrayList<IResource>(Arrays.asList(resources));
extraLocations.addAll(SourcePathUtils.getExtraSourcePaths());
if (progressMonitor != null) {
progressMonitor.beginTask("Searching", projects.size());
}
}
@Override
public boolean visit(final IResourceProxy proxy) {
if (progressMonitor.isCanceled()) {
return false;
}
if (!proxy.isAccessible()) {
return false;
}
if (proxy.isDerived()) {
return false;
}
final IResource resource = proxy.requestResource();
final IProject project = resource.getProject();
final boolean accessible = project != null && project.isAccessible();
if (project != null && !accessible) {
return false;
}
if (projects.remove(project) || projects.remove(resource)) {
progressMonitor.worked(1);
if (accessible) {
addPaths(project);
}
}
if (project == resource && accessible) {
addPathFiltersToContentProvider(project);
}
if (CommonUtils.isErlangFileContentFileName(resource.getName())
&& !isLostFound(resource.getProjectRelativePath())) {
final IContainer my_container = resource.getParent();
if (validPaths.contains(my_container.getFullPath())
|| !extraLocations.isEmpty()
&& extraLocations.contains(my_container.getLocation())) {
proxyContentProvider.add(resource, moduleFilter);
}
}
if (resource.getType() == IResource.FILE) {
return false;
}
return true;
}
private void addPathFiltersToContentProvider(final IProject project) {
// FIXME (JC) all this seems too much... is it really necessary?
// couldn't we just assume all links in external files should be
// matchable?
final IErlElementLocator model = ErlangEngine.getInstance().getModel();
final IErlProject erlProject = model.findProject(project);
if (erlProject != null) {
final ErlangProjectProperties properties = erlProject.getProperties();
final String extMods = properties.getExternalModules();
final List<String> files = new ArrayList<String>();
files.addAll(PreferencesUtils.unpackList(extMods));
final String extIncs = properties.getExternalIncludes();
files.addAll(PreferencesUtils.unpackList(extIncs));
final IPathVariableManager pvm = ResourcesPlugin.getWorkspace()
.getPathVariableManager();
for (final String file : files) {
IResource fres;
try {
fres = ResourceUtil.recursiveFindNamedResource(project, file,
null);
} catch (final CoreException e) {
fres = null;
}
if (fres != null) {
addFilePathsFromFile(project, pvm, fres);
}
}
}
}
private void addFilePathsFromFile(final IProject project,
final IPathVariableManager pvm, final IResource fres) {
final List<String> lines = PreferencesUtils.readFile(fres.getLocation()
.toString());
for (final String pref : lines) {
String path;
final IPath p = new Path(pref);
final IPath v = URIUtil.toPath(pvm.resolveURI(URIUtil.toURI(p)));
if (v.isAbsolute()) {
path = v.toString();
} else {
path = project.getLocation().append(v).toString();
}
proxyContentProvider.add(path, moduleFilter);
}
}
private void addPaths(final IProject project) {
final IErlProject erlProject = ErlangEngine.getInstance().getModel()
.getErlangProject(project);
if (erlProject != null) {
validPaths.addAll(getFullPaths(project, erlProject.getProperties()
.getIncludeDirs()));
validPaths.addAll(getFullPaths(project, erlProject.getProperties()
.getSourceDirs()));
final Collection<IPath> extras = Lists.newArrayList();
for (final IPath p : SourcePathUtils.getExtraSourcePathsForModel(project)) {
extras.add(p);
}
validPaths.addAll(getFullPaths(project, extras));
}
}
private Set<IPath> getFullPaths(final IProject project,
final Collection<IPath> sourcePaths) {
final HashSet<IPath> result = new HashSet<IPath>();
for (final IPath path : sourcePaths) {
final String path_string = path.toString();
if (".".equals(path_string)) {
result.add(project.getFullPath());
} else {
result.add(project.getFolder(path).getFullPath());
}
}
return result;
}
}
protected class MatchAnySearchPattern extends SearchPattern {
public MatchAnySearchPattern() {
super(SearchPattern.RULE_PATTERN_MATCH);
}
@Override
public void setPattern(final String stringPattern) {
patternText = stringPattern;
if ("".equals(stringPattern)) {
super.setPattern(stringPattern);
} else {
super.setPattern("*" + stringPattern + "*");
}
}
}
/**
* Filters resources using pattern and showDerived flag. It overrides
* ItemsFilter.
*/
protected class ModuleFilter extends ItemsFilter {
private final IContainer filterContainer;
private final int filterTypeMask;
private final boolean allow_Hrl;
/**
* Creates new ResourceFilter instance
*
* @param container
* @param showDerived
* flag which determine showing derived elements
* @param typeMask
* @param searchPattern
*/
public ModuleFilter(final IContainer container, final int typeMask,
final boolean allowHrl, final SearchPattern searchPattern) {
super(searchPattern);
filterContainer = container;
filterTypeMask = typeMask;
allow_Hrl = allowHrl;
}
// /**
// * Creates new ResourceFilter instance
// */
// public ModuleFilter() {
// super();
// filterContainer = container;
// filterTypeMask = typeMask;
// allowHrl = true;
// }
/**
* @param item
* Must be instance of IResource, otherwise
* <code>false</code> will be returned.
* @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter#isConsistentItem(java.lang.Object)
*/
@Override
public boolean isConsistentItem(final Object item) {
if (item instanceof String) {
return true;
}
if (!(item instanceof IResource)) {
return false;
}
final IResource resource = (IResource) item;
if (filterContainer.findMember(resource.getFullPath()) != null) {
return true;
}
return false;
}
/**
* @param item
* Must be instance of IResource, otherwise
* <code>false</code> will be returned.
* @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter#matchItem(java.lang.Object)
*/
@Override
public boolean matchItem(final Object item) {
if (item instanceof String) {
final Path path = new Path((String) item);
return matches(path.lastSegment().toString());
}
if (!(item instanceof IResource)) {
return false;
}
final IResource resource = (IResource) item;
if ((filterTypeMask & resource.getType()) == 0) {
return false;
}
final String name = resource.getName();
if (!allow_Hrl && name.toLowerCase().endsWith(".hrl")) {
return false;
}
if (matches(name)) {
final ResourceAttributes attrs = resource.getResourceAttributes();
return attrs != null && !attrs.isSymbolicLink();
}
return false;
}
@Override
public boolean isSubFilter(final ItemsFilter filter) {
if (!super.isSubFilter(filter)) {
return false;
}
if (filter instanceof ModuleFilter) {
return true;
}
return false;
}
@Override
public boolean equalsFilter(final ItemsFilter iFilter) {
if (!super.equalsFilter(iFilter)) {
return false;
}
if (iFilter instanceof ModuleFilter) {
return true;
}
return false;
}
}
/**
* Extends the <code>SelectionHistory</code>, providing support for
* <code>OpenTypeHistory</code>.
*/
protected class ModuleSelectionHistory extends SelectionHistory {
/**
* Creates new instance of TypeSelectionHistory
*/
public ModuleSelectionHistory() {
super();
}
@Override
public synchronized void accessed(final Object object) {
super.accessed(object);
}
@Override
public synchronized boolean remove(final Object element) {
// OpenModuleHistory.getInstance().remove((TypeNameMatch) element);
return super.remove(element);
}
@Override
public void load(final IMemento memento) {
// TypeNameMatch[] types = OpenTypeHistory.getInstance()
// .getTypeInfos();
//
// for (int i = types.length - 1; i >= 0; i--) { // see
// // https://bugs.eclipse.org/bugs/show_bug.cgi?id=205314
// TypeNameMatch type = types[i];
// accessed(type);
// }
}
@Override
public void save(final IMemento memento) {
persistHistory();
}
/**
* Stores contents of the local history into persistent history
* container.
*/
private synchronized void persistHistory() {
// if (getReturnCode() == OK) {
// Object[] items = getHistoryItems();
// for (int i = 0; i < items.length; i++) {
// OpenTypeHistory.getInstance().accessed(
// (TypeNameMatch) items[i]);
// }
// }
}
@Override
protected Object restoreItemFromMemento(final IMemento element) {
return null;
}
@Override
protected void storeItemToMemento(final Object item, final IMemento element) {
}
}
}