KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > drjava > model > definitions > DefinitionsDocument


1 /*BEGIN_COPYRIGHT_BLOCK
2  *
3  * This file is part of DrJava. Download the current version of this project from http://www.drjava.org/
4  * or http://sourceforge.net/projects/drjava/
5  *
6  * DrJava Open Source License
7  *
8  * Copyright (C) 2001-2006 JavaPLT group at Rice University (javaplt@rice.edu). All rights reserved.
9  *
10  * Developed by: Java Programming Languages Team, Rice University, http://www.cs.rice.edu/~javaplt/
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13  * documentation files (the "Software"), to deal with the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15  * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16  *
17  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the
18  * following disclaimers.
19  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
20  * following disclaimers in the documentation and/or other materials provided with the distribution.
21  * - Neither the names of DrJava, the JavaPLT, Rice University, nor the names of its contributors may be used to
22  * endorse or promote products derived from this Software without specific prior written permission.
23  * - Products derived from this software may not be called "DrJava" nor use the term "DrJava" as part of their
24  * names without prior written permission from the JavaPLT group. For permission, write to javaplt@rice.edu.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
27  * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30  * WITH THE SOFTWARE.
31  *
32 END_COPYRIGHT_BLOCK*/

33
34 package edu.rice.cs.drjava.model.definitions;
35
36 import javax.swing.text.*;
37 import javax.swing.undo.*;
38 import javax.swing.event.DocumentEvent JavaDoc;
39 import java.util.LinkedList JavaDoc;
40 import java.util.List JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.WeakHashMap JavaDoc;
43 import java.lang.ref.WeakReference JavaDoc;
44
45 import java.io.File JavaDoc;
46
47 import edu.rice.cs.drjava.model.definitions.reducedmodel.*;
48 import edu.rice.cs.util.Log;
49 import edu.rice.cs.util.UnexpectedException;
50 import edu.rice.cs.util.swing.Utilities;
51 import edu.rice.cs.drjava.model.definitions.indent.Indenter;
52 import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
53 import edu.rice.cs.drjava.model.*;
54
55
56 /** The document model for the definitions pane.
57  *
58  * This implementation of <code>Document</code> contains a "reduced model". The reduced model is automatically kept
59  * in sync when this document is updated. Also, that synchronization is maintained even across undo/redo -- this is
60  * done by making the undo/redo commands know how to restore the reduced model state.
61  *
62  * The reduced model is not thread-safe, so it is essential that ONLY this DefinitionsDocument call methods on it.
63  * Any information from the reduced model should be obtained through helper methods on DefinitionsDocument, and ALL
64  * methods in DefinitionsDocument which reference the reduced model (via the _reduced field) MUST be synchronized.
65  * This prevents any thread from seeing an inconsistent state in the middle of another thread's changes.
66  *
67  * @see BraceReduction
68  * @see ReducedModelControl
69  * @see ReducedModelComment
70  * @see ReducedModelBrace
71  */

72 public class DefinitionsDocument extends AbstractDJDocument implements Finalizable<DefinitionsDocument> {
73   
74   public static final Log _log = new Log("GlobalModel.txt", false);
75   private static final int NO_COMMENT_OFFSET = 0;
76   private static final int WING_COMMENT_OFFSET = 2;
77   
78   private volatile List JavaDoc<DocumentClosedListener> _closedListeners = new LinkedList JavaDoc<DocumentClosedListener>();
79   
80   public void addDocumentClosedListener(DocumentClosedListener l) {
81     synchronized(_closedListeners) { _closedListeners.add(l); }
82   }
83   
84   public void removeDocumentClosedListener(DocumentClosedListener l) {
85     synchronized(_closedListeners) { _closedListeners.remove(l); }
86   }
87   
88   // begin debug code
89

90 // private boolean _closed = false;
91
//
92
// protected void throwErrorHuh() {
93
// if (_closed) throw new RuntimeException("Definitions Document is closed, yet is being used");
94
// }
95

96   /** Called when this is kicked out of the document cache so that the references made to it may
97    * be released so that this can be GC'd. */

98   public void close() {
99     _removeIndenter();
100     synchronized(_closedListeners) {
101       for (DocumentClosedListener l: _closedListeners) { l.close(); }
102       _closedListeners = new LinkedList JavaDoc<DocumentClosedListener>();
103     }
104   }
105   
106   // end debug code
107

108   /** The maximum number of undos the model can remember */
109   private static final int UNDO_LIMIT = 1000;
110   /** Specifies if tabs are removed on open and converted to spaces. */
111   private static boolean _tabsRemoved = true;
112   /** Specifies if the document has been modified since the last save. */
113   private volatile boolean _isModifiedSinceSave = false;
114   /** Specifies if classFile is in sync with current state of the document */
115   private volatile boolean _classFileInSync = false;
116   /** Cached location, aides in determining line number. */
117   private volatile int _cachedLocation;
118   /** Cached current line number. */
119   private volatile int _cachedLineNum;
120   /** Cached location of previous line. */
121   private volatile int _cachedPrevLineLoc;
122   /** Cached location of next line. */
123   private volatile int _cachedNextLineLoc;
124
125   /** The package name last extracted from this document. */
126   private volatile String JavaDoc _packageName;
127   
128   private volatile File JavaDoc _classFile;
129
130   /** This reference to the OpenDefinitionsDocument is needed so that the document iterator
131    * (the DefaultGlobalModel) can find the next ODD given a DD. */

132   private volatile OpenDefinitionsDocument _odd;
133   
134   private volatile CompoundUndoManager _undoManager;
135   
136   /** Keeps track of the listeners to this model. */
137   private final GlobalEventNotifier _notifier;
138   
139   /* Relying on the following definition in AbstractDJDocument. It must be placed there to be initialized before use!
140   protected static final Object _wrappedPosListLock = new Object();
141   */

142   
143   /** List with weak references to positions. */
144   private volatile LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>> _wrappedPosList;
145   
146   /** Convenience constructor for using a custom indenter.
147    * @param indenter custom indenter class
148    * @param notifier used by CompoundUndoManager to announce undoable edits
149    */

150   public DefinitionsDocument(Indenter indenter, GlobalEventNotifier notifier) {
151     super(indenter);
152     _notifier = notifier;
153     _init();
154     resetUndoManager();
155   }
156
157   /** Main constructor. This has an obnoxious dependency on GlobalEventNotifier, which is passed through here only
158    * for a single usage in CompoundUndoManager. TODO: find a better way.
159    * @param notifier used by CompoundUndoManager to announce undoable edits
160    */

161   public DefinitionsDocument(GlobalEventNotifier notifier) {
162     super();
163     _notifier = notifier;
164     _init();
165     resetUndoManager();
166   }
167
168   /** Main constructor. This has an obnoxious dependency on GlobalEventNotifier, which is passed through here only
169    * for a single usage in CompoundUndoManager. TODO: find a better way.
170    * @param notifier used by CompoundUndoManager to announce undoable edits
171    */

172   public DefinitionsDocument(GlobalEventNotifier notifier, CompoundUndoManager undoManager) {
173     super();
174     _notifier = notifier;
175     _init();
176     _undoManager = undoManager;
177   }
178   
179   
180 // public void setUndoManager(CompoundUndoManager undoManager) {
181
// if (undoManager != null)
182
// _undoManager = undoManager;
183
// }
184

185   /** Returns a new indenter. */
186   protected Indenter makeNewIndenter(int indentLevel) { return new Indenter(indentLevel); }
187   
188   /** Private common helper for constructors. */
189   private void _init() {
190     _odd = null;
191     _cachedLocation = 0;
192     _cachedLineNum = 1;
193     _cachedPrevLineLoc = -1;
194     _cachedNextLineLoc = -1;
195     _classFile = null;
196     _cacheInUse = false;
197   }
198   
199   /* acquireReadLock, releaseReadLock, acquireWriteLock, releaseWriteLock are inherited from AbstractDJDocument. */
200    
201   /** Sets the OpenDefinitionsDocument that holds this DefinitionsDocument (the odd can only be set once).
202    * @param odd the OpenDefinitionsDocument to set as this DD's holder
203    */

204   public void setOpenDefDoc(OpenDefinitionsDocument odd) { if (_odd == null) _odd = odd; }
205   
206   /** @return the OpenDefinitonsDocument that is associated with this DefinitionsDocument. */
207   public OpenDefinitionsDocument getOpenDefDoc() {
208     if (_odd == null)
209       throw new IllegalStateException JavaDoc("The OpenDefinitionsDocument for this DefinitionsDocument has never been set");
210     else return _odd;
211   }
212   
213   protected void _styleChanged() {
214     acquireWriteLock();
215     try {
216       int length = getLength() - _currentLocation;
217       
218       //DrJava.consoleErr().println("Changed: " + _currentLocation + ", " + length);
219
DocumentEvent JavaDoc evt = new DefaultDocumentEvent(_currentLocation, length, DocumentEvent.EventType.CHANGE);
220       fireChangedUpdate(evt);
221     }
222     finally { releaseWriteLock(); }
223   }
224   
225   
226   /**
227    * Returns whether this document is currently untitled
228    * (indicating whether it has a file yet or not).
229    * @return true if the document is untitled and has no file
230    */

231 // public boolean isUntitled() {
232
// return (_file == null);
233
// }
234

235   /**
236    * Returns the file for this document. If the document
237    * is untitled and has no file, it throws an IllegalStateException.
238    * @return the file for this document
239    * @throws IllegalStateException if file has not been set
240    * @throws FileMovedException if file has been moved or deleted from its previous location
241    */

242 // public File getFilex()
243
// throws IllegalStateException , FileMovedException {
244
// if (_file == null) {
245
// throw new IllegalStateException(
246
// "This document does not yet have a file.");
247
// }
248
// //does the file actually exist?
249
// if (_file.exists()) {
250
// return _file;
251
// }
252
// else {
253
// throw new FileMovedException(_file,
254
// "This document's file has been moved or deleted.");
255
// }
256
// }
257
//
258
// /**
259
// * Returns the name of this file, or "(untitled)" if no file.
260
// */
261
// public String getFilenamex() {
262
// String filename = "(Untitled)";
263
// try {
264
// File file = getFilex();
265
// filename = file.getName();
266
// }
267
// catch (IllegalStateException ise) {
268
// // No file, leave as "untitled"
269
// }
270
// catch (FileMovedException fme) {
271
// // Recover, even though file has been deleted
272
// File file = fme.getFile();
273
// filename = file.getName();
274
// }
275
// return filename;
276
// }
277

278
279 // public void setFile(File file) {
280
// _file = file;
281
//
282
// //jim: maybe need lock
283
// if (_file != null) {
284
// _timestamp = _file.lastModified();
285
// }
286
// }
287
//
288
// public long getTimestamp() {
289
// return _timestamp;
290
// }
291

292
293   /** Gets the package and main class/interface name of this OpenDefinitionsDocument
294    * @return the qualified main class/interface name
295    */

296   public String JavaDoc getQualifiedClassName() throws ClassNameNotFoundException {
297     return _getPackageQualifier() + getMainClassName();
298   }
299
300   /** Gets fully qualified class name of the top level class enclosing the given position. */
301   public String JavaDoc getQualifiedClassName(int pos) throws ClassNameNotFoundException {
302     return _getPackageQualifier() + getEnclosingTopLevelClassName(pos);
303   }
304
305   /** Gets an appropriate prefix to fully qualify a class name. Returns this class's package followed by a dot, or the
306    * empty string if no package name is found.
307    */

308   protected String JavaDoc _getPackageQualifier() {
309     String JavaDoc packageName = getPackageName();
310     if ((packageName != null) && (! packageName.equals(""))) { packageName = packageName + "."; }
311     return packageName;
312   }
313
314   public void setClassFileInSync(boolean inSync) { _classFileInSync = inSync; }
315
316   public boolean getClassFileInSync() { return _classFileInSync; }
317
318   public void setCachedClassFile(File JavaDoc classFile) { _classFile = classFile; }
319
320   public File JavaDoc getCachedClassFile() { return _classFile; }
321
322   /** Inserts a string of text into the document. This is not where we do custom processing of the insert; that is
323    * done in {@link #insertUpdate}.
324    */

325   public void insertString(int offset, String JavaDoc str, AttributeSet a) throws BadLocationException {
326     
327     // If _removeTabs is set to true, remove all tabs from str.
328
// It is a current invariant of the tabification functionality that
329
// the document contains no tabs, but we want to allow the user
330
// to override this functionality.
331

332     acquireWriteLock();
333     try {
334       if (_tabsRemoved) str = _removeTabs(str);
335       setModifiedSinceSave();
336       super.insertString(offset, str, a);
337     }
338     finally { releaseWriteLock(); }
339   }
340   
341   
342   /** Removes a block of text from the specified location. We don't update the reduced model here; that happens
343    * in {@link #removeUpdate}.
344    */

345   public void remove(int offset, int len) throws BadLocationException {
346     
347     if (len == 0) return;
348     
349     acquireWriteLock();
350     try {
351       setModifiedSinceSave();
352       super.remove(offset, len);
353     }
354     finally { releaseWriteLock(); }
355   }
356
357   /** Given a String, return a new String will all tabs converted to spaces. Each tab is converted
358    * to one space, since changing the number of characters within insertString screws things up.
359    * @param source the String to be converted.
360    * @return a String will all the tabs converted to spaces
361    */

362   static String JavaDoc _removeTabs(final String JavaDoc source) {
363 // clearCache(); // Clear the helper method cache // Goofy code! Eliminated when method was converted to static.
364
return source.replace('\t', ' ');
365   }
366
367   /** Resets the modification state of this document to be consistent with state of _undoManager. Called whenever
368    * an undo or redo is performed. */

369   public void updateModifiedSinceSave() {
370     
371     acquireWriteLock();
372     try {
373     _isModifiedSinceSave = _undoManager.isModified();
374 // System.out.println("DefinitionsDocument: set modified? " + _modifiedSinceSave);
375
}
376     finally {
377       if (! _isModifiedSinceSave && _odd != null) _odd.documentReset();
378       releaseWriteLock();
379 // Utilities.showDebug("DefintionsDocument: _modifiedSinceSave = " + _modifiedSinceSave);
380
}
381   }
382   
383    /** Sets the modification state of this document to true and updates the state of the associated _odd.
384     * Assumes that write lock is already held. */

385   private void setModifiedSinceSave() {
386     if (! _isModifiedSinceSave) {
387       _isModifiedSinceSave = true;
388       _classFileInSync = false;
389       if (_odd != null) _odd.documentModified();
390     }
391   }
392   
393   /** Resets the modification state of this document. Used after a document has been saved or reverted. */
394   public void resetModification() {
395     acquireWriteLock();
396     try {
397       _isModifiedSinceSave = false;
398       _undoManager.documentSaved();
399     }
400     finally {
401       if (_odd != null) _odd.documentReset(); // null test required for some unit tests
402
releaseWriteLock();
403
404     }
405   }
406   
407   /** Determines if the document has been modified since the last save.
408    * @return true if the document has been modified
409    */

410   public boolean isModifiedSinceSave() {
411     acquireReadLock();
412     try { return _isModifiedSinceSave; }
413     finally { releaseReadLock(); }
414   }
415   
416   /** Return the current column of the cursor position. Uses a 0 based index. */
417   public int getCurrentCol() {
418     // throwErrorHuh();`
419
int here = _currentLocation;
420     int startOfLine = getLineStartPos(here);
421     return here - startOfLine;
422   }
423
424   /** Return the current line of the cursor position. Uses a 1-based index. */
425   public int getCurrentLine() {
426     acquireReadLock();
427     try {
428       synchronized(_reduced) {
429         int here = _currentLocation;
430         if (_cachedLocation > getLength()) {
431           // we can't know the last line number after a delete; starting over.
432
_cachedLocation = 0;
433           _cachedLineNum = 1;
434         }
435         if (_cachedNextLineLoc > getLength()) _cachedNextLineLoc = -1;
436         // let's see if we get off easy
437
if ( ! (_cachedPrevLineLoc < here && here < _cachedNextLineLoc )) {
438           
439           // this if improves performance when moving from the end of the document to the beginnning.
440
// in essence, it calculates the line number from scratch
441
if (_cachedLocation - here > here) {
442             _cachedLocation = 0;
443             _cachedLineNum = 1;
444           }
445           int lineOffset = _getRelativeLine();
446           _cachedLineNum = _cachedLineNum + lineOffset;
447           
448         }
449         _cachedLocation = here;
450         _cachedPrevLineLoc = getLineStartPos(here);
451         _cachedNextLineLoc = getLineEndPos(here);
452         return _cachedLineNum;
453       }
454     }
455     finally { releaseReadLock(); }
456   }
457
458   /** This method returns the relative offset of line number from the previous location in the document.
459     * Assumes the readLock is already held.
460     */

461   private int _getRelativeLine() {
462     
463     int count = 0;
464     int currLoc = _currentLocation;
465     setCurrentLocation(_cachedLocation);
466
467     if (_cachedLocation > currLoc) {
468       // we moved backward
469
int prevLineLoc = getLineStartPos( _cachedLocation );
470       while (prevLineLoc > currLoc) {
471         count--;
472         prevLineLoc = getLineStartPos( prevLineLoc - 1 );
473       }
474     }
475
476     else {
477       // we moved forward
478
int nextLineLoc = getLineEndPos( _cachedLocation );
479       while (nextLineLoc < currLoc) {
480         count++;
481         nextLineLoc = getLineEndPos( nextLineLoc + 1 );
482       }
483     }
484     setCurrentLocation(currLoc);
485     return count;
486   }
487
488   /** Returns the offset corresponding to the first character of the given line number,
489    * or -1 if the lineNum is not found. Avoid locking the document by copying its text.
490    * @param lineNum the line number for which to calculate the offset.
491    * @return the offset of the first character in the given line number
492    */

493   public int getOffset(int lineNum) {
494     if (lineNum < 0) return -1;
495     String JavaDoc defsText = getText();
496     int curLine = 1;
497     int offset = 0; // offset is number of chars from beginning of file
498

499     // offset is always pointing to the first character in a line
500
// at the top of the loop
501
while (offset < defsText.length()) {
502       
503       if (curLine == lineNum) return offset;
504       
505       int nextNewline = defsText.indexOf('\n', offset);
506       if (nextNewline == -1) return -1; // end of the document, and couldn't find the supplied lineNum
507

508       curLine++;
509       offset = nextNewline + 1;
510     }
511     return -1;
512   }
513
514   /** Returns true iff tabs are to removed on text insertion. */
515   public boolean tabsRemoved() { return _tabsRemoved; }
516  
517   /** Comments out all lines between selStart and selEnd, inclusive. The current cursor position is maintained
518    * after the operation.
519    * @param selStart the document offset for the start of the selection
520    * @param selEnd the document offset for the end of the selection
521    */

522   public int commentLines(int selStart, int selEnd) {
523     
524     //int key = _undoManager.startCompoundEdit(); //Uncommented in regards to the FrenchKeyBoardFix
525
int toReturn = selEnd;
526     if (selStart == selEnd) {
527       acquireWriteLock();
528       try {
529         synchronized(_reduced) {
530           setCurrentLocation(selStart);
531           Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
532           _commentLine();
533           toReturn += WING_COMMENT_OFFSET;
534           //int caretPos = getCaretPosition();
535
//_doc().setCurrentLocation(caretPos);
536
}
537       }
538       catch (BadLocationException e) { throw new UnexpectedException(e); }
539       finally { releaseWriteLock(); }
540     }
541     else toReturn = _commentBlock(selStart, selEnd);
542     _undoManager.endLastCompoundEdit(); //Changed from endCompoundEdit(key) for FrenchKeyBoardFix
543
return toReturn;
544   }
545  
546
547   /** Comments out the lines between and including the lines containing points start and end, using wing
548    * comments -- "// ".
549    *
550    * @param start Position in document to start commenting from
551    * @param end Position in document to end commenting at
552    */

553   private int _commentBlock(final int start, final int end) {
554     int afterCommentEnd = end;
555     acquireWriteLock();
556     try {
557       // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc doing the
558
// indentLine calls.
559
final Position endPos = this.createUnwrappedPosition(end);
560       // Iterate, line by line, until we get to/past the end
561
int walker = start;
562       synchronized(_reduced) {
563         while (walker < endPos.getOffset()) {
564           setCurrentLocation(walker);
565           // Keep pointer to walker position that will stay current
566
// regardless of how commentLine changes things
567
Position walkerPos = this.createUnwrappedPosition(walker);
568           // Comment out current line
569
_commentLine(); // must be atomic
570
afterCommentEnd += WING_COMMENT_OFFSET;
571           // Move back to walker spot
572
setCurrentLocation(walkerPos.getOffset());
573           walker = walkerPos.getOffset();
574           // Adding 1 makes us point to the first character AFTER the next newline.
575
// We don't actually move yet. That happens at the top of the loop,
576
// after we check if we're past the end.
577
walker += _reduced.getDistToNextNewline() + 1;
578           //DrJava.consoleOut().println("progress: " + (100*(walker-start)/(end-start)));
579
}
580       }
581     }
582     catch (BadLocationException e) { throw new UnexpectedException(e); }
583     finally { releaseWriteLock(); }
584     return afterCommentEnd;
585   }
586
587   /** Comments out a single line with wing comments -- "// ".
588    * @pre this.writeLock() and _reduced lock are already held! */

589   private void _commentLine() {
590     // Insert "// " at the beginning of the line.
591
// Using null for AttributeSet follows convention in this class.
592
try { insertString(_currentLocation - getCurrentCol(), "//", null); }
593     catch (BadLocationException e) { throw new UnexpectedException(e); }
594   }
595
596   /** Uncomments all lines between selStart and selEnd, inclusive.
597    * The current cursor position is maintained after the operation.
598    * @param selStart the document offset for the start of the selection
599    * @param selEnd the document offset for the end of the selection
600    */

601   public int uncommentLines(int selStart, int selEnd) {
602  
603     //int key = _undoManager.startCompoundEdit(); //commented out for FrenchKeyBoardFix
604
int toReturn = selEnd;
605     if (selStart == selEnd) {
606       acquireWriteLock();
607       try {
608         synchronized(_reduced) {
609           setCurrentLocation(selStart);
610           Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
611           _uncommentLine(); // accesses _reduced
612
toReturn -= WING_COMMENT_OFFSET;
613           //int caretPos = getCaretPosition();
614
//_doc().setCurrentLocation(caretPos);
615
//setCurrentLocation(oldCurrentPosition.getOffset());
616
}
617       }
618       catch (BadLocationException e) { throw new UnexpectedException(e); }
619       finally { releaseWriteLock(); }
620     }
621     else toReturn = _uncommentBlock(selStart, selEnd);
622     //_undoManager.endCompoundEdit(key); //Commented out for FrenchKeyBoardFix, Replaced with endLastCompoundEdit();
623
_undoManager.endLastCompoundEdit();
624     return toReturn;
625   }
626
627   /** Uncomments all lines between and including the lines containing
628    * points start and end.
629    * @param start Position in document to start commenting from
630    * @param end Position in document to end commenting at
631    */

632   private int _uncommentBlock(final int start, final int end) {
633     int afterUncommentEnd = end;
634     acquireWriteLock();
635     try {
636       // Keep marker at the end. This Position will be the correct endpoint no matter how we change the doc
637
// doing the indentLine calls.
638
final Position endPos = this.createUnwrappedPosition(end);
639       // Iterate, line by line, until we get to/past the end
640
int walker = start;
641       synchronized(_reduced) {
642         while (walker < endPos.getOffset()) {
643           setCurrentLocation(walker);
644           // Keep pointer to walker position that will stay current
645
// regardless of how commentLine changes things
646
Position walkerPos = this.createUnwrappedPosition(walker);
647           // uncomment current line
648
afterUncommentEnd-= _uncommentLine(); // accesses _reduced
649
// Move back to walker spot
650
setCurrentLocation(walkerPos.getOffset());
651           walker = walkerPos.getOffset();
652           // Adding 1 makes us point to the first character AFTER the next newline.
653
// We don't actually move yet. That happens at the top of the loop,
654
// after we check if we're past the end.
655
walker += _reduced.getDistToNextNewline() + 1;
656           //DrJava.consoleOut().println("progress: " + (100*(walker-start)/(end-start)));
657
}
658       }
659     }
660     catch (BadLocationException e) { throw new UnexpectedException(e); }
661     finally { releaseWriteLock(); }
662     return afterUncommentEnd;
663   }
664
665   /** Uncomments a single line. This simply looks for a leading "//". Assumes that _reduced lock is already held and
666    * that acquireWriteLock is already held.
667    * @pre theads hold this.writeLock() and _reduced lock
668    */

669   private int _uncommentLine() throws BadLocationException {
670     // Look for "//" at the beginning of the line, and remove it.
671
int curCol = getCurrentCol();
672     int lineStart = _currentLocation - curCol;
673     String JavaDoc text = getText(lineStart, curCol + _reduced.getDistToNextNewline());
674     int pos = text.indexOf("//");
675     
676     // System.out.println("" + _currentLocation + " " + curCol + " "
677
// + text + " " + pos + " " + _reduced.getDistToNextNewline());
678

679     // Look for any non-whitespace chars before the "//" on the line.
680
boolean goodWing = true;
681     for (int i = pos-1; i >= 0; i--) {
682       char c = text.charAt(i);
683       // If a previous char is not whitespace, we're not looking at a wing comment.
684
if (c != ' ') {
685         goodWing = false;
686         return NO_COMMENT_OFFSET;
687       }
688     }
689     
690     // If a wing comment wasn't found, or if the wings aren't the first
691
// non-whitespace characters on the line, do nothing.
692
if (pos >= 0 && goodWing) {
693       // Otherwise, remove the wings.
694
remove(lineStart + pos, 2);
695       //_indentLine(Indenter.OTHER);
696
return WING_COMMENT_OFFSET;
697     }
698     return NO_COMMENT_OFFSET;
699   }
700
701   /** Goes to a particular line in the document. */
702   public void gotoLine(int line) {
703
704     int dist;
705     if (line < 0) return;
706     int actualLine =1;
707     
708     acquireReadLock();
709     int len = getLength();
710     try {
711       synchronized(_reduced) {
712         setCurrentLocation(0);
713         for (int i = 1; (i < line) && (_currentLocation < len); i++) {
714           dist = _reduced.getDistToNextNewline();
715           if (_currentLocation + dist < len) dist++;
716           actualLine++;
717           move(dist);
718         }
719         _cachedLineNum = actualLine;
720         _cachedLocation = _currentLocation;
721         _cachedPrevLineLoc = getLineStartPos(_currentLocation);
722         _cachedNextLineLoc = getLineEndPos(_currentLocation);
723       }
724     }
725     finally { releaseReadLock(); }
726   }
727   
728   private int _findNextOpenSquiggly(String JavaDoc text, int pos) throws BadLocationException {
729     // acquireReadLock assumed to be held,
730
int i;
731     int reducedPos = pos;
732     
733     synchronized(_reduced) {
734       final int origLocation = _currentLocation;
735       // Move reduced model to location pos
736
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
737

738       // Walk forward from specificed position
739
i = text.indexOf('{', reducedPos);
740       while(i>-1) {
741         // Move reduced model to walker's location
742
_reduced.move(i - reducedPos); // reduced model points to i
743
reducedPos = i; // reduced model points to reducedPos
744

745         // Check if matching keyword should be ignored because it is within a comment, or quotes
746
ReducedModelState state = _reduced.getStateAtCurrent();
747         if (!state.equals(ReducedModelState.FREE) || _isStartOfComment(text, i)
748               || ((i > 0) && _isStartOfComment(text, i - 1))) {
749           i = text.indexOf('{', reducedPos+1);
750           continue; // ignore matching brace
751
}
752         else {
753           break; // found our brace
754
}
755       } // end synchronized
756

757       _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
758
}
759     
760     if (i == -1) reducedPos = ERROR_INDEX; // No matching brace was found
761
return reducedPos;
762   }
763   
764   private int _findPrevKeyword(String JavaDoc text, String JavaDoc kw, int pos) throws BadLocationException {
765     // acquireReadLock assumed to be held,
766
int i;
767     int reducedPos = pos;
768     
769     synchronized(_reduced) {
770       final int origLocation = _currentLocation;
771       // Move reduced model to location pos
772
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
773

774       // Walk backwards from specificed position
775
i = text.lastIndexOf(kw, reducedPos);
776       while(i >- 1) {
777         // Check that this is the beginning of a word
778
if (i > 0) {
779           if (Character.isJavaIdentifierPart(text.charAt(i-1))) {
780             // not begining
781
i = text.lastIndexOf(kw, i-1);
782             continue; // ignore matching keyword
783
}
784         }
785         // Check that this not just the beginning of a longer word
786
if (i+kw.length()<text.length()) {
787           if (Character.isJavaIdentifierPart(text.charAt(i+kw.length()))) {
788             // not begining
789
i = text.lastIndexOf(kw, i-1);
790             continue; // ignore matching keyword
791
}
792         }
793         
794         // Move reduced model to walker's location
795
_reduced.move(i - reducedPos); // reduced model points to i
796
reducedPos = i; // reduced model points to reducedPos
797

798         // Check if matching keyword should be ignored because it is within a comment, or quotes
799
ReducedModelState state = _reduced.getStateAtCurrent();
800         if (!state.equals(ReducedModelState.FREE) || _isStartOfComment(text, i)
801               || ((i > 0) && _isStartOfComment(text, i - 1))) {
802           i = text.lastIndexOf(kw, reducedPos-1);
803           continue; // ignore matching keyword
804
}
805         else {
806           break; // found our keyword
807
}
808       } // end synchronized/
809

810       _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
811
}
812     
813     if (i == -1) reducedPos = ERROR_INDEX; // No matching keyword was found
814
return reducedPos;
815   }
816  
817 // public static boolean log = true;
818

819   /** Searching backwards finds the name of the enclosing named class or interface. NB: ignores comments.
820    * WARNING: In long source files and when contained in anonymous inner classes, this function might take a LONG time.
821    * @param pos Position to start from
822    * @param qual true to find the fully qualified class name
823    * @return name of the enclosing named class or interface
824    */

825   public String JavaDoc getEnclosingClassName(int pos, boolean qual) throws BadLocationException, ClassNameNotFoundException {
826 // boolean oldLog = log; log = false;
827
// Check cache
828
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("getEnclosingClassName:").append(pos);
829     keyBuf.append(":").append(qual);
830     String JavaDoc key = keyBuf.toString();
831     String JavaDoc cached = (String JavaDoc) _checkCache(key);
832     if (cached != null) return cached;
833
834     char[] delims = {'{','}','(',')','[',']','+','-','/','*',';',':','=',
835       '!','@','#','$','%','^','~','\\','"','`','|'};
836     String JavaDoc name = "";
837
838     acquireReadLock();
839     try {
840       String JavaDoc text = getText(DOCSTART, pos+1);
841       
842       int curPos = pos;
843       
844       do {
845         if ((text.charAt(curPos)!='{') || (text.charAt(curPos)!='}')) { ++curPos; }
846         
847 // if (oldLog) System.out.println("curPos=" + curPos + " `" +
848
// text.substring(Math.max(0,curPos-10), Math.min(text.length(), curPos+1)) + "`");
849

850         curPos = findPrevEnclosingBrace(curPos, '{', '}');
851         if (curPos==ERROR_INDEX) { break; }
852         int classPos = _findPrevKeyword(text, "class", curPos);
853         int interPos = _findPrevKeyword(text, "interface", curPos);
854         int otherPos = findPrevDelimiter(curPos, delims);
855         int newPos = ERROR_INDEX;
856         // see if there's a ) closer by
857
int closeParenPos = findPrevNonWSCharPos(curPos);
858         if ((closeParenPos!=ERROR_INDEX) && (text.charAt(closeParenPos)==')')) {
859           // yes, find the matching (
860
int openParenPos = findPrevEnclosingBrace(closeParenPos, '(', ')');
861           if ((openParenPos!=ERROR_INDEX) && (text.charAt(openParenPos)=='(')) {
862             // this might be an inner class
863
newPos = _findPrevKeyword(text, "new", openParenPos);
864 // if (oldLog) System.out.println("\tnew found at "+newPos+", openSquigglyPos="+curPos);
865
if (! _isAnonymousInnerClass(newPos, curPos)) {
866               // not an anonymous inner class
867
newPos = ERROR_INDEX;
868             }
869           }
870         }
871 // if (oldLog) System.out.println("curPos="+curPos+" `"+text.substring(Math.max(0,curPos-10),curPos+1)+"`");
872
// if (oldLog) System.out.println("\tclass="+classPos+", inter="+interPos+", other="+otherPos+", new="+newPos+" `" +
873
// text.substring(Math.max(0,otherPos-10),otherPos+1)+"`");
874
while((classPos!=ERROR_INDEX) || (interPos!=ERROR_INDEX) || (newPos!=ERROR_INDEX)) {
875           if (newPos!=ERROR_INDEX) {
876 // if (oldLog) System.out.println("\tanonymous inner class! newPos = "+newPos);
877
classPos = ERROR_INDEX;
878             interPos = ERROR_INDEX;
879             break;
880           }
881           else if ((otherPos>classPos) && (otherPos>interPos)) {
882             if ((text.charAt(otherPos)!='{') || (text.charAt(otherPos)!='}')) { ++otherPos; }
883             curPos = findPrevEnclosingBrace(otherPos, '{', '}');
884             classPos = _findPrevKeyword(text, "class", curPos);
885             interPos = _findPrevKeyword(text, "interface", curPos);
886             otherPos = findPrevDelimiter(curPos, delims);
887             newPos = ERROR_INDEX;
888             // see if there's a ) closer by
889
closeParenPos = findPrevNonWSCharPos(curPos);
890 // if (closeParenPos!=ERROR_INDEX) if (oldLog) System.out.println("nonWS before curPos = " + closeParenPos +
891
// " `"+text.charAt(closeParenPos)+"`");
892
if ((closeParenPos!=ERROR_INDEX) && (text.charAt(closeParenPos)==')')) {
893               // yes, find the matching (
894
int openParenPos = findPrevEnclosingBrace(closeParenPos, '(', ')');
895               if ((openParenPos!=ERROR_INDEX) && (text.charAt(openParenPos)=='(')) {
896                 // this might be an inner class
897
newPos = _findPrevKeyword(text, "new", openParenPos);
898 // if (oldLog) System.out.println("\tnew found at " + newPos + ", openSquigglyPos=" + curPos);
899
if (_isAnonymousInnerClass(newPos, curPos)) {
900                   // yes, anonymous inner class
901
}
902                 else {
903                   newPos = ERROR_INDEX;
904                 }
905               }
906             }
907 // if (oldLog) System.out.println("curPos=" +curPos+" `"+text.substring(Math.max(0,curPos-10),curPos+1)+"`");
908
// if (oldLog) System.out.println("\tclass="+classPos+", inter="+interPos+", other="+otherPos+" `"+
909
// text.substring(Math.max(0,otherPos-10),otherPos+1)+"`");
910
}
911           else {
912             // either class or interface found first
913
curPos = Math.max(classPos, Math.max(interPos, newPos));
914             break;
915           }
916         }
917         
918         if ((classPos!=ERROR_INDEX) || (interPos!=ERROR_INDEX)) {
919           if (classPos>interPos) {
920             // class found first
921
curPos += "class".length();
922           }
923           else {
924             // interface found first
925
curPos += "interface".length();
926           }
927           int nameStart = getFirstNonWSCharPos(curPos);
928           if (nameStart==ERROR_INDEX) { throw new ClassNameNotFoundException("Cannot determine enclosing class name"); }
929           int nameEnd = nameStart+1;
930           while(nameEnd<text.length()) {
931             if ((!Character.isJavaIdentifierPart(text.charAt(nameEnd))) && (text.charAt(nameEnd)!='.')) {
932               // delimiter found
933
break;
934             }
935             ++nameEnd;
936           }
937           name = text.substring(nameStart,nameEnd) + '$' + name;
938         }
939         else if (newPos!=ERROR_INDEX) {
940           name = String.valueOf(_getAnonymousInnerClassIndex(curPos)) + "$" + name;
941           curPos = newPos;
942         }
943         else {
944           // neither class nor interface found
945
break;
946         }
947       } while(qual);
948     }
949     finally { releaseReadLock(); }
950     
951     // chop off '$' at the end.
952
if (name.length()>0) name = name.substring(0, name.length()-1);
953     
954     if (qual) {
955       String JavaDoc pn = getPackageName();
956       if ((pn.length()>0) && (name.length()>0)) {
957         name = getPackageName() + "." + name;
958       }
959     }
960 // log = oldLog;
961
return name;
962   }
963   
964   /** Returns true if this position is the instantiation of an anonymous inner class.
965    * @param newPos position of "new"
966    * @param openSquigglyPos position of the next '{'
967    * @return true if anonymous inner class instantiation
968    */

969   private boolean _isAnonymousInnerClass(int newPos, int openSquigglyPos) throws BadLocationException {
970 // String t = getText(DOCSTART, openSquigglyPos+1);
971
// System.out.print ("_isAnonymousInnerClass("+newPos+", "+openSquigglyPos+")");
972
// System.out.println("_isAnonymousInnerClass("+newPos+", "+openSquigglyPos+"): `"+
973
// t.substring(newPos, openSquigglyPos+1)+"`");
974

975     // Check cache
976
final StringBuilder JavaDoc keyBuf =
977       new StringBuilder JavaDoc("_getAnonymousInnerClassIndex:").append(newPos).append(':').append(openSquigglyPos);
978     String JavaDoc key = keyBuf.toString();
979     Boolean JavaDoc cached = (Boolean JavaDoc) _checkCache(key);
980     if (cached != null) {
981 // System.out.println(" ==> "+cached);
982
return cached;
983     }
984
985     // acquireReadLock assumed to be held
986
cached = false;
987     String JavaDoc text = getText(DOCSTART, openSquigglyPos+1);
988     int origNewPos = newPos;
989     newPos += "new".length();
990     int classStart = getFirstNonWSCharPos(newPos);
991     if (classStart!=ERROR_INDEX) {
992       int classEnd = classStart+1;
993       while(classEnd<text.length()) {
994         if ((!Character.isJavaIdentifierPart(text.charAt(classEnd))) && (text.charAt(classEnd)!='.')) {
995           // delimiter found
996
break;
997         }
998         ++classEnd;
999       }
1000      // System.out.println("\tclass = `"+text.substring(classStart,classEnd)+"`");
1001
int parenStart = getFirstNonWSCharPos(classEnd);
1002      if (parenStart!=ERROR_INDEX) {
1003        int origParenStart = parenStart;
1004
1005        // System.out.println("\tfirst non-whitespace after class = "+parenStart+" `"+text.charAt(parenStart)+"`");
1006
if (text.charAt(origParenStart)=='<') {
1007          parenStart = ERROR_INDEX;
1008          // might be a generic class
1009
int closePointyBracket = findNextEnclosingBrace(origParenStart, '<', '>');
1010          if (closePointyBracket!=ERROR_INDEX) {
1011            if (text.charAt(closePointyBracket)=='>') {
1012              parenStart = getFirstNonWSCharPos(closePointyBracket+1);
1013            }
1014          }
1015        }
1016      }
1017      if (parenStart!=ERROR_INDEX) {
1018        if (text.charAt(parenStart)=='(') {
1019          synchronized(_reduced) {
1020            final int origLocation = _currentLocation;
1021            _reduced.move(parenStart+1 - origLocation); // reduced model points to pos == parenStart+1
1022
int parenEnd = balanceForward();
1023            _reduced.move(origLocation - (parenStart+1)); // Restore the state of the reduced model;
1024
if (parenEnd > -1) {
1025              parenEnd = parenEnd + parenStart+1;
1026              // System.out.println("\tafter closing paren = "+parenEnd);
1027
int afterParen = getFirstNonWSCharPos(parenEnd);
1028              // System.out.println("\tfirst non-whitespace after paren = "+parenStart+" `"+text.charAt(afterParen)+"`");
1029
cached = (afterParen==openSquigglyPos);
1030            }
1031          }
1032        }
1033      }
1034    }
1035    
1036    _storeInCache(key, cached);
1037    
1038// System.out.println(" ==> "+cached);
1039
return cached;
1040  }
1041  
1042  /** Gets the package name embedded in the text of this document by minimally parsing the document to find the
1043   * package statement. If package statement is not found or is ill-formed, returns "" as the package name.
1044   * @return The name of package embedded in this document. If there is no well-formed package statement,
1045   * returns "" as the package name.
1046   */

1047  public String JavaDoc getPackageName() {
1048    try { return getStrictPackageName(); }
1049    catch(InvalidPackageException e) { return ""; }
1050  }
1051 
1052  /**
1053   * Return the index of the anonymous inner class being instantiated at the specified position.
1054   * @param position of the opening curly brace of the anonymous inner class
1055   * @return anonymous class index
1056   */

1057  int _getAnonymousInnerClassIndex(int pos) throws BadLocationException, ClassNameNotFoundException {
1058// boolean oldLog = log; log = false;
1059

1060    // Check cache
1061
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("_getAnonymousInnerClassIndex:").append(pos);
1062    final String JavaDoc key = keyBuf.toString();
1063    final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1064    if (cached != null) {
1065// log = oldLog;
1066
return cached.intValue();
1067    }
1068
1069    // acquireReadLock assumed to be held
1070
--pos; // move outside the curly brace
1071
char[] delims = {'{','}','(',')','[',']','+','-','/','*',';',':','=',
1072      '!','@','#','$','%','^','~','\\','"','`','|'};
1073    String JavaDoc className = getEnclosingClassName(pos, true);
1074    String JavaDoc text = getText(DOCSTART, pos);
1075    int index = 1;
1076    int newPos = pos;
1077// if (oldLog) System.out.println("anon before "+pos+" enclosed by "+className);
1078
while((newPos = _findPrevKeyword(text, "new", newPos-1)) != ERROR_INDEX) {
1079// if (oldLog) System.out.println("new found at "+newPos);
1080
int afterNewPos = newPos + "new".length();
1081      int classStart = getFirstNonWSCharPos(afterNewPos);
1082      if (classStart==ERROR_INDEX) { continue; }
1083      int classEnd = classStart+1;
1084      while(classEnd<text.length()) {
1085        if ((!Character.isJavaIdentifierPart(text.charAt(classEnd))) && (text.charAt(classEnd)!='.')) {
1086          // delimiter found
1087
break;
1088        }
1089        ++classEnd;
1090      }
1091// if (oldLog) System.out.println("\tclass = `"+text.substring(classStart,classEnd)+"`");
1092
int parenStart = getFirstNonWSCharPos(classEnd);
1093      if (parenStart==ERROR_INDEX) { continue; }
1094      int origParenStart = parenStart;
1095      
1096// if (oldLog) System.out.println("\tfirst non-whitespace after class = "+parenStart+" `"+text.charAt(parenStart)+"`");
1097
if (text.charAt(origParenStart)=='<') {
1098        parenStart = ERROR_INDEX;
1099        // might be a generic class
1100
int closePointyBracket = findNextEnclosingBrace(origParenStart, '<', '>');
1101        if (closePointyBracket!=ERROR_INDEX) {
1102          if (text.charAt(closePointyBracket)=='>') {
1103            parenStart = getFirstNonWSCharPos(closePointyBracket+1);
1104          }
1105        }
1106      }
1107      if (parenStart==ERROR_INDEX) { continue; }
1108      if (text.charAt(parenStart)!='(') { continue; }
1109      int parenEnd = findNextEnclosingBrace(parenStart, '(', ')');
1110    
1111      int nextOpenSquiggly = _findNextOpenSquiggly(text, parenEnd);
1112      if (nextOpenSquiggly==ERROR_INDEX) { continue; }
1113// if (oldLog) System.out.println("{ found at "+nextOpenSquiggly+": `"+text.substring(newPos, nextOpenSquiggly+1)+"`");
1114
// if (oldLog) System.out.println("_isAnonymousInnerClass("+newPos+", "+nextOpenSquiggly+")");
1115
if (_isAnonymousInnerClass(newPos, nextOpenSquiggly)) {
1116// if (oldLog) System.out.println("is anonymous inner class");
1117
String JavaDoc cn = getEnclosingClassName(newPos, true);
1118// if (oldLog) System.out.println("enclosing class = "+cn);
1119
if (!cn.startsWith(className)) { break; }
1120        else if (!cn.equals(className)) {
1121          newPos = findPrevEnclosingBrace(newPos, '{', '}');
1122          continue;
1123        }
1124        else {
1125          ++index;
1126        }
1127      }
1128    }
1129    _storeInCache(key, new Integer JavaDoc(index));
1130// oldLog = log;
1131
return index;
1132  }
1133
1134  /** Gets the name of the package this source file claims it's in (with the package keyword). It does this by
1135   * minimally parsing the source file to find the package statement.
1136   * @return The name of package declared for this source file OR the empty string if there is no package
1137   * statement (and thus the source file is in the empty package).
1138   * @exception InvalidPackageException if there is some sort of a <TT>package</TT> statement but the defined
1139   * package does not match or exist.
1140   */

1141  protected String JavaDoc getStrictPackageName() throws InvalidPackageException {
1142    
1143// Utilities.show("getPackageName() called on " + this);
1144
/* Buffer for constructing the package name. */
1145    final StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
1146    int oldLocation = 0; // javac requires this bogus initialization
1147

1148    acquireReadLock();
1149    try {
1150      final String JavaDoc text = getText();
1151      final int docLength = text.length();
1152      if (docLength == 0) return "";
1153      
1154      // perturbing reduced model, which is reset in finally clause
1155
synchronized(_reduced) {
1156        oldLocation = _currentLocation;
1157        try {
1158          setCurrentLocation(0);
1159          
1160          /* The location of the first non-whitespace character that is not inside a string or comment. */
1161          int firstNormalLocation = 0;
1162          while (firstNormalLocation < docLength) {
1163            setCurrentLocation(firstNormalLocation);
1164            
1165            if (_reduced.currentToken().getHighlightState() == HighlightStatus.NORMAL) {
1166              // OK, it's normal -- so if it's not whitespace, we found the spot
1167
char curChar = text.charAt(firstNormalLocation);
1168              if (! Character.isWhitespace(curChar)) break;
1169            }
1170            firstNormalLocation++;
1171          }
1172          
1173          // Now there are two possibilities: firstNormalLocation is at the first spot of a non-whitespace character
1174
// that's NORMAL, or it's at the end of the document.
1175

1176          if (firstNormalLocation == docLength) return "";
1177          
1178          final int strlen = "package".length();
1179          
1180          final int endLocation = firstNormalLocation + strlen;
1181          
1182          if ((firstNormalLocation + strlen > docLength) ||
1183              ! text.substring(firstNormalLocation, endLocation).equals("package")) {
1184            // The first normal text is not "package" or there is not enough text for there to be a package statement.
1185
// Thus, there is no valid package statement.
1186
return "";
1187          }
1188          
1189          // OK, we must have found a package statement.
1190
// Now let's find the semicolon. Again, the semicolon must be free.
1191
int afterPackage = firstNormalLocation + strlen;
1192          
1193          int semicolonLocation = afterPackage;
1194          do {
1195            semicolonLocation = text.indexOf(";", semicolonLocation + 1);
1196            if (semicolonLocation == -1)
1197              throw new InvalidPackageException(firstNormalLocation,
1198                                                "No semicolon found to terminate package statement!");
1199            setCurrentLocation(semicolonLocation);
1200          }
1201          while (_reduced.currentToken().getHighlightState() != HighlightStatus.NORMAL);
1202          
1203          // Now we have semicolon location. We'll gather text in between one character at a time for simplicity.
1204
for (int walk = afterPackage + 1; walk < semicolonLocation; walk++) {
1205            setCurrentLocation(walk);
1206            if (_reduced.currentToken().getHighlightState() == HighlightStatus.NORMAL) {
1207              char curChar = text.charAt(walk);
1208              if (! Character.isWhitespace(curChar)) buf.append(curChar);
1209            }
1210          }
1211          
1212          String JavaDoc toReturn = buf.toString();
1213          if (toReturn.equals(""))
1214            throw new InvalidPackageException(firstNormalLocation,
1215                                              "Package name was not specified after the package keyword!");
1216          return toReturn;
1217        }
1218        finally { // reset oldLocation
1219
setCurrentLocation(0); // Why?
1220
setCurrentLocation(oldLocation);
1221        }
1222      }
1223    }
1224    finally { releaseReadLock(); }
1225  }
1226
1227  /** Returns the name of the class or interface enclosing the caret position at the top level.
1228   * @return Name of enclosing class or interface
1229   * @throws ClassNameNotFoundException if no enclosing class found
1230   */

1231  public String JavaDoc getEnclosingTopLevelClassName(int pos) throws ClassNameNotFoundException {
1232    acquireReadLock();
1233    synchronized(_reduced) {
1234      int oldLocation = _currentLocation;
1235      try {
1236        setCurrentLocation(pos);
1237        IndentInfo info = getIndentInformation();
1238        
1239        // Find top level open brace
1240
int topLevelBracePos = -1;
1241        String JavaDoc braceType = info.braceTypeCurrent;
1242        while (!braceType.equals(IndentInfo.noBrace)) {
1243          if (braceType.equals(IndentInfo.openSquiggly)) {
1244            topLevelBracePos = _currentLocation - info.distToBraceCurrent;
1245          }
1246          move(-info.distToBraceCurrent);
1247          info = getIndentInformation();
1248          braceType = info.braceTypeCurrent;
1249        }
1250        if (topLevelBracePos == -1) {
1251          // No top level brace was found, so we can't find a top level class name
1252
setCurrentLocation(oldLocation);
1253          throw new ClassNameNotFoundException("no top level brace found");
1254        }
1255        
1256        char[] delims = {'{', '}', ';'};
1257        int prevDelimPos = findPrevDelimiter(topLevelBracePos, delims);
1258        if (prevDelimPos == ERROR_INDEX) {
1259          // Search from start of doc
1260
prevDelimPos = DOCSTART;
1261        }
1262        else prevDelimPos++;
1263        setCurrentLocation(oldLocation);
1264        
1265        // Parse out the class name
1266
return getNextTopLevelClassName(prevDelimPos, topLevelBracePos);
1267      }
1268      catch (BadLocationException ble) { throw new UnexpectedException(ble); }
1269      finally {
1270        setCurrentLocation(oldLocation);
1271        releaseReadLock();
1272      }
1273    }
1274  }
1275  
1276  /** Gets the name of first class/interface decclared in file among the definitions anchored at:
1277   * @param indexOfClass index in this of a top-level occurrence of class
1278   * @param indexOfInterface index in this of a top-level occurrence of interface
1279   */

1280  private String JavaDoc getFirstClassName(int indexOfClass, int indexOfInterface) throws ClassNameNotFoundException {
1281    
1282    if ((indexOfClass == -1) && (indexOfInterface == -1)) throw ClassNameNotFoundException.DEFAULT;
1283    if ((indexOfInterface == -1) || (indexOfClass != -1 && indexOfClass < indexOfInterface))
1284          return getNextIdentifier(indexOfClass + "class".length());
1285    return getNextIdentifier(indexOfInterface + "interface".length());
1286  }
1287  
1288  /** Gets the name of the document's main class: the document's only public class/interface or
1289    * first top level class if document contains no public classes or interfaces. */

1290  public String JavaDoc getMainClassName() throws ClassNameNotFoundException {
1291    acquireReadLock();
1292    synchronized(_reduced) {
1293      final int oldLocation = _currentLocation;
1294      
1295      try {
1296        setCurrentLocation(0);
1297        final String JavaDoc text = getText();
1298        
1299        final int indexOfClass = _findKeywordAtToplevel("class", text, 0);
1300        final int indexOfInterface = _findKeywordAtToplevel("interface", text, 0);
1301        final int indexOfPublic = _findKeywordAtToplevel("public", text, 0);
1302        
1303        if (indexOfPublic == -1) return getFirstClassName(indexOfClass, indexOfInterface);
1304        
1305// _log.log("text =\n" + text);
1306
// _log.log("indexOfClass = " + indexOfClass + "; indexOfPublic = " + indexOfPublic);
1307

1308        // There is an explicit public declaration
1309
final int afterPublic = indexOfPublic + "public".length();
1310        final String JavaDoc subText = text.substring(afterPublic);
1311        setCurrentLocation(afterPublic);
1312// _log.log("After public text = '" + subText + "'");
1313
int indexOfPublicClass = _findKeywordAtToplevel("class", subText, afterPublic); // relative offset
1314
if (indexOfPublicClass != -1) indexOfPublicClass += afterPublic;
1315        int indexOfPublicInterface = _findKeywordAtToplevel("interface", subText, afterPublic); // relative offset
1316
if (indexOfPublicInterface != -1) indexOfPublicInterface += afterPublic;
1317// _log.log("indexOfPublicClass = " + indexOfPublicClass + " indexOfPublicInterface = " + indexOfPublicInterface);
1318

1319        return getFirstClassName(indexOfPublicClass, indexOfPublicInterface);
1320        
1321      }
1322      finally {
1323        setCurrentLocation(oldLocation);
1324        releaseReadLock();
1325      }
1326    }
1327  }
1328
1329  /** Gets the name of the top level class in this source file. This attempts to find the first declaration
1330   * of a class or interface.
1331   * @return The name of first class in the file
1332   * @throws ClassNameNotFoundException if no top level class found
1333   */

1334  public String JavaDoc getFirstTopLevelClassName() throws ClassNameNotFoundException {
1335    return getNextTopLevelClassName(0, getLength());
1336  }
1337
1338  // note: need to update this to work with pos
1339
public String JavaDoc getNextTopLevelClassName(int startPos, int endPos) 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 JavaDoc text = getText(startPos, textLength);
1349        
1350        int index;
1351        
1352        int indexOfClass = _findKeywordAtToplevel("class", text, startPos);
1353        int indexOfInterface = _findKeywordAtToplevel("interface", text, startPos);
1354        int indexOfEnum = _findKeywordAtToplevel("enum",text,startPos);
1355        
1356        //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
1357
//level interface, AND the same for top level enum, then the class is the first top level declaration
1358
if (indexOfClass > -1 && (indexOfInterface <= -1 || indexOfClass < indexOfInterface)
1359              && (indexOfEnum <= -1 || indexOfClass < indexOfEnum)) {
1360          index = indexOfClass + "class".length();
1361        }
1362        else if (indexOfInterface > -1 && (indexOfClass <= -1 || indexOfInterface < indexOfClass)
1363                  && (indexOfEnum <= -1 || indexOfInterface < indexOfEnum)) {
1364          index = indexOfInterface + "interface".length();
1365        }
1366        else if (indexOfEnum > -1 && (indexOfClass <= -1 || indexOfEnum < indexOfClass)
1367                   && (indexOfInterface <= -1 || indexOfEnum < indexOfInterface)) {
1368          index = indexOfEnum + "enum".length();
1369        }
1370        else {
1371          // no index was valid
1372
throw ClassNameNotFoundException.DEFAULT;
1373        }
1374        
1375        // we have a valid index
1376
return getNextIdentifier(startPos + index);
1377      }
1378      catch (BadLocationException ble) { throw new UnexpectedException(ble); }
1379      catch (IllegalStateException JavaDoc e) { throw new ClassNameNotFoundException("No top level class name found"); }
1380      finally {
1381        setCurrentLocation(oldLocation);
1382        releaseReadLock();
1383      }
1384    }
1385  }
1386  
1387  /** Finds the next identifier (following a non-whitespace character) in the document starting at start. Assumes that
1388    * read lock and _reduced lock are already held. */

1389  private String JavaDoc getNextIdentifier(final int startPos) throws ClassNameNotFoundException {
1390    
1391// _log.log("getNextIdentifer(" + startPos + ") called");
1392

1393// int index = 0;
1394
// int length = 0;
1395
// int endIndex = 0;
1396
// String text = "";
1397
// int i;
1398
try {
1399      // first find index of first non whitespace (from the index in document)
1400
int index = getFirstNonWSCharPos(startPos);
1401      if (index == -1) throw new IllegalStateException JavaDoc("No identifier found");
1402      
1403      String JavaDoc text = getText();
1404      int length = text.length();
1405      int endIndex = length; //just in case no whitespace at end of file
1406

1407// _log.log("In getNextIdentifer text = \n" + text);
1408
// _log.log("index = " + index + "; length = " + length);
1409

1410      //find index of next delimiter or whitespace
1411
char c;
1412      for (int i = index; i < length; i++) {
1413        c = text.charAt(i);
1414        if (! Character.isJavaIdentifierPart(c)) {
1415          endIndex = i;
1416          break;
1417        }
1418      }
1419// _log.log("endIndex = " + endIndex);
1420
return text.substring(index, endIndex);
1421    }
1422    catch(BadLocationException e) {
1423// System.err.println("text =\n" + text);
1424
// System.err.println("The document =\n" + getText());
1425
// System.err.println("startPos = " + startPos + "; length = " + length + "; index = " + index + "; endIndex = " + endIndex);
1426
throw new UnexpectedException(e);
1427    }
1428  }
1429
1430  /** Finds the first occurrence of the keyword within the text (located at textOffset in this documennt) that is not
1431    * enclosed within a brace or comment and is followed by whitespace.
1432    * @param keyword the keyword for which to search
1433    * @param text in which to search
1434    * @param textOffset Offset at which the text occurs in the document
1435    * @return index of the keyword in text, or -1 if the keyword is not found or not followed by whitespace
1436    */

1437  private int _findKeywordAtToplevel(String JavaDoc keyword, String JavaDoc text, int textOffset) {
1438    
1439    acquireReadLock();
1440    synchronized(_reduced) {
1441      int oldLocation = _currentLocation;
1442      int index = 0;
1443      try {
1444        while (true) {
1445          index = text.indexOf(keyword, index);
1446          if (index == -1) break; // not found
1447
else {
1448            // found a match, check quality
1449
setCurrentLocation(textOffset + index);
1450            
1451            // check that the keyword is not in a comment and is followed by whitespace
1452
ReducedToken rt = _reduced.currentToken();
1453            int indexPastKeyword = index + keyword.length();
1454            if (indexPastKeyword < text.length()) {
1455              if (rt.getState() == ReducedModelStates.FREE &&
1456                  Character.isWhitespace(text.charAt(indexPastKeyword))) {
1457                // found a match but may not be at top level
1458
if (! posNotInBlock(index)) index = -1; //in a paren phrase, gone too far
1459
break;
1460              }
1461              else index++; //move past so we can search again
1462
}
1463            else { // No space found past the keyword
1464
index = -1;
1465              break;
1466            }
1467          }
1468        }
1469        setCurrentLocation(oldLocation);
1470// _log.log("findKeyWord(" + keyword + ", ..., " + textOffset + ")");
1471
return index;
1472      }
1473      finally { releaseReadLock(); }
1474    }
1475  }
1476  
1477  /** Wrapper for Position objects to allow relinking to a new Document. */
1478  //TODO: move this class to OpenDefinitionsDocument interface
1479
public static class WrappedPosition implements Position {
1480    private Position _wrapped;
1481    public WrappedPosition(Position w) { setWrapped(w); }
1482    public void setWrapped(Position w) { _wrapped = w; }
1483    public int getOffset() { return _wrapped.getOffset(); }
1484  }
1485  
1486  /** Factory method for created WrappedPositions. Stores the created Position instance
1487   * so it can be linked to a different DefinitionsDocument later. */

1488  public Position createPosition(int offs) throws BadLocationException {
1489    WrappedPosition wp = new WrappedPosition(createUnwrappedPosition(offs));
1490    synchronized(_wrappedPosListLock) {
1491      if (_wrappedPosList == null) _wrappedPosList = new LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>>();
1492      _wrappedPosList.add(new WeakReference JavaDoc<WrappedPosition>(wp));
1493    }
1494    return wp;
1495  }
1496  
1497  /** Remove all positions that have been garbage-collected from the list of positions, then return a weakly-linked
1498    * hashmap with positions and their current offsets.
1499    * @return list of weak references to all positions that have been created and that have not been garbage-collected yet.
1500    */

1501  public WeakHashMap JavaDoc<WrappedPosition, Integer JavaDoc> getWrappedPositionOffsets() {
1502    LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>> newList = new LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>>();
1503    synchronized(_wrappedPosListLock) {
1504      if (_wrappedPosList == null) { _wrappedPosList = new LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>>(); }
1505      WeakHashMap JavaDoc<WrappedPosition, Integer JavaDoc> ret = new WeakHashMap JavaDoc<WrappedPosition, Integer JavaDoc>(_wrappedPosList.size());
1506      
1507      for (WeakReference JavaDoc<WrappedPosition> wr: _wrappedPosList) {
1508        if (wr.get() != null) {
1509          // hasn't been garbage-collected yet
1510
newList.add(wr);
1511          ret.put(wr.get(), wr.get().getOffset());
1512        }
1513      }
1514      _wrappedPosList.clear();
1515      _wrappedPosList = newList;
1516    return ret;
1517    }
1518  }
1519 
1520  /** Re-create the wrapped positions in the hashmap, update the wrapped position, and add them to the list.
1521    * @param whm weakly-linked hashmap of wrapped positions and their offsets
1522    */

1523  public void setWrappedPositionOffsets(WeakHashMap JavaDoc<WrappedPosition, Integer JavaDoc> whm) throws BadLocationException {
1524    synchronized(_wrappedPosListLock) {
1525      if (_wrappedPosList == null) { _wrappedPosList = new LinkedList JavaDoc<WeakReference JavaDoc<WrappedPosition>>(); }
1526      _wrappedPosList.clear();
1527      
1528      for(Map.Entry JavaDoc<WrappedPosition, Integer JavaDoc> entry: whm.entrySet()) {
1529        if (entry.getKey() != null) {
1530          // hasn't been garbage-collected yet
1531
WrappedPosition wp = entry.getKey();
1532          wp.setWrapped(createUnwrappedPosition(entry.getValue()));
1533          _wrappedPosList.add(new WeakReference JavaDoc<WrappedPosition>(wp));
1534        }
1535      }
1536    }
1537  }
1538  
1539  /** Appending any information for the reduced model from each undo command */
1540  private static class CommandUndoableEdit extends AbstractUndoableEdit {
1541    private final Runnable JavaDoc _undoCommand;
1542    private final Runnable JavaDoc _redoCommand;
1543
1544    public CommandUndoableEdit(final Runnable JavaDoc undoCommand, final Runnable JavaDoc redoCommand) {
1545      _undoCommand = undoCommand;
1546      _redoCommand = redoCommand;
1547    }
1548
1549    public void undo() throws CannotUndoException {
1550      super.undo();
1551      _undoCommand.run();
1552    }
1553
1554    public void redo() throws CannotRedoException {
1555      super.redo();
1556      _redoCommand.run();
1557    }
1558
1559    public boolean isSignificant() { return false; }
1560  }
1561
1562  /**
1563   * Getter method for CompoundUndoManager
1564   * @return _undoManager
1565   */

1566  public CompoundUndoManager getUndoManager() { return _undoManager; }
1567
1568  /** Resets the undo manager. */
1569  public void resetUndoManager() {
1570    _undoManager = new CompoundUndoManager(_notifier);
1571    _undoManager.setLimit(UNDO_LIMIT);
1572  }
1573
1574  /** Public accessor for the next undo action. */
1575  public UndoableEdit getNextUndo() { return _undoManager.getNextUndo(); }
1576
1577  /** Public accessor for the next undo action. */
1578  public UndoableEdit getNextRedo() { return _undoManager.getNextRedo(); }
1579
1580  /** Informs this document's undo manager that the document has been saved. */
1581  public void documentSaved() { _undoManager.documentSaved(); }
1582  
1583  protected int startCompoundEdit() { return _undoManager.startCompoundEdit(); }
1584  
1585  protected void endCompoundEdit(int key) {
1586    _undoManager.endCompoundEdit(key);
1587  }
1588  
1589  //This method added for FrenchKeyBoardFix
1590
protected void endLastCompoundEdit() { _undoManager.endLastCompoundEdit(); }
1591   
1592  protected void addUndoRedo(AbstractDocument.DefaultDocumentEvent chng, Runnable JavaDoc undoCommand, Runnable JavaDoc doCommand) {
1593    chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
1594  }
1595  
1596  
1597  /**
1598   * Is used to be able to call editToBeUndone and editToBeRedone since they
1599   * are protected methods in UndoManager
1600   */

1601  /*
1602  private class OurUndoManager extends UndoManager {
1603    private boolean _compoundEditState = false;
1604    private OurCompoundEdit _compoundEdit;
1605
1606    public void startCompoundEdit() {
1607      if (_compoundEditState) {
1608        throw new IllegalStateException("Cannot start a compound edit while making a compound edit");
1609      }
1610      _compoundEditState = true;
1611      _compoundEdit = new OurCompoundEdit();
1612    }
1613
1614    public void endCompoundEdit() {
1615      if (!_compoundEditState) {
1616        throw new IllegalStateException("Cannot end a compound edit while not making a compound edit");
1617      }
1618      _compoundEditState = false;
1619      _compoundEdit.end();
1620      super.addEdit(_compoundEdit);
1621    }
1622
1623    public UndoableEdit getNextUndo() {
1624      return editToBeUndone();
1625    }
1626
1627    public UndoableEdit getNextRedo() {
1628      return editToBeRedone();
1629    }
1630
1631    public boolean addEdit(UndoableEdit e) {
1632      if (_compoundEditState) {
1633        return _compoundEdit.addEdit(e);
1634      }
1635      else {
1636        return super.addEdit(e);
1637      }
1638    }
1639  }
1640
1641
1642  public java.util.Vector getEdits() {
1643     return _undoManager._compoundEdit.getEdits();
1644  }
1645
1646  private class OurCompoundEdit extends CompoundEdit {
1647     public java.util.Vector getEdits() {
1648        return edits;
1649     }
1650  }
1651  */

1652  
1653  /**
1654   * used to help track down memory leaks
1655   */

1656// protected void finalize() throws Throwable{
1657
// System.out.println("destroying DefDocument for " + _odd);
1658
// super.finalize();
1659
// }
1660
//
1661
// private List<Pair<Option, OptionListener>> _optionListeners = new LinkedList<Option, OptionListener>>();
1662
//
1663
// public void clearOptionListeners() {
1664
// for (Pair<Option, OptionListener> l: _optionListeners) {
1665
// DrJava.getConfig().removeOptionListener( l.getFirst(), l.getSecond());
1666
// }
1667
// _optionListeners.clear();
1668
// }
1669
//
1670
// public void addOptionListener(Option op, OptionListener l) {
1671
// DrJava.getConfig().addOptionListener(op, l);
1672
// _optionListeners.add(new Pair<Option, OptionListener>(op, l));
1673
// }
1674

1675  /** This list of listeners to notify when we are finalized. */
1676  private List JavaDoc<FinalizationListener<DefinitionsDocument>> _finalizationListeners =
1677    new LinkedList JavaDoc<FinalizationListener<DefinitionsDocument>>();
1678  
1679  /**
1680   * Registers a finalization listener with the specific instance of the ddoc
1681   * <p><b>NOTE:</b><i>This should only be used by test cases. This is to ensure that
1682   * we don't spring memory leaks by allowing our unit tests to keep track of
1683   * whether objects are being finalized (garbage collected)</i></p>
1684   * @param fl the listener to register
1685   */

1686  public void addFinalizationListener(FinalizationListener<DefinitionsDocument> fl) {
1687    synchronized(_finalizationListeners) { _finalizationListeners.add(fl); }
1688  }
1689  
1690  public List JavaDoc<FinalizationListener<DefinitionsDocument>> getFinalizationListeners() {
1691    return _finalizationListeners;
1692  }
1693
1694  /** This is called when this method is GC'd. Since this class implements
1695   * edu.rice.cs.drjava.model.Finalizable, it must notify its listeners
1696   */

1697  protected void finalize() {
1698    FinalizationEvent<DefinitionsDocument> fe = new FinalizationEvent<DefinitionsDocument>(this);
1699    synchronized(_finalizationListeners) {
1700      for (FinalizationListener<DefinitionsDocument> fl: _finalizationListeners) {
1701        fl.finalized(fe);
1702      }
1703    }
1704  }
1705  
1706  public String JavaDoc toString() { return "ddoc for " + _odd; }
1707}
1708
Popular Tags