0001: /*BEGIN_COPYRIGHT_BLOCK
0002: *
0003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
0004: * All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions are met:
0008: * * Redistributions of source code must retain the above copyright
0009: * notice, this list of conditions and the following disclaimer.
0010: * * Redistributions in binary form must reproduce the above copyright
0011: * notice, this list of conditions and the following disclaimer in the
0012: * documentation and/or other materials provided with the distribution.
0013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
0014: * names of its contributors may be used to endorse or promote products
0015: * derived from this software without specific prior written permission.
0016: *
0017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028: *
0029: * This software is Open Source Initiative approved Open Source Software.
0030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
0031: *
0032: * This file is part of DrJava. Download the current version of this project
0033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
0034: *
0035: * END_COPYRIGHT_BLOCK*/
0036:
0037: package edu.rice.cs.drjava.model.definitions;
0038:
0039: import javax.swing.text.*;
0040: import javax.swing.undo.*;
0041: import javax.swing.event.DocumentEvent;
0042: import java.util.LinkedList;
0043: import java.util.List;
0044: import java.util.Map;
0045: import java.util.WeakHashMap;
0046: import java.lang.ref.WeakReference;
0047:
0048: import java.io.File;
0049: import java.io.Reader;
0050: import java.io.StringReader;
0051: import java.io.IOException;
0052:
0053: import edu.rice.cs.drjava.model.definitions.reducedmodel.*;
0054: import koala.dynamicjava.parser.Parser;
0055: import koala.dynamicjava.parser.ParseException;
0056: import edu.rice.cs.util.Log;
0057: import edu.rice.cs.util.UnexpectedException;
0058: import edu.rice.cs.util.swing.Utilities;
0059: import edu.rice.cs.drjava.model.definitions.indent.Indenter;
0060: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
0061: import edu.rice.cs.drjava.model.*;
0062:
0063: /** The document model for the definitions pane.
0064: *
0065: * This implementation of <code>Document</code> contains a "reduced model". The reduced model is automatically kept
0066: * in sync when this document is updated. Also, that synchronization is maintained even across undo/redo -- this is
0067: * done by making the undo/redo commands know how to restore the reduced model state.
0068: *
0069: * The reduced model is not thread-safe, so it is essential that ONLY this DefinitionsDocument call methods on it.
0070: * Any information from the reduced model should be obtained through helper methods on DefinitionsDocument, and ALL
0071: * methods in DefinitionsDocument which reference the reduced model (via the _reduced field) MUST be synchronized.
0072: * This prevents any thread from seeing an inconsistent state in the middle of another thread's changes.
0073: *
0074: * @see BraceReduction
0075: * @see ReducedModelControl
0076: * @see ReducedModelComment
0077: * @see ReducedModelBrace
0078: */
0079: public class DefinitionsDocument extends AbstractDJDocument implements
0080: Finalizable<DefinitionsDocument> {
0081:
0082: public static final Log _log = new Log("GlobalModel.txt", false);
0083: private static final int NO_COMMENT_OFFSET = 0;
0084: private static final int WING_COMMENT_OFFSET = 2;
0085:
0086: private volatile List<DocumentClosedListener> _closedListeners = new LinkedList<DocumentClosedListener>();
0087:
0088: public void addDocumentClosedListener(DocumentClosedListener l) {
0089: synchronized (_closedListeners) {
0090: _closedListeners.add(l);
0091: }
0092: }
0093:
0094: public void removeDocumentClosedListener(DocumentClosedListener l) {
0095: synchronized (_closedListeners) {
0096: _closedListeners.remove(l);
0097: }
0098: }
0099:
0100: // begin debug code
0101:
0102: // private boolean _closed = false;
0103: //
0104: // protected void throwErrorHuh() {
0105: // if (_closed) throw new RuntimeException("Definitions Document is closed, yet is being used");
0106: // }
0107:
0108: /** Called when this is kicked out of the document cache so that the references made to it may
0109: * be released so that this can be GC'd. */
0110: public void close() {
0111: _removeIndenter();
0112: synchronized (_closedListeners) {
0113: for (DocumentClosedListener l : _closedListeners) {
0114: l.close();
0115: }
0116: _closedListeners = new LinkedList<DocumentClosedListener>();
0117: }
0118: }
0119:
0120: // end debug code
0121:
0122: /** The maximum number of undos the model can remember */
0123: private static final int UNDO_LIMIT = 1000;
0124: /** Specifies if tabs are removed on open and converted to spaces. */
0125: private static boolean _tabsRemoved = true;
0126: /** Specifies if the document has been modified since the last save. */
0127: private volatile boolean _isModifiedSinceSave = false;
0128: /** Cached location, aides in determining line number. */
0129: private volatile int _cachedLocation;
0130: /** Cached current line number. */
0131: private volatile int _cachedLineNum;
0132: /** Cached location of previous line. */
0133: private volatile int _cachedPrevLineLoc;
0134: /** Cached location of next line. */
0135: private volatile int _cachedNextLineLoc;
0136:
0137: /** This reference to the OpenDefinitionsDocument is needed so that the document iterator
0138: * (the DefaultGlobalModel) can find the next ODD given a DD. */
0139: private volatile OpenDefinitionsDocument _odd;
0140:
0141: private volatile CompoundUndoManager _undoManager;
0142:
0143: /** Keeps track of the listeners to this model. */
0144: private final GlobalEventNotifier _notifier;
0145:
0146: /* Relying on the following definition in AbstractDJDocument. It must be placed there to be initialized before use!
0147: protected static final Object _wrappedPosListLock = new Object();
0148: */
0149:
0150: /** List with weak references to positions. */
0151: private volatile LinkedList<WeakReference<WrappedPosition>> _wrappedPosList;
0152:
0153: /** Convenience constructor for using a custom indenter.
0154: * @param indenter custom indenter class
0155: * @param notifier used by CompoundUndoManager to announce undoable edits
0156: */
0157: public DefinitionsDocument(Indenter indenter,
0158: GlobalEventNotifier notifier) {
0159: super (indenter);
0160: _notifier = notifier;
0161: _init();
0162: resetUndoManager();
0163: }
0164:
0165: /** Main constructor. This has an obnoxious dependency on GlobalEventNotifier, which is passed through here only
0166: * for a single usage in CompoundUndoManager. TODO: find a better way.
0167: * @param notifier used by CompoundUndoManager to announce undoable edits
0168: */
0169: public DefinitionsDocument(GlobalEventNotifier notifier) {
0170: super ();
0171: _notifier = notifier;
0172: _init();
0173: resetUndoManager();
0174: }
0175:
0176: /** Main constructor. This has an obnoxious dependency on GlobalEventNotifier, which is passed through here only
0177: * for a single usage in CompoundUndoManager. TODO: find a better way.
0178: * @param notifier used by CompoundUndoManager to announce undoable edits
0179: */
0180: public DefinitionsDocument(GlobalEventNotifier notifier,
0181: CompoundUndoManager undoManager) {
0182: super ();
0183: _notifier = notifier;
0184: _init();
0185: _undoManager = undoManager;
0186: }
0187:
0188: // public void setUndoManager(CompoundUndoManager undoManager) {
0189: // if (undoManager != null)
0190: // _undoManager = undoManager;
0191: // }
0192:
0193: /** Returns a new indenter. */
0194: protected Indenter makeNewIndenter(int indentLevel) {
0195: return new Indenter(indentLevel);
0196: }
0197:
0198: /** Private common helper for constructors. */
0199: private void _init() {
0200: _odd = null;
0201: _cachedLocation = 0;
0202: _cachedLineNum = 1;
0203: _cachedPrevLineLoc = -1;
0204: _cachedNextLineLoc = -1;
0205: _cacheInUse = false;
0206: }
0207:
0208: /* acquireReadLock, releaseReadLock, acquireWriteLock, releaseWriteLock are inherited from AbstractDJDocument. */
0209:
0210: /** Sets the OpenDefinitionsDocument that holds this DefinitionsDocument (the odd can only be set once).
0211: * @param odd the OpenDefinitionsDocument to set as this DD's holder
0212: */
0213: public void setOpenDefDoc(OpenDefinitionsDocument odd) {
0214: if (_odd == null)
0215: _odd = odd;
0216: }
0217:
0218: /** @return the OpenDefinitonsDocument that is associated with this DefinitionsDocument. */
0219: public OpenDefinitionsDocument getOpenDefDoc() {
0220: if (_odd == null)
0221: throw new IllegalStateException(
0222: "The OpenDefinitionsDocument for this DefinitionsDocument has never been set");
0223: else
0224: return _odd;
0225: }
0226:
0227: protected void _styleChanged() {
0228: acquireWriteLock();
0229: try {
0230: int length = getLength() - _currentLocation;
0231:
0232: //DrJava.consoleErr().println("Changed: " + _currentLocation + ", " + length);
0233: DocumentEvent evt = new DefaultDocumentEvent(
0234: _currentLocation, length,
0235: DocumentEvent.EventType.CHANGE);
0236: fireChangedUpdate(evt);
0237: } finally {
0238: releaseWriteLock();
0239: }
0240: }
0241:
0242: /**
0243: * Returns whether this document is currently untitled
0244: * (indicating whether it has a file yet or not).
0245: * @return true if the document is untitled and has no file
0246: */
0247: // public boolean isUntitled() {
0248: // return (_file == null);
0249: // }
0250: /**
0251: * Returns the file for this document. If the document
0252: * is untitled and has no file, it throws an IllegalStateException.
0253: * @return the file for this document
0254: * @throws IllegalStateException if file has not been set
0255: * @throws FileMovedException if file has been moved or deleted from its previous location
0256: */
0257: // public File getFilex()
0258: // throws IllegalStateException , FileMovedException {
0259: // if (_file == null) {
0260: // throw new IllegalStateException(
0261: // "This document does not yet have a file.");
0262: // }
0263: // //does the file actually exist?
0264: // if (_file.exists()) {
0265: // return _file;
0266: // }
0267: // else {
0268: // throw new FileMovedException(_file,
0269: // "This document's file has been moved or deleted.");
0270: // }
0271: // }
0272: //
0273: // /**
0274: // * Returns the name of this file, or "(untitled)" if no file.
0275: // */
0276: // public String getFilenamex() {
0277: // String filename = "(Untitled)";
0278: // try {
0279: // File file = getFilex();
0280: // filename = file.getName();
0281: // }
0282: // catch (IllegalStateException ise) {
0283: // // No file, leave as "untitled"
0284: // }
0285: // catch (FileMovedException fme) {
0286: // // Recover, even though file has been deleted
0287: // File file = fme.getFile();
0288: // filename = file.getName();
0289: // }
0290: // return filename;
0291: // }
0292:
0293: // public void setFile(File file) {
0294: // _file = file;
0295: //
0296: // //jim: maybe need lock
0297: // if (_file != null) {
0298: // _timestamp = _file.lastModified();
0299: // }
0300: // }
0301: //
0302: // public long getTimestamp() {
0303: // return _timestamp;
0304: // }
0305:
0306: /** Gets the package and main class/interface name of this OpenDefinitionsDocument
0307: * @return the qualified main class/interface name
0308: */
0309: public String getQualifiedClassName()
0310: throws ClassNameNotFoundException {
0311: return _getPackageQualifier() + getMainClassName();
0312: }
0313:
0314: /** Gets fully qualified class name of the top level class enclosing the given position. */
0315: public String getQualifiedClassName(int pos)
0316: throws ClassNameNotFoundException {
0317: return _getPackageQualifier()
0318: + getEnclosingTopLevelClassName(pos);
0319: }
0320:
0321: /** Gets an appropriate prefix to fully qualify a class name. Returns this class's package followed by a dot, or the
0322: * empty string if no package name is found.
0323: */
0324: protected String _getPackageQualifier() {
0325: String packageName = getPackageName();
0326: if ((packageName != null) && (!packageName.equals(""))) {
0327: packageName = packageName + ".";
0328: }
0329: return packageName;
0330: }
0331:
0332: /** Inserts a string of text into the document. This is not where we do custom processing of the insert; that is
0333: * done in {@link #insertUpdate}.
0334: */
0335: public void insertString(int offset, String str, AttributeSet a)
0336: throws BadLocationException {
0337:
0338: // If _removeTabs is set to true, remove all tabs from str.
0339: // It is a current invariant of the tabification functionality that
0340: // the document contains no tabs, but we want to allow the user
0341: // to override this functionality.
0342:
0343: acquireWriteLock();
0344: try {
0345: if (_tabsRemoved)
0346: str = _removeTabs(str);
0347: setModifiedSinceSave();
0348: super .insertString(offset, str, a);
0349: } finally {
0350: releaseWriteLock();
0351: }
0352: }
0353:
0354: /** Removes a block of text from the specified location. We don't update the reduced model here; that happens
0355: * in {@link #removeUpdate}.
0356: */
0357: public void remove(int offset, int len) throws BadLocationException {
0358:
0359: if (len == 0)
0360: return;
0361:
0362: acquireWriteLock();
0363: try {
0364: setModifiedSinceSave();
0365: super .remove(offset, len);
0366: } finally {
0367: releaseWriteLock();
0368: }
0369: }
0370:
0371: /** Given a String, return a new String will all tabs converted to spaces. Each tab is converted
0372: * to one space, since changing the number of characters within insertString screws things up.
0373: * @param source the String to be converted.
0374: * @return a String will all the tabs converted to spaces
0375: */
0376: static String _removeTabs(final String source) {
0377: // clearCache(); // Clear the helper method cache // Goofy code! Eliminated when method was converted to static.
0378: return source.replace('\t', ' ');
0379: }
0380:
0381: /** Resets the modification state of this document to be consistent with state of _undoManager. Called whenever
0382: * an undo or redo is performed. */
0383: public void updateModifiedSinceSave() {
0384:
0385: acquireWriteLock();
0386: try {
0387: _isModifiedSinceSave = _undoManager.isModified();
0388: // System.out.println("DefinitionsDocument: set modified? " + _modifiedSinceSave);
0389: } finally {
0390: if (!_isModifiedSinceSave && _odd != null)
0391: _odd.documentReset();
0392: releaseWriteLock();
0393: // Utilities.showDebug("DefintionsDocument: _modifiedSinceSave = " + _modifiedSinceSave);
0394: }
0395: }
0396:
0397: /** Sets the modification state of this document to true and updates the state of the associated _odd.
0398: * Assumes that write lock is already held. */
0399: private void setModifiedSinceSave() {
0400: if (!_isModifiedSinceSave) {
0401: _isModifiedSinceSave = true;
0402: if (_odd != null)
0403: _odd.documentModified();
0404: }
0405: }
0406:
0407: /** Resets the modification state of this document. Used after a document has been saved or reverted. */
0408: public void resetModification() {
0409: acquireWriteLock();
0410: try {
0411: _isModifiedSinceSave = false;
0412: _undoManager.documentSaved();
0413: } finally {
0414: if (_odd != null)
0415: _odd.documentReset(); // null test required for some unit tests
0416: releaseWriteLock();
0417:
0418: }
0419: }
0420:
0421: /** Determines if the document has been modified since the last save.
0422: * @return true if the document has been modified
0423: */
0424: public boolean isModifiedSinceSave() {
0425: acquireReadLock();
0426: try {
0427: return _isModifiedSinceSave;
0428: } finally {
0429: releaseReadLock();
0430: }
0431: }
0432:
0433: /** Return the current column of the cursor position. Uses a 0 based index. */
0434: public int getCurrentCol() {
0435: acquireReadLock();
0436: try {
0437: Element root = getDefaultRootElement();
0438: int line = root.getElementIndex(_currentLocation);
0439: return _currentLocation
0440: - root.getElement(line).getStartOffset();
0441: } finally {
0442: releaseReadLock();
0443: }
0444: }
0445:
0446: /** Return the current line of the cursor position. Uses a 1-based index. */
0447: public int getCurrentLine() {
0448: acquireReadLock();
0449: try {
0450: return getDefaultRootElement().getElementIndex(
0451: _currentLocation) + 1;
0452: } // line indices are 1-based
0453: finally {
0454: releaseReadLock();
0455: }
0456: }
0457:
0458: /** Returns the offset corresponding to the first character of the given line number,
0459: * or -1 if the lineNum is not found. Avoid locking the document by copying its text.
0460: * @param lineNum the line number for which to calculate the offset.
0461: * @return the offset of the first character in the given line number
0462: */
0463: public int getOffset(int lineNum) {
0464: if (lineNum < 0)
0465: return -1;
0466: String defsText = getText();
0467: int curLine = 1;
0468: int offset = 0; // offset is number of chars from beginning of file
0469:
0470: // offset is always pointing to the first character in a line
0471: // at the top of the loop
0472: while (offset < defsText.length()) {
0473:
0474: if (curLine == lineNum)
0475: return offset;
0476:
0477: int nextNewline = defsText.indexOf('\n', offset);
0478: if (nextNewline == -1)
0479: return -1; // end of the document, and couldn't find the supplied lineNum
0480:
0481: curLine++;
0482: offset = nextNewline + 1;
0483: }
0484: return -1;
0485: }
0486:
0487: /** Returns true iff tabs are to removed on text insertion. */
0488: public boolean tabsRemoved() {
0489: return _tabsRemoved;
0490: }
0491:
0492: /** Comments out all lines between selStart and selEnd, inclusive. The current cursor position is maintained
0493: * after the operation.
0494: * @param selStart the document offset for the start of the selection
0495: * @param selEnd the document offset for the end of the selection
0496: */
0497: public int commentLines(int selStart, int selEnd) {
0498:
0499: //int key = _undoManager.startCompoundEdit(); //Uncommented in regards to the FrenchKeyBoardFix
0500: int toReturn = selEnd;
0501: if (selStart == selEnd) {
0502: acquireWriteLock();
0503: try {
0504: synchronized (_reduced) {
0505: setCurrentLocation(selStart);
0506: Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
0507: _commentLine();
0508: toReturn += WING_COMMENT_OFFSET;
0509: //int caretPos = getCaretPosition();
0510: //_doc().setCurrentLocation(caretPos);
0511: }
0512: } catch (BadLocationException e) {
0513: throw new UnexpectedException(e);
0514: } finally {
0515: releaseWriteLock();
0516: }
0517: } else
0518: toReturn = _commentBlock(selStart, selEnd);
0519: _undoManager.endLastCompoundEdit(); //Changed from endCompoundEdit(key) for FrenchKeyBoardFix
0520: return toReturn;
0521: }
0522:
0523: /** Comments out the lines between and including the lines containing points start and end, using wing
0524: * comments -- "// ".
0525: *
0526: * @param start Position in document to start commenting from
0527: * @param end Position in document to end commenting at
0528: */
0529: private int _commentBlock(final int start, final int end) {
0530: int afterCommentEnd = end;
0531: acquireWriteLock();
0532: try {
0533: // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc doing the
0534: // indentLine calls.
0535: final Position endPos = this .createUnwrappedPosition(end);
0536: // Iterate, line by line, until we get to/past the end
0537: int walker = start;
0538: synchronized (_reduced) {
0539: while (walker < endPos.getOffset()) {
0540: setCurrentLocation(walker);
0541: // Keep pointer to walker position that will stay current
0542: // regardless of how commentLine changes things
0543: Position walkerPos = this
0544: .createUnwrappedPosition(walker);
0545: // Comment out current line
0546: _commentLine(); // must be atomic
0547: afterCommentEnd += WING_COMMENT_OFFSET;
0548: // Move back to walker spot
0549: setCurrentLocation(walkerPos.getOffset());
0550: walker = walkerPos.getOffset();
0551: // Adding 1 makes us point to the first character AFTER the next newline.
0552: // We don't actually move yet. That happens at the top of the loop,
0553: // after we check if we're past the end.
0554: walker += _reduced.getDistToNextNewline() + 1;
0555: //DrJava.consoleOut().println("progress: " + (100*(walker-start)/(end-start)));
0556: }
0557: }
0558: } catch (BadLocationException e) {
0559: throw new UnexpectedException(e);
0560: } finally {
0561: releaseWriteLock();
0562: }
0563: return afterCommentEnd;
0564: }
0565:
0566: /** Comments out a single line with wing comments -- "// ".
0567: * @pre this.writeLock() and _reduced lock are already held! */
0568: private void _commentLine() {
0569: // Insert "// " at the beginning of the line.
0570: // Using null for AttributeSet follows convention in this class.
0571: try {
0572: insertString(_currentLocation - getCurrentCol(), "//", null);
0573: } catch (BadLocationException e) {
0574: throw new UnexpectedException(e);
0575: }
0576: }
0577:
0578: /** Uncomments all lines between selStart and selEnd, inclusive.
0579: * The current cursor position is maintained after the operation.
0580: * @param selStart the document offset for the start of the selection
0581: * @param selEnd the document offset for the end of the selection
0582: */
0583: public int uncommentLines(int selStart, int selEnd) {
0584:
0585: //int key = _undoManager.startCompoundEdit(); //commented out for FrenchKeyBoardFix
0586: int toReturn = selEnd;
0587: if (selStart == selEnd) {
0588: acquireWriteLock();
0589: try {
0590: synchronized (_reduced) {
0591: setCurrentLocation(selStart);
0592: Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
0593: _uncommentLine(); // accesses _reduced
0594: toReturn -= WING_COMMENT_OFFSET;
0595: //int caretPos = getCaretPosition();
0596: //_doc().setCurrentLocation(caretPos);
0597: //setCurrentLocation(oldCurrentPosition.getOffset());
0598: }
0599: } catch (BadLocationException e) {
0600: throw new UnexpectedException(e);
0601: } finally {
0602: releaseWriteLock();
0603: }
0604: } else
0605: toReturn = _uncommentBlock(selStart, selEnd);
0606: //_undoManager.endCompoundEdit(key); //Commented out for FrenchKeyBoardFix, Replaced with endLastCompoundEdit();
0607: _undoManager.endLastCompoundEdit();
0608: return toReturn;
0609: }
0610:
0611: /** Uncomments all lines between and including the lines containing
0612: * points start and end.
0613: * @param start Position in document to start commenting from
0614: * @param end Position in document to end commenting at
0615: */
0616: private int _uncommentBlock(final int start, final int end) {
0617: int afterUncommentEnd = end;
0618: acquireWriteLock();
0619: try {
0620: // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc
0621: // doing the indentLine calls.
0622: final Position endPos = this .createUnwrappedPosition(end);
0623: // Iterate, line by line, until we get to/past the end
0624: int walker = start;
0625: synchronized (_reduced) {
0626: while (walker < endPos.getOffset()) {
0627: setCurrentLocation(walker);
0628: // Keep pointer to walker position that will stay current
0629: // regardless of how commentLine changes things
0630: Position walkerPos = this
0631: .createUnwrappedPosition(walker);
0632: // uncomment current line
0633: afterUncommentEnd -= _uncommentLine(); // accesses _reduced
0634: // Move back to walker spot
0635: setCurrentLocation(walkerPos.getOffset());
0636: walker = walkerPos.getOffset();
0637: // Adding 1 makes us point to the first character AFTER the next newline.
0638: // We don't actually move yet. That happens at the top of the loop,
0639: // after we check if we're past the end.
0640: walker += _reduced.getDistToNextNewline() + 1;
0641: //DrJava.consoleOut().println("progress: " + (100*(walker-start)/(end-start)));
0642: }
0643: }
0644: } catch (BadLocationException e) {
0645: throw new UnexpectedException(e);
0646: } finally {
0647: releaseWriteLock();
0648: }
0649: return afterUncommentEnd;
0650: }
0651:
0652: /** Uncomments a single line. This simply looks for a leading "//". Assumes that _reduced lock is already held and
0653: * that acquireWriteLock is already held.
0654: * @pre theads hold this.writeLock() and _reduced lock
0655: */
0656: private int _uncommentLine() throws BadLocationException {
0657: // Look for "//" at the beginning of the line, and remove it.
0658: int curCol = getCurrentCol();
0659: int lineStart = _currentLocation - curCol;
0660: String text = getText(lineStart, curCol
0661: + _reduced.getDistToNextNewline());
0662: int pos = text.indexOf("//");
0663:
0664: // System.out.println("" + _currentLocation + " " + curCol + " "
0665: // + text + " " + pos + " " + _reduced.getDistToNextNewline());
0666:
0667: // Look for any non-whitespace chars before the "//" on the line.
0668: boolean goodWing = true;
0669: for (int i = pos - 1; i >= 0; i--) {
0670: char c = text.charAt(i);
0671: // If a previous char is not whitespace, we're not looking at a wing comment.
0672: if (c != ' ') {
0673: goodWing = false;
0674: return NO_COMMENT_OFFSET;
0675: }
0676: }
0677:
0678: // If a wing comment wasn't found, or if the wings aren't the first
0679: // non-whitespace characters on the line, do nothing.
0680: if (pos >= 0 && goodWing) {
0681: // Otherwise, remove the wings.
0682: remove(lineStart + pos, 2);
0683: //_indentLine(Indenter.IndentReason.OTHER);
0684: return WING_COMMENT_OFFSET;
0685: }
0686: return NO_COMMENT_OFFSET;
0687: }
0688:
0689: /** Goes to a particular line in the document. */
0690: public void gotoLine(int line) {
0691:
0692: int dist;
0693: if (line < 0)
0694: return;
0695: int actualLine = 1;
0696:
0697: acquireReadLock();
0698: int len = getLength();
0699: try {
0700: synchronized (_reduced) {
0701: setCurrentLocation(0);
0702: for (int i = 1; (i < line) && (_currentLocation < len); i++) {
0703: dist = _reduced.getDistToNextNewline();
0704: if (_currentLocation + dist < len)
0705: dist++;
0706: actualLine++;
0707: move(dist);
0708: }
0709: _cachedLineNum = actualLine;
0710: _cachedLocation = _currentLocation;
0711: _cachedPrevLineLoc = getLineStartPos(_currentLocation);
0712: _cachedNextLineLoc = getLineEndPos(_currentLocation);
0713: }
0714: } finally {
0715: releaseReadLock();
0716: }
0717: }
0718:
0719: private int _findNextOpenSquiggly(String text, int pos)
0720: throws BadLocationException {
0721: // acquireReadLock assumed to be held,
0722: int i;
0723: int reducedPos = pos;
0724:
0725: synchronized (_reduced) {
0726: final int origLocation = _currentLocation;
0727: // Move reduced model to location pos
0728: _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0729:
0730: // Walk forward from specificed position
0731: i = text.indexOf('{', reducedPos);
0732: while (i > -1) {
0733: // Move reduced model to walker's location
0734: _reduced.move(i - reducedPos); // reduced model points to i
0735: reducedPos = i; // reduced model points to reducedPos
0736:
0737: // Check if matching keyword should be ignored because it is within a comment, or quotes
0738: ReducedModelState state = _reduced.getStateAtCurrent();
0739: if (!state.equals(ReducedModelState.FREE)
0740: || _isStartOfComment(text, i)
0741: || ((i > 0) && _isStartOfComment(text, i - 1))) {
0742: i = text.indexOf('{', reducedPos + 1);
0743: continue; // ignore matching brace
0744: } else {
0745: break; // found our brace
0746: }
0747: } // end synchronized
0748:
0749: _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
0750: }
0751:
0752: if (i == -1)
0753: reducedPos = ERROR_INDEX; // No matching brace was found
0754: return reducedPos;
0755: }
0756:
0757: private int _findPrevKeyword(String text, String kw, int pos)
0758: throws BadLocationException {
0759: // acquireReadLock assumed to be held,
0760: int i;
0761: int reducedPos = pos;
0762:
0763: synchronized (_reduced) {
0764: final int origLocation = _currentLocation;
0765: // Move reduced model to location pos
0766: _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0767:
0768: // Walk backwards from specificed position
0769: i = text.lastIndexOf(kw, reducedPos);
0770: while (i > -1) {
0771: // Check that this is the beginning of a word
0772: if (i > 0) {
0773: if (Character.isJavaIdentifierPart(text
0774: .charAt(i - 1))) {
0775: // not begining
0776: i = text.lastIndexOf(kw, i - 1);
0777: continue; // ignore matching keyword
0778: }
0779: }
0780: // Check that this not just the beginning of a longer word
0781: if (i + kw.length() < text.length()) {
0782: if (Character.isJavaIdentifierPart(text.charAt(i
0783: + kw.length()))) {
0784: // not begining
0785: i = text.lastIndexOf(kw, i - 1);
0786: continue; // ignore matching keyword
0787: }
0788: }
0789:
0790: // Move reduced model to walker's location
0791: _reduced.move(i - reducedPos); // reduced model points to i
0792: reducedPos = i; // reduced model points to reducedPos
0793:
0794: // Check if matching keyword should be ignored because it is within a comment, or quotes
0795: ReducedModelState state = _reduced.getStateAtCurrent();
0796: if (!state.equals(ReducedModelState.FREE)
0797: || _isStartOfComment(text, i)
0798: || ((i > 0) && _isStartOfComment(text, i - 1))) {
0799: i = text.lastIndexOf(kw, reducedPos - 1);
0800: continue; // ignore matching keyword
0801: } else {
0802: break; // found our keyword
0803: }
0804: } // end synchronized/
0805:
0806: _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
0807: }
0808:
0809: if (i == -1)
0810: reducedPos = ERROR_INDEX; // No matching keyword was found
0811: return reducedPos;
0812: }
0813:
0814: // public static boolean log = true;
0815:
0816: /** Searching backwards finds the name of the enclosing named class or interface. NB: ignores comments.
0817: * WARNING: In long source files and when contained in anonymous inner classes, this function might take a LONG time.
0818: * @param pos Position to start from
0819: * @param qual true to find the fully qualified class name
0820: * @return name of the enclosing named class or interface
0821: */
0822: public String getEnclosingClassName(int pos, boolean qual)
0823: throws BadLocationException, ClassNameNotFoundException {
0824: // boolean oldLog = log; log = false;
0825: // Check cache
0826: final StringBuilder keyBuf = new StringBuilder(
0827: "getEnclosingClassName:").append(pos);
0828: keyBuf.append(":").append(qual);
0829: String key = keyBuf.toString();
0830: String cached = (String) _checkCache(key);
0831: if (cached != null)
0832: return cached;
0833:
0834: char[] delims = { '{', '}', '(', ')', '[', ']', '+', '-', '/',
0835: '*', ';', ':', '=', '!', '@', '#', '$', '%', '^', '~',
0836: '\\', '"', '`', '|' };
0837: String name = "";
0838:
0839: acquireReadLock();
0840: try {
0841: String text = getText(DOCSTART, pos + 1);
0842:
0843: int curPos = pos;
0844:
0845: do {
0846: if ((text.charAt(curPos) != '{')
0847: || (text.charAt(curPos) != '}')) {
0848: ++curPos;
0849: }
0850:
0851: // if (oldLog) System.out.println("curPos=" + curPos + " `" +
0852: // text.substring(Math.max(0,curPos-10), Math.min(text.length(), curPos+1)) + "`");
0853:
0854: curPos = findPrevEnclosingBrace(curPos, '{', '}');
0855: if (curPos == ERROR_INDEX) {
0856: break;
0857: }
0858: int classPos = _findPrevKeyword(text, "class", curPos);
0859: int interPos = _findPrevKeyword(text, "interface",
0860: curPos);
0861: int otherPos = findPrevDelimiter(curPos, delims);
0862: int newPos = ERROR_INDEX;
0863: // see if there's a ) closer by
0864: int closeParenPos = findPrevNonWSCharPos(curPos);
0865: if ((closeParenPos != ERROR_INDEX)
0866: && (text.charAt(closeParenPos) == ')')) {
0867: // yes, find the matching (
0868: int openParenPos = findPrevEnclosingBrace(
0869: closeParenPos, '(', ')');
0870: if ((openParenPos != ERROR_INDEX)
0871: && (text.charAt(openParenPos) == '(')) {
0872: // this might be an inner class
0873: newPos = _findPrevKeyword(text, "new",
0874: openParenPos);
0875: // if (oldLog) System.out.println("\tnew found at "+newPos+", openSquigglyPos="+curPos);
0876: if (!_isAnonymousInnerClass(newPos, curPos)) {
0877: // not an anonymous inner class
0878: newPos = ERROR_INDEX;
0879: }
0880: }
0881: }
0882: // if (oldLog) System.out.println("curPos="+curPos+" `"+text.substring(Math.max(0,curPos-10),curPos+1)+"`");
0883: // if (oldLog) System.out.println("\tclass="+classPos+", inter="+interPos+", other="+otherPos+", new="+newPos+" `" +
0884: // text.substring(Math.max(0,otherPos-10),otherPos+1)+"`");
0885: while ((classPos != ERROR_INDEX)
0886: || (interPos != ERROR_INDEX)
0887: || (newPos != ERROR_INDEX)) {
0888: if (newPos != ERROR_INDEX) {
0889: // if (oldLog) System.out.println("\tanonymous inner class! newPos = "+newPos);
0890: classPos = ERROR_INDEX;
0891: interPos = ERROR_INDEX;
0892: break;
0893: } else if ((otherPos > classPos)
0894: && (otherPos > interPos)) {
0895: if ((text.charAt(otherPos) != '{')
0896: || (text.charAt(otherPos) != '}')) {
0897: ++otherPos;
0898: }
0899: curPos = findPrevEnclosingBrace(otherPos, '{',
0900: '}');
0901: classPos = _findPrevKeyword(text, "class",
0902: curPos);
0903: interPos = _findPrevKeyword(text, "interface",
0904: curPos);
0905: otherPos = findPrevDelimiter(curPos, delims);
0906: newPos = ERROR_INDEX;
0907: // see if there's a ) closer by
0908: closeParenPos = findPrevNonWSCharPos(curPos);
0909: // if (closeParenPos!=ERROR_INDEX) if (oldLog) System.out.println("nonWS before curPos = " + closeParenPos +
0910: // " `"+text.charAt(closeParenPos)+"`");
0911: if ((closeParenPos != ERROR_INDEX)
0912: && (text.charAt(closeParenPos) == ')')) {
0913: // yes, find the matching (
0914: int openParenPos = findPrevEnclosingBrace(
0915: closeParenPos, '(', ')');
0916: if ((openParenPos != ERROR_INDEX)
0917: && (text.charAt(openParenPos) == '(')) {
0918: // this might be an inner class
0919: newPos = _findPrevKeyword(text, "new",
0920: openParenPos);
0921: // if (oldLog) System.out.println("\tnew found at " + newPos + ", openSquigglyPos=" + curPos);
0922: if (_isAnonymousInnerClass(newPos,
0923: curPos)) {
0924: // yes, anonymous inner class
0925: } else {
0926: newPos = ERROR_INDEX;
0927: }
0928: }
0929: }
0930: // if (oldLog) System.out.println("curPos=" +curPos+" `"+text.substring(Math.max(0,curPos-10),curPos+1)+"`");
0931: // if (oldLog) System.out.println("\tclass="+classPos+", inter="+interPos+", other="+otherPos+" `"+
0932: // text.substring(Math.max(0,otherPos-10),otherPos+1)+"`");
0933: } else {
0934: // either class or interface found first
0935: curPos = Math.max(classPos, Math.max(interPos,
0936: newPos));
0937: break;
0938: }
0939: }
0940:
0941: if ((classPos != ERROR_INDEX)
0942: || (interPos != ERROR_INDEX)) {
0943: if (classPos > interPos) {
0944: // class found first
0945: curPos += "class".length();
0946: } else {
0947: // interface found first
0948: curPos += "interface".length();
0949: }
0950: int nameStart = getFirstNonWSCharPos(curPos);
0951: if (nameStart == ERROR_INDEX) {
0952: throw new ClassNameNotFoundException(
0953: "Cannot determine enclosing class name");
0954: }
0955: int nameEnd = nameStart + 1;
0956: while (nameEnd < text.length()) {
0957: if ((!Character.isJavaIdentifierPart(text
0958: .charAt(nameEnd)))
0959: && (text.charAt(nameEnd) != '.')) {
0960: // delimiter found
0961: break;
0962: }
0963: ++nameEnd;
0964: }
0965: name = text.substring(nameStart, nameEnd) + '$'
0966: + name;
0967: } else if (newPos != ERROR_INDEX) {
0968: name = String
0969: .valueOf(_getAnonymousInnerClassIndex(curPos))
0970: + "$" + name;
0971: curPos = newPos;
0972: } else {
0973: // neither class nor interface found
0974: break;
0975: }
0976: } while (qual);
0977: } finally {
0978: releaseReadLock();
0979: }
0980:
0981: // chop off '$' at the end.
0982: if (name.length() > 0)
0983: name = name.substring(0, name.length() - 1);
0984:
0985: if (qual) {
0986: String pn = getPackageName();
0987: if ((pn.length() > 0) && (name.length() > 0)) {
0988: name = getPackageName() + "." + name;
0989: }
0990: }
0991: // log = oldLog;
0992: return name;
0993: }
0994:
0995: /** Returns true if this position is the instantiation of an anonymous inner class.
0996: * @param newPos position of "new"
0997: * @param openSquigglyPos position of the next '{'
0998: * @return true if anonymous inner class instantiation
0999: */
1000: private boolean _isAnonymousInnerClass(int newPos,
1001: int openSquigglyPos) throws BadLocationException {
1002: // String t = getText(DOCSTART, openSquigglyPos+1);
1003: // System.out.print ("_isAnonymousInnerClass("+newPos+", "+openSquigglyPos+")");
1004: // System.out.println("_isAnonymousInnerClass("+newPos+", "+openSquigglyPos+"): `"+
1005: // t.substring(newPos, openSquigglyPos+1)+"`");
1006:
1007: // Check cache
1008: final StringBuilder keyBuf = new StringBuilder(
1009: "_getAnonymousInnerClassIndex:").append(newPos).append(
1010: ':').append(openSquigglyPos);
1011: String key = keyBuf.toString();
1012: Boolean cached = (Boolean) _checkCache(key);
1013: if (cached != null) {
1014: // System.out.println(" ==> "+cached);
1015: return cached;
1016: }
1017:
1018: // acquireReadLock assumed to be held
1019: cached = false;
1020: String text = getText(DOCSTART, openSquigglyPos + 1);
1021: int origNewPos = newPos;
1022: newPos += "new".length();
1023: int classStart = getFirstNonWSCharPos(newPos);
1024: if (classStart != ERROR_INDEX) {
1025: int classEnd = classStart + 1;
1026: while (classEnd < text.length()) {
1027: if ((!Character.isJavaIdentifierPart(text
1028: .charAt(classEnd)))
1029: && (text.charAt(classEnd) != '.')) {
1030: // delimiter found
1031: break;
1032: }
1033: ++classEnd;
1034: }
1035: // System.out.println("\tclass = `"+text.substring(classStart,classEnd)+"`");
1036: int parenStart = getFirstNonWSCharPos(classEnd);
1037: if (parenStart != ERROR_INDEX) {
1038: int origParenStart = parenStart;
1039:
1040: // System.out.println("\tfirst non-whitespace after class = "+parenStart+" `"+text.charAt(parenStart)+"`");
1041: if (text.charAt(origParenStart) == '<') {
1042: parenStart = ERROR_INDEX;
1043: // might be a generic class
1044: int closePointyBracket = findNextEnclosingBrace(
1045: origParenStart, '<', '>');
1046: if (closePointyBracket != ERROR_INDEX) {
1047: if (text.charAt(closePointyBracket) == '>') {
1048: parenStart = getFirstNonWSCharPos(closePointyBracket + 1);
1049: }
1050: }
1051: }
1052: }
1053: if (parenStart != ERROR_INDEX) {
1054: if (text.charAt(parenStart) == '(') {
1055: synchronized (_reduced) {
1056: final int origLocation = _currentLocation;
1057: _reduced.move(parenStart + 1 - origLocation); // reduced model points to pos == parenStart+1
1058: int parenEnd = balanceForward();
1059: _reduced.move(origLocation - (parenStart + 1)); // Restore the state of the reduced model;
1060: if (parenEnd > -1) {
1061: parenEnd = parenEnd + parenStart + 1;
1062: // System.out.println("\tafter closing paren = "+parenEnd);
1063: int afterParen = getFirstNonWSCharPos(parenEnd);
1064: // System.out.println("\tfirst non-whitespace after paren = "+parenStart+" `"+text.charAt(afterParen)+"`");
1065: cached = (afterParen == openSquigglyPos);
1066: }
1067: }
1068: }
1069: }
1070: }
1071:
1072: _storeInCache(key, cached);
1073:
1074: // System.out.println(" ==> "+cached);
1075: return cached;
1076: }
1077:
1078: /** Gets the package name embedded in the text of this document by minimally parsing the document to find the
1079: * package statement. If package statement is not found or is ill-formed, returns "" as the package name.
1080: * @return The name of package embedded in this document. If there is no well-formed package statement,
1081: * returns "" as the package name.
1082: */
1083: public String getPackageName() {
1084: Reader r;
1085: acquireReadLock();
1086: try {
1087: r = new StringReader(getText());
1088: } finally {
1089: releaseReadLock();
1090: }
1091: try {
1092: return new Parser(r).packageDeclaration().getName();
1093: } catch (ParseException e) {
1094: return "";
1095: }
1096: // addresses bug [ 1815387 ] Editor should discard parse errors for now
1097: // we should upgrade our parser to handle @
1098: catch (koala.dynamicjava.parser.TokenMgrError e) {
1099: return "";
1100: } finally {
1101: try {
1102: r.close();
1103: } catch (IOException e) { /* ignore */
1104: }
1105: }
1106: }
1107:
1108: /**
1109: * Return the index of the anonymous inner class being instantiated at the specified position.
1110: * @param position of the opening curly brace of the anonymous inner class
1111: * @return anonymous class index
1112: */
1113: int _getAnonymousInnerClassIndex(int pos)
1114: throws BadLocationException, ClassNameNotFoundException {
1115: // boolean oldLog = log; log = false;
1116:
1117: // Check cache
1118: final StringBuilder keyBuf = new StringBuilder(
1119: "_getAnonymousInnerClassIndex:").append(pos);
1120: final String key = keyBuf.toString();
1121: final Integer cached = (Integer) _checkCache(key);
1122: if (cached != null) {
1123: // log = oldLog;
1124: return cached.intValue();
1125: }
1126:
1127: // acquireReadLock assumed to be held
1128: --pos; // move outside the curly brace
1129: char[] delims = { '{', '}', '(', ')', '[', ']', '+', '-', '/',
1130: '*', ';', ':', '=', '!', '@', '#', '$', '%', '^', '~',
1131: '\\', '"', '`', '|' };
1132: String className = getEnclosingClassName(pos, true);
1133: String text = getText(DOCSTART, pos);
1134: int index = 1;
1135: int newPos = pos;
1136: // if (oldLog) System.out.println("anon before "+pos+" enclosed by "+className);
1137: while ((newPos = _findPrevKeyword(text, "new", newPos - 1)) != ERROR_INDEX) {
1138: // if (oldLog) System.out.println("new found at "+newPos);
1139: int afterNewPos = newPos + "new".length();
1140: int classStart = getFirstNonWSCharPos(afterNewPos);
1141: if (classStart == ERROR_INDEX) {
1142: continue;
1143: }
1144: int classEnd = classStart + 1;
1145: while (classEnd < text.length()) {
1146: if ((!Character.isJavaIdentifierPart(text
1147: .charAt(classEnd)))
1148: && (text.charAt(classEnd) != '.')) {
1149: // delimiter found
1150: break;
1151: }
1152: ++classEnd;
1153: }
1154: // if (oldLog) System.out.println("\tclass = `"+text.substring(classStart,classEnd)+"`");
1155: int parenStart = getFirstNonWSCharPos(classEnd);
1156: if (parenStart == ERROR_INDEX) {
1157: continue;
1158: }
1159: int origParenStart = parenStart;
1160:
1161: // if (oldLog) System.out.println("\tfirst non-whitespace after class = "+parenStart+" `"+text.charAt(parenStart)+"`");
1162: if (text.charAt(origParenStart) == '<') {
1163: parenStart = ERROR_INDEX;
1164: // might be a generic class
1165: int closePointyBracket = findNextEnclosingBrace(
1166: origParenStart, '<', '>');
1167: if (closePointyBracket != ERROR_INDEX) {
1168: if (text.charAt(closePointyBracket) == '>') {
1169: parenStart = getFirstNonWSCharPos(closePointyBracket + 1);
1170: }
1171: }
1172: }
1173: if (parenStart == ERROR_INDEX) {
1174: continue;
1175: }
1176: if (text.charAt(parenStart) != '(') {
1177: continue;
1178: }
1179: int parenEnd = findNextEnclosingBrace(parenStart, '(', ')');
1180:
1181: int nextOpenSquiggly = _findNextOpenSquiggly(text, parenEnd);
1182: if (nextOpenSquiggly == ERROR_INDEX) {
1183: continue;
1184: }
1185: // if (oldLog) System.out.println("{ found at "+nextOpenSquiggly+": `"+text.substring(newPos, nextOpenSquiggly+1)+"`");
1186: // if (oldLog) System.out.println("_isAnonymousInnerClass("+newPos+", "+nextOpenSquiggly+")");
1187: if (_isAnonymousInnerClass(newPos, nextOpenSquiggly)) {
1188: // if (oldLog) System.out.println("is anonymous inner class");
1189: String cn = getEnclosingClassName(newPos, true);
1190: // if (oldLog) System.out.println("enclosing class = "+cn);
1191: if (!cn.startsWith(className)) {
1192: break;
1193: } else if (!cn.equals(className)) {
1194: newPos = findPrevEnclosingBrace(newPos, '{', '}');
1195: continue;
1196: } else {
1197: ++index;
1198: }
1199: }
1200: }
1201: _storeInCache(key, new Integer(index));
1202: // oldLog = log;
1203: return index;
1204: }
1205:
1206: /** Returns the name of the class or interface enclosing the caret position at the top level.
1207: * @return Name of enclosing class or interface
1208: * @throws ClassNameNotFoundException if no enclosing class found
1209: */
1210: public String getEnclosingTopLevelClassName(int pos)
1211: throws ClassNameNotFoundException {
1212: acquireReadLock();
1213: synchronized (_reduced) {
1214: int oldLocation = _currentLocation;
1215: try {
1216: setCurrentLocation(pos);
1217: IndentInfo info = getIndentInformation();
1218:
1219: // Find top level open brace
1220: int topLevelBracePos = -1;
1221: String braceType = info.braceTypeCurrent;
1222: while (!braceType.equals(IndentInfo.noBrace)) {
1223: if (braceType.equals(IndentInfo.openSquiggly)) {
1224: topLevelBracePos = _currentLocation
1225: - info.distToBraceCurrent;
1226: }
1227: move(-info.distToBraceCurrent);
1228: info = getIndentInformation();
1229: braceType = info.braceTypeCurrent;
1230: }
1231: if (topLevelBracePos == -1) {
1232: // No top level brace was found, so we can't find a top level class name
1233: setCurrentLocation(oldLocation);
1234: throw new ClassNameNotFoundException(
1235: "no top level brace found");
1236: }
1237:
1238: char[] delims = { '{', '}', ';' };
1239: int prevDelimPos = findPrevDelimiter(topLevelBracePos,
1240: delims);
1241: if (prevDelimPos == ERROR_INDEX) {
1242: // Search from start of doc
1243: prevDelimPos = DOCSTART;
1244: } else
1245: prevDelimPos++;
1246: setCurrentLocation(oldLocation);
1247:
1248: // Parse out the class name
1249: return getNextTopLevelClassName(prevDelimPos,
1250: topLevelBracePos);
1251: } catch (BadLocationException ble) {
1252: throw new UnexpectedException(ble);
1253: } finally {
1254: setCurrentLocation(oldLocation);
1255: releaseReadLock();
1256: }
1257: }
1258: }
1259:
1260: /** Gets the name of first class/interface decclared in file among the definitions anchored at:
1261: * @param indexOfClass index in this of a top-level occurrence of class
1262: * @param indexOfInterface index in this of a top-level occurrence of interface
1263: */
1264: private String getFirstClassName(int indexOfClass,
1265: int indexOfInterface) throws ClassNameNotFoundException {
1266:
1267: if ((indexOfClass == -1) && (indexOfInterface == -1))
1268: throw ClassNameNotFoundException.DEFAULT;
1269: if ((indexOfInterface == -1)
1270: || (indexOfClass != -1 && indexOfClass < indexOfInterface))
1271: return getNextIdentifier(indexOfClass + "class".length());
1272: return getNextIdentifier(indexOfInterface
1273: + "interface".length());
1274: }
1275:
1276: /** Gets the name of the document's main class: the document's only public class/interface or
1277: * first top level class if document contains no public classes or interfaces. */
1278: public String getMainClassName() throws ClassNameNotFoundException {
1279: acquireReadLock();
1280: synchronized (_reduced) {
1281: final int oldLocation = _currentLocation;
1282:
1283: try {
1284: setCurrentLocation(0);
1285: final String text = getText();
1286:
1287: final int indexOfClass = _findKeywordAtToplevel(
1288: "class", text, 0);
1289: final int indexOfInterface = _findKeywordAtToplevel(
1290: "interface", text, 0);
1291: final int indexOfPublic = _findKeywordAtToplevel(
1292: "public", text, 0);
1293:
1294: if (indexOfPublic == -1)
1295: return getFirstClassName(indexOfClass,
1296: indexOfInterface);
1297:
1298: // _log.log("text =\n" + text);
1299: // _log.log("indexOfClass = " + indexOfClass + "; indexOfPublic = " + indexOfPublic);
1300:
1301: // There is an explicit public declaration
1302: final int afterPublic = indexOfPublic
1303: + "public".length();
1304: final String subText = text.substring(afterPublic);
1305: setCurrentLocation(afterPublic);
1306: // _log.log("After public text = '" + subText + "'");
1307: int indexOfPublicClass = _findKeywordAtToplevel(
1308: "class", subText, afterPublic); // relative offset
1309: if (indexOfPublicClass != -1)
1310: indexOfPublicClass += afterPublic;
1311: int indexOfPublicInterface = _findKeywordAtToplevel(
1312: "interface", subText, afterPublic); // relative offset
1313: if (indexOfPublicInterface != -1)
1314: indexOfPublicInterface += afterPublic;
1315: // _log.log("indexOfPublicClass = " + indexOfPublicClass + " indexOfPublicInterface = " + indexOfPublicInterface);
1316:
1317: return getFirstClassName(indexOfPublicClass,
1318: indexOfPublicInterface);
1319:
1320: } finally {
1321: setCurrentLocation(oldLocation);
1322: releaseReadLock();
1323: }
1324: }
1325: }
1326:
1327: /** Gets the name of the top level class in this source file. This attempts to find the first declaration
1328: * of a class or interface.
1329: * @return The name of first class in the file
1330: * @throws ClassNameNotFoundException if no top level class found
1331: */
1332: public String getFirstTopLevelClassName()
1333: throws ClassNameNotFoundException {
1334: return getNextTopLevelClassName(0, getLength());
1335: }
1336:
1337: // note: need to update this to work with pos
1338: public String getNextTopLevelClassName(int startPos, int endPos)
1339: throws ClassNameNotFoundException {
1340:
1341: acquireReadLock();
1342: synchronized (_reduced) {
1343: int oldLocation = _currentLocation;
1344:
1345: try {
1346: setCurrentLocation(startPos);
1347: final int textLength = endPos - startPos;
1348: final String text = getText(startPos, textLength);
1349:
1350: int index;
1351:
1352: int indexOfClass = _findKeywordAtToplevel("class",
1353: text, startPos);
1354: int indexOfInterface = _findKeywordAtToplevel(
1355: "interface", text, startPos);
1356: int indexOfEnum = _findKeywordAtToplevel("enum", text,
1357: startPos);
1358:
1359: //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
1360: //level interface, AND the same for top level enum, then the class is the first top level declaration
1361: if (indexOfClass > -1
1362: && (indexOfInterface <= -1 || indexOfClass < indexOfInterface)
1363: && (indexOfEnum <= -1 || indexOfClass < indexOfEnum)) {
1364: index = indexOfClass + "class".length();
1365: } else if (indexOfInterface > -1
1366: && (indexOfClass <= -1 || indexOfInterface < indexOfClass)
1367: && (indexOfEnum <= -1 || indexOfInterface < indexOfEnum)) {
1368: index = indexOfInterface + "interface".length();
1369: } else if (indexOfEnum > -1
1370: && (indexOfClass <= -1 || indexOfEnum < indexOfClass)
1371: && (indexOfInterface <= -1 || indexOfEnum < indexOfInterface)) {
1372: index = indexOfEnum + "enum".length();
1373: } else {
1374: // no index was valid
1375: throw ClassNameNotFoundException.DEFAULT;
1376: }
1377:
1378: // we have a valid index
1379: return getNextIdentifier(startPos + index);
1380: } catch (BadLocationException ble) {
1381: throw new UnexpectedException(ble);
1382: } catch (IllegalStateException e) {
1383: throw new ClassNameNotFoundException(
1384: "No top level class name found");
1385: } finally {
1386: setCurrentLocation(oldLocation);
1387: releaseReadLock();
1388: }
1389: }
1390: }
1391:
1392: /** Finds the next identifier (following a non-whitespace character) in the document starting at start. Assumes that
1393: * read lock and _reduced lock are already held. */
1394: private String getNextIdentifier(final int startPos)
1395: throws ClassNameNotFoundException {
1396:
1397: // _log.log("getNextIdentifer(" + startPos + ") called");
1398:
1399: // int index = 0;
1400: // int length = 0;
1401: // int endIndex = 0;
1402: // String text = "";
1403: // int i;
1404: try {
1405: // first find index of first non whitespace (from the index in document)
1406: int index = getFirstNonWSCharPos(startPos);
1407: if (index == -1)
1408: throw new IllegalStateException("No identifier found");
1409:
1410: String text = getText();
1411: int length = text.length();
1412: int endIndex = length; //just in case no whitespace at end of file
1413:
1414: // _log.log("In getNextIdentifer text = \n" + text);
1415: // _log.log("index = " + index + "; length = " + length);
1416:
1417: //find index of next delimiter or whitespace
1418: char c;
1419: for (int i = index; i < length; i++) {
1420: c = text.charAt(i);
1421: if (!Character.isJavaIdentifierPart(c)) {
1422: endIndex = i;
1423: break;
1424: }
1425: }
1426: // _log.log("endIndex = " + endIndex);
1427: return text.substring(index, endIndex);
1428: } catch (BadLocationException e) {
1429: // System.err.println("text =\n" + text);
1430: // System.err.println("The document =\n" + getText());
1431: // System.err.println("startPos = " + startPos + "; length = " + length + "; index = " + index + "; endIndex = " + endIndex);
1432: throw new UnexpectedException(e);
1433: }
1434: }
1435:
1436: /** Finds the first occurrence of the keyword within the text (located at textOffset in this documennt) that is not
1437: * enclosed within a brace or comment and is followed by whitespace.
1438: * @param keyword the keyword for which to search
1439: * @param text in which to search
1440: * @param textOffset Offset at which the text occurs in the document
1441: * @return index of the keyword in text, or -1 if the keyword is not found or not followed by whitespace
1442: */
1443: private int _findKeywordAtToplevel(String keyword, String text,
1444: int textOffset) {
1445:
1446: acquireReadLock();
1447: synchronized (_reduced) {
1448: int oldLocation = _currentLocation;
1449: int index = 0;
1450: try {
1451: while (true) {
1452: index = text.indexOf(keyword, index);
1453: if (index == -1)
1454: break; // not found
1455: else {
1456: // found a match, check quality
1457: setCurrentLocation(textOffset + index);
1458:
1459: // check that the keyword is not in a comment and is followed by whitespace
1460: ReducedToken rt = _reduced.currentToken();
1461: int indexPastKeyword = index + keyword.length();
1462: if (indexPastKeyword < text.length()) {
1463: if (rt.getState() == ReducedModelStates.FREE
1464: && Character.isWhitespace(text
1465: .charAt(indexPastKeyword))) {
1466: // found a match but may not be at top level
1467: if (!posNotInBlock(index))
1468: index = -1; //in a paren phrase, gone too far
1469: break;
1470: } else
1471: index++; //move past so we can search again
1472: } else { // No space found past the keyword
1473: index = -1;
1474: break;
1475: }
1476: }
1477: }
1478: setCurrentLocation(oldLocation);
1479: // _log.log("findKeyWord(" + keyword + ", ..., " + textOffset + ")");
1480: return index;
1481: } finally {
1482: releaseReadLock();
1483: }
1484: }
1485: }
1486:
1487: /** Wrapper for Position objects to allow relinking to a new Document. */
1488: //TODO: move this class to OpenDefinitionsDocument interface
1489: public static class WrappedPosition implements Position {
1490: private Position _wrapped;
1491:
1492: public WrappedPosition(Position w) {
1493: setWrapped(w);
1494: }
1495:
1496: public void setWrapped(Position w) {
1497: _wrapped = w;
1498: }
1499:
1500: public int getOffset() {
1501: return _wrapped.getOffset();
1502: }
1503: }
1504:
1505: /** Factory method for created WrappedPositions. Stores the created Position instance
1506: * so it can be linked to a different DefinitionsDocument later. */
1507: public Position createPosition(int offs)
1508: throws BadLocationException {
1509: WrappedPosition wp = new WrappedPosition(
1510: createUnwrappedPosition(offs));
1511: synchronized (_wrappedPosListLock) {
1512: if (_wrappedPosList == null)
1513: _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>();
1514: _wrappedPosList.add(new WeakReference<WrappedPosition>(wp));
1515: }
1516: return wp;
1517: }
1518:
1519: /** Remove all positions that have been garbage-collected from the list of positions, then return a weakly-linked
1520: * hashmap with positions and their current offsets.
1521: * @return list of weak references to all positions that have been created and that have not been garbage-collected yet.
1522: */
1523: public WeakHashMap<WrappedPosition, Integer> getWrappedPositionOffsets() {
1524: LinkedList<WeakReference<WrappedPosition>> newList = new LinkedList<WeakReference<WrappedPosition>>();
1525: synchronized (_wrappedPosListLock) {
1526: if (_wrappedPosList == null) {
1527: _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>();
1528: }
1529: WeakHashMap<WrappedPosition, Integer> ret = new WeakHashMap<WrappedPosition, Integer>(
1530: _wrappedPosList.size());
1531:
1532: for (WeakReference<WrappedPosition> wr : _wrappedPosList) {
1533: if (wr.get() != null) {
1534: // hasn't been garbage-collected yet
1535: newList.add(wr);
1536: ret.put(wr.get(), wr.get().getOffset());
1537: }
1538: }
1539: _wrappedPosList.clear();
1540: _wrappedPosList = newList;
1541: return ret;
1542: }
1543: }
1544:
1545: /** Re-create the wrapped positions in the hashmap, update the wrapped position, and add them to the list.
1546: * @param whm weakly-linked hashmap of wrapped positions and their offsets
1547: */
1548: public void setWrappedPositionOffsets(
1549: WeakHashMap<WrappedPosition, Integer> whm)
1550: throws BadLocationException {
1551: synchronized (_wrappedPosListLock) {
1552: if (_wrappedPosList == null) {
1553: _wrappedPosList = new LinkedList<WeakReference<WrappedPosition>>();
1554: }
1555: _wrappedPosList.clear();
1556:
1557: for (Map.Entry<WrappedPosition, Integer> entry : whm
1558: .entrySet()) {
1559: if (entry.getKey() != null) {
1560: // hasn't been garbage-collected yet
1561: WrappedPosition wp = entry.getKey();
1562: wp.setWrapped(createUnwrappedPosition(entry
1563: .getValue()));
1564: _wrappedPosList
1565: .add(new WeakReference<WrappedPosition>(wp));
1566: }
1567: }
1568: }
1569: }
1570:
1571: /** Appending any information for the reduced model from each undo command */
1572: private static class CommandUndoableEdit extends
1573: AbstractUndoableEdit {
1574: private final Runnable _undoCommand;
1575: private final Runnable _redoCommand;
1576:
1577: public CommandUndoableEdit(final Runnable undoCommand,
1578: final Runnable redoCommand) {
1579: _undoCommand = undoCommand;
1580: _redoCommand = redoCommand;
1581: }
1582:
1583: public void undo() throws CannotUndoException {
1584: super .undo();
1585: _undoCommand.run();
1586: }
1587:
1588: public void redo() throws CannotRedoException {
1589: super .redo();
1590: _redoCommand.run();
1591: }
1592:
1593: public boolean isSignificant() {
1594: return false;
1595: }
1596: }
1597:
1598: /**
1599: * Getter method for CompoundUndoManager
1600: * @return _undoManager
1601: */
1602: public CompoundUndoManager getUndoManager() {
1603: return _undoManager;
1604: }
1605:
1606: /** Resets the undo manager. */
1607: public void resetUndoManager() {
1608: _undoManager = new CompoundUndoManager(_notifier);
1609: _undoManager.setLimit(UNDO_LIMIT);
1610: }
1611:
1612: /** Public accessor for the next undo action. */
1613: public UndoableEdit getNextUndo() {
1614: return _undoManager.getNextUndo();
1615: }
1616:
1617: /** Public accessor for the next undo action. */
1618: public UndoableEdit getNextRedo() {
1619: return _undoManager.getNextRedo();
1620: }
1621:
1622: /** Informs this document's undo manager that the document has been saved. */
1623: public void documentSaved() {
1624: _undoManager.documentSaved();
1625: }
1626:
1627: protected int startCompoundEdit() {
1628: return _undoManager.startCompoundEdit();
1629: }
1630:
1631: protected void endCompoundEdit(int key) {
1632: _undoManager.endCompoundEdit(key);
1633: }
1634:
1635: //This method added for FrenchKeyBoardFix
1636: protected void endLastCompoundEdit() {
1637: _undoManager.endLastCompoundEdit();
1638: }
1639:
1640: protected void addUndoRedo(
1641: AbstractDocument.DefaultDocumentEvent chng,
1642: Runnable undoCommand, Runnable doCommand) {
1643: chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
1644: }
1645:
1646: /**
1647: * Is used to be able to call editToBeUndone and editToBeRedone since they
1648: * are protected methods in UndoManager
1649: */
1650: /*
1651: private class OurUndoManager extends UndoManager {
1652: private boolean _compoundEditState = false;
1653: private OurCompoundEdit _compoundEdit;
1654:
1655: public void startCompoundEdit() {
1656: if (_compoundEditState) {
1657: throw new IllegalStateException("Cannot start a compound edit while making a compound edit");
1658: }
1659: _compoundEditState = true;
1660: _compoundEdit = new OurCompoundEdit();
1661: }
1662:
1663: public void endCompoundEdit() {
1664: if (!_compoundEditState) {
1665: throw new IllegalStateException("Cannot end a compound edit while not making a compound edit");
1666: }
1667: _compoundEditState = false;
1668: _compoundEdit.end();
1669: super.addEdit(_compoundEdit);
1670: }
1671:
1672: public UndoableEdit getNextUndo() {
1673: return editToBeUndone();
1674: }
1675:
1676: public UndoableEdit getNextRedo() {
1677: return editToBeRedone();
1678: }
1679:
1680: public boolean addEdit(UndoableEdit e) {
1681: if (_compoundEditState) {
1682: return _compoundEdit.addEdit(e);
1683: }
1684: else {
1685: return super.addEdit(e);
1686: }
1687: }
1688: }
1689:
1690:
1691: public java.util.Vector getEdits() {
1692: return _undoManager._compoundEdit.getEdits();
1693: }
1694:
1695: private class OurCompoundEdit extends CompoundEdit {
1696: public java.util.Vector getEdits() {
1697: return edits;
1698: }
1699: }
1700: */
1701:
1702: /**
1703: * used to help track down memory leaks
1704: */
1705: // protected void finalize() throws Throwable{
1706: // System.out.println("destroying DefDocument for " + _odd);
1707: // super.finalize();
1708: // }
1709: //
1710: // private List<Pair<Option, OptionListener>> _optionListeners = new LinkedList<Option, OptionListener>>();
1711: //
1712: // public void clearOptionListeners() {
1713: // for (Pair<Option, OptionListener> l: _optionListeners) {
1714: // DrJava.getConfig().removeOptionListener( l.getFirst(), l.getSecond());
1715: // }
1716: // _optionListeners.clear();
1717: // }
1718: //
1719: // public void addOptionListener(Option op, OptionListener l) {
1720: // DrJava.getConfig().addOptionListener(op, l);
1721: // _optionListeners.add(new Pair<Option, OptionListener>(op, l));
1722: // }
1723: /** This list of listeners to notify when we are finalized. */
1724: private List<FinalizationListener<DefinitionsDocument>> _finalizationListeners = new LinkedList<FinalizationListener<DefinitionsDocument>>();
1725:
1726: /**
1727: * Registers a finalization listener with the specific instance of the ddoc
1728: * <p><b>NOTE:</b><i>This should only be used by test cases. This is to ensure that
1729: * we don't spring memory leaks by allowing our unit tests to keep track of
1730: * whether objects are being finalized (garbage collected)</i></p>
1731: * @param fl the listener to register
1732: */
1733: public void addFinalizationListener(
1734: FinalizationListener<DefinitionsDocument> fl) {
1735: synchronized (_finalizationListeners) {
1736: _finalizationListeners.add(fl);
1737: }
1738: }
1739:
1740: public List<FinalizationListener<DefinitionsDocument>> getFinalizationListeners() {
1741: return _finalizationListeners;
1742: }
1743:
1744: /** This is called when this method is GC'd. Since this class implements
1745: * edu.rice.cs.drjava.model.Finalizable, it must notify its listeners
1746: */
1747: protected void finalize() {
1748: FinalizationEvent<DefinitionsDocument> fe = new FinalizationEvent<DefinitionsDocument>(
1749: this );
1750: synchronized (_finalizationListeners) {
1751: for (FinalizationListener<DefinitionsDocument> fl : _finalizationListeners) {
1752: fl.finalized(fe);
1753: }
1754: }
1755: }
1756:
1757: public String toString() {
1758: return "ddoc for " + _odd;
1759: }
1760: }
|