001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model;
038:
039: import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelStates;
040:
041: import edu.rice.cs.util.UnexpectedException;
042: import edu.rice.cs.util.swing.DocumentIterator;
043: import edu.rice.cs.util.swing.Utilities;
044: import edu.rice.cs.util.text.AbstractDocumentInterface;
045: import edu.rice.cs.util.Lambda;
046: import edu.rice.cs.util.Log;
047: import edu.rice.cs.util.StringOps;
048:
049: import java.awt.EventQueue;
050:
051: import javax.swing.text.BadLocationException;
052: import javax.swing.text.Position;
053:
054: /** Implementation of logic of find/replace over a document.
055: * @version $Id: FindReplaceMachine.java 4255 2007-08-28 19:17:37Z mgricken $
056: */
057: public class FindReplaceMachine {
058:
059: // TODO: is _start still used in any way that matters?
060:
061: static private Log _log = new Log("FindReplace.txt", false);
062:
063: /* Visible machine state; manipulated directly or indirectly by FindReplacePanel. */
064: private OpenDefinitionsDocument _doc; // Current search document
065: private OpenDefinitionsDocument _firstDoc; // First document where searching started (when searching all documents)
066: // private Position _current; // Position of the cursor in _doc when machine is stopped
067: private int _current; // Position of the cursor in _doc when machine is stopped
068: // private Position _start; // Position in _doc from which searching started or will start.
069: private String _findWord; // Word to find. */
070: private String _replaceWord; // Word to replace _findword.
071: private boolean _matchCase;
072: private boolean _matchWholeWord;
073: private boolean _searchAllDocuments; // Whether to search all documents (or just the current document)
074: private boolean _isForward; // Whether search direction is forward (false means backward)
075: private boolean _ignoreCommentsAndStrings; // Whether to ignore matches in comments and strings
076: private String _lastFindWord; // Last word found; set to null by FindReplacePanel if caret is updated
077: private boolean _skipText; // Whether to skip over the current match if direction is reversed
078: private DocumentIterator _docIterator; // An iterator of open documents; _doc is current
079: private SingleDisplayModel _model;
080:
081: /** Standard Constructor.
082: * Creates new machine to perform find/replace operations on a particular document starting from a given position.
083: * @param docIterator an object that allows navigation through open Swing documents (it is DefaultGlobalModel)
084: * @exception BadLocationException
085: */
086: public FindReplaceMachine(SingleDisplayModel model,
087: DocumentIterator docIterator) {
088: _skipText = false;
089: // _checkAllDocsWrapped = false;
090: // _allDocsWrapped = false;
091: _model = model;
092: _docIterator = docIterator;
093: _current = -1;
094: setFindAnyOccurrence();
095: setFindWord("");
096: setReplaceWord("");
097: setSearchBackwards(false);
098: setMatchCase(true);
099: setSearchAllDocuments(false);
100: setIgnoreCommentsAndStrings(false);
101: }
102:
103: public void cleanUp() {
104: _docIterator = null;
105: setFindWord("");
106: _doc = null;
107: }
108:
109: /** Called when the current position is updated in the document implying _skipText should not be set
110: * if the user toggles _searchBackwards
111: */
112: public void positionChanged() {
113: _lastFindWord = null;
114: _skipText = false;
115: }
116:
117: public void setLastFindWord() {
118: _lastFindWord = _findWord;
119: }
120:
121: public boolean getSearchBackwards() {
122: return !_isForward;
123: }
124:
125: public void setSearchBackwards(boolean searchBackwards) {
126: if (_isForward == searchBackwards) {
127: // If we switch from searching forward to searching backwards or viceversa, isOnMatch is true, and _findword is the
128: // same as the _lastFindWord, we know the user just found _findWord, so skip over this match.
129: if (onMatch() && _findWord.equals(_lastFindWord))
130: _skipText = true;
131: else
132: _skipText = false;
133: }
134: _isForward = !searchBackwards;
135: }
136:
137: public void setMatchCase(boolean matchCase) {
138: _matchCase = matchCase;
139: }
140:
141: public void setMatchWholeWord() {
142: _matchWholeWord = true;
143: }
144:
145: public void setFindAnyOccurrence() {
146: _matchWholeWord = false;
147: }
148:
149: public void setSearchAllDocuments(boolean searchAllDocuments) {
150: _searchAllDocuments = searchAllDocuments;
151: }
152:
153: public void setIgnoreCommentsAndStrings(
154: boolean ignoreCommentsAndStrings) {
155: _ignoreCommentsAndStrings = ignoreCommentsAndStrings;
156: }
157:
158: public void setDocument(OpenDefinitionsDocument doc) {
159: _doc = doc;
160: }
161:
162: public void setFirstDoc(OpenDefinitionsDocument firstDoc) {
163: _firstDoc = firstDoc;
164: }
165:
166: public void setPosition(int pos) {
167: // System.err.println("Setting position " + pos + " in doc [" + _doc.getText() + "]");
168: // assert (pos >= 0) && (pos <= _doc.getLength());
169: //try { //_current = _doc.createPosition(pos);
170: _current = pos;
171: //}
172: //catch (BadLocationException ble) { throw new UnexpectedException(ble); }
173: }
174:
175: /** Gets the character offset to which this machine is currently pointing. */
176: public int getCurrentOffset() { //return _current.getOffset();
177: return _current;
178: }
179:
180: public String getFindWord() {
181: return _findWord;
182: }
183:
184: public String getReplaceWord() {
185: return _replaceWord;
186: }
187:
188: public boolean getSearchAllDocuments() {
189: return _searchAllDocuments;
190: }
191:
192: public OpenDefinitionsDocument getDocument() {
193: return _doc;
194: }
195:
196: public OpenDefinitionsDocument getFirstDoc() {
197: return _firstDoc;
198: }
199:
200: /** Change the word being sought.
201: * @param word the new word to seek
202: */
203: public void setFindWord(String word) {
204: _findWord = StringOps.replace(word, StringOps.EOL, "\n");
205: }
206:
207: /** Change the replacing word.
208: * @param word the new replacing word
209: */
210: public void setReplaceWord(String word) {
211: _replaceWord = StringOps.replace(word, StringOps.EOL, "\n");
212: }
213:
214: /** Determine if the machine is on an instance of the find word. Only executes in event thread except for
215: * initialization.
216: * @return true if the current position is right after an instance of the find word.
217: */
218: public boolean onMatch() {
219:
220: // assert EventQueue.isDispatchThread();
221:
222: String findWord = _findWord;
223: int wordLen, off;
224:
225: if (_current == -1)
226: return false;
227:
228: wordLen = findWord.length();
229: if (_isForward)
230: off = getCurrentOffset() - wordLen;
231: else
232: off = getCurrentOffset();
233:
234: if (off < 0)
235: return false;
236:
237: String matchSpace;
238: try {
239: if (off + wordLen > _doc.getLength())
240: return false;
241: matchSpace = _doc.getText(off, wordLen);
242: } catch (BadLocationException e) {
243: throw new UnexpectedException(e);
244: }
245:
246: if (!_matchCase) {
247: matchSpace = matchSpace.toLowerCase();
248: findWord = findWord.toLowerCase();
249: }
250: return matchSpace.equals(findWord);
251: }
252:
253: /** If we're on a match for the find word, replace it with the replace word. Only executes in event thread. */
254: public boolean replaceCurrent() {
255:
256: assert EventQueue.isDispatchThread();
257:
258: if (!onMatch())
259: return false;
260: _doc.acquireWriteLock();
261: try {
262: // boolean atStart = false;
263: int offset = getCurrentOffset();
264: if (_isForward)
265: offset -= _findWord.length(); // position is now on left edge of match
266: // assert _findWord.equals(_doc.getText(offset, _findWord.length()));
267:
268: // Utilities.show("ReplaceCurrent called. _doc = " + _doc.getText() + " offset = " + offset + " _findWord = " + _findWord);
269:
270: _doc.remove(offset, _findWord.length());
271:
272: // if (position == 0) atStart = true;
273: _doc.insertString(offset, _replaceWord, null);
274:
275: // update _current Position
276: if (_isForward)
277: setPosition(offset + _replaceWord.length());
278: else
279: setPosition(offset);
280:
281: return true;
282: } catch (BadLocationException e) {
283: throw new UnexpectedException(e);
284: } finally {
285: _doc.releaseWriteLock();
286: }
287: }
288:
289: /** Replaces all occurences of the find word with the replace word in the current document of in all documents
290: * depending the value of the machine register _searchAllDocuments.
291: * @return the number of replacements
292: */
293: public int replaceAll() {
294: return replaceAll(_searchAllDocuments);
295: }
296:
297: /** Replaces all occurences of the find word with the replace word in the current document of in all documents
298: * depending the value of the flag searchAll.
299: * @return the number of replacements
300: */
301: private int replaceAll(boolean searchAll) {
302: if (searchAll) {
303: OpenDefinitionsDocument startDoc = _doc;
304: int count = 0; // the number of replacements done so farr
305: int n = _docIterator.getDocumentCount();
306: for (int i = 0; i < n; i++) {
307: // replace all in the rest of the documents
308: count += _replaceAllInCurrentDoc();
309: _doc = _docIterator.getNextDocument(_doc);
310: }
311:
312: // update display (adding "*") in navigatgorPane
313: _model.getDocumentNavigator().repaint();
314:
315: return count;
316: } else
317: return _replaceAllInCurrentDoc();
318: }
319:
320: /** Replaces all occurences of _findWord with _replaceWord in _doc. Never searches in other documents. Starts at
321: * the beginning or the end of the document (depending on find direction). This convention ensures that matches
322: * created by string replacement will not be replaced as in the following example:<p>
323: * findString: "hello"<br>
324: * replaceString: "e"<br>
325: * document text: "hhellollo"<p>
326: * Depending on the cursor position, clicking replace all could either make the document text read "hello"
327: * (which is correct) or "e". This is because of the behavior of findNext(), and it would be incorrect
328: * to change that behavior. Only executes in event thread.
329: * @return the number of replacements
330: */
331: private int _replaceAllInCurrentDoc() {
332:
333: assert EventQueue.isDispatchThread();
334:
335: if (_isForward)
336: setPosition(0);
337: else
338: setPosition(_doc.getLength());
339:
340: int count = 0;
341: FindResult fr = findNext(false); // find next match in current doc
342: // Utilities.show(fr + " returned by call on findNext()");
343:
344: while (!fr.getWrapped()) {
345: replaceCurrent(); // sets writeLock so that other threads do not see inconsistent state
346: count++;
347: // Utilities.show("Found " + count + " occurrences. Calling findNext() inside loop");
348: fr = findNext(false); // find next match in current doc
349: // Utilities.show("Call on findNext() returned " + fr.toString() + "in doc '" + _doc.getText() + "'");
350: }
351: return count;
352: }
353:
354: /** Processes all occurences of the find word with the replace word in the current document or in all documents
355: * depending the value of the machine register _searchAllDocuments.
356: * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
357: * @return the number of processed occurrences
358: */
359: public int processAll(Lambda<Void, FindResult> findAction) {
360: return processAll(findAction, _searchAllDocuments);
361: }
362:
363: /** Processes all occurences of the find word with the replace word in the current document of in all documents
364: * depending the value of the flag searchAll. Only executes in event thread.
365: * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
366: * @return the number of replacements
367: */
368: private int processAll(Lambda<Void, FindResult> findAction,
369: boolean searchAll) {
370:
371: assert EventQueue.isDispatchThread();
372:
373: if (searchAll) {
374: OpenDefinitionsDocument startDoc = _doc;
375: int count = 0; // the number of replacements done so farr
376: int n = _docIterator.getDocumentCount();
377: for (int i = 0; i < n; i++) {
378: // process all in the rest of the documents
379: count += _processAllInCurrentDoc(findAction);
380: _doc = _docIterator.getNextDocument(_doc);
381: }
382:
383: // update display (perhaps adding "*") in navigatgorPane
384: _model.getDocumentNavigator().repaint();
385:
386: return count;
387: } else
388: return _processAllInCurrentDoc(findAction);
389: }
390:
391: /** Processes all occurences of _findWord in _doc. Never processes other documents. Starts at
392: * the beginning or the end of the document (depending on find direction). This convention ensures that matches
393: * created by string replacement will not be replaced as in the following example:<p>
394: * findString: "hello"<br>
395: * replaceString: "e"<br>
396: * document text: "hhellollo"<p>
397: * Only executes in event thread.
398: * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
399: * @return the number of replacements
400: */
401: private int _processAllInCurrentDoc(
402: Lambda<Void, FindResult> findAction) {
403:
404: if (_isForward)
405: setPosition(0);
406: else
407: setPosition(_doc.getLength());
408:
409: int count = 0;
410: FindResult fr = findNext(false); // find next match in current doc
411:
412: while (!fr.getWrapped()) {
413: findAction.apply(fr);
414: count++;
415: fr = findNext(false); // find next match in current doc
416: }
417: return count;
418: }
419:
420: public FindResult findNext() {
421: return findNext(_searchAllDocuments);
422: }
423:
424: /** Finds the next occurrence of the find word and returns an offset at the end of that occurrence or -1 if the word
425: * was not found. Selectors should select backwards the length of the find word from the find offset. This
426: * position is stored in the current offset of the machine, and that is why it is after: in subsequent searches, the
427: * same instance won't be found twice. In a backward search, the position returned is at the beginning of the word.
428: * Also returns a flag indicating whether the end of the document was reached and wrapped around. This is done
429: * using the FindResult class which contains the matching document, an integer offset and two flag indicated whether
430: * the search wrapped (within _doc and across all documents). Only executes in the event thread.
431: * @param searchAll whether to search all documents (or just _doc)
432: * @return a FindResult object containing foundOffset and a flag indicating wrapping to the beginning during a search
433: */
434: private FindResult findNext(boolean searchAll) {
435:
436: assert EventQueue.isDispatchThread();
437:
438: // Find next match, if any, in _doc.
439: FindResult fr;
440: int start;
441: int len;
442:
443: // If the user just found a match and toggled the "Search Backwards" option, we should skip the matched text.
444: if (_skipText) { // adjust position (offset)
445: // System.err.println("Skip text is true! Last find word = " + _lastFindWord);
446: int wordLen = _lastFindWord.length();
447: if (_isForward)
448: setPosition(getCurrentOffset() + wordLen);
449: else
450: setPosition(getCurrentOffset() - wordLen);
451: positionChanged();
452: }
453:
454: // System.err.println("findNext(" + searchAll + ") called with _doc = [" + _doc.getText() + "] and offset = " + _current.getOffset());
455:
456: int offset = getCurrentOffset();
457: // System.err.println("findNext(" + searchAll + ") called; initial offset is " + offset);
458: // System.err.println("_doc = [" + _doc.getText() + "], _doc.getLength() = " + _doc.getLength());
459: if (_isForward) {
460: start = offset;
461: len = _doc.getLength() - offset;
462: } else {
463: start = 0;
464: len = offset;
465: }
466: fr = _findNextInDoc(_doc, start, len, searchAll);
467: if ((fr.getFoundOffset() >= 0) || !searchAll)
468: return fr; // match found in _doc or search is local
469:
470: // find match in other docs
471: return _findNextInOtherDocs(_doc, start, len);
472: }
473:
474: /** Finds next match in specified doc only. If searching forward, len must be doc.getLength(). If searching backward,
475: * start must be 0. If searchAll, suppress executing in-document wrapped search, because it must be deferred. Assumes
476: * acquireReadLock is already held. Note than this method does a wrapped search if specified search fails.
477: */
478: private FindResult _findNextInDoc(OpenDefinitionsDocument doc,
479: int start, int len, boolean searchAll) {
480: // search from current position to "end" of document ("end" is start if searching backward)
481: _log.log("_findNextInDoc([" + doc.getText() + "], " + start
482: + ", " + len + ", " + searchAll + ")");
483: FindResult fr = _findNextInDocSegment(doc, start, len);
484: if (fr.getFoundOffset() >= 0 || searchAll)
485: return fr;
486:
487: return _findWrapped(doc, start, len, false); // last arg is false because search has not wrapped through all docs
488: }
489:
490: /** Helper method for findNext that looks for a match after searching has wrapped off the "end" (start if searching
491: * backward) of the document. Assumes acquireReadLock is already held!
492: * INVARIANT (! _isForward => start = 0) && (_isForward => start + len = doc.getLength()).
493: * @param doc the document in which search wrapped
494: * @param start location of preceding text segment where search FAILED.
495: * @param len length of text segment previously searched
496: * @param allWrapped whether this wrapped search is being performed after an all document search has wrapped
497: * @return the offset where the instance was found. Returns -1 if no instance was found between start and end
498: */
499: private FindResult _findWrapped(OpenDefinitionsDocument doc,
500: int start, int len, boolean allWrapped) {
501:
502: assert (_isForward && start + len == doc.getLength())
503: || (!_isForward && start == 0);
504:
505: _log.log("_findWrapped(" + doc + ", " + start + ", " + len
506: + ", " + allWrapped + ") docLength = "
507: + doc.getLength() + ", _isForward = " + _isForward);
508:
509: if (doc.getLength() == 0)
510: return new FindResult(doc, -1, true, allWrapped);
511:
512: final int newLen, newStart;
513: if (_isForward) {
514: newStart = 0;
515: newLen = start;
516: } else {
517: newStart = len;
518: newLen = doc.getLength() - len;
519: }
520: _log.log("Calling _findNextInDocSegment(" + doc.getText()
521: + ", newStart = " + newStart + ", newLen = " + newLen
522: + ", allWrapped = " + allWrapped
523: + ") and _isForward = " + _isForward);
524: return _findNextInDocSegment(doc, newStart, newLen, true,
525: allWrapped);
526: }
527:
528: /** Find first valid match withing specified segment of doc. */
529: private FindResult _findNextInDocSegment(
530: OpenDefinitionsDocument doc, int start, int len) {
531: return _findNextInDocSegment(doc, start, len, false, false);
532: }
533:
534: /** Main helper method for findNext... that searches for _findWord inside the specified document segment. Assumes
535: * acquireReadLock is already held!
536: * @param doc document to be searched
537: * @param start the location (offset) of the text segment to be searched
538: * @param len the length of the text segment to be searched
539: * @param whether this search should span all documents
540: * @param wrapped whether this search is after wrapping around the document
541: * @param allWrapped whether this seach is after wrapping around all documents
542: * @return a FindResult object with foundOffset and a flag indicating wrapping to the beginning during a search. The
543: * foundOffset returned insided the FindResult is -1 if no instance was found.
544: */
545: private FindResult _findNextInDocSegment(
546: final OpenDefinitionsDocument doc, final int start,
547: final int origLen, final boolean wrapped,
548: final boolean allWrapped) {
549: // Utilities.show("called _findNextInDocSegment(" + doc.getText() + ",\n" + start + ", " + len + ", " + wrapped + " ...)");
550:
551: assert start > -1;
552:
553: final int docLen = doc.getLength();
554: ; // The length of the segment to be searched
555:
556: final int len = (origLen < 0) ? docLen - start : origLen; // set len for end of doc if origLen < 0
557:
558: if (len == 0 || docLen == 0)
559: return new FindResult(doc, -1, wrapped, allWrapped);
560:
561: String text; // The text segment to be searched
562: final String findWord; // copy of word being searched (so it can converted to lower case if necessary
563: final int wordLen = _findWord.length(); // length of search key (word being searched fo
564:
565: try {
566:
567: // if (wrapped && allWrapped) Utilities.show(start +", " + len + ", " + docLen + ", doc = '" + doc.getText() + "'");
568: //doc.acquireReadLock();
569: text = doc.getText(start, len);
570: //finally { doc.releaseReadLock(); }
571:
572: if (!_matchCase) {
573: text = text.toLowerCase();
574: findWord = _findWord.toLowerCase(); // does not affect wordLen
575: } else
576: findWord = _findWord;
577: // if (wrapped && allWrapped) Utilities.show("Executing loop with findWord = " + findWord + "; text = " + text + "; len = " + len);
578:
579: // loop to find first valid (not ignored) occurrence of findWord
580: // loop carried variables are rem, foundOffset;
581: // loop invariant variables are _doc, docLen, _isForward, findWord, wordLen, start, len.
582: // Invariant: on forwardsearch, foundOffset + rem == len; on backward search foundOffset == rem.
583: // loop exits by returning match (as FindResult) or by falling through with no match.
584: // if match is returned, _current has been updated to match location
585: int foundOffset = _isForward ? 0 : len;
586: int rem = len;
587: // _log.log("Starting search loop; text = '" + text + "' findWord = '" + findWord + "' forward? = " + _isForward + " rem = " + rem + " foundOffset = " + foundOffset);
588: while (rem >= wordLen) {
589:
590: // Find next match in text
591: foundOffset = _isForward ? text.indexOf(findWord,
592: foundOffset) : text.lastIndexOf(findWord,
593: foundOffset);
594: // _log.log("foundOffset = " + foundOffset);
595: if (foundOffset < 0)
596: break; // no valid match in this document
597: int foundLocation = start + foundOffset;
598: int matchLocation;
599:
600: if (_isForward) {
601: foundOffset += wordLen; // skip over matched word
602: // text = text.substring(adjustedOffset, len); // len is length of text before update
603: rem = len - foundOffset; // len is updated to length of remaining text to search
604: matchLocation = foundLocation + wordLen; // matchLocation is index in _doc of right edge of match
605: // _current = docToSearch.createPosition(start); // put caret at beginning of found word
606: } else {
607:
608: foundOffset -= wordLen; // skip over matched word
609: rem = foundOffset; // rem is adjusted to match foundOffset
610: matchLocation = foundLocation; // matchLocation is index in _doc of left edge of match
611: // text = text.substring(0, len); // len is length of text after update
612: // _current = docToSearch.createPosition(foundLocation); // put caret at end of found word
613: }
614: // _log.log("rem = " + rem);
615: doc.setCurrentLocation(foundLocation); // _shouldIgnore below uses reduced model
616:
617: // _log.log("Finished iteration with text = " + text + "; len = " + len + "; foundLocation = " + foundLocation);
618: assert foundLocation > -1;
619: if (_shouldIgnore(foundLocation, doc))
620: continue;
621:
622: //_current = doc.createPosition(matchLocation); // formerly doc.createPosition(...)
623: setPosition(matchLocation);
624:
625: // System.err.println("Returning result = " + new FindResult(doc, matchLocation, wrapped, allWrapped));
626:
627: return new FindResult(doc, matchLocation, wrapped,
628: allWrapped); // return valid match
629: }
630: } catch (BadLocationException e) {
631: throw new UnexpectedException(e);
632: }
633:
634: // loop fell through; search failed in doc segment
635: return new FindResult(doc, -1, wrapped, allWrapped);
636: }
637:
638: /** Searches all documents following startDoc for _findWord, cycling through the documents in the direction specified
639: * by _isForward. If the search cycles back to doc without finding a match, performs a wrapped search on doc.
640: * @param startDoc document where searching started and just failed
641: * @param start location in startDoc of the document segment where search failed.
642: * @param len length of the text segment where search failed.
643: * @return the FindResult containing the information for where we found _findWord or a dummy FindResult.
644: */
645: private FindResult _findNextInOtherDocs(
646: final OpenDefinitionsDocument startDoc, int start, int len) {
647:
648: // System.err.println("_findNextInOtherDocs(" + startDoc.getText() + ", " + start + ", " + len + ")");
649:
650: boolean allWrapped = false;
651: _doc = _isForward ? _docIterator.getNextDocument(startDoc)
652: : _docIterator.getPrevDocument(startDoc);
653:
654: while (_doc != startDoc) {
655: if (_doc == _firstDoc)
656: allWrapped = true;
657:
658: // System.err.println("_doc = [" + _doc.getText() + "]");
659:
660: // if (_isForward) setPosition(0);
661: // else setPosition(_doc.getLength());
662:
663: // find next match in _doc
664: _doc.acquireReadLock();
665: FindResult fr;
666: try {
667: fr = _findNextInDocSegment(_doc, 0, _doc.getLength(),
668: false, allWrapped);
669: } finally {
670: _doc.releaseReadLock();
671: }
672:
673: if (fr.getFoundOffset() >= 0)
674: return fr;
675:
676: // System.err.println("Advancing from '" + _doc.getText() + "' to next doc");
677: _doc = _isForward ? _docIterator.getNextDocument(_doc)
678: : _docIterator.getPrevDocument(_doc);
679: // System.err.println("Next doc is: '" + _doc.getText() + "'");
680: }
681:
682: // No valid match found; perform wrapped search. _findWrapped assumes acquireReadLock is held.
683: startDoc.acquireReadLock();
684: try {
685: return _findWrapped(startDoc, start, len, true);
686: } // last arg is true because searching all docs has wrapped
687: finally {
688: startDoc.releaseReadLock();
689: }
690: }
691:
692: /** Determines whether the whole find word is found at the input position
693: * @param doc - the document where an instance of the find word was found
694: * @param foundOffset - the position where that instance was found
695: * @return true if the whole word is found at foundOffset, false otherwise
696: */
697: private boolean wholeWordFoundAtCurrent(
698: OpenDefinitionsDocument doc, int foundOffset) {
699:
700: char leftOfMatch = 0; // forced initialization
701: char rightOfMatch = 0; // forced initialization
702: int leftLoc = foundOffset - 1;
703: int rightLoc = foundOffset + _findWord.length();
704: boolean leftOutOfBounds = false;
705: boolean rightOutOfBounds = false;
706:
707: doc.acquireReadLock();
708: try {
709: try {
710: leftOfMatch = doc.getText(leftLoc, 1).charAt(0);
711: } catch (BadLocationException e) {
712: leftOutOfBounds = true;
713: } catch (IndexOutOfBoundsException e) {
714: leftOutOfBounds = true;
715: }
716: try {
717: rightOfMatch = doc.getText(rightLoc, 1).charAt(0);
718: } catch (BadLocationException e) {
719: rightOutOfBounds = true;
720: } catch (IndexOutOfBoundsException e) {
721: rightOutOfBounds = true;
722: }
723: } finally {
724: doc.releaseReadLock();
725: }
726:
727: if (!leftOutOfBounds && !rightOutOfBounds)
728: return isDelimiter(rightOfMatch)
729: && isDelimiter(leftOfMatch);
730: if (!leftOutOfBounds)
731: return isDelimiter(leftOfMatch);
732: if (!rightOutOfBounds)
733: return isDelimiter(rightOfMatch);
734: return true;
735: }
736:
737: /** Determines whether a character is a delimiter (not a letter or digit) as a helper to wholeWordFoundAtCurrent
738: *
739: * @param ch - a character
740: * @return true if ch is a delimiter, false otherwise
741: */
742: private boolean isDelimiter(char ch) {
743: return !Character.isLetterOrDigit(ch);
744: }
745:
746: /** Returns true if the currently found instance should be ignored (either because it is inside a string or comment or
747: * because it does not match the whole word when either or both of those conditions are set to true). Only executes
748: * in event thread.
749: * @param foundOffset the location of the instance found
750: * @param doc the current document where the instance was found
751: * @return true if the location should be ignored, false otherwise
752: */
753: private boolean _shouldIgnore(int foundOffset,
754: OpenDefinitionsDocument odd) {
755:
756: assert EventQueue.isDispatchThread();
757:
758: return (_matchWholeWord && !wholeWordFoundAtCurrent(odd,
759: foundOffset))
760: || (_ignoreCommentsAndStrings && odd
761: .getStateAtCurrent() != ReducedModelStates.FREE);
762: }
763: }
|