blob: e3b74212b035fe500dccbb401c277be3fc600101 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Tasktop Technologies.
* 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:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.sandbox.search.ui.provider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.mylyn.sandbox.search.ui.SearchCallback;
import org.eclipse.mylyn.sandbox.search.ui.SearchCriteria;
import org.eclipse.mylyn.sandbox.search.ui.SearchProvider;
import org.eclipse.mylyn.sandbox.search.ui.SearchResult;
import org.eclipse.osgi.util.NLS;
/**
* A search provider that operates over java.io
*
* @author David Green
*/
public class BasicSearchProvider extends SearchProvider {
private abstract class FileMatcher {
public abstract boolean matches(IFileStore file, IProgressMonitor monitor);
}
private class CompositeFileMatcher extends FileMatcher {
private final List<FileMatcher> delegates;
private final boolean allMatch;
/**
* @param allMatch
* indicate if one delegate must match (false) or if all delegates must match (true)
*/
public CompositeFileMatcher(boolean allMatch) {
this.allMatch = allMatch;
delegates = new ArrayList<FileMatcher>();
}
public CompositeFileMatcher(List<FileMatcher> delegates, boolean allMatch) {
this.delegates = delegates;
this.allMatch = allMatch;
}
public void add(FileMatcher matcher) {
delegates.add(matcher);
}
@Override
public boolean matches(IFileStore file, IProgressMonitor monitor) {
for (FileMatcher matcher : delegates) {
if (monitor.isCanceled()) {
return false;
}
if (!matcher.matches(file, monitor)) {
if (allMatch) {
return false;
}
} else if (!allMatch) {
return true;
}
}
return allMatch ? true : false;
}
}
private class FileNameMatcher extends FileMatcher {
private final Pattern pattern;
public FileNameMatcher(String matchPattern) {
String regex = ".*?"; //$NON-NLS-1$
regex += patternToRegex(matchPattern);
regex += ".*"; //$NON-NLS-1$
pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
}
@Override
public boolean matches(IFileStore file, IProgressMonitor monitor) {
String name = file.getName();
return pattern.matcher(name).matches();
}
}
private class FileContentMatcher extends FileMatcher {
private final Pattern pattern;
private final int maxMatchingCharacters = 1024 * 16;
public FileContentMatcher(SearchCriteria searchSpecification) {
pattern = Pattern.compile(patternToRegex(searchSpecification.getText().trim()), Pattern.CASE_INSENSITIVE);
}
@Override
public boolean matches(IFileStore file, IProgressMonitor monitor) {
monitor.subTask(file.toString());
try {
InputStream inputStream = file.openInputStream(EFS.NONE, monitor);
try {
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(inputStream));
try {
ReaderCharSequence charSequence = new ReaderCharSequence(maxMatchingCharacters, reader, monitor);
return pattern.matcher(charSequence).find();
} finally {
reader.close();
}
} finally {
inputStream.close();
}
} catch (IOException e) {
// ignore
} catch (CoreException e) {
// ignore
}
return false;
}
}
@Override
public void performSearch(SearchCriteria searchSpecification, SearchCallback callback, IProgressMonitor m)
throws CoreException {
SubMonitor monitor = SubMonitor.convert(m);
monitor.beginTask(NLS.bind(Messages.BasicSearchProvider_0, searchSpecification.getText()),
searchSpecification.getMaximumResults() > 0
? searchSpecification.getMaximumResults()
: IProgressMonitor.UNKNOWN);
try {
FileMatcher matcher = computeMatcher(searchSpecification);
File[] roots = File.listRoots();
if (roots != null) {
int matchCount = 0;
Stack<IFileStore> state = new Stack<IFileStore>();
// reverse-order iteration so that the first one is the last pushed on the stack
for (int x = roots.length - 1; x >= 0; --x) {
if (monitor.isCanceled()) {
break;
}
File root = roots[x];
IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(root);
state.push(fileStore);
}
try {
while (!state.isEmpty() && !monitor.isCanceled()) {
IFileStore fileStore = state.pop();
IFileInfo fileInfo = fileStore.fetchInfo();
if (isDefaultIgnore(fileStore, fileInfo)) {
// ignore
} else if (fileInfo.isDirectory()) {
monitor.subTask(fileStore.toString());
IFileStore[] childStores = fileStore.childStores(EFS.NONE, monitor.newChild(0));
for (IFileStore child : childStores) {
state.push(child);
}
} else {
if (matcher.matches(fileStore, monitor.newChild(0))) {
monitor.worked(1);
callback.searchResult(new SearchResult(fileStore.toLocalFile(EFS.NONE,
monitor.newChild(0))));
if (++matchCount >= searchSpecification.getMaximumResults()) {
break;
}
}
}
}
} catch (OperationCanceledException oce) {
// ignore
} catch (CoreException e) {
if (e.getStatus().getSeverity() != IStatus.CANCEL) {
throw e;
}
}
}
} finally {
monitor.done();
}
}
private boolean isDefaultIgnore(IFileStore fileStore, IFileInfo fileInfo) {
if (fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK) || fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN)) {
// ignore, we don't follow symbolic links or hidden files
return true;
} else {
String name = fileStore.getName();
if (fileInfo.isDirectory()) {
if ((name.equals("Windows") || name.equals("$Recycle.Bin")) && fileStore.getParent() != null && fileStore.getParent().getParent() == null) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
} else if (name.startsWith(".")) { //$NON-NLS-1$
return true;
}
} else {
if (name.endsWith(".dll") || name.endsWith(".exe") || name.endsWith(".sys") || name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".bin")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
return true;
}
}
}
return false;
}
private FileMatcher computeMatcher(SearchCriteria searchSpecification) {
List<FileMatcher> filenameMatchers = new ArrayList<FileMatcher>();
for (String filenamePattern : searchSpecification.getFilenamePatterns()) {
if (filenamePattern.length() > 0) {
if (filenamePattern.equals("*") || filenamePattern.equals("*.*")) { //$NON-NLS-1$//$NON-NLS-2$
// every file matches
filenameMatchers.clear();
break;
} else {
filenameMatchers.add(new FileNameMatcher(filenamePattern));
}
}
}
CompositeFileMatcher fileMatcher = new CompositeFileMatcher(true);
if (!filenameMatchers.isEmpty()) {
fileMatcher.add(filenameMatchers.size() == 1 ? filenameMatchers.get(0) : new CompositeFileMatcher(
filenameMatchers, false));
}
if (searchSpecification.getText() != null && searchSpecification.getText().trim().length() > 0) {
fileMatcher.add(new FileContentMatcher(searchSpecification));
}
return fileMatcher;
}
private String patternToRegex(String matchPattern) {
String regex = ""; //$NON-NLS-1$
for (char c : matchPattern.toCharArray()) {
if (Character.isLetterOrDigit(c)) {
regex += c;
} else {
if (c == '*') {
regex += ".*"; //$NON-NLS-1$
} else if (c == '?') {
regex += "."; //$NON-NLS-1$
} else {
regex += "\\"; //$NON-NLS-1$
regex += c;
}
}
}
return regex;
}
}