/******************************************************************************* * Copyright (c) 2000, 2007 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 com.aptana.ide.search.epl.internal.filesystem.text; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.search.internal.core.text.DocumentCharSequence; import org.eclipse.search.internal.ui.Messages; import org.eclipse.search.internal.ui.SearchMessages; import org.eclipse.search.internal.ui.SearchPlugin; import org.eclipse.search.ui.NewSearchUI; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPathEditorInput; import org.eclipse.ui.IURIEditorInput; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.texteditor.ITextEditor; import com.aptana.ide.search.epl.filesystem.text.FileSystemTextSearchScope; import com.aptana.ide.search.epl.filesystem.text.FileTextSearchMatchAccess; import com.aptana.ide.search.epl.filesystem.text.FileTextSearchRequestor; /** * The visitor that does the actual work. */ public class FileTextSearchVisitor { /** * @author Pavel Petrochenko */ public static class ReusableMatchAccess extends FileTextSearchMatchAccess { private static final int MAX_LINE_LENGTH = 300; private int fOffset; private int fLength; private int lineNumber = -1; private File fFile; private CharSequence fContent; private String lineContent; /** * @return line number */ public int getLineNumber() { return this.lineNumber; } /** * @return line content */ public String getLineContent() { return this.lineContent; } /** * @param file * @param offset * @param length * @param content */ public void initialize(File file, int offset, int length, CharSequence content) { this.fFile = file; this.fOffset = offset; this.fLength = length; this.fContent = content; } /** * @param file * @param start * @param length * @param content * @param document */ public void initialize(File file, int start, int length, CharSequence content, IDocument document) { initialize(file, start, length, content); if (document != null) { try { this.lineNumber = document.getLineOfOffset(start); IRegion lineInformation = document.getLineInformation(this.lineNumber); if (lineInformation.getLength() < ReusableMatchAccess.MAX_LINE_LENGTH) { this.lineContent = document.get(lineInformation.getOffset(), lineInformation.getLength()); } else { int ka = start - ReusableMatchAccess.MAX_LINE_LENGTH / 3; if (ka < 0) { ka = 0; } int max = Math.min(document.getLength() - 1, ka + ReusableMatchAccess.MAX_LINE_LENGTH); this.lineContent = document.get(ka, max); } } catch (BadLocationException e) { } } else { int ka = start - ReusableMatchAccess.MAX_LINE_LENGTH / 3; if (ka < 0) { ka = 0; } int max = Math.min(content.length() - 1, ka + ReusableMatchAccess.MAX_LINE_LENGTH); this.lineContent = content.subSequence(ka, max).toString(); } } /** * @see com.aptana.ide.search.epl.filesystem.text.FileTextSearchMatchAccess#getFileSystemFile() */ public File getFileSystemFile() { return this.fFile; } /** * @see org.eclipse.search.core.text.TextSearchMatchAccess#getMatchOffset() */ public int getMatchOffset() { return this.fOffset; } /** * @see org.eclipse.search.core.text.TextSearchMatchAccess#getMatchLength() */ public int getMatchLength() { return this.fLength; } /** * @see org.eclipse.search.core.text.TextSearchMatchAccess#getFileContentLength() */ public int getFileContentLength() { return this.fContent.length(); } /** * @see org.eclipse.search.core.text.TextSearchMatchAccess#getFileContentChar(int) */ public char getFileContentChar(int offset) { return this.fContent.charAt(offset); } /** * @see org.eclipse.search.core.text.TextSearchMatchAccess#getFileContent(int, int) */ public String getFileContent(int offset, int length) { return this.fContent.subSequence(offset, offset + length).toString(); // must pass a copy! } } private final FileTextSearchRequestor fCollector; private final Matcher fMatcher; private Map fDocumentsInEditors; private IProgressMonitor fProgressMonitor; private int fNumberOfScannedFiles; private int fNumberOfFilesToScan; private File fCurrentFile; private final MultiStatus fStatus; private final FileCharSequenceProvider fFileCharSequenceProvider; private final ReusableMatchAccess fMatchAccess; /** * @param collector * @param searchPattern */ public FileTextSearchVisitor(FileTextSearchRequestor collector, Pattern searchPattern) { this.fCollector = collector; this.fStatus = new MultiStatus(NewSearchUI.PLUGIN_ID, IStatus.OK, SearchMessages.TextSearchEngine_statusMessage, null); this.fMatcher = searchPattern.pattern().length() == 0 ? null : searchPattern.matcher(new String()); this.fFileCharSequenceProvider = new FileCharSequenceProvider(); this.fMatchAccess = new ReusableMatchAccess(); } /** * @param files * @param monitor * @return */ public IStatus search(File[] files, IProgressMonitor monitor) { this.fProgressMonitor = monitor == null ? new NullProgressMonitor() : monitor; this.fNumberOfScannedFiles = 0; this.fNumberOfFilesToScan = files.length; this.fCurrentFile = null; Job monitorUpdateJob = new Job(SearchMessages.TextSearchVisitor_progress_updating_job) { private int fLastNumberOfScannedFiles = 0; public IStatus run(IProgressMonitor inner) { while (!inner.isCanceled()) { File file = FileTextSearchVisitor.this.fCurrentFile; if (file != null) { String fileName = file.getName(); Object[] args = { fileName, new Integer(FileTextSearchVisitor.this.fNumberOfScannedFiles), new Integer(FileTextSearchVisitor.this.fNumberOfFilesToScan) }; FileTextSearchVisitor.this.fProgressMonitor.subTask(Messages.format( SearchMessages.TextSearchVisitor_scanning, args)); int steps = FileTextSearchVisitor.this.fNumberOfScannedFiles - this.fLastNumberOfScannedFiles; FileTextSearchVisitor.this.fProgressMonitor.worked(steps); this.fLastNumberOfScannedFiles += steps; } try { Thread.sleep(100); } catch (InterruptedException e) { return Status.OK_STATUS; } } return Status.OK_STATUS; } }; try { String taskName = this.fMatcher == null ? SearchMessages.TextSearchVisitor_filesearch_task_label : Messages .format(SearchMessages.TextSearchVisitor_textsearch_task_label, this.fMatcher.pattern().pattern()); this.fProgressMonitor.beginTask(taskName, this.fNumberOfFilesToScan); monitorUpdateJob.setSystem(true); monitorUpdateJob.schedule(); try { initDocuments(); this.fCollector.beginReporting(); this.processFiles(files); return this.fStatus; } finally { monitorUpdateJob.cancel(); } } finally { this.fProgressMonitor.done(); this.fCollector.endReporting(); } } /** * @param scope * @param monitor * @return result */ public IStatus search(FileSystemTextSearchScope scope, IProgressMonitor monitor) { monitor.setTaskName(com.aptana.ide.search.epl.internal.filesystem.text.Messages.START_SEARCH); File[] evaluateFilesInScope = scope.evaluateFilesInScope(this.fStatus); monitor.setTaskName(com.aptana.ide.search.epl.internal.filesystem.text.Messages.PERFORM_SEARCH); return this.search(evaluateFilesInScope, monitor); } /** * */ public void initDocuments() { this.fDocumentsInEditors = this.evalNonFileBufferDocuments(); } private void processFiles(File[] files) { this.fDocumentsInEditors = this.evalNonFileBufferDocuments(); for (int i = 0; i < files.length; i++) { this.fCurrentFile = files[i]; boolean res = this.processFile(this.fCurrentFile); if (!res) { break; } } this.fDocumentsInEditors = null; } /** * @return returns a map from File to IDocument for all open, dirty editors */ private Map evalNonFileBufferDocuments() { Map result = new HashMap(); IWorkbench workbench = SearchPlugin.getDefault().getWorkbench(); IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); for (int i = 0; i < windows.length; i++) { IWorkbenchPage[] pages = windows[i].getPages(); for (int x = 0; x < pages.length; x++) { IEditorReference[] editorRefs = pages[x].getEditorReferences(); for (int z = 0; z < editorRefs.length; z++) { IEditorPart ep = editorRefs[z].getEditor(false); if ((ep instanceof ITextEditor) && ep.isDirty()) { // only dirty editors this.evaluateTextEditor(result, ep); } } } } return result; } private void evaluateTextEditor(Map result, IEditorPart ep) { IEditorInput input = ep.getEditorInput(); if (input instanceof IPathEditorInput) { File file = new File(((IPathEditorInput) input).getPath().toOSString()); processFile(result, ep, input, file); } else if (input instanceof IURIEditorInput) { URI uri = ((IURIEditorInput) input).getURI(); if ("file".equals(uri.getScheme())) { processFile(result, ep, input, new File(uri)); } } else if (input instanceof IFileEditorInput){ IFileEditorInput fi=(IFileEditorInput) input; processFile(result, ep, input, fi.getFile().getLocation().toFile()); } } private void processFile(Map result, IEditorPart ep, IEditorInput input, File file) { if (!result.containsKey(file)) { // take the first editor found ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(new Path(file.getAbsolutePath()), LocationKind.LOCATION); if (textFileBuffer != null) { // file buffer has precedence result.put(file, textFileBuffer.getDocument()); } else { // use document provider IDocument document = ((ITextEditor) ep).getDocumentProvider().getDocument(input); if (document != null) { result.put(file, document); } } } } /** * @param file * @return */ public boolean processFile(File file) { try { if (!this.fCollector.acceptFile(file) || (this.fMatcher == null)) { return true; } IDocument document = this.getOpenDocument(file); if (document != null) { DocumentCharSequence documentCharSequence = new DocumentCharSequence(document); // assume all documents are non-binary this.locateMatches(file, documentCharSequence, document); } else { CharSequence seq = null; try { seq = this.fFileCharSequenceProvider.newCharSequence(file); Document doc = null; if (seq.length() < 1000 * 1000) { String string = seq.toString(); doc = new Document(string); seq = string; } if (!this.fCollector.reportBinaryFile(file) && this.hasBinaryContent(seq, file)) { return true; } this.locateMatches(file, seq, doc); } catch (FileCharSequenceProvider.FileCharSequenceException e) { e.throwWrappedException(); } finally { if (seq != null) { try { this.fFileCharSequenceProvider.releaseCharSequence(seq); } catch (IOException e) { SearchPlugin.log(e); } } } } } catch (UnsupportedCharsetException e) { String[] args = { this.getCharSetName(file), file.getAbsolutePath().toString() }; String message = Messages.format(SearchMessages.TextSearchVisitor_unsupportedcharset, args); this.fStatus.add(new Status(IStatus.WARNING, NewSearchUI.PLUGIN_ID, IStatus.WARNING, message, e)); } catch (IllegalCharsetNameException e) { String[] args = { this.getCharSetName(file), file.getAbsolutePath().toString() }; String message = Messages.format(SearchMessages.TextSearchVisitor_illegalcharset, args); this.fStatus.add(new Status(IStatus.WARNING, NewSearchUI.PLUGIN_ID, IStatus.WARNING, message, e)); } catch (IOException e) { String[] args = { this.getExceptionMessage(e), file.getAbsolutePath().toString() }; String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); this.fStatus.add(new Status(IStatus.WARNING, NewSearchUI.PLUGIN_ID, IStatus.WARNING, message, e)); } catch (CoreException e) { String[] args = { this.getExceptionMessage(e), file.getAbsolutePath().toString() }; String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); this.fStatus.add(new Status(IStatus.WARNING, NewSearchUI.PLUGIN_ID, IStatus.WARNING, message, e)); } catch (StackOverflowError e) { String message = SearchMessages.TextSearchVisitor_patterntoocomplex0; this.fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); return false; } finally { this.fNumberOfScannedFiles++; } if (this.fProgressMonitor.isCanceled()) { throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); } return true; } private boolean hasBinaryContent(CharSequence seq, File file) throws CoreException { try { int limit = FileCharSequenceProvider.BUFFER_SIZE; for (int i = 0; i < limit; i++) { if (seq.charAt(i) == '\0') { return true; } } } catch (IndexOutOfBoundsException e) { } return false; } private void locateMatches(File file, CharSequence searchInput, IDocument document) throws CoreException { try { this.fMatcher.reset(searchInput); int k = 0; while (this.fMatcher.find()) { int start = this.fMatcher.start(); int end = this.fMatcher.end(); if (end != start) { // don't report 0-length matches //converting from character indices to code point indices start = Character.codePointCount(searchInput, 0, start); end = Character.codePointCount(searchInput, 0, end); //reporting this.fMatchAccess.initialize(file, start, end - start, searchInput, document); boolean res = this.fCollector.acceptPatternMatch(this.fMatchAccess); if (!res) { return; // no further reporting requested } } if (k++ == 20) { if (this.fProgressMonitor.isCanceled()) { throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); } k = 0; } } } finally { this.fMatchAccess.initialize(null, 0, 0, new String()); // clear references } } private String getExceptionMessage(Exception e) { String message = e.getLocalizedMessage(); if (message == null) { return e.getClass().getName(); } return message; } private IDocument getOpenDocument(File file) { IDocument document = (IDocument) this.fDocumentsInEditors.get(file); if (document == null) { ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(new Path(file.getAbsolutePath()), LocationKind.LOCATION); if (textFileBuffer != null) { document = textFileBuffer.getDocument(); } } return document; } private String getCharSetName(File file) { return Charset.defaultCharset().name(); } /** * @return */ public IProgressMonitor getFProgressMonitor() { return this.fProgressMonitor; } /** * @param progressMonitor */ public void setFProgressMonitor(IProgressMonitor progressMonitor) { this.fProgressMonitor = progressMonitor; } }