Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 1,445   Methods: 72
NCLOC: 769   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
DefinitionsDocument.java 65.3% 77% 76.4% 73.6%
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.definitions;
 38   
 39    import java.awt.EventQueue;
 40    import javax.swing.text.*;
 41    import javax.swing.undo.*;
 42    import javax.swing.event.DocumentEvent;
 43    import java.util.LinkedList;
 44    import java.util.List;
 45    import java.util.Map;
 46    import java.util.WeakHashMap;
 47    import java.lang.ref.WeakReference;
 48   
 49    import java.io.Reader;
 50    import java.io.StringReader;
 51    import java.io.IOException;
 52   
 53    import koala.dynamicjava.parser.impl.Parser;
 54    import koala.dynamicjava.parser.impl.ParseException;
 55    import koala.dynamicjava.parser.impl.TokenMgrError;
 56   
 57    import edu.rice.cs.drjava.DrJava;
 58    import edu.rice.cs.drjava.model.definitions.reducedmodel.*;
 59    import edu.rice.cs.util.Log;
 60    import edu.rice.cs.util.UnexpectedException;
 61    import edu.rice.cs.util.swing.Utilities;
 62    import edu.rice.cs.drjava.model.definitions.indent.Indenter;
 63    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 64    import edu.rice.cs.drjava.model.*;
 65   
 66    import static edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelStates.*;
 67   
 68    /** The document model for the definitions pane; it contains a reduced model since it extends AbstractDJDocument.
 69    * @see AbstractDJDocument
 70    */
 71    public class DefinitionsDocument extends AbstractDJDocument implements Finalizable<DefinitionsDocument> {
 72   
 73    public static final Log _log = new Log("GlobalModel.txt", false);
 74    private static final int NO_COMMENT_OFFSET = 0;
 75    private static final int WING_COMMENT_OFFSET = 2;
 76   
 77    private final List<DocumentClosedListener> _closedListeners = new LinkedList<DocumentClosedListener>();
 78   
 79  146 public void addDocumentClosedListener(DocumentClosedListener l) {
 80  146 synchronized(_closedListeners) { _closedListeners.add(l); }
 81    }
 82   
 83  0 public void removeDocumentClosedListener(DocumentClosedListener l) {
 84  0 synchronized(_closedListeners) { _closedListeners.remove(l); }
 85    }
 86   
 87    // begin debug code
 88   
 89    /** Closes this DefinitionsDocument (but not the enclosing OpenDefinitionsDocument). Called when this is kicked out
 90    * of the document cache so that this can be GC'd. */
 91  117 public void close() {
 92  117 _removeIndenter();
 93  117 synchronized(_closedListeners) {
 94  58 for (DocumentClosedListener l: _closedListeners) { l.close(); }
 95  117 _closedListeners.clear();
 96    }
 97    }
 98    // end debug code
 99   
 100    /** The maximum number of undos the model can remember */
 101    private static final int UNDO_LIMIT = 1000;
 102    /** Specifies if tabs are removed on open and converted to spaces. */
 103    private static boolean _tabsRemoved = true;
 104   
 105    /** Specifies if the document has been modified since the last save. Modified under write lock. */
 106    private volatile boolean _isModifiedSinceSave = false;
 107   
 108    /** This reference to the OpenDefinitionsDocument is needed so that the document iterator
 109    * (the DefaultGlobalModel) can find the next ODD given a DD. */
 110    private volatile OpenDefinitionsDocument _odd;
 111   
 112    private volatile CompoundUndoManager _undoManager;
 113   
 114    /** Keeps track of the listeners to this model. */
 115    private final GlobalEventNotifier _notifier;
 116   
 117    /** Uses an updated version of the DefaultEditorKit */
 118    private final DefinitionsEditorKit _editor;
 119   
 120    /* Unfortunately, we must use the following shared static lock as the lock object for Lock for _wrappedPosList because
 121    * here is no lightweight way to dynamically construct a dedicated lock for this variable. The lock is accessed by a
 122    * super*-class (AbstractDocument calls createPostion) during object initialization (when no initialization has yet
 123    * been performed for this class). Java inexplicably forbids access to uninitialized volatile variables. (Why?
 124    * hey have blank values just like blank final fields!) so a demand-driven initialization scheme like double-check
 125    * cannot be used. Using "this" as a lock appear to create a deadlock. The "this" lock may be used for other
 126    * purposes. */
 127   
 128    private static final Object _wrappedPosListLock = new Object();
 129   
 130    /** List with weak references to positions. */
 131    private volatile LinkedList<WeakReference<WrappedPosition>> _wrappedPosList;
 132   
 133    /** Root constructor that other constructors call; not used directly
 134    * @param indenter custom indenter class
 135    * @param notifier used by CompoundUndoManager to announce undoable edits
 136    */
 137  448 private DefinitionsDocument(Indenter indenter, GlobalEventNotifier notifier, CompoundUndoManager undoManager) {
 138  448 super(indenter);
 139  448 _notifier = notifier;
 140  448 _editor = new DefinitionsEditorKit(notifier);
 141  448 _undoManager = undoManager;
 142    }
 143   
 144    /** Convenience constructor used ?? */
 145  448 public DefinitionsDocument(Indenter indenter, GlobalEventNotifier notifier) {
 146    // initialize _indenter, _notifier, _undoManager, _wrappedPosListLock, _editor
 147  448 this(indenter, notifier, new CompoundUndoManager(notifier));
 148    // finish setting up _undomanager
 149  448 _undoManager.setLimit(UNDO_LIMIT);
 150    }
 151   
 152    /** Convenience constructor.
 153    * @param notifier used by CompoundUndoManager to announce undoable edits
 154    */
 155  448 public DefinitionsDocument(GlobalEventNotifier notifier) {
 156  448 this(new Indenter(DrJava.getConfig().getSetting(INDENT_LEVEL).intValue()), notifier);
 157    }
 158   
 159    /** Main constructor.
 160    * @param notifier used by CompoundUndoManager to announce undoable edits
 161    */
 162  0 public DefinitionsDocument(GlobalEventNotifier notifier, CompoundUndoManager undoManager) {
 163  0 this(new Indenter(DrJava.getConfig().getSetting(INDENT_LEVEL).intValue()), notifier, undoManager);
 164    }
 165   
 166    /** Returns the document's editor */
 167  0 public DefinitionsEditorKit getEditor() { return _editor; }
 168   
 169    /** Returns a new indenter. */
 170  0 protected Indenter makeNewIndenter(int indentLevel) { return new Indenter(indentLevel); }
 171   
 172    /** Sets the OpenDefinitionsDocument that holds this DefinitionsDocument (the odd can only be set once).
 173    * @param odd the OpenDefinitionsDocument to set as this DD's holder
 174    */
 175  343 public void setOpenDefDoc(OpenDefinitionsDocument odd) { if (_odd == null) _odd = odd; }
 176   
 177    /** @return the OpenDefinitonsDocument that is associated with this DefinitionsDocument. */
 178  84 public OpenDefinitionsDocument getOpenDefDoc() {
 179  84 if (_odd == null)
 180  0 throw new IllegalStateException("The OpenDefinitionsDocument for this DefinitionsDocument has never been set");
 181  84 else return _odd;
 182    }
 183   
 184    /** Recolors the rest of the document based on the change that triggered this call. */
 185  752 protected void _styleChanged() {
 186   
 187  752 int length = getLength() - _currentLocation;
 188   
 189    //DrJava.consoleErr().println("Changed: " + _currentLocation + ", " + length);
 190  752 DocumentEvent evt = new DefaultDocumentEvent(_currentLocation, length, DocumentEvent.EventType.CHANGE);
 191  752 fireChangedUpdate(evt);
 192    }
 193   
 194    // /** Returns whether this document is currently untitled
 195    // * (indicating whether it has a file yet or not).
 196    // * @return true if the document is untitled and has no file
 197    // */
 198    // public boolean isUntitled() { return (_file == null); }
 199   
 200    // /** Returns the file for this document. If the document
 201    // * is untitled and has no file, it throws an IllegalStateException.
 202    // * @return the file for this document
 203    // * @throws IllegalStateException if file has not been set
 204    // * @throws FileMovedException if file has been moved or deleted from its previous location
 205    // */
 206    // public File getFilex() throws IllegalStateException , FileMovedException {
 207    // if (_file == null) {
 208    // throw new IllegalStateException("This document does not yet have a file.");
 209    // }
 210    // //does the file actually exist?
 211    // if (_file.exists()) return _file;
 212    // else throw new FileMovedException(_file, "This document's file has been moved or deleted.");
 213    // }
 214    //
 215    // /** Returns the name of this file, or "(untitled)" if no file. */
 216    // public String getFilenamex() {
 217    // String filename = "(Untitled)";
 218    // try {
 219    // File file = getFilex();
 220    // filename = file.getName();
 221    // }
 222    // catch (IllegalStateException ise) {
 223    // // No file, leave as "untitled"
 224    // }
 225    // catch (FileMovedException fme) {
 226    // // Recover, even though file has been deleted
 227    // File file = fme.getFile();
 228    // filename = file.getName();
 229    // }
 230    // return filename;
 231    // }
 232   
 233   
 234    // public void setFile(File file) {
 235    // _file = file;
 236    //
 237    // //jim: maybe need lock
 238    // if (_file != null) {
 239    // _timestamp = _file.lastModified();
 240    // }
 241    // }
 242    //
 243    // public long getTimestamp() {
 244    // return _timestamp;
 245    // }
 246   
 247   
 248    /** Gets the package and main class/interface name of this OpenDefinitionsDocument
 249    * @return the qualified main class/interface name
 250    */
 251  115 public String getQualifiedClassName() throws ClassNameNotFoundException {
 252  115 return getPackageQualifier() + getMainClassName();
 253    }
 254   
 255    /** Gets fully qualified class name of the top level class enclosing the given position. */
 256  0 public String getQualifiedClassName(int pos) throws ClassNameNotFoundException {
 257  0 return getPackageQualifier() + getEnclosingTopLevelClassName(pos);
 258    }
 259   
 260    /** Gets an appropriate prefix to fully qualify a class name. Returns this class's package followed by a dot, or the
 261    * empty string if no package name is found.
 262    */
 263  115 private String getPackageQualifier() {
 264  115 String packageName = getPackageName();
 265  11 if ((packageName != null) && (! packageName.equals(""))) { packageName = packageName + "."; }
 266  115 return packageName;
 267    }
 268   
 269    /** Inserts a string of text into the document. This is not where we do custom processing of the insert; that is
 270    * done in {@link #insertUpdate}. If _removeTabs is set to true, remove all tabs from str. It is a current invariant
 271    * of the tabification functionality that the document contains no tabs, but we want to allow the user to override
 272    * this functionality.
 273    */
 274  584 public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
 275  584 if (_tabsRemoved) str = _removeTabs(str);
 276  584 _setModifiedSinceSave();
 277  584 super.insertString(offset, str, a);
 278    }
 279   
 280    /** Removes a block of text from the specified location. We don't update the reduced model here; that happens
 281    * in {@link #removeUpdate}.
 282    */
 283  239 public void remove(int offset, int len) throws BadLocationException {
 284   
 285  111 if (len == 0) return;
 286  128 _setModifiedSinceSave();
 287  128 super.remove(offset, len);
 288    }
 289   
 290    /** Given a String, return a new String will all tabs converted to spaces. Each tab is converted
 291    * to one space, since changing the number of characters within insertString screws things up.
 292    * @param source the String to be converted.
 293    * @return a String will all the tabs converted to spaces
 294    */
 295  584 static String _removeTabs(final String source) { return source.replace('\t', ' '); }
 296   
 297    /** Resets the modification state of this document to be consistent with state of _undoManager. Called whenever
 298    * an undo or redo is performed. */
 299  1 public void updateModifiedSinceSave() {
 300  1 _isModifiedSinceSave = _undoManager.isModified();
 301  1 if (_odd != null) _odd.documentReset();
 302    }
 303   
 304    /** Sets the modification state of this document to true and updates the state of the associated _odd.
 305    * Assumes that write lock is already held.
 306    */
 307  712 private void _setModifiedSinceSave() {
 308  712 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 309  712 if (! _isModifiedSinceSave) {
 310  322 _isModifiedSinceSave = true;
 311  218 if (_odd != null) _odd.documentModified(); // null test required for some unit tests
 312    }
 313    }
 314   
 315    /** Resets the modification state of this document. Used after a document has been saved or reverted. */
 316  430 public void resetModification() {
 317  430 _isModifiedSinceSave = false;
 318  430 _undoManager.documentSaved();
 319  430 if (_odd != null) _odd.documentReset(); // null test required for some unit tests
 320    }
 321   
 322    /** Determines if the document has been modified since the last save.
 323    * @return true if the document has been modified
 324    */
 325  2198 public boolean isModifiedSinceSave() { return _isModifiedSinceSave; }
 326   
 327    /** Return the current column of the cursor position. Uses a 0 based index. */
 328  95 public int getCurrentCol() {
 329  95 Element root = getDefaultRootElement();
 330  95 int line = root.getElementIndex(_currentLocation);
 331  95 return _currentLocation - root.getElement(line).getStartOffset();
 332    }
 333   
 334    /** Return the current line of the cursor position. Uses a 1-based index. */
 335  141 public int getCurrentLine() { return getLineOfOffset(_currentLocation); }
 336   
 337    /** Return the line number corresponding to offset. Uses a 1-based index. */
 338  141 public int getLineOfOffset(int offset) { return getDefaultRootElement().getElementIndex(offset) + 1; }
 339   
 340    /** Returns the offset corresponding to the first character of the given line number, or -1 if the lineNum is not
 341    * found. Line number counting begins with 1 not 0. Assumes read lock is already held.
 342    * @param lineNum the line number for which to calculate the offset.
 343    * @return the offset of the first character in the given line number
 344    */
 345  0 public int _getOffset(int lineNum) {
 346  0 if (lineNum <= 0) return -1;
 347  0 if (lineNum == 1) return 0;
 348   
 349    // synchronized(_reduced) {
 350  0 final int origPos = getCurrentLocation();
 351  0 try {
 352  0 final int docLen = getLength();
 353   
 354  0 setCurrentLocation(0); // _currentLocation is candidate offset to return
 355  0 int i;
 356  0 for (i = 1; (i < lineNum) && (_currentLocation < docLen); i++) {
 357  0 int dist = _reduced.getDistToNextNewline(); // or end of doc
 358  0 if (_currentLocation + dist < docLen) dist++; // skip newline
 359  0 move(dist); // updates _currentLocation to beginning of line # (i + 1)
 360    }
 361  0 if (i == lineNum) return _currentLocation;
 362  0 else return -1;
 363    }
 364  0 finally { setCurrentLocation(origPos); }
 365    // }
 366    }
 367   
 368   
 369    /** Returns true iff tabs are to removed on text insertion. */
 370  0 public boolean tabsRemoved() { return _tabsRemoved; }
 371   
 372    /** Comments out all lines between selStart and selEnd, inclusive. The cursor position is unchanged by the operation.
 373    * @param selStart the document offset for the start of the selection
 374    * @param selEnd the document offset for the end of the selection
 375    */
 376  3 public int commentLines(int selStart, int selEnd) {
 377   
 378    //int key = _undoManager.startCompoundEdit(); //Uncommented in regards to the FrenchKeyBoardFix
 379  3 int toReturn = selEnd;
 380  3 if (selStart == selEnd) {
 381  0 setCurrentLocation(_getLineStartPos(selStart));
 382    // Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
 383  0 _commentLine();
 384  0 toReturn += WING_COMMENT_OFFSET;
 385    }
 386  3 else toReturn = commentBlock(selStart, selEnd);
 387  3 _undoManager.endLastCompoundEdit(); //Changed from endCompoundEdit(key) for FrenchKeyBoardFix
 388  3 return toReturn;
 389    }
 390   
 391   
 392    /** Comments out the lines between start and end inclusive, using wing comments -- "// ".
 393    * @param start Position in document to start commenting from
 394    * @param end Position in document to end commenting at
 395    */
 396  3 private int commentBlock(final int start, final int end) {
 397  3 int afterCommentEnd = end;
 398  3 try {
 399    // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc doing the
 400    // indentLine calls.
 401  3 final Position endPos = this.createUnwrappedPosition(end);
 402    // Iterate, line by line, until we get to/past the end
 403  3 int walker = _getLineStartPos(start);
 404  3 while (walker < endPos.getOffset()) {
 405  11 setCurrentLocation(walker); // Update cursor
 406   
 407  11 _commentLine(); // Comment out current line; must be atomic
 408  11 afterCommentEnd += WING_COMMENT_OFFSET;
 409   
 410  11 walker = walker + 2; // Skip over inserted slashes; getDistToNewline(walker) = 0 if not advanced
 411  11 setCurrentLocation(walker); // reset currentLocation to position past newline
 412   
 413    // Adding 1 makes us point to the first character AFTER the next newline.
 414  11 walker += _reduced.getDistToNextNewline() + 1;
 415    }
 416    }
 417  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 418  3 return afterCommentEnd;
 419    }
 420   
 421    /** Comments out a single line with wing comments -- "// ". Assumes that _currentLocation is the beginning of the
 422    * line to be commented out. Only runs in event thread. */
 423  11 private void _commentLine() {
 424    // Insert "// " at the beginning of the line.
 425    // Using null for AttributeSet follows convention in this class.
 426  11 try { insertString(_currentLocation, "//", null); }
 427  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 428    }
 429   
 430    /** Uncomments all lines between selStart and selEnd, inclusive. The cursor position is unchanged by the operation.
 431    * @param selStart the document offset for the start of the selection
 432    * @param selEnd the document offset for the end of the selection
 433    */
 434  8 public int uncommentLines(int selStart, int selEnd) {
 435   
 436    //int key = _undoManager.startCompoundEdit(); //commented out for FrenchKeyBoardFix
 437  8 int toReturn = selEnd;
 438  8 if (selStart == selEnd) {
 439  3 try {
 440  3 setCurrentLocation(_getLineStartPos(selStart));
 441  3 _uncommentLine(); // accesses _reduced
 442  3 toReturn -= WING_COMMENT_OFFSET;
 443    }
 444  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 445    }
 446  5 else toReturn = uncommentBlock(selStart, selEnd);
 447    //_undoManager.endCompoundEdit(key); //Commented out for FrenchKeyBoardFix, Replaced with endLastCompoundEdit();
 448  8 _undoManager.endLastCompoundEdit();
 449  8 return toReturn;
 450    }
 451   
 452    /** Uncomments all lines between start and end inclusive.
 453    * @param start Position in document to start commenting from
 454    * @param end Position in document to end commenting at
 455    */
 456  5 private int uncommentBlock(final int start, final int end) {
 457  5 int afterUncommentEnd = end;
 458  5 try {
 459    // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc
 460    // doing the indentLine calls.
 461  5 final Position endPos = this.createUnwrappedPosition(end);
 462    // Iterate, line by line, until we get to/past the end
 463   
 464  5 int walker = _getLineStartPos(start);
 465    // Utilities.show("Initial walker pos = " + walker);
 466  5 while (walker < endPos.getOffset()) {
 467  18 setCurrentLocation(walker); // Move cursor to walker position
 468  18 int diff = _uncommentLine(); // Uncomment current line, accessing the reduced model
 469  18 afterUncommentEnd -= diff; // Update afterUncommentEnd
 470  18 walker = _getLineEndPos(walker) + 1; // Update walker pos to point to beginning of next line
 471    // Utilities.show("Updated value of walker = " + walker);
 472    }
 473    }
 474  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 475  5 return afterUncommentEnd;
 476    }
 477   
 478    /** Uncomments a single line. This simply looks for a leading "//". Assumes that cursor is already located at the
 479    * beginning of line. Also assumes that write lock and _reduced lock are already held.
 480    */
 481  21 private int _uncommentLine() throws BadLocationException {
 482    // Look for "//" at the beginning of the line, and remove it.
 483    // Utilities.show("Uncomment line at location " + _currentLocation);
 484    // Utilities.show("Preceding char = '" + getText().charAt(_currentLocation - 1) + "'");
 485    // Utilities.show("Line = \n" + getText(_currentLocation, getLineEndPos(_currentLocation) - _currentLocation + 1));
 486  21 int pos1 = getText().indexOf("//", _currentLocation); // TODO: get text of current line instead of whole document
 487  2 if (pos1 < 0) return NO_COMMENT_OFFSET;
 488  19 int pos2 = getFirstNonWSCharPos(_currentLocation, true);
 489    // Utilities.show("Pos1 = " + pos1 + " Pos2 = " + pos2);
 490  6 if (pos1 != pos2) return NO_COMMENT_OFFSET;
 491   
 492  13 remove(pos1, 2);
 493  13 return WING_COMMENT_OFFSET;
 494    }
 495   
 496    /** Goes to a particular line in the document. */
 497  2 public void gotoLine(int line) {
 498   
 499  2 int dist;
 500  0 if (line < 0) return;
 501  2 int actualLine = 1;
 502   
 503  2 int len = getLength();
 504  2 setCurrentLocation(0);
 505  2 for (int i = 1; (i < line) && (_currentLocation < len); i++) {
 506  3 dist = _reduced.getDistToNextNewline();
 507  3 if (_currentLocation + dist < len) dist++;
 508  3 actualLine++;
 509  3 move(dist); // updates _currentLocation
 510    }
 511    }
 512   
 513    /** Assumes that read lock is already held. */
 514  2406 private int _findNextOpenCurly(String text, int pos) throws BadLocationException {
 515   
 516  2406 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 517  2406 int i;
 518  2406 int reducedPos = pos;
 519   
 520    // synchronized(_reduced) {
 521  2406 final int origLocation = _currentLocation;
 522    // Move reduced model to location pos
 523  2406 _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
 524   
 525    // Walk forward from specificed position
 526  2406 i = text.indexOf('{', reducedPos);
 527  2406 while (i >- 1) {
 528    // Move reduced model to walker's location
 529  1591 _reduced.move(i - reducedPos); // reduced model points to i
 530  1591 reducedPos = i; // reduced model points to reducedPos
 531   
 532    // Check if matching keyword should be ignored because it is within a comment, or quotes
 533  1591 ReducedModelState state = _reduced.getStateAtCurrent();
 534  1591 if (!state.equals(FREE) || _isStartOfComment(text, i)
 535    || ((i > 0) && _isStartOfComment(text, i - 1))) {
 536  0 i = text.indexOf('{', reducedPos+1);
 537  0 continue; // ignore matching brace
 538    }
 539    else {
 540  1591 break; // found our brace
 541    }
 542    }
 543  2406 _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
 544    // } // end synchronized
 545   
 546  815 if (i == -1) reducedPos = -1; // No matching brace was found
 547  2406 return reducedPos;
 548    }
 549   
 550    /** Assuming that text is a document prefix including offset pos, finds the index of the keyword kw
 551    * searching back from pos.
 552    */
 553  18573 public int _findPrevKeyword(String text, String kw, int pos) throws BadLocationException {
 554   
 555  18573 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 556   
 557  18573 int i;
 558  18573 int reducedPos = pos;
 559   
 560    // synchronized(_reduced) {
 561  18573 final int origLocation = _currentLocation;
 562    // Move reduced model to location pos
 563  18573 _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
 564   
 565    // Walk backwards from specificed position
 566  18573 i = text.lastIndexOf(kw, reducedPos);
 567  18573 while (i >- 1) {
 568    // Check that this is the beginning of a word
 569  15208 if (i > 0) {
 570  15208 if (Character.isJavaIdentifierPart(text.charAt(i-1))) {
 571    // not begining
 572  0 i = text.lastIndexOf(kw, i - 1);
 573  0 continue; // ignore matching keyword
 574    }
 575    }
 576    // Check that this not just the beginning of a longer word
 577  15208 if (i + kw.length() < text.length()) {
 578  15208 if (Character.isJavaIdentifierPart(text.charAt(i+kw.length()))) {
 579    // not begining
 580  0 i = text.lastIndexOf(kw, i-1);
 581  0 continue; // ignore matching keyword
 582    }
 583    }
 584   
 585    // Move reduced model to walker's location
 586  15208 _reduced.move(i - reducedPos); // reduced model points to i
 587  15208 reducedPos = i; // reduced model points to reducedPos
 588   
 589    // Check if matching keyword should be ignored because it is within a comment, or quotes
 590  15208 ReducedModelState state = _reduced.getStateAtCurrent();
 591  15208 if (!state.equals(FREE) || _isStartOfComment(text, i) || ((i > 0) && _isStartOfComment(text, i - 1))) {
 592  0 i = text.lastIndexOf(kw, reducedPos-1);
 593  0 continue; // ignore matching keyword
 594    }
 595  15208 else break; // found our keyword
 596    } // end synchronized/
 597   
 598  18573 _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
 599    // }
 600   
 601  3365 if (i == -1) reducedPos = -1; // No matching keyword was found
 602  18573 return reducedPos;
 603    }
 604   
 605    // public static boolean log = true;
 606   
 607    /** Searches backwards to find the name of the enclosing named class or interface. NB: ignores comments.
 608    * WARNING: In long source files and when contained in anonymous inner classes, this function might take a LONG time.
 609    * @param pos Position to start from
 610    * @param qual true to find the fully qualified class name
 611    * @return name of the enclosing named class or interface
 612    */
 613  168 public String getEnclosingClassName(int pos, boolean qual) throws BadLocationException, ClassNameNotFoundException {
 614  168 return _getEnclosingClassName(pos, qual);
 615    }
 616   
 617    /** Searches backwards to find the name of the enclosing named class or interface. NB: ignores comments. Only runs in
 618    * event thread.
 619    * WARNING: In long source files and when contained in anonymous inner classes, this function might take a LONG time.
 620    * @param pos Position to start from
 621    * @param qual true to find the fully qualified class name
 622    * @return name of the enclosing named class or interface
 623    */
 624  2171 public String _getEnclosingClassName(final int pos, final boolean qual) throws BadLocationException,
 625    ClassNameNotFoundException {
 626   
 627  2171 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 628   
 629    // Check cache
 630  2171 final Query key = new Query.EnclosingClassName(pos, qual);
 631  2171 final String cached = (String) _checkCache(key);
 632  0 if (cached != null) return cached;
 633   
 634  2171 final char[] delims = {'{','}','(',')','[',']','+','-','/','*',';',':','=','!','@','#','$','%','^','~','\\','"','`','|'};
 635  2171 String name = "";
 636   
 637  2171 final String text = getText(0, pos);
 638   
 639  2171 int curPos = pos;
 640   
 641  2171 do {
 642    // if (text.charAt(curPos) != '{' || text.charAt(curPos) != '}') ++curPos;
 643   
 644    // if (oldLog) System.err.println("curPos=" + curPos + " `" +
 645    // text.substring(Math.max(0,curPos-10), Math.min(text.length(), curPos+1)) + "`");
 646   
 647  5694 curPos = findPrevEnclosingBrace(curPos, '{', '}');
 648  2091 if (curPos == -1) { break; }
 649  3603 int classPos = _findPrevKeyword(text, "class", curPos);
 650  3603 int interPos = _findPrevKeyword(text, "interface", curPos);
 651  3603 int otherPos = findPrevDelimiter(curPos, delims);
 652  3603 int newPos = -1;
 653    // see if there's a ) closer by
 654  3603 int closeParenPos = _findPrevNonWSCharPos(curPos);
 655  3603 if (closeParenPos != -1 && text.charAt(closeParenPos) == ')') {
 656    // yes, find the matching (
 657  2653 int openParenPos = findPrevEnclosingBrace(closeParenPos, '(', ')');
 658  2653 if (openParenPos != -1 && text.charAt(openParenPos) == '(') {
 659    // this might be an inner class
 660  2653 newPos = _findPrevKeyword(text, "new", openParenPos);
 661    // if (oldLog) System.err.println("\tnew found at " + newPos + ", openCurlyPos=" + curPos);
 662  2653 if (! _isAnonymousInnerClass(newPos, curPos)) {
 663    // not an anonymous inner class
 664  2608 newPos = -1;
 665    }
 666    }
 667    }
 668   
 669  6223 while (classPos != -1 || interPos != -1 || newPos != -1) {
 670  6223 if (newPos != -1) {
 671  584 classPos = -1;
 672  584 interPos = -1;
 673  584 break;
 674    }
 675  5639 else if (otherPos > classPos && otherPos > interPos) {
 676  2620 if (text.charAt(otherPos) != '{' || text.charAt(otherPos) != '}') ++otherPos;
 677  2620 curPos = findPrevEnclosingBrace(otherPos, '{', '}');
 678  2620 classPos = _findPrevKeyword(text, "class", curPos);
 679  2620 interPos = _findPrevKeyword(text, "interface", curPos);
 680  2620 otherPos = findPrevDelimiter(curPos, delims);
 681  2620 newPos = -1;
 682    // see if there's a ) closer by
 683  2620 closeParenPos = _findPrevNonWSCharPos(curPos);
 684  2620 if (closeParenPos != -1 && text.charAt(closeParenPos) == ')') {
 685    // yes, find the matching (
 686  539 int openParenPos = findPrevEnclosingBrace(closeParenPos, '(', ')');
 687  539 if (openParenPos != -1 && text.charAt(openParenPos) == '(') {
 688    // this might be an inner class
 689  539 newPos = _findPrevKeyword(text, "new", openParenPos);
 690    // if (oldLog) System.err.println("\tnew found at " + newPos + ", openCurlyPos=" + curPos);
 691  0 if (! _isAnonymousInnerClass(newPos, curPos)) newPos = -1;
 692    }
 693    }
 694    }
 695    else {
 696    // either class or interface found first
 697  3019 curPos = Math.max(classPos, Math.max(interPos, newPos));
 698  3019 break;
 699    }
 700    }
 701   
 702  3603 if (classPos != -1 || interPos != -1) {
 703  3015 if (classPos > interPos) curPos += "class".length(); // class found first
 704  4 else curPos += "interface".length(); // interface found first
 705   
 706  3019 int nameStart = getFirstNonWSCharPos(curPos);
 707  0 if (nameStart==-1) { throw new ClassNameNotFoundException("Cannot determine enclosing class name"); }
 708  3019 int nameEnd = nameStart + 1;
 709  18365 while (nameEnd < text.length()) {
 710  3019 if (! Character.isJavaIdentifierPart(text.charAt(nameEnd)) && text.charAt(nameEnd) != '.') break;
 711  15346 ++nameEnd;
 712    }
 713  3019 name = text.substring(nameStart,nameEnd) + '$' + name;
 714    }
 715  584 else if (newPos != -1) {
 716  584 name = String.valueOf(_getAnonymousInnerClassIndex(curPos)) + "$" + name;
 717  584 curPos = newPos;
 718    }
 719  0 else break; // neither class nor interface found (exiting loop if qual == true)
 720  3603 } while(qual);
 721   
 722    // chop off '$' at the end.
 723  2163 if (name.length() > 0) name = name.substring(0, name.length() - 1);
 724   
 725  2171 if (qual) {
 726  2087 String pn = getPackageName();
 727  2087 if ((pn.length() > 0) && (name.length() > 0)) {
 728  2083 name = getPackageName() + "." + name;
 729    }
 730    }
 731    // log = oldLog;
 732  2171 _storeInCache(key, name, pos);
 733  2171 return name;
 734    }
 735   
 736    /** Returns true if this position is the instantiation of an anonymous inner class. Only runs in the event thread.
 737    * @param pos position of "new"
 738    * @param openCurlyPos position of the next '{'
 739    * @return true if anonymous inner class instantiation
 740    */
 741   
 742  4783 public boolean _isAnonymousInnerClass(final int pos, final int openCurlyPos) throws BadLocationException {
 743    // String t = getText(0, openCurlyPos+1);
 744    // System.err.print("_isAnonymousInnerClass(" + pos + ", " + openCurlyPos + ")");
 745    // System.err.println("_isAnonymousInnerClass(" + pos + ", " + openCurlyPos + "): `" +
 746    // t.substring(pos, openCurlyPos+1) + "`");
 747   
 748    // Check cache
 749  4783 final Query key = new Query.AnonymousInnerClass(pos, openCurlyPos);
 750  4783 Boolean cached = (Boolean) _checkCache(key);
 751  4783 if (cached != null) {
 752    // System.err.println(" ==> " + cached);
 753  0 return cached;
 754    }
 755  4783 int newPos = pos;
 756    // synchronized(_reduced) {
 757  4783 cached = false;
 758   
 759  4783 String text = getText(0, openCurlyPos + 1); // includes open Curly brace
 760  4783 newPos += "new".length();
 761  4783 int classStart = getFirstNonWSCharPos(newPos);
 762  4783 if (classStart != -1) {
 763  4783 int classEnd = classStart + 1;
 764  33545 while (classEnd < text.length()) {
 765  33545 if (! Character.isJavaIdentifierPart(text.charAt(classEnd)) && text.charAt(classEnd) != '.') {
 766    // delimiter found
 767  4783 break;
 768    }
 769  28762 ++classEnd;
 770    }
 771   
 772    /* Determine parenStart, the postion immediately before the open parenthesis following the superclass name. */
 773    // System.err.println("\tclass = `" + text.substring(classStart,classEnd) + "`");
 774  4783 int parenStart = getFirstNonWSCharPos(classEnd);
 775  4783 if (parenStart != -1) {
 776  4783 int origParenStart = parenStart;
 777   
 778    // System.err.println("\tfirst non-whitespace after class = " + parenStart + " `" + text.charAt(parenStart) + "`");
 779  4783 if (text.charAt(origParenStart) == '<') {
 780  1111 parenStart = -1;
 781    // might be a generic class
 782  1111 int closePointyBracket = findNextEnclosingBrace(origParenStart, '<', '>');
 783  1111 if (closePointyBracket != -1) {
 784  1111 if (text.charAt(closePointyBracket) == '>') {
 785  1111 parenStart = getFirstNonWSCharPos(closePointyBracket+1);
 786    }
 787    }
 788    }
 789    }
 790   
 791  4783 if (parenStart != -1) {
 792  4783 if (text.charAt(parenStart) == '(') {
 793  3970 setCurrentLocation(parenStart + 1); // reduced model points to pos == parenStart + 1
 794  3970 int parenEnd = balanceForward();
 795  3970 if (parenEnd > -1) {
 796  3970 parenEnd = parenEnd + parenStart + 1;
 797    // System.err.println("\tafter closing paren = " + parenEnd);
 798  3970 int afterParen = getFirstNonWSCharPos(parenEnd);
 799    // System.err.println("\tfirst non-whitespace after paren = " + parenStart + " `" + text.charAt(afterParen) + "`");
 800  3970 cached = (afterParen == openCurlyPos);
 801    }
 802    }
 803    }
 804    }
 805  4783 _storeInCache(key, cached, openCurlyPos);
 806    // System.err.println(" ==> " + cached);
 807  4783 return cached;
 808    // }
 809    }
 810   
 811    /** Gets the package name embedded in the text of this document by minimally parsing the document to find the
 812    * package statement. If package statement is not found or is ill-formed, returns "" as the package name.
 813    * @return the name of package embedded in this document. If there is no well-formed package statement,
 814    * returns "" as the package name.
 815    */
 816  4798 public String getPackageName() {
 817    // assert EventQueue.isDispatchThread();
 818  4798 Reader r;
 819  4798 r = new StringReader(getText()); // getText() is cheap if document is not resident
 820  4798 try { return new Parser(r).packageDeclaration(Parser.DeclType.TOP).getName(); }
 821  599 catch (ParseException e) { return ""; }
 822    // addresses bug [ 1815387 ] Editor should discard parse errors for now
 823    // we should upgrade our parser to handle @
 824  0 catch (TokenMgrError e) { return ""; }
 825    catch (Error e) {
 826    // JavaCharStream does not use a useful exception type for escape character errors
 827  0 String msg = e.getMessage();
 828  0 if (msg != null && msg.startsWith("Invalid escape character")) {
 829  0 return "";
 830    }
 831  0 else { throw e; }
 832    }
 833    finally {
 834  4798 try { r.close(); }
 835    catch (IOException e) { /* ignore */ }
 836    }
 837    }
 838   
 839    /** Returns the index of the anonymous inner class being instantiated at the specified position (where openining brace
 840    * for anonymous inner class is pos). Only runs in event thread.
 841    * @param pos is position of the opening curly brace of the anonymous inner class
 842    * @return anonymous class index
 843    */
 844  589 int _getAnonymousInnerClassIndex(final int pos) throws BadLocationException, ClassNameNotFoundException {
 845    // boolean oldLog = true; // log; log = false;
 846   
 847  589 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 848   
 849    // Check cache
 850  589 final Query key = new Query.AnonymousInnerClassIndex(pos);
 851  589 final Integer cached = (Integer) _checkCache(key);
 852  589 if (cached != null) {
 853    // log = oldLog;
 854  0 return cached.intValue();
 855    }
 856   
 857  589 int newPos = pos; // formerly pos -1 // move outside the curly brace? Corrected to do nothing since already outside
 858   
 859    // final char[] delims = {'{','}','(',')','[',']','+','-','/','*',';',':','=','!','@','#','$','%','^','~','\\','"','`','|'};
 860   
 861  589 final String className = _getEnclosingClassName(newPos - 2 , true); // class name must be followed by at least "()"
 862  589 final String text = getText(0, newPos - 2); // excludes miminal (empty) argument list after class name
 863  589 int index = 1;
 864   
 865    // if (oldLog) System.err.println("anon before " + pos + " enclosed by " + className);
 866  ? while ((newPos = _findPrevKeyword(text, "new", newPos - 4)) != -1) { // excludes space + minimal class name + args
 867    // if (oldLog) System.err.println("new found at " + newPos);
 868  2406 int afterNewPos = newPos + "new".length();
 869  2406 int classStart = getFirstNonWSCharPos(afterNewPos);
 870  0 if (classStart == -1) { continue; }
 871  2406 int classEnd = classStart + 1;
 872  17842 while (classEnd < text.length()) {
 873  17842 if (! Character.isJavaIdentifierPart(text.charAt(classEnd)) && text.charAt(classEnd) != '.') {
 874    // delimiter found
 875  2406 break;
 876    }
 877  15436 ++classEnd;
 878    }
 879    // if (oldLog) System.err.println("\tclass = `" + text.substring(classStart,classEnd) + "`");
 880  2406 int parenStart = getFirstNonWSCharPos(classEnd);
 881  0 if (parenStart == -1) { continue; }
 882  2406 int origParenStart = parenStart;
 883   
 884    // if (oldLog) System.err.println("\tfirst non-whitespace after class = " + parenStart + " `" + text.charAt(parenStart) + "`");
 885  2406 if (text.charAt(origParenStart) == '<') {
 886  441 parenStart = -1;
 887    // might be a generic class
 888  441 int closePointyBracket = findNextEnclosingBrace(origParenStart, '<', '>');
 889  441 if (closePointyBracket != -1) {
 890  441 if (text.charAt(closePointyBracket) == '>') {
 891  441 parenStart = getFirstNonWSCharPos(closePointyBracket + 1);
 892    }
 893    }
 894    }
 895  0 if (parenStart == -1) { continue; }
 896  0 if (text.charAt(parenStart) != '(') { continue; }
 897  2406 int parenEnd = findNextEnclosingBrace(parenStart, '(', ')');
 898   
 899  2406 int nextOpenCurly = _findNextOpenCurly(text, parenEnd);
 900  815 if (nextOpenCurly == -1) { continue; }
 901    // if (oldLog) System.err.println("{ found at " + nextOpenCurly + ": `" +
 902    // text.substring(newPos, nextOpenCurly + 1) + "`");
 903    // if (oldLog) System.err.println("_isAnonymousInnerClass(" + newPos + ", " + nextOpenCurly + ")");
 904  1591 if (_isAnonymousInnerClass(newPos, nextOpenCurly)) {
 905    // if (oldLog) System.err.println("is anonymous inner class");
 906  1414 String cn = _getEnclosingClassName(newPos, true);
 907    // if (oldLog) System.err.println("enclosing class = " + cn);
 908  60 if (! cn.startsWith(className)) { break; }
 909  1354 else if (! cn.equals(className)) {
 910  704 newPos = findPrevEnclosingBrace(newPos, '{', '}');
 911  704 continue;
 912    }
 913  650 else ++index;
 914    }
 915    }
 916  589 _storeInCache(key, index, pos);
 917    // oldLog = log;
 918  589 return index;
 919    }
 920   
 921    /** Returns the name of the class or interface enclosing the caret position at the top level.
 922    * @return Name of enclosing class or interface
 923    * @throws ClassNameNotFoundException if no enclosing class found
 924    */
 925  0 public String getEnclosingTopLevelClassName(int pos) throws ClassNameNotFoundException {
 926  0 int oldPos = _currentLocation;
 927  0 try {
 928  0 setCurrentLocation(pos);
 929  0 BraceInfo info = _getEnclosingBrace();
 930   
 931    // Find top level open brace
 932  0 int topLevelBracePos = -1;
 933  0 String braceType = info.braceType();
 934  0 while (! braceType.equals(BraceInfo.NONE)) {
 935  0 if (braceType.equals(BraceInfo.OPEN_CURLY)) {
 936  0 topLevelBracePos = _currentLocation - info.distance();
 937    }
 938  0 move(-info.distance());
 939  0 info = _getEnclosingBrace();
 940  0 braceType = info.braceType();
 941    }
 942  0 if (topLevelBracePos == -1) {
 943    // No top level brace was found, so we can't find a top level class name
 944  0 setCurrentLocation(oldPos);
 945  0 throw new ClassNameNotFoundException("no top level brace found");
 946    }
 947   
 948  0 char[] delims = {'{', '}', ';'};
 949  0 int prevDelimPos = findPrevDelimiter(topLevelBracePos, delims);
 950  0 if (prevDelimPos == -1) {
 951    // Search from start of doc
 952  0 prevDelimPos = 0;
 953    }
 954  0 else prevDelimPos++;
 955  0 setCurrentLocation(oldPos);
 956   
 957    // Parse out the class name
 958  0 return getNextTopLevelClassName(prevDelimPos, topLevelBracePos);
 959    }
 960  0 catch (BadLocationException ble) { throw new UnexpectedException(ble); }
 961  0 finally { setCurrentLocation(oldPos); }
 962    }
 963   
 964    /** Gets the name of first class/interface/enum declared in file among the definitions anchored at:
 965    * @param indexOfClass index in this of a top-level occurrence of class
 966    * @param indexOfInterface index in this of a top-level occurrence of interface
 967    * @param indexOfEnum index in this of a top-level occurrence of enum
 968    */
 969  133 private String getFirstClassName(int indexOfClass, int indexOfInterface,
 970    int indexOfEnum) throws ClassNameNotFoundException {
 971  133 try {
 972  2 if ((indexOfClass == -1) && (indexOfInterface == -1) && (indexOfEnum == -1)) throw ClassNameNotFoundException.DEFAULT;
 973   
 974    // should we convert this to a sorted queue or something like that?
 975    // should we have to extend this past three keywords, it will get rather hard to maintain
 976  131 if ((indexOfEnum == -1) ||
 977    ((indexOfClass != -1) && (indexOfClass < indexOfEnum)) ||
 978    ((indexOfInterface != -1) && (indexOfInterface < indexOfEnum))) {
 979    // either "enum" not found, or "enum" found after "class" or "interface"
 980    // "enum" is irrelevant
 981    // we know that at least one of indexOfClass and indexOfInterface is != -1
 982  125 if ((indexOfInterface == -1) ||
 983    ((indexOfClass != -1) && (indexOfClass < indexOfInterface))) {
 984    // either "interface" not found, or "interface" found after "class"
 985  116 return getNextIdentifier(indexOfClass + "class".length());
 986    }
 987    else {
 988    // "interface" found, and found before "class"
 989  9 return getNextIdentifier(indexOfInterface + "interface".length());
 990    }
 991    }
 992    else {
 993    // "enum" found, and found before "class" and "interface"
 994  6 return getNextIdentifier(indexOfEnum + "enum".length());
 995    }
 996    }
 997  0 catch(IllegalStateException ise) { throw ClassNameNotFoundException.DEFAULT; }
 998    }
 999   
 1000    /** Gets the name of the document's main class: the document's only public class/interface or
 1001    * first top level class if document contains no public classes or interfaces. */
 1002  133 public String getMainClassName() throws ClassNameNotFoundException {
 1003  133 final int oldPos = _currentLocation;
 1004   
 1005  133 try {
 1006  133 setCurrentLocation(0);
 1007  133 final String text = getText(); // getText() is cheap if document is not resident
 1008   
 1009  133 final int indexOfClass = _findKeywordAtToplevel("class", text, 0);
 1010  133 final int indexOfInterface = _findKeywordAtToplevel("interface", text, 0);
 1011  133 final int indexOfEnum = _findKeywordAtToplevel("enum", text, 0);
 1012  133 final int indexOfPublic = _findKeywordAtToplevel("public", text, 0);
 1013   
 1014  75 if (indexOfPublic == -1) return getFirstClassName(indexOfClass, indexOfInterface, indexOfEnum);
 1015   
 1016    // _log.log("text =\n" + text);
 1017    // _log.log("indexOfClass = " + indexOfClass + "; indexOfPublic = " + indexOfPublic);
 1018   
 1019    // There is an explicit public declaration
 1020  58 final int afterPublic = indexOfPublic + "public".length();
 1021  58 final String subText = text.substring(afterPublic);
 1022  58 setCurrentLocation(afterPublic);
 1023    // _log.log("After public text = '" + subText + "'");
 1024  58 int indexOfPublicClass = _findKeywordAtToplevel("class", subText, afterPublic); // relative offset
 1025  54 if (indexOfPublicClass != -1) indexOfPublicClass += afterPublic;
 1026  58 int indexOfPublicInterface = _findKeywordAtToplevel("interface", subText, afterPublic); // relative offset
 1027  11 if (indexOfPublicInterface != -1) indexOfPublicInterface += afterPublic;
 1028  58 int indexOfPublicEnum = _findKeywordAtToplevel("enum", subText, afterPublic); // relative offset
 1029  4 if (indexOfPublicEnum != -1) indexOfPublicEnum += afterPublic;
 1030    // _log.log("indexOfPublicClass = " + indexOfPublicClass + " indexOfPublicInterface = " + indexOfPublicInterface);
 1031   
 1032  58 return getFirstClassName(indexOfPublicClass, indexOfPublicInterface, indexOfPublicEnum);
 1033   
 1034    }
 1035  133 finally { setCurrentLocation(oldPos); }
 1036    }
 1037   
 1038    /** Gets the name of the top level class in this source file by finding the first declaration of a class or interface.
 1039    * @return The name of first class in the file
 1040    * @throws ClassNameNotFoundException if no top level class found
 1041    */
 1042  0 public String getFirstTopLevelClassName() throws ClassNameNotFoundException {
 1043  0 return getNextTopLevelClassName(0, getLength());
 1044    }
 1045   
 1046    // note: need to update this to work with pos
 1047  0 public String getNextTopLevelClassName(int startPos, int endPos) throws ClassNameNotFoundException {
 1048  0 int oldPos = _currentLocation;
 1049   
 1050  0 try {
 1051  0 setCurrentLocation(startPos);
 1052  0 final int textLength = endPos - startPos;
 1053  0 final String text = getText(startPos, textLength);
 1054   
 1055  0 int index;
 1056   
 1057  0 int indexOfClass = _findKeywordAtToplevel("class", text, startPos);
 1058  0 int indexOfInterface = _findKeywordAtToplevel("interface", text, startPos);
 1059  0 int indexOfEnum = _findKeywordAtToplevel("enum",text,startPos);
 1060   
 1061    //If class exists at top level AND either there is no interface at top level or the index of class precedes the index of the top
 1062    //level interface, AND the same for top level enum, then the class is the first top level declaration
 1063  0 if (indexOfClass > -1 && (indexOfInterface <= -1 || indexOfClass < indexOfInterface)
 1064    && (indexOfEnum <= -1 || indexOfClass < indexOfEnum)) {
 1065  0 index = indexOfClass + "class".length();
 1066    }
 1067  0 else if (indexOfInterface > -1 && (indexOfClass <= -1 || indexOfInterface < indexOfClass)
 1068    && (indexOfEnum <= -1 || indexOfInterface < indexOfEnum)) {
 1069  0 index = indexOfInterface + "interface".length();
 1070    }
 1071  0 else if (indexOfEnum > -1 && (indexOfClass <= -1 || indexOfEnum < indexOfClass)
 1072    && (indexOfInterface <= -1 || indexOfEnum < indexOfInterface)) {
 1073  0 index = indexOfEnum + "enum".length();
 1074    }
 1075    else {
 1076    // no index was valid
 1077  0 throw ClassNameNotFoundException.DEFAULT;
 1078    }
 1079   
 1080    // we have a valid index
 1081  0 return getNextIdentifier(startPos + index);
 1082    }
 1083  0 catch (BadLocationException ble) { throw new UnexpectedException(ble); }
 1084  0 catch (IllegalStateException e) { throw new ClassNameNotFoundException("No top level class name found"); }
 1085  0 finally { setCurrentLocation(oldPos); }
 1086    }
 1087   
 1088    /** Finds the next identifier (following a non-whitespace character) in the document starting at start. Assumes that
 1089    * read lock and _reduced lock are already held. */
 1090  131 private String getNextIdentifier(final int startPos) throws ClassNameNotFoundException {
 1091   
 1092    // _log.log("getNextIdentifer(" + startPos + ") called");
 1093   
 1094    // int index = 0;
 1095    // int length = 0;
 1096    // int endIndex = 0;
 1097    // String text = "";
 1098    // int i;
 1099  131 try {
 1100    // first find index of first non whitespace (from the index in document)
 1101  131 int index = getFirstNonWSCharPos(startPos);
 1102  0 if (index == -1) throw new IllegalStateException("No identifier found");
 1103   
 1104  131 String text = getText();
 1105  131 int length = text.length();
 1106  131 int endIndex = length; //just in case no whitespace at end of file
 1107   
 1108    // _log.log("In getNextIdentifer text = \n" + text);
 1109    // _log.log("index = " + index + "; length = " + length);
 1110   
 1111    //find index of next delimiter or whitespace
 1112  131 char c;
 1113  1468 for (int i = index; i < length; i++) {
 1114  1468 c = text.charAt(i);
 1115  1468 if (! Character.isJavaIdentifierPart(c)) {
 1116  131 endIndex = i;
 1117  131 break;
 1118    }
 1119    }
 1120    // _log.log("endIndex = " + endIndex);
 1121  131 return text.substring(index, endIndex);
 1122    }
 1123    catch(BadLocationException e) {
 1124    // System.err.println("text =\n" + text);
 1125    // System.err.println("The document =\n" + getText());
 1126    // System.err.println("startPos = " + startPos + "; length = " + length + "; index = " + index + "; endIndex = " + endIndex);
 1127  0 throw new UnexpectedException(e);
 1128    }
 1129    }
 1130   
 1131    /** Finds the first occurrence of the keyword within the text (located at textOffset in this documennt) that is not
 1132    * enclosed within a brace or comment and is followed by whitespace.
 1133    * @param keyword the keyword for which to search
 1134    * @param text in which to search
 1135    * @param textOffset Offset at which the text occurs in the document
 1136    * @return index of the keyword in text, or -1 if the keyword is not found or not followed by whitespace
 1137    */
 1138  706 private int _findKeywordAtToplevel(String keyword, String text, int textOffset) {
 1139   
 1140  706 int oldPos = _currentLocation;
 1141  706 int index = 0;
 1142  706 while (true) {
 1143  707 index = text.indexOf(keyword, index);
 1144  382 if (index == -1) break; // not found
 1145    else {
 1146    // found a match, check quality
 1147  325 setCurrentLocation(textOffset + index);
 1148   
 1149    // check that the keyword is not in a comment and is followed by whitespace
 1150  325 int indexPastKeyword = index + keyword.length();
 1151  325 if (indexPastKeyword < text.length()) {
 1152  325 if (! isShadowed() && Character.isWhitespace(text.charAt(indexPastKeyword))) {
 1153    // found a match but may not be at top level
 1154  60 if (! notInBlock(index)) index = -1; //in a paren phrase, gone too far
 1155  324 break;
 1156    }
 1157  1 else index++; //move past so we can search again
 1158    }
 1159    else { // No space found past the keyword
 1160  0 index = -1;
 1161  0 break;
 1162    }
 1163    }
 1164    }
 1165  706 setCurrentLocation(oldPos);
 1166    // _log.log("findKeyWord(" + keyword + ", ..., " + textOffset + ")");
 1167  706 return index;
 1168    }
 1169   
 1170    /** Wrapper for Position objects to allow relinking to a new Document. */
 1171    public static class WrappedPosition implements Position {
 1172    private Position _wrapped;
 1173    /** Constructor is only called from createPosition below. */
 1174  5871 WrappedPosition(Position w) { setWrapped(w); }
 1175  5871 public void setWrapped(Position w) { _wrapped = w; }
 1176  42265 public int getOffset() { return _wrapped.getOffset(); }
 1177    }
 1178   
 1179    /** Factory method for created WrappedPositions. Stores the created Position instance so it can be linked to a
 1180    * different DefinitionsDocument later.
 1181    */
 1182  5871 public Position createPosition(final int offset) throws BadLocationException {
 1183    /* The following attempt to defer document loading did not work because offset became stale. Postions must be
 1184    * created eagerly. */
 1185    // WrappedPosition wp = new WrappedPosition(new LazyPosition(new Suspension<Position>() {
 1186    // public Position eval() {
 1187    // try { return createUnwrappedPosition(offset); }
 1188    // catch(BadLocationException e) { throw new UnexpectedException(e); }
 1189    // }
 1190    // }));
 1191  5871 WrappedPosition wp = new WrappedPosition(createUnwrappedPosition(offset));
 1192  5871 synchronized(_wrappedPosListLock) {
 1193  448 if (_wrappedPosList == null) _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>();
 1194  5871 _wrappedPosList.add(new WeakReference<WrappedPosition>(wp));
 1195    }
 1196  5871 return wp;
 1197    }
 1198   
 1199    /** Remove all positions that have been garbage-collected from the list of positions, then return a weakly-linked
 1200    * hashmap with positions and their current offsets.
 1201    * @return list of weak references to all positions that have been created and that have not been garbage-collected yet.
 1202    */
 1203  29 public WeakHashMap<WrappedPosition, Integer> getWrappedPositionOffsets() {
 1204  29 LinkedList<WeakReference<WrappedPosition>> newList = new LinkedList<WeakReference<WrappedPosition>>();
 1205  29 synchronized(_wrappedPosListLock) {
 1206  0 if (_wrappedPosList == null) { _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>(); }
 1207  29 WeakHashMap<WrappedPosition, Integer> ret = new WeakHashMap<WrappedPosition, Integer>(_wrappedPosList.size());
 1208   
 1209  29 for (WeakReference<WrappedPosition> wr: _wrappedPosList) {
 1210  0 if (wr.get() != null) {
 1211    // hasn't been garbage-collected yet
 1212  0 newList.add(wr);
 1213  0 ret.put(wr.get(), wr.get().getOffset());
 1214    }
 1215    }
 1216  29 _wrappedPosList.clear();
 1217  29 _wrappedPosList = newList;
 1218  29 return ret;
 1219    }
 1220    }
 1221   
 1222    /** Re-create the wrapped positions in the hashmap, update the wrapped position, and add them to the list.
 1223    * @param whm weakly-linked hashmap of wrapped positions and their offsets
 1224    */
 1225  343 public void setWrappedPositionOffsets(WeakHashMap<WrappedPosition, Integer> whm) throws BadLocationException {
 1226  343 synchronized(_wrappedPosListLock) {
 1227  0 if (_wrappedPosList == null) { _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>(); }
 1228  343 _wrappedPosList.clear();
 1229   
 1230  343 for(Map.Entry<WrappedPosition, Integer> entry: whm.entrySet()) {
 1231  0 if (entry.getKey() != null) {
 1232    // hasn't been garbage-collected yet
 1233  0 WrappedPosition wp = entry.getKey();
 1234  0 wp.setWrapped(createUnwrappedPosition(entry.getValue()));
 1235  0 _wrappedPosList.add(new WeakReference<WrappedPosition>(wp));
 1236    }
 1237    }
 1238    }
 1239    }
 1240   
 1241    /** Appending any information for the reduced model from each undo command */
 1242    private static class CommandUndoableEdit extends AbstractUndoableEdit {
 1243    private final Runnable _undoCommand;
 1244    private final Runnable _redoCommand;
 1245   
 1246  710 public CommandUndoableEdit(final Runnable undoCommand, final Runnable redoCommand) {
 1247  710 _undoCommand = undoCommand;
 1248  710 _redoCommand = redoCommand;
 1249    }
 1250   
 1251  23 public void undo() throws CannotUndoException {
 1252  23 super.undo();
 1253  23 _undoCommand.run();
 1254    }
 1255   
 1256  19 public void redo() throws CannotRedoException {
 1257  19 super.redo();
 1258  19 _redoCommand.run();
 1259    }
 1260   
 1261  0 public boolean isSignificant() { return false; }
 1262    }
 1263   
 1264    /** Getter method for CompoundUndoManager
 1265    * @return _undoManager
 1266    */
 1267  438 public CompoundUndoManager getUndoManager() { return _undoManager; }
 1268   
 1269    /** Resets the undo manager. */
 1270  118 public void resetUndoManager() {
 1271  118 _undoManager = new CompoundUndoManager(_notifier);
 1272  118 _undoManager.setLimit(UNDO_LIMIT);
 1273    }
 1274   
 1275    /** Public accessor for the next undo action. */
 1276  0 public UndoableEdit getNextUndo() { return _undoManager.getNextUndo(); }
 1277   
 1278    /** Public accessor for the next undo action. */
 1279  0 public UndoableEdit getNextRedo() { return _undoManager.getNextRedo(); }
 1280   
 1281    /** Informs this document's undo manager that the document has been saved. */
 1282  0 public void documentSaved() { _undoManager.documentSaved(); }
 1283   
 1284  0 protected int startCompoundEdit() { return _undoManager.startCompoundEdit(); }
 1285   
 1286  0 protected void endCompoundEdit(int key) { _undoManager.endCompoundEdit(key); }
 1287   
 1288    //This method added for FrenchKeyBoardFix
 1289  66 protected void endLastCompoundEdit() { _undoManager.endLastCompoundEdit(); }
 1290   
 1291  710 protected void addUndoRedo(AbstractDocument.DefaultDocumentEvent chng, Runnable undoCommand, Runnable doCommand) {
 1292  710 chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
 1293    }
 1294   
 1295   
 1296    /** Formerly used to call editToBeUndone and editToBeRedone since they are protected methods in UndoManager. */
 1297    // private class OurUndoManager extends UndoManager {
 1298    // private boolean _compoundEditState = false;
 1299    // private OurCompoundEdit _compoundEdit;
 1300    //
 1301    // public void startCompoundEdit() {
 1302    // if (_compoundEditState) {
 1303    // throw new IllegalStateException("Cannot start a compound edit while making a compound edit");
 1304    // }
 1305    // _compoundEditState = true;
 1306    // _compoundEdit = new OurCompoundEdit();
 1307    // }
 1308    //
 1309    // public void endCompoundEdit() {
 1310    // if (!_compoundEditState) {
 1311    // throw new IllegalStateException("Cannot end a compound edit while not making a compound edit");
 1312    // }
 1313    // _compoundEditState = false;
 1314    // _compoundEdit.end();
 1315    // super.addEdit(_compoundEdit);
 1316    // }
 1317    //
 1318    // public UndoableEdit getNextUndo() {
 1319    // return editToBeUndone();
 1320    // }
 1321    //
 1322    // public UndoableEdit getNextRedo() {
 1323    // return editToBeRedone();
 1324    // }
 1325    //
 1326    // public boolean addEdit(UndoableEdit e) {
 1327    // if (_compoundEditState) {
 1328    // return _compoundEdit.addEdit(e);
 1329    // }
 1330    // else {
 1331    // return super.addEdit(e);
 1332    // }
 1333    // }
 1334    // }
 1335    //
 1336    //
 1337    // public java.util.Vector getEdits() {
 1338    // return _undoManager._compoundEdit.getEdits();
 1339    // }
 1340    //
 1341    // private class OurCompoundEdit extends CompoundEdit {
 1342    // public java.util.Vector getEdits() {
 1343    // return edits;
 1344    // }
 1345    // }
 1346   
 1347    /** Formerly used to help track down memory leaks */
 1348    // protected void finalize() throws Throwable{
 1349    // System.err.println("destroying DefDocument for " + _odd);
 1350    // super.finalize();
 1351    // }
 1352    //
 1353    // private List<Pair<Option, OptionListener>> _optionListeners = new LinkedList<Option, OptionListener>>();
 1354    //
 1355    // public void clearOptionListeners() {
 1356    // for (Pair<Option, OptionListener> l: _optionListeners) {
 1357    // DrJava.getConfig().removeOptionListener( l.getFirst(), l.getSecond());
 1358    // }
 1359    // _optionListeners.clear();
 1360    // }
 1361    //
 1362    // public void addOptionListener(Option op, OptionListener l) {
 1363    // DrJava.getConfig().addOptionListener(op, l);
 1364    // _optionListeners.add(new Pair<Option, OptionListener>(op, l));
 1365    // }
 1366   
 1367    /** This list of listeners to notify when we are finalized. */
 1368    private List<FinalizationListener<DefinitionsDocument>> _finalizationListeners =
 1369    new LinkedList<FinalizationListener<DefinitionsDocument>>();
 1370   
 1371    /** Registers a finalization listener with the specific instance of the ddoc
 1372    * <p><b>NOTE:</b><i>This should only be used by test cases. This is to ensure that
 1373    * we don't spring memory leaks by allowing our unit tests to keep track of
 1374    * whether objects are being finalized (garbage collected)</i></p>
 1375    * @param fl the listener to register
 1376    */
 1377  16 public void addFinalizationListener(FinalizationListener<DefinitionsDocument> fl) {
 1378  16 synchronized(_finalizationListeners) { _finalizationListeners.add(fl); }
 1379    }
 1380   
 1381  30 public List<FinalizationListener<DefinitionsDocument>> getFinalizationListeners() {
 1382  30 return _finalizationListeners;
 1383    }
 1384   
 1385    /** This is called when this method is GC'd. Since this class implements edu.rice.cs.drjava.model.Finalizable, it
 1386    * must notify its listeners
 1387    */
 1388  83 protected void finalize() {
 1389  83 FinalizationEvent<DefinitionsDocument> fe = new FinalizationEvent<DefinitionsDocument>(this);
 1390  83 synchronized(_finalizationListeners) {
 1391  83 for (FinalizationListener<DefinitionsDocument> fl: _finalizationListeners) {
 1392  12 fl.finalized(fe);
 1393    }
 1394    }
 1395    }
 1396   
 1397  0 public String toString() { return "ddoc for " + _odd; }
 1398   
 1399    /** Returns true if one of the words 'class', 'interface' or 'enum' is found
 1400    * in non-comment text. */
 1401  28 public boolean containsClassOrInterfaceOrEnum() throws BadLocationException {
 1402   
 1403  28 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1404  28 int i, j;
 1405  28 int reducedPos = 0;
 1406   
 1407  28 final String text = getText();
 1408  28 final int origLocation = _currentLocation;
 1409  28 try {
 1410    // Move reduced model to beginning of file
 1411  28 _reduced.move(-origLocation);
 1412   
 1413    // Walk forward from specificed position
 1414  28 i = text.indexOf("class", reducedPos);
 1415  28 j = text.indexOf("interface", reducedPos);
 1416  0 if (i==-1) i = j; else if (j >= 0) i = Math.min(i,j);
 1417  28 j = text.indexOf("enum", reducedPos);
 1418  0 if (i==-1) i = j; else if (j >= 0) i = Math.min(i,j);
 1419  28 while (i > - 1) {
 1420    // Move reduced model to walker's location
 1421  28 _reduced.move(i - reducedPos); // reduced model points to i
 1422  28 reducedPos = i; // reduced model points to reducedPos
 1423   
 1424    // Check if matching keyword should be ignored because it is within a comment, or quotes
 1425  28 ReducedModelState state = _reduced.getStateAtCurrent();
 1426  28 if (!state.equals(FREE) || _isStartOfComment(text, i) || ((i > 0) && _isStartOfComment(text, i - 1))) {
 1427  15 i = text.indexOf("class", reducedPos+1);
 1428  15 j = text.indexOf("interface", reducedPos+1);
 1429  0 if (i==-1) i = j; else if (j >= 0) i = Math.min(i,j);
 1430  15 j = text.indexOf("enum", reducedPos+1);
 1431  0 if (i==-1) i = j; else if (j >= 0) i = Math.min(i,j);
 1432  15 continue; // ignore match
 1433    }
 1434    else {
 1435  13 return true; // found match
 1436    }
 1437    }
 1438   
 1439  15 return false;
 1440    }
 1441    finally {
 1442  28 _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
 1443    }
 1444    }
 1445    }