001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.console;
011:
012: import java.util.ArrayList;
013: import java.util.Iterator;
014: import java.util.regex.Matcher;
015: import java.util.regex.Pattern;
016:
017: import org.eclipse.core.runtime.IProgressMonitor;
018: import org.eclipse.core.runtime.IStatus;
019: import org.eclipse.core.runtime.Status;
020: import org.eclipse.core.runtime.jobs.Job;
021: import org.eclipse.jface.text.BadLocationException;
022: import org.eclipse.jface.text.DocumentEvent;
023: import org.eclipse.jface.text.IDocument;
024: import org.eclipse.jface.text.IDocumentListener;
025: import org.eclipse.ui.console.ConsolePlugin;
026: import org.eclipse.ui.console.IPatternMatchListener;
027: import org.eclipse.ui.console.PatternMatchEvent;
028: import org.eclipse.ui.console.TextConsole;
029:
030: public class ConsolePatternMatcher implements IDocumentListener {
031:
032: private MatchJob fMatchJob = new MatchJob();
033:
034: /**
035: * Collection of compiled pattern match listeners
036: */
037: private ArrayList fPatterns = new ArrayList();
038:
039: private TextConsole fConsole;
040:
041: private boolean fFinalMatch;
042:
043: private boolean fScheduleFinal;
044:
045: public ConsolePatternMatcher(TextConsole console) {
046: fConsole = console;
047: }
048:
049: private class MatchJob extends Job {
050: MatchJob() {
051: super ("Match Job"); //$NON-NLS-1$
052: setSystem(true);
053: }
054:
055: /*
056: * (non-Javadoc)
057: *
058: * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
059: */
060: protected IStatus run(IProgressMonitor monitor) {
061: IDocument doc = fConsole.getDocument();
062: String text = null;
063: int prevBaseOffset = -1;
064: if (doc != null && !monitor.isCanceled()) {
065: int endOfSearch = doc.getLength();
066: int indexOfLastChar = endOfSearch;
067: if (indexOfLastChar > 0) {
068: indexOfLastChar--;
069: }
070: int lastLineToSearch = 0;
071: int offsetOfLastLineToSearch = 0;
072: try {
073: lastLineToSearch = doc
074: .getLineOfOffset(indexOfLastChar);
075: offsetOfLastLineToSearch = doc
076: .getLineOffset(lastLineToSearch);
077: } catch (BadLocationException e) {
078: // perhaps the buffer was re-set
079: return Status.OK_STATUS;
080: }
081: for (int i = 0; i < fPatterns.size(); i++) {
082: if (monitor.isCanceled()) {
083: break;
084: }
085: CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) fPatterns
086: .get(i);
087: int baseOffset = notifier.end;
088: int lengthToSearch = endOfSearch - baseOffset;
089: if (lengthToSearch > 0) {
090: try {
091: if (prevBaseOffset != baseOffset) {
092: // reuse the text string if possible
093: text = doc.get(baseOffset,
094: lengthToSearch);
095: }
096: Matcher reg = notifier.pattern
097: .matcher(text);
098: Matcher quick = null;
099: if (notifier.qualifier != null) {
100: quick = notifier.qualifier
101: .matcher(text);
102: }
103: int startOfNextSearch = 0;
104: int endOfLastMatch = -1;
105: int lineOfLastMatch = -1;
106: while ((startOfNextSearch < lengthToSearch)
107: && !monitor.isCanceled()) {
108: if (quick != null) {
109: if (quick.find(startOfNextSearch)) {
110: // start searching on the beginning
111: // of the line where the potential
112: // match was found, or after the
113: // last match on the same line
114: int matchLine = doc
115: .getLineOfOffset(baseOffset
116: + quick.start());
117: if (lineOfLastMatch == matchLine) {
118: startOfNextSearch = endOfLastMatch;
119: } else {
120: startOfNextSearch = doc
121: .getLineOffset(matchLine)
122: - baseOffset;
123: }
124: } else {
125: startOfNextSearch = lengthToSearch;
126: }
127: }
128: if (startOfNextSearch < 0) {
129: startOfNextSearch = 0;
130: }
131: if (startOfNextSearch < lengthToSearch) {
132: if (reg.find(startOfNextSearch)) {
133: endOfLastMatch = reg.end();
134: lineOfLastMatch = doc
135: .getLineOfOffset(baseOffset
136: + endOfLastMatch
137: - 1);
138: int regStart = reg.start();
139: IPatternMatchListener listener = notifier.listener;
140: if (listener != null
141: && !monitor
142: .isCanceled()) {
143: listener
144: .matchFound(new PatternMatchEvent(
145: fConsole,
146: baseOffset
147: + regStart,
148: endOfLastMatch
149: - regStart));
150: }
151: startOfNextSearch = endOfLastMatch;
152: } else {
153: startOfNextSearch = lengthToSearch;
154: }
155: }
156: }
157: // update start of next search to the last line
158: // searched
159: // or the end of the last match if it was on the
160: // line that
161: // was last searched
162: if (lastLineToSearch == lineOfLastMatch) {
163: notifier.end = baseOffset
164: + endOfLastMatch;
165: } else {
166: notifier.end = offsetOfLastLineToSearch;
167: }
168: } catch (BadLocationException e) {
169: ConsolePlugin.log(e);
170: }
171: }
172: prevBaseOffset = baseOffset;
173: }
174: }
175:
176: if (fFinalMatch) {
177: disconnect();
178: fConsole.matcherFinished();
179: } else if (fScheduleFinal) {
180: fFinalMatch = true;
181: schedule();
182: }
183:
184: return Status.OK_STATUS;
185: }
186:
187: public boolean belongsTo(Object family) {
188: return family == fConsole;
189: }
190:
191: }
192:
193: private class CompiledPatternMatchListener {
194: Pattern pattern;
195:
196: Pattern qualifier;
197:
198: IPatternMatchListener listener;
199:
200: int end = 0;
201:
202: CompiledPatternMatchListener(Pattern pattern,
203: Pattern qualifier, IPatternMatchListener matchListener) {
204: this .pattern = pattern;
205: this .listener = matchListener;
206: this .qualifier = qualifier;
207: }
208:
209: public void dispose() {
210: listener.disconnect();
211: pattern = null;
212: qualifier = null;
213: listener = null;
214: }
215: }
216:
217: /**
218: * Adds the given pattern match listener to this console. The listener will
219: * be connected and receive match notifications.
220: *
221: * @param matchListener
222: * the pattern match listener to add
223: */
224: public void addPatternMatchListener(
225: IPatternMatchListener matchListener) {
226: synchronized (fPatterns) {
227:
228: // check for dups
229: for (Iterator iter = fPatterns.iterator(); iter.hasNext();) {
230: CompiledPatternMatchListener element = (CompiledPatternMatchListener) iter
231: .next();
232: if (element.listener == matchListener) {
233: return;
234: }
235: }
236:
237: if (matchListener == null
238: || matchListener.getPattern() == null) {
239: throw new IllegalArgumentException(
240: "Pattern cannot be null"); //$NON-NLS-1$
241: }
242:
243: Pattern pattern = Pattern.compile(matchListener
244: .getPattern(), matchListener.getCompilerFlags());
245: String qualifier = matchListener.getLineQualifier();
246: Pattern qPattern = null;
247: if (qualifier != null) {
248: qPattern = Pattern.compile(qualifier, matchListener
249: .getCompilerFlags());
250: }
251: CompiledPatternMatchListener notifier = new CompiledPatternMatchListener(
252: pattern, qPattern, matchListener);
253: fPatterns.add(notifier);
254: matchListener.connect(fConsole);
255: fMatchJob.schedule();
256: }
257: }
258:
259: /**
260: * Removes the given pattern match listener from this console. The listener
261: * will be disconnected and will no longer receive match notifications.
262: *
263: * @param matchListener
264: * the pattern match listener to remove.
265: */
266: public void removePatternMatchListener(
267: IPatternMatchListener matchListener) {
268: synchronized (fPatterns) {
269: for (Iterator iter = fPatterns.iterator(); iter.hasNext();) {
270: CompiledPatternMatchListener element = (CompiledPatternMatchListener) iter
271: .next();
272: if (element.listener == matchListener) {
273: iter.remove();
274: matchListener.disconnect();
275: }
276: }
277: }
278: }
279:
280: public void disconnect() {
281: fMatchJob.cancel();
282: synchronized (fPatterns) {
283: Iterator iterator = fPatterns.iterator();
284: while (iterator.hasNext()) {
285: CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iterator
286: .next();
287: notifier.dispose();
288: }
289: fPatterns.clear();
290: }
291: }
292:
293: /*
294: * (non-Javadoc)
295: *
296: * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
297: */
298: public void documentAboutToBeChanged(DocumentEvent event) {
299: }
300:
301: /*
302: * (non-Javadoc)
303: *
304: * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
305: */
306: public void documentChanged(DocumentEvent event) {
307: if (event.fLength > 0) {
308: synchronized (fPatterns) {
309: if (event.fDocument.getLength() == 0) {
310: // document has been cleared, reset match listeners
311: Iterator iter = fPatterns.iterator();
312: while (iter.hasNext()) {
313: CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iter
314: .next();
315: notifier.end = 0;
316: }
317: } else {
318: if (event.fOffset == 0) {
319: //document was trimmed
320: Iterator iter = fPatterns.iterator();
321: while (iter.hasNext()) {
322: CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iter
323: .next();
324: notifier.end = notifier.end > event.fLength ? notifier.end
325: - event.fLength
326: : 0;
327: }
328: }
329: }
330: }
331: }
332: fMatchJob.schedule();
333: }
334:
335: public void forceFinalMatching() {
336: fScheduleFinal = true;
337: fMatchJob.schedule();
338: }
339:
340: }
|