Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 723   Methods: 48
NCLOC: 363   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
FindReplaceMachine.java 68.8% 77.4% 70.8% 74.1%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model;
 38   
 39    import edu.rice.cs.plt.lambda.Runnable1;
 40    import edu.rice.cs.util.UnexpectedException;
 41    import edu.rice.cs.util.swing.DocumentIterator;
 42    import edu.rice.cs.util.swing.Utilities;
 43    import edu.rice.cs.util.Log;
 44    import edu.rice.cs.util.StringOps;
 45    import edu.rice.cs.drjava.config.OptionConstants;
 46   
 47    import java.awt.EventQueue;
 48   
 49    import javax.swing.text.BadLocationException;
 50    import javax.swing.JOptionPane;
 51    import java.awt.Component;
 52   
 53    /** Implementation of logic of find/replace over a document.
 54    * @version $Id: FindReplaceMachine.java 5354 2010-08-10 22:41:14Z mgricken $
 55    */
 56    public class FindReplaceMachine {
 57   
 58    static private Log _log = new Log("FindReplace.txt", false);
 59   
 60    /* Visible machine state; manipulated directly or indirectly by FindReplacePanel. */
 61    private OpenDefinitionsDocument _doc; // Current search document
 62    private OpenDefinitionsDocument _firstDoc; // First document where searching started (when searching all documents)
 63    // private Position _current; // Position of the cursor in _doc when machine is stopped
 64    private int _current; // Position of the cursor in _doc when machine is stopped
 65    private MovingDocumentRegion _selectionRegion; // selected text region
 66    // private Position _start; // Position in _doc from which searching started or will start.
 67    private String _findWord; // Word to find. */
 68    private String _replaceWord; // Word to replace _findword.
 69    private boolean _matchCase;
 70    private boolean _matchWholeWord;
 71    private boolean _searchAllDocuments; // Whether to search all documents (or just the current document)
 72    private boolean _searchSelectionOnly; // Whether to search only the selection
 73    private boolean _isForward; // Whether search direction is forward (false means backward)
 74    private boolean _ignoreCommentsAndStrings; // Whether to ignore matches in comments and strings
 75    private boolean _ignoreTestCases; // Whether to ignore documents that end in *Test.java
 76    private String _lastFindWord; // Last word found; set to null by FindReplacePanel if caret is updated
 77    private boolean _skipText; // Whether to skip over the current match if direction is reversed
 78    private DocumentIterator _docIterator; // An iterator of open documents; _doc is current
 79    private SingleDisplayModel _model;
 80    private Component _frame;
 81   
 82    /** Standard Constructor.
 83    * Creates new machine to perform find/replace operations on a particular document starting from a given position.
 84    * @param docIterator an object that allows navigation through open Swing documents (it is DefaultGlobalModel)
 85    */
 86  64 public FindReplaceMachine(SingleDisplayModel model, DocumentIterator docIterator, Component frame) {
 87  64 _skipText = false;
 88    // _checkAllDocsWrapped = false;
 89    // _allDocsWrapped = false;
 90  64 _model = model;
 91  64 _frame = frame;
 92  64 _docIterator = docIterator;
 93  64 _current = -1;
 94  64 setFindAnyOccurrence();
 95  64 setFindWord("");
 96  64 setReplaceWord("");
 97  64 setSearchBackwards(false);
 98  64 setMatchCase(true);
 99  64 setSearchAllDocuments(false);
 100  64 setSearchSelectionOnly(false);
 101  64 setIgnoreCommentsAndStrings(false);
 102  64 setIgnoreTestCases(false);
 103    }
 104   
 105  26 public void cleanUp() {
 106  26 _docIterator = null;
 107  26 setFindWord("");
 108  26 _doc = null;
 109    }
 110   
 111    /** Called when the current position is updated in the document implying _skipText should not be set
 112    * if the user toggles _searchBackwards
 113    */
 114  14 public void positionChanged() {
 115  14 _lastFindWord = null;
 116  14 _skipText = false;
 117    }
 118   
 119  14 public void setLastFindWord() { _lastFindWord = _findWord; }
 120   
 121  0 public boolean isSearchBackwards() { return ! _isForward; }
 122   
 123  103 public void setSearchBackwards(boolean searchBackwards) {
 124  103 if (_isForward == searchBackwards) {
 125    // If we switch from searching forward to searching backwards or vice versa, isOnMatch is true, and _findword is
 126    // the same as the _lastFindWord, we know the user just found _findWord, so skip over this match.
 127  13 if (onMatch() && _findWord.equals(_lastFindWord)) _skipText = true;
 128  79 else _skipText = false;
 129    }
 130  103 _isForward = ! searchBackwards;
 131    }
 132   
 133  114 public void setMatchCase(boolean matchCase) { _matchCase = matchCase; }
 134  0 public boolean getMatchCase() { return _matchCase; }
 135   
 136  3 public void setMatchWholeWord() { _matchWholeWord = true; }
 137   
 138  0 public boolean getMatchWholeWord() { return _matchWholeWord; }
 139   
 140  102 public void setFindAnyOccurrence() { _matchWholeWord = false; }
 141   
 142  104 public void setSearchAllDocuments(boolean searchAllDocuments) { _searchAllDocuments = searchAllDocuments; }
 143   
 144  102 public void setSearchSelectionOnly(boolean searchSelectionOnly) { _searchSelectionOnly = searchSelectionOnly; }
 145   
 146  104 public void setIgnoreCommentsAndStrings(boolean ignoreCommentsAndStrings) {
 147  104 _ignoreCommentsAndStrings = ignoreCommentsAndStrings;
 148    }
 149  0 public boolean getIgnoreCommentsAndStrings() { return _ignoreCommentsAndStrings; }
 150   
 151  102 public void setIgnoreTestCases(boolean ignoreTestCases) {
 152  102 _ignoreTestCases = ignoreTestCases;
 153    }
 154  0 public boolean getIgnoreTestCases() { return _ignoreTestCases; }
 155   
 156  26 public void setDocument(OpenDefinitionsDocument doc) { _doc = doc; }
 157   
 158  2 public void setFirstDoc(OpenDefinitionsDocument firstDoc) { _firstDoc = firstDoc; }
 159   
 160  234 public void setPosition(int pos) { _current = pos; }
 161   
 162    /** Gets the character offset to which this machine is currently pointing. */
 163  506 public int getCurrentOffset() { //return _current.getOffset();
 164  506 return _current;
 165    }
 166   
 167  0 public String getFindWord() { return _findWord; }
 168   
 169  0 public String getReplaceWord() { return _replaceWord; }
 170   
 171  0 public boolean getSearchAllDocuments() { return _searchAllDocuments; }
 172   
 173  0 public boolean getSearchSelectionOnly() { return _searchSelectionOnly; }
 174   
 175  56 public OpenDefinitionsDocument getDocument() { return _doc; }
 176   
 177  0 public OpenDefinitionsDocument getFirstDoc() { return _firstDoc; }
 178   
 179    /** Change the word being sought.
 180    * @param word the new word to seek
 181    */
 182  124 public void setFindWord(String word) {
 183  124 _findWord = StringOps.replace(word, StringOps.EOL, "\n");
 184    }
 185   
 186    /** Change the replacing word.
 187    * @param word the new replacing word
 188    */
 189  73 public void setReplaceWord(String word) {
 190  73 _replaceWord = StringOps.replace(word, StringOps.EOL,"\n");
 191    }
 192   
 193    /** Determine if the machine is on an instance of the find word. Only executes in event thread except for
 194    * initialization.
 195    * @return true if the current position is right after an instance of the find word.
 196    */
 197  238 public boolean onMatch() {
 198   
 199    // Should be fixed now because of invokeAndWait in MainFrame constructor
 200    // (was: this invariant doesn't hold. See DrJava bug #2321815)
 201  238 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 202   
 203  238 String findWord = _findWord;
 204  238 int wordLen, off;
 205   
 206  64 if(_current == -1) return false;
 207   
 208  174 wordLen = findWord.length();
 209  98 if (_isForward) off = getCurrentOffset() - wordLen;
 210  76 else off = getCurrentOffset();
 211   
 212  2 if (off < 0) return false;
 213   
 214  172 String matchSpace;
 215  172 try {
 216  0 if (off + wordLen > _doc.getLength()) return false;
 217  172 matchSpace = _doc.getText(off, wordLen);
 218    }
 219  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 220   
 221  172 if (!_matchCase) {
 222  79 matchSpace = matchSpace.toLowerCase();
 223  79 findWord = findWord.toLowerCase();
 224    }
 225  172 return matchSpace.equals(findWord);
 226    }
 227   
 228    /** If we're on a match for the find word, replace it with the replace word. Only executes in event thread. */
 229  33 public boolean replaceCurrent() {
 230   
 231  33 assert EventQueue.isDispatchThread();
 232   
 233  0 if (! onMatch()) return false;
 234  33 try {
 235    // boolean atStart = false;
 236  33 int offset = getCurrentOffset();
 237  10 if (_isForward) offset -= _findWord.length(); // position is now on left edge of match
 238    // assert _findWord.equals(_doc.getText(offset, _findWord.length()));
 239   
 240    // Utilities.show("ReplaceCurrent called. _doc = " + _doc.getText() + " offset = " + offset + " _findWord = " +
 241    // _findWord);
 242   
 243  33 _doc.remove(offset, _findWord.length());
 244   
 245    // if (position == 0) atStart = true;
 246  33 _doc.insertString(offset, _replaceWord, null); // could use _insertString if we had the DefinitionsDocument
 247   
 248    // update _current Position
 249  10 if (_isForward) setPosition(offset + _replaceWord.length());
 250  23 else setPosition(offset);
 251   
 252  33 return true;
 253    }
 254  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 255    }
 256   
 257    /** Set the selected text region.
 258    * @param s selected region
 259    */
 260  0 public void setSelection(MovingDocumentRegion s) {
 261  0 _selectionRegion = s;
 262    }
 263   
 264    /** Replaces all occurrences of the find word with the replace word in the current document of in all documents
 265    * depending the value of the machine register _searchAllDocuments.
 266    * @return the number of replacements
 267    */
 268  9 public int replaceAll() {
 269  9 return replaceAll(_searchAllDocuments, _searchSelectionOnly);
 270    }
 271   
 272    /** Replaces all occurences of the find word with the replace word in the current document of in all documents or
 273    * in the current selection of the current document depending the value of the flag searchAll
 274    * @return the number of replacements
 275    */
 276  9 private int replaceAll(boolean searchAll, boolean searchSelectionOnly) {
 277  9 if (searchAll) {
 278  2 int count = 0; // the number of replacements done so far
 279  2 int n = _docIterator.getDocumentCount();
 280  2 for (int i = 0; i < n; i++) {
 281    // replace all in the rest of the documents
 282  8 count += _replaceAllInCurrentDoc(false);
 283  8 _doc = _docIterator.getNextDocument(_doc, _frame);
 284   
 285  0 if(_doc==null) break;
 286    }
 287   
 288    // update display (adding "*") in navigatgorPane
 289  2 _model.getDocumentNavigator().repaint();
 290   
 291  2 return count;
 292    }
 293  7 else if(searchSelectionOnly) {
 294  0 int count = 0;
 295  0 count += _replaceAllInCurrentDoc(searchSelectionOnly);
 296  0 return count;
 297    }
 298    else
 299  7 return _replaceAllInCurrentDoc(false);
 300    }
 301   
 302    /** Replaces all occurences of _findWord with _replaceWord in _doc. Never searches in other documents. Starts at
 303    * the beginning or the end of the document (depending on find direction). This convention ensures that matches
 304    * created by string replacement will not be replaced as in the following example:<p>
 305    * findString: "hello"<br>
 306    * replaceString: "e"<br>
 307    * document text: "hhellollo"<p>
 308    * Depending on the cursor position, clicking replace all could either make the document text read "hello"
 309    * (which is correct) or "e". This is because of the behavior of findNext(), and it would be incorrect
 310    * to change that behavior. Only executes in event thread.
 311    * @return the number of replacements
 312    */
 313  15 private int _replaceAllInCurrentDoc(boolean searchSelectionOnly) {
 314   
 315  15 assert EventQueue.isDispatchThread();
 316   
 317  15 if(!searchSelectionOnly) {
 318  15 _selectionRegion = new MovingDocumentRegion(_doc, 0, _doc.getLength(),
 319    _doc._getLineStartPos(0),
 320    _doc._getLineEndPos(_doc.getLength()));
 321    }
 322  4 if (_isForward) setPosition(_selectionRegion.getStartOffset());
 323  11 else setPosition(_selectionRegion.getEndOffset());
 324   
 325  15 int count = 0;
 326  15 FindResult fr = findNext(false); // find next match in current doc
 327    // Utilities.show(fr + " returned by call on findNext()");
 328   
 329  15 while (!fr.getWrapped() && fr.getFoundOffset() <= _selectionRegion.getEndOffset()) {
 330  32 replaceCurrent();
 331  32 count++;
 332    // Utilities.show("Found " + count + " occurrences. Calling findNext() inside loop");
 333  32 fr = findNext(false); // find next match in current doc
 334    // Utilities.show("Call on findNext() returned " + fr.toString() + "in doc '" +
 335    // _doc.getText().substring(0,fr.getFoundOffset()) + "[|]" + _doc.getText().substring(fr.getFoundOffset()) + "'");
 336    }
 337  15 return count;
 338    }
 339   
 340    /** Processes all occurences of the find word with the replace word in the current document or in all documents
 341    * depending the value of the machine register _searchAllDocuments.
 342    * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
 343    * @return the number of processed occurrences
 344    */
 345  0 public int processAll(Runnable1<FindResult> findAction, MovingDocumentRegion region) {
 346  0 _selectionRegion = region;
 347  0 return processAll(findAction, _searchAllDocuments, _searchSelectionOnly);
 348    }
 349   
 350    /** Processes all occurences of the find word with the replace word in the current document or in all documents
 351    * depending the value of the flag searchAll. Assumes that findAction does not modify the document it processes.
 352    * Only executes in event thread.
 353    * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
 354    * @return the number of replacements
 355    */
 356  0 private int processAll(Runnable1<FindResult> findAction, boolean searchAll, boolean searchSelectionOnly) {
 357   
 358  0 assert EventQueue.isDispatchThread();
 359   
 360  0 if (searchAll) {
 361  0 int count = 0; // the number of replacements done so far
 362  0 int n = _docIterator.getDocumentCount();
 363  0 for (int i = 0; i < n; i++) {
 364    // process all in the rest of the documents
 365  0 count += _processAllInCurrentDoc(findAction, false);
 366  0 _doc = _docIterator.getNextDocument(_doc, _frame);
 367   
 368  0 if(_doc==null) break;
 369    }
 370   
 371    // update display (perhaps adding "*") in navigatgorPane
 372  0 _model.getDocumentNavigator().repaint();
 373   
 374  0 return count;
 375    }
 376  0 else if(searchSelectionOnly) {
 377  0 int count = 0;
 378  0 count += _processAllInCurrentDoc(findAction, searchSelectionOnly);
 379  0 return count;
 380    }
 381  0 else return _processAllInCurrentDoc(findAction, false);
 382    }
 383   
 384    /** Processes all occurences of _findWord in _doc. Never processes other documents. Starts at the beginning or the
 385    * end of the document (depending on find direction). This convention ensures that matches created by string
 386    * replacement will not be replaced as in the following example:<p>
 387    * findString: "hello"<br>
 388    * replaceString: "e"<br>
 389    * document text: "hhellollo"<p>
 390    * Assumes this has mutually exclusive access to _doc (e.g., by hourglassOn) and findAction does not modify _doc.
 391    * Only executes in event thread.
 392    * @param findAction action to perform on the occurrences; input is the FindResult, output is ignored
 393    * @return the number of replacements
 394    */
 395  0 private int _processAllInCurrentDoc(Runnable1<FindResult> findAction, boolean searchSelectionOnly) {
 396  0 if(!searchSelectionOnly) {
 397  0 _selectionRegion = new MovingDocumentRegion(_doc, 0, _doc.getLength(),
 398    _doc._getLineStartPos(0),
 399    _doc._getLineEndPos(_doc.getLength()));
 400    }
 401  0 if (_isForward) setPosition(_selectionRegion.getStartOffset());
 402  0 else setPosition(_selectionRegion.getEndOffset());
 403   
 404  0 int count = 0;
 405  0 FindResult fr = findNext(false); // find next match in current doc
 406   
 407  0 while (! fr.getWrapped() && fr.getFoundOffset() <= _selectionRegion.getEndOffset()) {
 408  0 findAction.run(fr);
 409  0 count++;
 410  0 fr = findNext(false); // find next match in current doc
 411    }
 412  0 return count;
 413    }
 414   
 415  114 public FindResult findNext() { return findNext(_searchAllDocuments); }
 416   
 417    /** Finds the next occurrence of the find word and returns an offset at the end of that occurrence or -1 if the word
 418    * was not found. In a forward search, the match offset is the RIGHT edge of the word. In subsequent searches, the
 419    * same instance won't be found again. In a backward search, the position returned is the LEFT edge of the word.
 420    * Also returns a flag indicating whether the end of the document was reached and wrapped around. This is done
 421    * using the FindResult class which contains the matching document, an integer offset and two flag indicated whether
 422    * the search wrapped (within _doc and across all documents). Only executes in the event thread.
 423    * @param searchAll whether to search all documents (or just _doc)
 424    * @return a FindResult object containing foundOffset and a flag indicating wrapping to the beginning during a search
 425    */
 426  161 private FindResult findNext(boolean searchAll) {
 427   
 428  161 assert EventQueue.isDispatchThread();
 429   
 430    // Find next match, if any, in _doc.
 431  161 FindResult fr;
 432  161 int start;
 433  161 int len;
 434   
 435    // If the user just found a match and toggled the "Search Backwards" option, we should skip the matched text.
 436  161 if (_skipText) { // adjust position (offset)
 437    // System.err.println("Skip text is true! Last find word = " + _lastFindWord);
 438  13 int wordLen = _lastFindWord.length();
 439  1 if (_isForward) setPosition(getCurrentOffset() + wordLen);
 440  12 else setPosition(getCurrentOffset() - wordLen);
 441  13 positionChanged();
 442    }
 443   
 444    // System.err.println("findNext(" + searchAll + ") called with _doc = [" + _doc.getText() + "] and offset = " +
 445    // _current.getOffset());
 446   
 447  161 int offset = getCurrentOffset();
 448    // System.err.println("findNext(" + searchAll + ") called; initial offset is " + offset);
 449    // System.err.println("_doc = [" + _doc.getText() + "], _doc.getLength() = " + _doc.getLength());
 450  161 if (_isForward) {
 451  81 start = offset;
 452  81 len = _doc.getLength() - offset;
 453    }
 454    else {
 455  80 start = 0;
 456  80 len = offset;
 457    }
 458  161 fr = _findNextInDoc(_doc, start, len, searchAll);
 459  149 if (fr.getFoundOffset() >= 0 || ! searchAll) return fr; // match found in _doc or search is local
 460   
 461    // find match in other docs
 462  12 return _findNextInOtherDocs(_doc, start, len);
 463    }
 464   
 465   
 466    /** Finds next match in specified doc only. If searching forward, len must be doc.getLength(). If searching backward,
 467    * start must be 0. If searchAll, suppress executing in-document wrapped search, because it must be deferred. Only
 468    * runs in the event thread. Note than this method does a wrapped search if specified search fails.
 469    */
 470  161 private FindResult _findNextInDoc(OpenDefinitionsDocument doc, int start, int len, boolean searchAll) {
 471    // search from current position to "end" of document ("end" is start if searching backward)
 472    // Utilities.show("_findNextInDoc([" + doc.getText() + "], " + start + ", " + len + ", " + searchAll + ")");
 473    // _log.log("_findNextInDoc([" + doc.getText() + "], " + start + ", " + len + ", " + searchAll + ")");
 474  161 FindResult fr = _findNextInDocSegment(doc, start, len);
 475  122 if (fr.getFoundOffset() >= 0 || searchAll) return fr;
 476   
 477  39 return _findWrapped(doc, start, len, false); // last arg is false because search has not wrapped through all docs
 478    }
 479   
 480    /** Helper method for findNext that looks for a match after searching has wrapped off the "end" (start if searching
 481    * backward) of the document. Only runs in event thread.
 482    * INVARIANT (! _isForward => start = 0) && (_isForward => start + len = doc.getLength()).
 483    * @param doc the document in which search wrapped
 484    * @param start the location of preceding text segment where search FAILED.
 485    * @param len the length of text segment previously searched
 486    * @param allWrapped whether this wrapped search is being performed after an all document search has wrapped
 487    * @return the offset where the instance was found. Returns -1 if no instance was found between start and end
 488    */
 489  39 private FindResult _findWrapped(OpenDefinitionsDocument doc, int start, int len, boolean allWrapped) {
 490   
 491  39 final int docLen = doc.getLength();
 492  2 if (docLen == 0) return new FindResult(doc, -1, true, allWrapped); // failure result
 493   
 494  37 final int wordLen = _findWord.length();
 495   
 496  37 assert (start >= 0 && start <= docLen) && (len >= 0 && len <= docLen) && wordLen > 0;
 497  37 assert (_isForward && start + len == docLen) || (! _isForward && start == 0);
 498    // Utilities.show("_findWrapped(" + doc + ", " + start + ", " + len + ", " + allWrapped + ") docLength = " +
 499    // doc.getLength() + ", _isForward = " + _isForward);
 500    // _log.log("_findWrapped(" + doc + ", " + start + ", " + len + ", " + allWrapped + ") docLength = " +
 501    // doc.getLength() + ", _isForward = " + _isForward);
 502   
 503  37 int newLen;
 504  37 int newStart;
 505   
 506  37 final int adjustment = wordLen - 1; // non-negative max size of the findWord suffix (prefix) within preceding text
 507   
 508  37 if (_isForward) {
 509  14 newStart = 0;
 510  14 newLen = start + adjustment; // formerly start, which was an annoying bug
 511  4 if (newLen > docLen) newLen = docLen;
 512    }
 513    else {
 514  23 newStart = len - adjustment;
 515  4 if (newStart < 0) newStart = 0;
 516  23 newLen = docLen - newStart;
 517    }
 518   
 519    // _log.log("Calling _findNextInDocSegment(" + doc.getText() + ", newStart = " + newStart + ", newLen = " +
 520    // newLen + ", allWrapped = " + allWrapped + ") and _isForward = " + _isForward);
 521  37 return _findNextInDocSegment(doc, newStart, newLen, true, allWrapped);
 522    }
 523   
 524    /** Find first valid match withing specified segment of doc. */
 525  161 private FindResult _findNextInDocSegment(OpenDefinitionsDocument doc, int start, int len) {
 526  161 return _findNextInDocSegment(doc, start, len, false, false);
 527    }
 528   
 529    /** Main helper method for findNext... that searches for _findWord inside the specified document segment. Only runs
 530    * in the event thread.
 531    * @param doc document to be searched
 532    * @param start the location (offset/left edge) of the text segment to be searched
 533    * @param len the requested length of the text segment to be searched
 534    * @param wrapped whether this search is after wrapping around the document
 535    * @param allWrapped whether this seach is after wrapping around all documents
 536    * @return a FindResult object with foundOffset and a flag indicating wrapping to the beginning during a search. The
 537    * foundOffset returned insided the FindResult is -1 if no instance was found.
 538    */
 539  212 private FindResult _findNextInDocSegment(final OpenDefinitionsDocument doc, final int start, int len,
 540    final boolean wrapped, final boolean allWrapped) {
 541    // Utilities.show("called _findNextInDocSegment(" + doc.getText() + ",\n" + start + ", " + len + ", " + wrapped +
 542    // " ...)");
 543  212 boolean inTestCase = false;
 544  212 for(String ext: OptionConstants.LANGUAGE_LEVEL_EXTENSIONS) {
 545  1060 inTestCase |= doc.getFileName().endsWith("Test"+ext);
 546    }
 547   
 548  212 if (!_ignoreTestCases || ! inTestCase) {
 549  212 final int docLen = doc.getLength();; // The length of the segment to be searched
 550  212 final int wordLen = _findWord.length(); // length of search key (word being searched for)
 551   
 552  212 assert (start >= 0 && start <= docLen) && (len >= 0 && len <= docLen);
 553   
 554  9 if (len == 0 || docLen == 0) return new FindResult(doc, -1, wrapped, allWrapped);
 555   
 556  0 if (start + len > docLen) len = docLen - start;
 557   
 558    // if (start + len > docLen) len = docLen - start;
 559   
 560  203 String text; // The text segment to be searched
 561  203 final String findWord; // copy of word being searched (so it can converted to lower case if necessary
 562   
 563  203 try {
 564   
 565    // if (wrapped && allWrapped) Utilities.show(start + ", " + len + ", " + docLen + ", doc = '" + doc.getText() + "'");
 566  203 text = doc.getText(start, len);
 567   
 568  203 if (! _matchCase) {
 569  106 text = text.toLowerCase();
 570  106 findWord = _findWord.toLowerCase(); // does not affect wordLen
 571    }
 572  97 else findWord = _findWord;
 573    // if (wrapped && allWrapped) Utilities.show("Executing loop with findWord = " + findWord + "; text = " + text +
 574    // "; len = " + len);
 575   
 576    // loop to find first valid (not ignored) occurrence of findWord
 577    // loop carried variables are rem, foundOffset;
 578    // loop invariant variables are _doc, docLen, _isForward, findWord, wordLen, start, len.
 579    // Invariant: on forwardsearch, foundOffset + rem == len; on backward search foundOffset == rem.
 580    // loop exits by returning match (as FindResult) or by falling through with no match.
 581    // if match is returned, _current has been updated to match location
 582  203 int foundOffset = _isForward? 0 : len;
 583  203 int rem = len;
 584    // _log.log("Starting search loop; text = '" + text + "' findWord = '" + findWord + "' forward? = " + _isForward +
 585    // " rem = " + rem + " foundOffset = " + foundOffset);
 586  203 while (rem >= wordLen) {
 587   
 588    // Find next match in text
 589  216 foundOffset = _isForward ? text.indexOf(findWord, foundOffset) : text.lastIndexOf(findWord, foundOffset);
 590    // _log.log("foundOffset = " + foundOffset);
 591  46 if (foundOffset < 0) break; // no valid match in this document
 592  170 int foundLocation = start + foundOffset;
 593  170 int matchLocation;
 594   
 595  170 if (_isForward) {
 596  88 foundOffset += wordLen; // skip over matched word
 597    // text = text.substring(adjustedOffset, len); // len is length of text before update
 598  88 rem = len - foundOffset; // len is updated to length of remaining text to search
 599  88 matchLocation = foundLocation + wordLen; // matchLocation is index in _doc of right edge of match
 600    // _current = docToSearch.createPosition(start); // put caret at beginning of found word
 601    }
 602    else {
 603   
 604  82 foundOffset -= wordLen; // skip over matched word
 605  82 rem = foundOffset; // rem is adjusted to match foundOffset
 606  82 matchLocation = foundLocation; // matchLocation is index in _doc of left edge of match
 607    // text = text.substring(0, len); // len is length of text after update
 608    // _current = docToSearch.createPosition(foundLocation); // put caret at end of found word
 609    }
 610    // _log.log("rem = " + rem);
 611   
 612    // _log.log("Finished iteration with text = " + text + "; len = " + len + "; foundLocation = " + foundLocation);
 613  170 assert foundLocation > -1;
 614  24 if (_shouldIgnore(foundLocation, doc)) continue;
 615   
 616    //_current = doc.createPosition(matchLocation); // formerly doc.createPosition(...)
 617  146 setPosition(matchLocation);
 618   
 619    // System.err.println("Returning result = " + new FindResult(doc, matchLocation, wrapped, allWrapped));
 620   
 621  146 return new FindResult(doc, matchLocation, wrapped, allWrapped); // return valid match
 622    }
 623    }
 624  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 625    }
 626    // loop fell through; search failed in doc segment
 627  57 return new FindResult(doc, -1, wrapped, allWrapped);
 628    }
 629   
 630    /** Searches all documents following startDoc for _findWord, cycling through the documents in the direction specified
 631    * by _isForward. If the search cycles back to doc without finding a match, performs a wrapped search on doc.
 632    * @param startDoc document where searching started and just failed
 633    * @param start location in startDoc of the document segment where search failed.
 634    * @param len length of the text segment where search failed.
 635    * @return the FindResult containing the information for where we found _findWord or a dummy FindResult.
 636    */
 637  12 private FindResult _findNextInOtherDocs(final OpenDefinitionsDocument startDoc, int start, int len) {
 638   
 639    // System.err.println("_findNextInOtherDocs(" + startDoc.getText() + ", " + start + ", " + len + ")");
 640   
 641  12 boolean allWrapped = false;
 642    // _doc may be null if the next document isn't found and the user didn't want to continue!
 643  12 _doc = _isForward ? _docIterator.getNextDocument(startDoc) : _docIterator.getPrevDocument(startDoc);
 644  0 if (_doc == null) return new FindResult(startDoc, -1, true, true);
 645   
 646  14 while (_doc != startDoc) {
 647  0 if (_doc == _firstDoc) allWrapped = true;
 648  14 boolean inTestCase = (_doc.getFileName().endsWith("Test.java"));
 649   
 650  14 if (! _ignoreTestCases || ! inTestCase) {
 651    // System.err.println("_doc = [" + _doc.getText() + "]");
 652   
 653    // if (_isForward) setPosition(0);
 654    // else setPosition(_doc.getLength());
 655   
 656   
 657    // find next match in _doc
 658  14 FindResult fr;
 659  14 fr = _findNextInDocSegment(_doc, 0, _doc.getLength(), false, allWrapped);
 660   
 661  12 if (fr.getFoundOffset() >= 0) return fr;
 662    }
 663    // System.err.println("Advancing from '" + _doc.getText() + "' to next doc");
 664    // _doc may be null if the next document isn't found and the user didn't want to continue!
 665  2 _doc = _isForward ? _docIterator.getNextDocument(_doc) : _docIterator.getPrevDocument(_doc);
 666  0 if (_doc == null) return new FindResult(startDoc, -1, true, true);
 667    // System.err.println("Next doc is: '" + _doc.getText() + "'");
 668    }
 669   
 670    // No valid match found; perform wrapped search. Only runs in event thread.
 671  0 return _findWrapped(startDoc, start, len, true); // last arg is true because searching all docs has wrapped
 672    }
 673   
 674    /** Determines whether the whole find word is found at the input position. Assumes read lock or hourglass is
 675    * already held.
 676    * @param doc - the document where an instance of the find word was found
 677    * @param foundOffset - the position where that instance was found
 678    * @return true if the whole word is found at foundOffset, false otherwise
 679    */
 680  69 private boolean wholeWordFoundAtCurrent(OpenDefinitionsDocument doc, int foundOffset) {
 681   
 682  69 char leftOfMatch = 0; // forced initialization
 683  69 char rightOfMatch = 0; // forced initialization
 684  69 int leftLoc = foundOffset - 1;
 685  69 int rightLoc = foundOffset + _findWord.length();
 686  69 boolean leftOutOfBounds = false;
 687  69 boolean rightOutOfBounds = false;
 688   
 689  69 try { leftOfMatch = doc.getText(leftLoc, 1).charAt(0); }
 690  2 catch (BadLocationException e) { leftOutOfBounds = true; }
 691  0 catch (IndexOutOfBoundsException e) { leftOutOfBounds = true; }
 692  69 try { rightOfMatch = doc.getText(rightLoc, 1).charAt(0); }
 693  0 catch (BadLocationException e) { rightOutOfBounds = true; }
 694  0 catch (IndexOutOfBoundsException e) { rightOutOfBounds = true; }
 695   
 696  67 if (! leftOutOfBounds && ! rightOutOfBounds) return isDelimiter(rightOfMatch) && isDelimiter(leftOfMatch);
 697  0 if (! leftOutOfBounds) return isDelimiter(leftOfMatch);
 698  2 if (! rightOutOfBounds) return isDelimiter(rightOfMatch);
 699  0 return true;
 700    }
 701   
 702    /** Determines whether a character is a delimiter (not a letter or digit) as a helper to wholeWordFoundAtCurrent
 703    *
 704    * @param ch - a character
 705    * @return true if ch is a delimiter, false otherwise
 706    */
 707  132 private boolean isDelimiter(char ch) { return ! Character.isLetterOrDigit(ch) && ch != '_'; }
 708   
 709    /** Returns true if the currently found instance should be ignored (either because it is inside a string or comment or
 710    * because it does not match the whole word when either or both of those conditions are set to true). Only executes
 711    * in event thread.
 712    * @param foundOffset the location of the instance found
 713    * @param odd the current document where the instance was found
 714    * @return true if the location should be ignored, false otherwise
 715    */
 716  170 private boolean _shouldIgnore(int foundOffset, OpenDefinitionsDocument odd) {
 717   
 718  170 assert EventQueue.isDispatchThread();
 719   
 720  170 return (_matchWholeWord && ! wholeWordFoundAtCurrent(odd, foundOffset)) ||
 721    (_ignoreCommentsAndStrings && odd.isShadowed(foundOffset));
 722    }
 723    }