KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > drjava > model > AbstractDJDocument


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;
35
36 import edu.rice.cs.drjava.DrJava;
37 import edu.rice.cs.drjava.config.OptionConstants;
38 import edu.rice.cs.drjava.config.OptionEvent;
39 import edu.rice.cs.drjava.config.OptionListener;
40
41 import edu.rice.cs.drjava.model.definitions.DefinitionsDocument;
42 import edu.rice.cs.drjava.model.definitions.indent.Indenter;
43 import edu.rice.cs.drjava.model.definitions.reducedmodel.BraceReduction;
44 import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl;
45 import edu.rice.cs.drjava.model.definitions.reducedmodel.HighlightStatus;
46 import edu.rice.cs.drjava.model.definitions.reducedmodel.IndentInfo;
47 import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState;
48 import edu.rice.cs.drjava.model.definitions.ClassNameNotFoundException;
49
50
51 import edu.rice.cs.util.OperationCanceledException;
52 import edu.rice.cs.util.UnexpectedException;
53 import edu.rice.cs.util.swing.Utilities;
54 import edu.rice.cs.util.text.SwingDocument;
55
56 import java.util.HashSet JavaDoc;
57 import java.util.Hashtable JavaDoc;
58 import java.util.StringTokenizer JavaDoc;
59 import java.util.Vector JavaDoc;
60 import javax.swing.ProgressMonitor JavaDoc;
61 import javax.swing.text.AbstractDocument JavaDoc;
62 import javax.swing.text.AttributeSet JavaDoc;
63 import javax.swing.text.BadLocationException JavaDoc;
64 import javax.swing.text.Position JavaDoc;
65
66 /** Class containing code shared between the DefinitionsDocument and the InteractionsDJDocument. */
67 public abstract class AbstractDJDocument extends SwingDocument implements DJDocument, OptionConstants {
68   
69   /*-------- FIELDS ----------*/
70   
71   /** A set of normal endings for lines. */
72   protected static final HashSet JavaDoc<String JavaDoc> _normEndings = _makeNormEndings();
73   /** A set of Java keywords. */
74   protected static final HashSet JavaDoc<String JavaDoc> _keywords = _makeKeywords();
75   /** A set of Java keywords. */
76   protected static final HashSet JavaDoc<String JavaDoc> _primTypes = _makePrimTypes();
77   /** The default indent setting. */
78   protected volatile int _indent = 2;
79   
80   /** The reduced model of the document (stored in field _reduced) handles most of the document logic and keeps
81    * track of state. This field together with _currentLocation function as a virtual object for purposes of
82    * synchronization. All operations that access or modify this virtual object should be synchronized on _reduced.
83    */

84   public final BraceReduction _reduced = new ReducedModelControl(); // public only for locking purposes
85

86   /** The absolute character offset in the document. */
87   protected volatile int _currentLocation = 0;
88   
89   /* The fields _helperCache, _helperCacheHistory, and _cacheInUse function as a virtual object that is synchronized
90    * on operations that access or modify any of these fields. The _helperCache object serves as the lock.
91    */

92   
93   /** Caches calls to the reduced model to speed up indent performance. Must be cleared every time
94    * the document is changed. Use by calling _checkCache and _storeInCache.
95    */

96   private final Hashtable JavaDoc<String JavaDoc, Object JavaDoc> _helperCache = new Hashtable JavaDoc<String JavaDoc, Object JavaDoc>();
97   
98   /** Keeps track of the order of elements added to the helper method cache, so that the oldest elements can be
99    * removed when the maximum size is reached. A true LRU cache might be more effective, though I'm not sure
100    * what the exact pattern of helper method reuse is-- this should be sufficient without significantly decreasing
101    * the effectiveness of the cache.
102    */

103   private final Vector JavaDoc<String JavaDoc> _helperCacheHistory = new Vector JavaDoc<String JavaDoc>();
104   
105   /** Whether anything is stored in the cache. It is used to avoid clearing the table
106    * unnecessarily on every change to the document.
107    */

108   protected volatile boolean _cacheInUse;
109   
110   /** Maximum number of elements to allow in the helper method cache. Only encountered when indenting
111    * very large blocks, since the cache is cleared after each change to the document.
112    */

113   private static final int MAX_CACHE_SIZE = 10000;
114   
115   /** Constant for starting position of document. */
116   public static final int DOCSTART = 0;
117   
118   /** Constant used by helper methods to indicate an error. */
119   public static final int ERROR_INDEX = -1;
120   
121   /** The instance of the indent decision tree used by Definitions documents. */
122   private volatile Indenter _indenter;
123   
124   /* Saved here to allow the listener to be removed easily. This is needed to allow for garbage collection. */
125   private volatile OptionListener<Integer JavaDoc> _listener1;
126   private volatile OptionListener<Boolean JavaDoc> _listener2;
127   
128   /*-------- CONSTRUCTORS --------*/
129   
130   /** Standard default constructor; required because a unary constructor is defined. */
131   protected AbstractDJDocument() {
132 // int ind = DrJava.getConfig().getSetting(INDENT_LEVEL).intValue();
133
// _indenter = makeNewIndenter(ind); //new Indenter(ind);
134
// _initNewIndenter();
135
}
136   
137   /** Constructor used to build a new document with an existing indenter. Only used in tests. */
138   protected AbstractDJDocument(Indenter indent) { _indenter = indent; }
139   
140   //-------- METHODS ---------//
141

142   /* acquireReadLock, releaseReadLock, acquireWriteLock, releaseWriteLock are inherited from SwingDocument. */
143   
144   /** Returns a new indenter. Assumes writeLock is held. */
145   protected abstract Indenter makeNewIndenter(int indentLevel);
146   
147   /** Get the indenter. Assumes writeLock is already held.
148     * @return the indenter
149     */

150   private Indenter getIndenter() {
151     if (_indenter == null) {
152       int ind = DrJava.getConfig().getSetting(INDENT_LEVEL).intValue();
153       _indenter = makeNewIndenter(ind); //new Indenter(ind);
154
_initNewIndenter();
155     }
156     return _indenter;
157   }
158   
159   /** Get the indent level.
160     * @return the indent level
161     */

162   public int getIndent() { return _indent; }
163   
164   /** Set the indent to a particular number of spaces.
165     * @param indent the size of indent that you want for the document
166     */

167   public void setIndent(final int indent) {
168     DrJava.getConfig().setSetting(INDENT_LEVEL, indent);
169     this._indent = indent;
170   }
171   
172   protected void _removeIndenter() {
173     DrJava.getConfig().removeOptionListener(INDENT_LEVEL, _listener1);
174     DrJava.getConfig().removeOptionListener(AUTO_CLOSE_COMMENTS, _listener2);
175   }
176   
177   /** Only called from within getIndenter(). */
178   private void _initNewIndenter() {
179     // Create the indenter from the config values
180

181     final Indenter indenter = _indenter;
182     
183     _listener1 = new OptionListener<Integer JavaDoc>() {
184       public void optionChanged(OptionEvent<Integer JavaDoc> oce) {
185         indenter.buildTree(oce.value.intValue());
186       }
187     };
188     
189     _listener2 = new OptionListener<Boolean JavaDoc>() {
190       public void optionChanged(OptionEvent<Boolean JavaDoc> oce) {
191         indenter.buildTree(DrJava.getConfig().getSetting(INDENT_LEVEL).intValue());
192       }
193     };
194     
195     DrJava.getConfig().addOptionListener(INDENT_LEVEL, _listener1);
196     DrJava.getConfig().addOptionListener(AUTO_CLOSE_COMMENTS, _listener2);
197   }
198   
199   
200   /** Create a set of normal endings, i.e., semi-colons and braces for the purposes of indenting.
201    * @return the set of normal endings
202    */

203   protected static HashSet JavaDoc<String JavaDoc> _makeNormEndings() {
204     HashSet JavaDoc<String JavaDoc> normEndings = new HashSet JavaDoc<String JavaDoc>();
205     normEndings.add(";");
206     normEndings.add("{");
207     normEndings.add("}");
208     normEndings.add("(");
209     return normEndings;
210   }
211   
212   /** Create a set of Java/GJ keywords for special coloring.
213     * @return the set of keywords
214     */

215   protected static HashSet JavaDoc<String JavaDoc> _makeKeywords() {
216     final String JavaDoc[] words = {
217       "import", "native", "package", "goto", "const", "if", "else", "switch", "while", "for", "do", "true", "false",
218       "null", "this", "super", "new", "instanceof", "return", "static", "synchronized", "transient", "volatile",
219       "final", "strictfp", "throw", "try", "catch", "finally", "throws", "extends", "implements", "interface", "class",
220       "break", "continue", "public", "protected", "private", "abstract", "case", "default", "assert", "enum"
221     };
222     HashSet JavaDoc<String JavaDoc> keywords = new HashSet JavaDoc<String JavaDoc>();
223     for (int i = 0; i < words.length; i++) { keywords.add(words[i]); }
224     return keywords;
225   }
226   
227   /** Create a set of Java/GJ primitive types for special coloring.
228     * @return the set of primitive types
229     */

230   protected static HashSet JavaDoc<String JavaDoc> _makePrimTypes() {
231     final String JavaDoc[] words = {
232       "boolean", "char", "byte", "short", "int", "long", "float", "double", "void",
233     };
234     HashSet JavaDoc<String JavaDoc> prims = new HashSet JavaDoc<String JavaDoc>();
235     for (String JavaDoc w: words) { prims.add(w); }
236     return prims;
237   }
238     
239   /** Return all highlight status info for text between start and end. This should collapse adjoining blocks
240     * with the same status into one.
241    */

242   public Vector JavaDoc<HighlightStatus> getHighlightStatus(int start, int end) {
243     
244     if (start == end) return new Vector JavaDoc<HighlightStatus>(0);
245     Vector JavaDoc<HighlightStatus> v;
246
247     acquireReadLock();
248     try {
249       synchronized(_reduced) {
250         setCurrentLocation(start);
251         /* Now ask reduced model for highlight status for chars till end */
252         v = _reduced.getHighlightStatus(start, end - start);
253         
254         /* Go through and find any NORMAL blocks. Within them check for keywords. */
255         for (int i = 0; i < v.size(); i++) {
256           HighlightStatus stat = v.get(i);
257           if (stat.getState() == HighlightStatus.NORMAL) i = _highlightKeywords(v, i);
258         }
259       }
260     }
261     finally { releaseReadLock(); }
262     
263     // bstoler: Previously we moved back to the old location. This was
264
// very bad and severly slowed down rendering when scrolling.
265
// This is because parts are rendered in order. Thus, if old location is
266
// 0, but now we've scrolled to display 100000-100100, if we keep
267
// jumping back to 0 after getting every bit of highlight, it slows
268
// stuff down incredibly.
269
//setCurrentLocation(oldLocation);
270
return v;
271   }
272   
273   /** Distinguishes keywords from normal text in the given HighlightStatus element. Specifically, it looks to see
274     * if the given text contains a keyword. If it does, it splits the HighlightStatus into separate blocks
275     * so that each keyword has its own block. This process identifies all keywords in the given block.
276     * Note that the given block must have state NORMAL. Assumes that readLock is ALREADY HELD.
277     *
278     * @param v Vector with highlight info
279     * @param i Index of the single HighlightStatus to check for keywords in
280     * @return the index into the vector of the last processed element
281     */

282   private int _highlightKeywords(Vector JavaDoc<HighlightStatus> v, int i) {
283     // Basically all non-alphanumeric chars are delimiters
284
final String JavaDoc delimiters = " \t\n\r{}()[].+-/*;:=!@#$%^&*~<>?,\"`'<>|";
285     final HighlightStatus original = v.get(i);
286     final String JavaDoc text;
287     
288     try { text = getText(original.getLocation(), original.getLength()); }
289     catch (BadLocationException JavaDoc e) { throw new UnexpectedException(e); }
290     
291     // Because this text is not quoted or commented, we can use the simpler tokenizer StringTokenizer. We have
292
// to return delimiters as tokens so we can keep track of positions in the original string.
293
StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(text, delimiters, true);
294     
295     // start and length of the text that has not yet been put back into the vector.
296
int start = original.getLocation();
297     int length = 0;
298     
299     // Remove the old element from the vector.
300
v.remove(i);
301     
302     // Index where we are in the vector. It's the location we would insert new things into.
303
int index = i;
304     
305     boolean process;
306     int state = 0;
307     while (tokenizer.hasMoreTokens()) {
308       String JavaDoc token = tokenizer.nextToken();
309       
310       //first check to see if we need highlighting
311
process = false;
312       if (_isType(token)) {
313         //right now keywords incl prim types, so must put this first
314
state = HighlightStatus.TYPE;
315         process = true;
316       }
317       else if (_keywords.contains(token)) {
318         state = HighlightStatus.KEYWORD;
319         process = true;
320       }
321       else if (_isNum(token)) {
322         state = HighlightStatus.NUMBER;
323         process = true;
324       }
325       
326       if (process) {
327         // first check if we had any text before the token
328
if (length != 0) {
329           HighlightStatus newStat = new HighlightStatus(start, length, original.getState());
330           v.add(index, newStat);
331           index++;
332           start += length;
333           length = 0;
334         }
335         
336         // Now pull off the keyword
337
int keywordLength = token.length();
338         v.add(index, new HighlightStatus(start, keywordLength, state));
339         index++;
340         // Move start to the end of the keyword
341
start += keywordLength;
342       }
343       else {
344         // This is not a keyword, so just keep accumulating length
345
length += token.length();
346       }
347     }
348     // Now check if there was any text left after the keywords.
349
if (length != 0) {
350       HighlightStatus newStat = new HighlightStatus(start, length, original.getState());
351       v.add(index, newStat);
352       index++;
353       length = 0;
354     }
355     // return one before because we need to point to the last one we inserted
356
return index - 1;
357   }
358   
359   
360   /** Checks to see if the current string is a number
361    * @return true if x is a parseable number
362    */

363   private boolean _isNum(String JavaDoc x) {
364     try {
365       Double.parseDouble(x);
366       return true;
367     }
368     catch (NumberFormatException JavaDoc e) { return false; }
369   }
370   
371   /** Checks to see if the current string is a type. A type is assumed to be a primitive type OR
372    * anything else that begins with a capitalized character
373    */

374   private boolean _isType(String JavaDoc x) {
375     if (_primTypes.contains(x)) return true;
376     
377     try { return Character.isUpperCase(x.charAt(0)); }
378     catch (IndexOutOfBoundsException JavaDoc e) { return false; }
379   }
380   
381   /** Returns whether the given text only has spaces. */
382   private boolean _hasOnlySpaces(String JavaDoc text) { return (text.trim().length() == 0); }
383   
384   /** Fire event that styles changed from current location to the end.
385    * Right now we do this every time there is an insertion or removal.
386    * Two possible future optimizations:
387    * <ol>
388    * <li>Only fire changed event if text other than that which was inserted
389    * or removed *actually* changed status. If we didn't changed the status
390    * of other text (by inserting or deleting unmatched pair of quote or
391    * comment chars), no change need be fired.
392    * <li>If a change must be fired, we could figure out the exact end
393    * of what has been changed. Right now we fire the event saying that
394    * everything changed to the end of the document.
395    * </ol>
396    *
397    * I don't think we'll need to do either one since it's still fast now.
398    * I think this is because the UI only actually paints the things on the screen anyway.
399    */

400   protected abstract void _styleChanged();
401   
402   /** Clears the memoizing cache for read operations on the document. This
403     * operation must be done before the document is modified since the contents
404     * of this cache are invalidated by any modification to the document.
405     */

406   protected void clearCache() {
407      synchronized(_helperCache) { if (_cacheInUse) _clearCache(); }
408   }
409   
410   /** Clears the helper method cache. Should be called every time the document is modified. */
411   private void _clearCache() {
412     _helperCache.clear();
413     _helperCacheHistory.clear();
414     _cacheInUse = false;
415   }
416       
417   /** Add a character to the underlying reduced model. ONLY called from _reduced synchronized code!
418    * @param curChar the character to be added. */

419   private void _addCharToReducedModel(char curChar) {
420     clearCache();
421    _reduced.insertChar(curChar);
422   }
423   
424   /** Get the current location of the cursor in the document. Unlike the usual swing document model,
425    * which is stateless, because of our implementation of the underlying reduced model, we need to
426    * keep track of the current location.
427    * @return where the cursor is as the number of characters into the document
428    */

429   public int getCurrentLocation() { return _currentLocation; }
430
431   /** Change the current location of the document
432    * @param loc the new absolute location
433    */

434   public void setCurrentLocation(int loc) {
435     acquireReadLock();
436     try {
437       synchronized(_reduced) {
438         int dist = loc - _currentLocation; // _currentLocation and _reduced can be updated asynchronously
439
_currentLocation = loc;
440         _reduced.move(dist);
441       }
442     }
443     finally { releaseReadLock(); }
444   }
445   
446   
447   /** The actual cursor movement logic. Helper for setCurrentLocation(int).
448    * @param dist the distance from the current location to the new location.
449    */

450   public void move(int dist) {
451     acquireReadLock();
452     try {
453       synchronized(_reduced) {
454         int newLoc = _currentLocation + dist;
455 // // location is set asynchronously when caret is moved so the following adjustment is necessary
456
// // should no longer be true
457
// if (newLoc < 0) {
458
// assert false; // should never get here
459
// dist -= newLoc; // increase dist by error in newLoc
460
// newLoc = 0;
461
// }
462
// else {
463
// int len = getLength();
464
// if (newLoc > len) {
465
// assert false; // should never get here
466
// dist -= (newLoc - len); // decrease dist by error in newLoc
467
// newLoc = len;
468
// }
469
// }
470
_currentLocation = newLoc;
471         _reduced.move(dist);
472       }
473     }
474     finally { releaseReadLock(); }
475   }
476   
477   /** Forwarding method to find the match for the closing brace immediately to the left, assuming
478    * there is such a brace.
479    * @return the relative distance backwards to the offset before the matching brace.
480    */

481   public int balanceBackward() {
482     acquireReadLock();
483     try {
484       synchronized(_reduced) { return _reduced.balanceBackward(); }
485     }
486     finally { releaseReadLock(); }
487   }
488   
489   /** Forwarding method to find the match for the open brace immediately to the right, assuming there
490     * is such a brace.
491     * @return the relative distance forwards to the offset after the matching brace.
492     */

493   public int balanceForward() {
494     acquireReadLock();
495     try { synchronized(_reduced) { return _reduced.balanceForward(); } }
496     finally { releaseReadLock(); }
497   }
498   
499   /** This method is used ONLY for testing. This method is UNSAFE in any other context!
500     * @return The reduced model of this document.
501     */

502   public BraceReduction getReduced() { return _reduced; }
503   
504   /** Returns the indent information for the current location. */
505   public IndentInfo getIndentInformation() {
506     // Check cache
507
String JavaDoc key = "getIndentInformation:" + _currentLocation;
508
509     IndentInfo cached = (IndentInfo) _checkCache(key);
510     if (cached != null) return cached;
511     
512     IndentInfo info;
513     acquireReadLock();
514     try {
515       synchronized(_reduced) { info = _reduced.getIndentInformation(); }
516       _storeInCache(key, info);
517     }
518     finally { releaseReadLock(); }
519
520     return info;
521   }
522   
523   public ReducedModelState stateAtRelLocation(int dist) {
524     acquireReadLock();
525     try { synchronized(_reduced) { return _reduced.moveWalkerGetState(dist); } }
526     finally { releaseReadLock(); }
527   }
528   
529   public ReducedModelState getStateAtCurrent() {
530     acquireReadLock();
531     try { synchronized(_reduced) { return _reduced.getStateAtCurrent(); } }
532     finally { releaseReadLock(); }
533   }
534   
535   public void resetReducedModelLocation() {
536     acquireReadLock();
537     try { synchronized(_reduced) { _reduced.resetLocation(); } }
538     finally { releaseReadLock(); }
539   }
540   
541   /** Searching backwards, finds the position of the enclosing brace. NB: ignores comments.
542     * @param pos Position to start from
543     * @param opening opening brace character
544     * @param closing closing brace character
545     * @return position of enclosing squiggly brace, or ERROR_INDEX if beginning
546     * of document is reached.
547     */

548   public int findPrevEnclosingBrace(int pos, char opening, char closing) throws BadLocationException JavaDoc {
549     // Check cache
550
final StringBuilder JavaDoc keyBuf =
551       new StringBuilder JavaDoc("findPrevEnclosingBrace:").append(opening).append(':').append(closing).append(':').append(pos);
552     final String JavaDoc key = keyBuf.toString();
553     final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
554     if (cached != null) return cached.intValue();
555     
556     if ((pos >= getLength()) || (pos == DOCSTART)) { return ERROR_INDEX; }
557     
558     final char[] delims = {opening, closing};
559     int reducedPos = pos;
560     int i; // index of for loop below
561
int braceBalance = 0;
562     
563     acquireReadLock();
564     try {
565       String JavaDoc text = getText(DOCSTART, pos);
566       
567       synchronized(_reduced) {
568         final int origLocation = _currentLocation;
569         // Move reduced model to location pos
570
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
571

572         // Walk backwards from specificed position
573
for (i = pos-1; i >= DOCSTART; i--) {
574           /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims,
575            * DOCSTART <= i < reducedPos <= pos */

576           
577           if (match(text.charAt(i),delims)) {
578             // Move reduced model to walker's location
579
_reduced.move(i - reducedPos); // reduced model points to i
580
reducedPos = i; // reduced model points to reducedPos
581

582             // Check if matching char should be ignored because it is within a comment,
583
// quotes, or ignored paren phrase
584
ReducedModelState state = _reduced.getStateAtCurrent();
585             if (!state.equals(ReducedModelState.FREE) || _isStartOfComment(text, i)
586                   || ((i > 0) && _isStartOfComment(text, i - 1)))
587               continue; // ignore matching char
588
else {
589               // found valid matching char
590
if (text.charAt(i)==closing) ++braceBalance;
591               else {
592                 if (braceBalance==0) break; // found our opening brace
593
--braceBalance;
594               }
595             }
596           }
597         }
598         
599         /* Invariant: same as for loop except that DOCSTART-1 <= i <= reducedPos <= pos */
600         
601         _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
602
} // end synchronized
603

604       if (i == DOCSTART-1) reducedPos = ERROR_INDEX; // No matching char was found
605
_storeInCache(key, reducedPos);
606     }
607     finally { releaseReadLock(); }
608     
609     // Return position of matching char or ERROR_INDEX
610
return reducedPos;
611   }
612     
613   /** Searching forward, finds the position of the enclosing squiggly brace. NB: ignores comments.
614    * @param pos Position to start from
615    * @param opening opening brace character
616    * @param closing closing brace character
617    * @return position of enclosing squiggly brace, or ERROR_INDEX if beginning of document is reached.
618    */

619   public int findNextEnclosingBrace(int pos, char opening, char closing) throws BadLocationException JavaDoc {
620     // Check cache
621
final StringBuilder JavaDoc keyBuf =
622       new StringBuilder JavaDoc("findNextEnclosingBrace:").append(opening).append(':').append(closing).append(':').append(pos);
623     final String JavaDoc key = keyBuf.toString();
624     final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
625     
626     if (cached != null) return cached.intValue();
627     if (pos>=getLength()-1) { return ERROR_INDEX; }
628     
629     final char[] delims = {opening, closing};
630     int reducedPos = pos;
631     int i; // index of for loop below
632
int braceBalance = 0;
633     
634     acquireReadLock();
635     String JavaDoc text = getText();
636     try {
637       synchronized(_reduced) {
638         final int origLocation = _currentLocation;
639         // Move reduced model to location pos
640
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
641

642         // Walk forward from specificed position
643
for (i = pos+1; i < text.length(); i++) {
644           /* Invariant: reduced model points to reducedPos, text[pos:i-1] contains no valid delims,
645            * pos <= reducedPos < i <= text.length() */

646           
647           if (match(text.charAt(i),delims)) {
648             // Move reduced model to walker's location
649
_reduced.move(i - reducedPos); // reduced model points to i
650
reducedPos = i; // reduced model points to reducedPos
651

652             // Check if matching char should be ignored because it is within a comment,
653
// quotes, or ignored paren phrase
654
ReducedModelState state = _reduced.getStateAtCurrent();
655             if (!state.equals(ReducedModelState.FREE) || _isStartOfComment(text, i)
656                   || ((i > 0) && _isStartOfComment(text, i - 1)))
657               continue; // ignore matching char
658
else {
659               // found valid matching char
660
if (text.charAt(i)==opening) {
661                 ++braceBalance;
662               }
663               else {
664                 if (braceBalance==0) break; // found our closing brace
665
--braceBalance;
666               }
667             }
668           }
669         }
670         
671         /* Invariant: same as for loop except that pos <= reducedPos <= i <= text.length() */
672         
673         _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
674
} // end synchronized
675

676       if (i == text.length()) reducedPos = ERROR_INDEX; // No matching char was found
677
_storeInCache(key, reducedPos);
678     }
679     finally { releaseReadLock(); }
680     
681     // Return position of matching char or ERROR_INDEX
682
return reducedPos;
683   }
684
685   /** Searching backwards, finds the position of the first character that is one of the given delimiters. Does
686    * not look for delimiters inside paren phrases (e.g., skips semicolons used inside for statements.)
687    * NB: ignores comments.
688    * @param pos Position to start from
689    * @param delims array of characters to search for
690    * @return position of first matching delimiter, or ERROR_INDEX if beginning of document is reached.
691    */

692   public int findPrevDelimiter(int pos, char[] delims) throws BadLocationException JavaDoc {
693     return findPrevDelimiter(pos, delims, true);
694   }
695   
696   /** Searching backwards, finds the position of the first character that is one of the given delimiters.
697    * Will not look for delimiters inside a paren phrase if skipParenPhrases is true. NB: ignores comments.
698    * @param pos Position to start from
699    * @param delims array of characters to search for
700    * @param skipParenPhrases whether to look for delimiters inside paren phrases
701    * @return position of first matching delimiter, or ERROR_INDEX if beginning of document is reached.
702    */

703   public int findPrevDelimiter(final int pos, final char[] delims, final boolean skipParenPhrases)
704     throws BadLocationException JavaDoc {
705     // Check cache
706
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("findPrevDelimiter:").append(pos);
707     for (char ch: delims) { keyBuf.append(':').append(ch); }
708     keyBuf.append(':').append(skipParenPhrases);
709     final String JavaDoc key = keyBuf.toString();
710     final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
711     if (cached != null) return cached.intValue();
712     
713     int reducedPos = pos;
714     int i; // index of for loop below
715
acquireReadLock();
716     try {
717       String JavaDoc text = getText(DOCSTART, pos);
718       
719       synchronized(_reduced) {
720         final int origLocation = _currentLocation;
721         // Move reduced model to location pos
722
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
723

724         // Walk backwards from specificed position
725
for (i = pos-1; i >= DOCSTART; i--) {
726           /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims,
727            * DOCSTART <= i < reducedPos <= pos */

728           
729           if (match(text.charAt(i),delims)) {
730             // Move reduced model to walker's location
731
_reduced.move(i - reducedPos); // reduced model points to i
732
reducedPos = i; // reduced model points to reducedPos
733

734             // Check if matching char should be ignored because it is within a comment, quotes, or ignored paren phrase
735
ReducedModelState state = _reduced.getStateAtCurrent();
736             if (! state.equals(ReducedModelState.FREE) || _isStartOfComment(text, i)
737                   || ((i > 0) && _isStartOfComment(text, i - 1)) || (skipParenPhrases && posInParenPhrase()))
738               continue; // ignore matching char
739
else break; // found valid matching char
740
}
741         }
742         
743         /* Invariant: same as for loop except that DOCSTART-1 <= i <= reducedPos <= pos */
744         
745         _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
746
} // end synchronized
747

748       if (i == DOCSTART-1) reducedPos = ERROR_INDEX; // No matching char was found
749
_storeInCache(key, reducedPos);
750     }
751     finally { releaseReadLock(); }
752     
753     // Return position of matching char or ERROR_INDEX
754
return reducedPos;
755   }
756   
757   private static boolean match(char c, char[] delims) {
758     for (char d : delims) { if (c == d) return true; } // Found matching delimiter
759
return false;
760   }
761         
762   /** This function finds the given character in the same statement as the given position, and before the given
763    * position. It is used by QuestionExistsCharInStmt and QuestionExistsCharInPrevStmt
764    */

765   public boolean findCharInStmtBeforePos(char findChar, int position) {
766     if (position == DefinitionsDocument.ERROR_INDEX) {
767       String JavaDoc mesg =
768         "Argument endChar to QuestionExistsCharInStmt must be a char that exists on the current line.";
769       throw new UnexpectedException(new IllegalArgumentException JavaDoc(mesg));
770     }
771     
772     char[] findCharDelims = {findChar, ';', '{', '}'};
773     int prevFindChar;
774     
775     // Find the position of the preceding occurrence findChar position (looking in paren phrases as well)
776
boolean found;
777     
778     acquireReadLock();
779     try {
780       prevFindChar = this.findPrevDelimiter(position, findCharDelims, false);
781       
782       if ((prevFindChar == DefinitionsDocument.ERROR_INDEX) || (prevFindChar < 0)) return false; // no such char
783

784       // Determine if prevFindChar is findChar or the end of statement delimiter
785
String JavaDoc foundString = this.getText(prevFindChar, 1);
786       char foundChar = foundString.charAt(0);
787       found = (foundChar == findChar);
788     }
789     catch (Throwable JavaDoc t) { throw new UnexpectedException(t); }
790     finally { releaseReadLock(); }
791     return found;
792   }
793   
794   /** Finds the position of the first non-whitespace, non-comment character before pos.
795    * Skips comments and all whitespace, including newlines.
796    * @param pos Position to start from
797    * @param whitespace chars considered as white space
798    * @return position of first non-whitespace character before pos OR ERROR_INDEX if no such char
799    */

800   public int findPrevCharPos(int pos, char[] whitespace) throws BadLocationException JavaDoc {
801     // Check cache
802
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("findPrevCharPos:").append(pos);
803     for (char ch: whitespace) { keyBuf.append( ':').append(ch); }
804     final String JavaDoc key = keyBuf.toString();
805     final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
806     if (cached != null) return cached.intValue();
807     
808     int reducedPos = pos;
809     int i = pos - 1;
810     String JavaDoc text;
811     acquireReadLock();
812     try {
813       text = getText(0, pos);
814       
815       synchronized(_reduced) {
816         
817         final int origLocation = _currentLocation;
818         // Move reduced model to location pos
819
_reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
820

821         // Walk backward from specified position
822

823         while (i >= 0) {
824           /* Invariant: reduced model points to reducedPos, i < reducedPos <= pos,
825            * text[i+1:pos-1] contains invalid chars */

826           
827           if (match(text.charAt(i), whitespace)) {
828             // ith char is whitespace
829
i--;
830             continue;
831           }
832           
833           // Found a non-whitespace char; move reduced model to location i
834
_reduced.move(i - reducedPos);
835           reducedPos = i; // reduced model points to i == reducedPos
836

837           // Check if matching char is within a comment (not including opening two characters)
838
if ((_reduced.getStateAtCurrent().equals(ReducedModelState.INSIDE_LINE_COMMENT)) ||
839               (_reduced.getStateAtCurrent().equals(ReducedModelState.INSIDE_BLOCK_COMMENT))) {
840             i--;
841             continue;
842           }
843           
844           if (_isReversteStartOfComment(text, i)) { /* char is second character in opening comment marker */
845             // Move i past the first comment character and continue searching
846
i = i - 2;
847             continue;
848           }
849           
850           // Found valid previous character
851
break;
852         }
853         
854         /* Exit invariant same as for loop except that i <= reducedPos because at break i = reducedPos */
855         _reduced.move(origLocation - reducedPos);
856       }
857       
858       int result = reducedPos;
859       if (i < 0) result = ERROR_INDEX;
860       _storeInCache(key, result);
861       return result;
862     }
863     finally { releaseReadLock(); }
864
865   }
866   
867   /** Checks the helper method cache for a stored value. Returns the value if it has been cached, or null
868    * otherwise. Calling convention for keys: methodName:arg1:arg2
869    * @param key Name of the method and arguments
870    */

871   protected Object JavaDoc _checkCache(String JavaDoc key) {
872     //_helperCache.put(key+"|time", new Long(System.currentTimeMillis()));
873
Object JavaDoc result = _helperCache.get(key); /* already synchronized by Hashtable */
874     //if (result != null) DrJava.consoleOut().println("Using cache for " + key);
875
return result;
876   }
877   
878   /** Stores the given result in the helper method cache. Calling convention for keys: methodName:arg1:arg2
879    * @param key Name of method and arguments
880    * @param result Result of the method call
881    */

882   protected void _storeInCache(String JavaDoc key, Object JavaDoc result) {
883     synchronized(_helperCache) {
884       _cacheInUse = true;
885       
886       // Prevent going over max size
887
if (_helperCache.size() >= MAX_CACHE_SIZE) {
888         if (_helperCacheHistory.size() > 0) {
889           _helperCache.remove( _helperCacheHistory.get(0) );
890           _helperCacheHistory.remove(0);
891         }
892         else { // Should not happen
893
throw new RuntimeException JavaDoc("Cache larger than cache history!");
894         }
895       }
896       Object JavaDoc prev = _helperCache.put(key, result);
897       // Add to history if the insert increased the size of the table
898
if (prev == null) _helperCacheHistory.add(key);
899     }
900   }
901   
902   /** Default indentation - uses OTHER flag and no progress indicator.
903    * @param selStart the offset of the initial character of the region to indent
904    * @param selEnd the offset of the last character of the region to indent
905    */

906   public void indentLines(int selStart, int selEnd) {
907     try { indentLines(selStart, selEnd, Indenter.OTHER, null); }
908     catch (OperationCanceledException oce) {
909       // Indenting without a ProgressMonitor should never be cancelled!
910
throw new UnexpectedException(oce);
911     }
912   }
913   
914   
915   /** Parameterized indentation for special-case handling. If selStart == selEnd, then the line containing the
916    * _currentLocation is indented. The values of selStart and selEnd are ignored!
917    * @param selStart the offset of the initial character of the region to indent
918    * @param selEnd the offset of the last character of the region to indent
919    * @param reason a flag from {@link Indenter} to indicate the reason for the indent
920    * (indent logic may vary slightly based on the trigger action)
921    * @param pm used to display progress, null if no reporting is desired
922    */

923   public void indentLines(int selStart, int selEnd, int reason, ProgressMonitor JavaDoc pm)
924     throws OperationCanceledException {
925     
926     // Begins a compound edit.
927
// int key = startCompoundEdit(); // commented out in connection with the FrenchKeyBoard Fix
928

929     acquireWriteLock();
930     try {
931       synchronized(_reduced) {
932         if (selStart == selEnd) { // single line to indent
933
// Utilities.showDebug("selStart = " + selStart + " currentLocation = " + _currentLocation);
934
Position JavaDoc oldCurrentPosition = createUnwrappedPosition(_currentLocation);
935           
936           // Indent, updating current location if necessary.
937
// Utilities.showDebug("Indenting line at offset " + selStart);
938
if (_indentLine(reason)) {
939             setCurrentLocation(oldCurrentPosition.getOffset());
940             if (onlyWhiteSpaceBeforeCurrent()) {
941               int space = getWhiteSpace();
942               move(space);
943             }
944           }
945         }
946         else _indentBlock(selStart, selEnd, reason, pm);
947       }
948     }
949     catch (Throwable JavaDoc t) { throw new UnexpectedException(t); }
950     finally { releaseWriteLock(); }
951     
952     // Ends the compound edit.
953
//endCompoundEdit(key); //Changed to endLastCompoundEdit in connection with the FrenchKeyBoard Fix
954
endLastCompoundEdit();
955   }
956   
957   /** Indents the lines between and including the lines containing points start and end. Assumes that writeLock
958    * and _reduced locks are already held.
959    * @param start Position in document to start indenting from
960    * @param end Position in document to end indenting at
961    * @param reason a flag from {@link Indenter} to indicate the reason for the indent
962    * (indent logic may vary slightly based on the trigger action)
963    * @param pm used to display progress, null if no reporting is desired
964    */

965   private void _indentBlock(final int start, final int end, int reason, ProgressMonitor JavaDoc pm)
966     throws OperationCanceledException, BadLocationException JavaDoc {
967     
968     // Keep marker at the end. This Position will be the correct endpoint no matter how we change
969
// the doc doing the indentLine calls.
970
final Position JavaDoc endPos = this.createUnwrappedPosition(end);
971     // Iterate, line by line, until we get to/past the end
972
int walker = start;
973     while (walker < endPos.getOffset()) {
974       setCurrentLocation(walker);
975       // Keep pointer to walker position that will stay current
976
// regardless of how indentLine changes things
977
Position JavaDoc walkerPos = this.createUnwrappedPosition(walker);
978       // Indent current line
979
// We ignore current location info from each line, because it probably doesn't make sense in a block context.
980
_indentLine(reason); // this operation is atomic
981
// Move back to walker spot
982
setCurrentLocation(walkerPos.getOffset());
983       walker = walkerPos.getOffset();
984       
985       if (pm != null) {
986         pm.setProgress(walker); // Update ProgressMonitor.
987
if (pm.isCanceled()) throw new OperationCanceledException(); // Check for cancel button-press.
988
}
989       
990       // Adding 1 makes us point to the first character AFTER the next newline. We don't actually move the
991
// location yet. That happens at the top of the loop, after we check if we're past the end.
992
walker += _reduced.getDistToNextNewline() + 1;
993     }
994   }
995   
996   /** Indents a line using the Indenter. Public ONLY for testing purposes. Assumes writeLock is already held.*/
997   public boolean _indentLine(int reason) { return getIndenter().indent(this, reason); }
998   
999   /** Returns the "intelligent" beginning of line. If currPos is to the right of the first
1000   * non-whitespace character, the position of the first non-whitespace character is returned.
1001   * If currPos is at or to the left of the first non-whitespace character, the beginning of
1002   * the line is returned.
1003   * @param currPos A position on the current line
1004   */

1005  public int getIntelligentBeginLinePos(int currPos) throws BadLocationException JavaDoc {
1006    String JavaDoc prefix;
1007    int firstChar;
1008    acquireReadLock();
1009    try {
1010      firstChar = getLineStartPos(currPos);
1011      prefix = getText(firstChar, currPos-firstChar);
1012    }
1013    finally { releaseReadLock(); }
1014    
1015    // Walk through string until we find a non-whitespace character
1016
int i;
1017    int len = prefix.length();
1018   
1019    for (i = 0; i < len; i++ ) { if (! Character.isWhitespace(prefix.charAt(i))) break; }
1020
1021    // If we found a non-WS char left of curr pos, return it
1022
if (i < len) {
1023      int firstRealChar = firstChar + i;
1024      if (firstRealChar < currPos) return firstRealChar;
1025    }
1026    // Otherwise, return the beginning of the line
1027
return firstChar;
1028  }
1029  
1030  /** Returns the indent level of the start of the statement that the cursor is on. Uses a default
1031   * set of delimiters. (';', '{', '}') and a default set of whitespace characters (' ', '\t', n', ',')
1032   * @param pos Cursor position
1033   */

1034  public String JavaDoc getIndentOfCurrStmt(int pos) throws BadLocationException JavaDoc {
1035    char[] delims = {';', '{', '}'};
1036    char[] whitespace = {' ', '\t', '\n', ','};
1037    return getIndentOfCurrStmt(pos, delims, whitespace);
1038  }
1039  
1040  /** Returns the indent level of the start of the statement that the cursor is on. Uses a default
1041   * set of whitespace characters: {' ', '\t', '\n', ','}
1042   * @param pos Cursor position
1043   */

1044  public String JavaDoc getIndentOfCurrStmt(int pos, char[] delims) throws BadLocationException JavaDoc {
1045    char[] whitespace = {' ', '\t', '\n',','};
1046    return getIndentOfCurrStmt(pos, delims, whitespace);
1047  }
1048  
1049  /** Returns the indent level of the start of the statement that the cursor is on.
1050   * @param pos Cursor position
1051   * @param delims Delimiter characters denoting end of statement
1052   * @param whitespace characters to skip when looking for beginning of next statement
1053   */

1054  public String JavaDoc getIndentOfCurrStmt(int pos, char[] delims, char[] whitespace) throws BadLocationException JavaDoc {
1055    // Check cache
1056
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("getIndentOfCurrStmt:").append(pos);
1057    for (char ch: delims) { keyBuf.append(':').append(ch); }
1058    final String JavaDoc key = keyBuf.toString();
1059    final String JavaDoc cached = (String JavaDoc) _checkCache(key);
1060    if (cached != null) return cached;
1061    
1062    String JavaDoc lineText;
1063    
1064    acquireReadLock();
1065    try {
1066      synchronized(_reduced) {
1067        // Get the start of the current line
1068
int lineStart = getLineStartPos(pos);
1069        
1070        // Find the previous delimiter that closes a statement
1071
boolean reachedStart = false;
1072        int prevDelim = lineStart;
1073        boolean ignoreParens = posInParenPhrase(prevDelim);
1074        
1075        do {
1076          prevDelim = findPrevDelimiter(prevDelim, delims, ignoreParens);
1077          if (prevDelim > 0 && prevDelim < getLength() && getText(prevDelim,1).charAt(0) == '{') break;
1078          if (prevDelim == ERROR_INDEX) { // no delimiter found
1079
reachedStart = true;
1080            break;
1081          }
1082          ignoreParens = posInParenPhrase(prevDelim);
1083        } while (ignoreParens);
1084    
1085        // From the previous delimiter, find the next non-whitespace character
1086
int nextNonWSChar;
1087        if (reachedStart) nextNonWSChar = getFirstNonWSCharPos(DOCSTART);
1088        else nextNonWSChar = getFirstNonWSCharPos(prevDelim+1, whitespace, false);
1089        
1090        // If the end of the document was reached
1091
if (nextNonWSChar == ERROR_INDEX) nextNonWSChar = getLength();
1092        
1093        // Get the start of the line of the non-ws char
1094
int lineStartStmt = getLineStartPos(nextNonWSChar);
1095        
1096        // Get the position of the first non-ws character on this line
1097
int lineFirstNonWS = getLineFirstCharPos(lineStartStmt);
1098        lineText = getText(lineStartStmt, lineFirstNonWS - lineStartStmt);
1099        _storeInCache(key, lineText);
1100      }
1101    }
1102    catch(Throwable JavaDoc t) { throw new UnexpectedException(t); }
1103    finally { releaseReadLock(); }
1104    
1105    return lineText;
1106  }
1107  
1108  /** Determines if the given character exists on the line where the given cursor position is. Does not
1109   * search in quotes or comments. <b>Does not work if character being searched for is a '/' or a '*'</b>.
1110   * @param pos Cursor position
1111   * @param findChar Character to search for
1112   * @return true if this node's rule holds.
1113   */

1114  public int findCharOnLine(int pos, char findChar) {
1115    // Check cache
1116
String JavaDoc key = "findCharOnLine:" + pos + ":" + findChar;
1117    Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1118    if (cached != null) return cached.intValue();
1119    
1120    int i;
1121    int matchIndex; // absolute index of matching character
1122

1123    acquireReadLock();
1124    try {
1125      synchronized(_reduced) {
1126        int here = _currentLocation;
1127        int lineStart = getLineStartPos(pos);
1128        int lineEnd = getLineEndPos(pos);
1129        String JavaDoc lineText = getText(lineStart, lineEnd - lineStart);
1130        i = lineText.indexOf(findChar, 0);
1131        matchIndex = i + lineStart;
1132        
1133        while (i != -1) { // match found
1134
/* Invariant: reduced model points to original location (here), lineText[0:i-1] does not contain valid
1135           * findChar, lineText[i] == findChar which may or may not be valid. */

1136          
1137          // Move reduced model to location of ith char
1138
_reduced.move(matchIndex - here); // move reduced model to location matchIndex
1139

1140          // Check if matching char is in comment or quotes
1141
if (_reduced.getStateAtCurrent().equals(ReducedModelState.FREE)) {
1142            // Found matching char
1143
_reduced.move(here - matchIndex); // Restore reduced model
1144
break;
1145          }
1146          
1147          // matching character is not valid, try again
1148
_reduced.move(here - matchIndex); // Restore reduced model
1149
i = lineText.indexOf(findChar, i+1);
1150        }
1151      }
1152      
1153      if (i == -1) matchIndex = ERROR_INDEX;
1154      _storeInCache(key, matchIndex);
1155    }
1156    catch (Throwable JavaDoc t) { throw new UnexpectedException(t); }
1157    finally { releaseReadLock(); }
1158    
1159    return matchIndex;
1160  }
1161  
1162  /** Returns the absolute position of the beginning of the current line. (Just after most recent newline, or DOCSTART)
1163   * Doesn't ignore comments.
1164   * @param pos Any position on the current line
1165   * @return position of the beginning of this line
1166   */

1167  public int getLineStartPos(final int pos) {
1168    if (pos < 0 || pos > getLength()) return -1;
1169    // Check cache
1170
String JavaDoc key = "getLineStartPos:" + pos;
1171    Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1172    if (cached != null) return cached.intValue();
1173    
1174    int dist;
1175    acquireReadLock();
1176    try {
1177      synchronized(_reduced) {
1178        int location = _currentLocation;
1179        _reduced.move(pos - location);
1180        dist = _reduced.getDistToPreviousNewline(0);
1181        _reduced.move(location - pos);
1182      }
1183      
1184      if (dist == -1) {
1185        // No previous newline was found; return DOCSTART
1186
_storeInCache(key, DOCSTART);
1187        return DOCSTART;
1188      }
1189      _storeInCache(key, pos - dist);
1190    }
1191    finally { releaseReadLock(); }
1192
1193    return pos - dist;
1194  }
1195  
1196  /** Returns the absolute position of the end of the current line. (At the next newline, or the end of the document.)
1197   * @param pos Any position on the current line
1198   * @return position of the end of this line
1199   */

1200  public int getLineEndPos(final int pos) {
1201    if (pos < 0 || pos > getLength()) return -1;
1202    
1203    // Check cache
1204
String JavaDoc key = "getLineEndPos:" + pos;
1205    Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1206    if (cached != null) return cached.intValue();
1207    
1208    int dist;
1209    acquireReadLock();
1210    try {
1211      synchronized(_reduced) {
1212        int location = _currentLocation;
1213        _reduced.move(pos - location);
1214        dist = _reduced.getDistToNextNewline();
1215        _reduced.move(location - pos);
1216      }
1217      _storeInCache(key, pos + dist);
1218    }
1219    finally { releaseReadLock(); }
1220   
1221    return pos + dist;
1222  }
1223  
1224  /** Returns the absolute position of the first non-whitespace character on the current line.
1225   * NB: Doesn't ignore comments.
1226   * @param pos position on the line
1227   * @return position of first non-whitespace character on this line, or the end
1228   * of the line if no non-whitespace character is found.
1229   */

1230  public int getLineFirstCharPos(int pos) throws BadLocationException JavaDoc {
1231    // Check cache
1232
String JavaDoc key = "getLineFirstCharPos:" + pos;
1233    Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1234    if (cached != null) return cached.intValue();
1235    
1236    acquireReadLock();
1237    try {
1238      int startLinePos = getLineStartPos(pos);
1239      int endLinePos = getLineEndPos(pos);
1240      
1241      // Get all text on this line
1242
String JavaDoc text = this.getText(startLinePos, endLinePos - startLinePos);
1243      int walker = 0;
1244      while (walker < text.length()) {
1245        if (text.charAt(walker) == ' ' || text.charAt(walker) == '\t') walker++;
1246        else {
1247          _storeInCache(key, startLinePos + walker);
1248          return startLinePos + walker;
1249        }
1250      }
1251      // No non-WS char found, so return last position on line
1252
_storeInCache(key, endLinePos);
1253      return endLinePos;
1254    }
1255    finally { releaseReadLock(); }
1256  }
1257  
1258  /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace,
1259    * including newlines
1260    * @param pos Position to start from
1261    * @return position of first non-whitespace character after pos, or ERROR_INDEX if end of document is reached
1262    */

1263  public int getFirstNonWSCharPos(int pos) throws BadLocationException JavaDoc {
1264    char[] whitespace = {' ', '\t', '\n'};
1265    return getFirstNonWSCharPos(pos, whitespace, false);
1266  }
1267  
1268  /** Similar to the single-argument version, but allows including comments.
1269    * @param pos Position to start from
1270    * @param acceptComments if true, find non-whitespace chars in comments
1271    * @return position of first non-whitespace character after pos,
1272    * or ERROR_INDEX if end of document is reached
1273    */

1274  public int getFirstNonWSCharPos(int pos, boolean acceptComments) throws BadLocationException JavaDoc {
1275    char[] whitespace = {' ', '\t', '\n'};
1276    return getFirstNonWSCharPos(pos, whitespace, acceptComments);
1277  }
1278  
1279  /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace,
1280   * including newlines.
1281   * @param pos Position to start from
1282   * @param whitespace array of whitespace chars to ignore
1283   * @param acceptComments if true, find non-whitespace chars in comments
1284   * @return position of first non-whitespace character after pos, or ERROR_INDEX if end of document is reached
1285   */

1286  public int getFirstNonWSCharPos(int pos, char[] whitespace, boolean acceptComments) throws BadLocationException JavaDoc {
1287    // Check cache
1288
final StringBuilder JavaDoc keyBuf = new StringBuilder JavaDoc("getFirstNonWSCharPos:").append(pos);
1289    for (char ch: whitespace) { keyBuf.append(':').append(ch); }
1290    final String JavaDoc key = keyBuf.toString();
1291    
1292    final Integer JavaDoc cached = (Integer JavaDoc) _checkCache(key);
1293    if (cached != null) return cached.intValue();
1294    
1295    int result = ERROR_INDEX; // variable used to hold result to be returned
1296

1297    acquireReadLock();
1298    try {
1299      
1300      int i = pos;
1301      int endPos = getLength();
1302      
1303      // Get text from pos to end of document
1304
String JavaDoc text = getText(pos, endPos - pos);
1305
1306      final int origLocation = _currentLocation;
1307      // Move reduced model to location pos
1308
synchronized(_reduced) {
1309        _reduced.move(pos - origLocation);
1310        int reducedPos = pos;
1311        
1312        //int iter = 0;
1313

1314        // Walk forward from specificed position
1315
while (i < endPos) {
1316          
1317          // Check if character is whitespace
1318
if (match(text.charAt(i-pos), whitespace)) {
1319            i++;
1320            continue;
1321          }
1322          // Found a non whitespace character
1323
// Move reduced model to walker's location
1324
_reduced.move(i - reducedPos); // reduced model points to location i
1325
reducedPos = i; // reduced mdoel points to location reducedPos
1326

1327          // Check if non-ws char is within comment and if we want to ignore them.
1328
if (! acceptComments &&
1329              ((_reduced.getStateAtCurrent().equals(ReducedModelState.INSIDE_LINE_COMMENT)) ||
1330               (_reduced.getStateAtCurrent().equals(ReducedModelState.INSIDE_BLOCK_COMMENT)))) {
1331            i++;
1332            continue;
1333          }
1334          
1335          // Check if non-ws char is part of comment opening market and if we want to ignore them
1336
if (! acceptComments && _isStartOfComment(text, i - pos)) {
1337            // ith char is first char in comment open market; skip past this marker
1338
// and continue searching
1339
i = i + 2;
1340            continue;
1341          }
1342      
1343          // Return position of matching char
1344
break;
1345        }
1346        _reduced.move(origLocation - reducedPos);
1347        
1348        result = reducedPos;
1349        if (i == endPos) result = ERROR_INDEX;
1350      }
1351      _storeInCache(key, result);
1352    }
1353    finally { releaseReadLock(); }
1354
1355    return result;
1356  }
1357  
1358  public int findPrevNonWSCharPos(int pos) throws BadLocationException JavaDoc {
1359    char[] whitespace = {' ', '\t', '\n'};
1360    return findPrevCharPos(pos, whitespace);
1361  }
1362  
1363  /** Helper method for getFirstNonWSCharPos Determines whether the current character is the start of a comment:
1364   * "/*" or "//"
1365   */

1366  protected static boolean _isStartOfComment(String JavaDoc text, int pos) {
1367    char currChar = text.charAt(pos);
1368    if (currChar == '/') {
1369      try {
1370        char afterCurrChar = text.charAt(pos + 1);
1371        if ((afterCurrChar == '/') || (afterCurrChar == '*')) return true;
1372      } catch (StringIndexOutOfBoundsException JavaDoc e) { }
1373    }
1374    return false;
1375  }
1376
1377  /** Helper method for findPrevNonWSCharPos. Determines whether the current character is the start of a comment
1378   * encountered from the end: '/' or '*' preceded by a '/'.
1379   * @return true if (pos-1,pos) == '/*' or '//'
1380   */

1381  protected static boolean _isReversteStartOfComment(String JavaDoc text, int pos) {
1382    char currChar = text.charAt(pos);
1383    if ((currChar == '/')||(currChar == '*')) {
1384      try {
1385        char beforeCurrChar = text.charAt(pos - 1);
1386        if (beforeCurrChar == '/') return true;
1387      } catch (StringIndexOutOfBoundsException JavaDoc e) { /* do nothing */ }
1388    }
1389    return false;
1390  }
1391  
1392  
1393  /** Returns true if the given position is inside a paren phrase.
1394   * @param pos the position we're looking at
1395   * @return true if pos is immediately inside parentheses
1396   */

1397  public boolean posInParenPhrase(int pos) {
1398    // Check cache
1399
String JavaDoc key = "posInParenPhrase:" + pos;
1400    Boolean JavaDoc cached = (Boolean JavaDoc) _checkCache(key);
1401    if (cached != null) return cached.booleanValue();
1402
1403    boolean inParenPhrase;
1404    
1405    acquireReadLock();
1406    try {
1407      synchronized(_reduced) {
1408        int here = _currentLocation;
1409        _reduced.move(pos - here);
1410        inParenPhrase = posInParenPhrase();
1411        _reduced.move(here - pos);
1412      }
1413      _storeInCache(key, Boolean.valueOf(inParenPhrase));
1414    }
1415    finally { releaseReadLock(); }
1416
1417    return inParenPhrase;
1418  }
1419
1420  /**
1421   * Returns true if the reduced model's current position is inside a paren phrase.
1422   * @return true if pos is immediately inside parentheses
1423   */

1424  public boolean posInParenPhrase() {
1425    IndentInfo info;
1426    acquireReadLock();
1427    try { synchronized(_reduced) { info = _reduced.getIndentInformation(); } }
1428    finally { releaseReadLock(); }
1429    return info.braceTypeCurrent.equals(IndentInfo.openParen);
1430  }
1431
1432  /** Returns true if the given position is not inside a paren/brace/etc phrase. Assumes that read lock is ALREADY HELD.
1433   * @param pos the position we're looking at
1434   * @return true if pos is immediately inside a paren/brace/etc
1435   */

1436  protected boolean posNotInBlock(int pos) {
1437    // Check cache
1438
String JavaDoc key = "posNotInBlock:" + pos;
1439    Boolean JavaDoc cached = (Boolean JavaDoc) _checkCache(key);
1440    if (cached != null) return cached.booleanValue();
1441    
1442    boolean notInParenPhrase;
1443    
1444    synchronized(_reduced) {
1445      int here = _currentLocation;
1446      _reduced.move(pos - here);
1447      IndentInfo info = _reduced.getIndentInformation();
1448      notInParenPhrase = info.braceTypeCurrent.equals(IndentInfo.noBrace);
1449      _reduced.move(here - pos);
1450    }
1451    _storeInCache(key, Boolean.valueOf(notInParenPhrase));
1452    return notInParenPhrase;
1453  }
1454  
1455  
1456  /** Gets the number of whitespace characters between the current location and the end of
1457   * the document or the first non-whitespace character, whichever comes first.
1458   * @return the number of whitespace characters
1459   */

1460  public int getWhiteSpace() {
1461    try { return getWhiteSpaceBetween(0, getLength() - _currentLocation); }
1462    catch (BadLocationException JavaDoc e) { e.printStackTrace(); }
1463    return -1;
1464  }
1465
1466  /** Starts at start and gets whitespace starting at relStart and either stopping at relEnd or at the first
1467   * non-white space char.
1468   * NOTE: relStart and relEnd are relative to where we are in the document relStart must be <= _currentLocation
1469   * @exception BadLocationException
1470   */

1471  private int getWhiteSpaceBetween(int relStart, int relEnd) throws BadLocationException JavaDoc {
1472    String JavaDoc text = this.getText(_currentLocation - relStart, Math.abs(relStart - relEnd));
1473    int i = 0;
1474    int length = text.length();
1475    while ((i < length) && (text.charAt(i) == ' '))
1476      i++;
1477    return i;
1478  }
1479  
1480  /** Returns true if the current line has only white space before the current location. Serves as a check so that
1481   * indentation will only move the caret when it is at or before the "smart" beginning of a line (i.e. the first
1482   * non-whitespace character
1483   * @return true if there are only whitespace characters before the current location on the current line.
1484   */

1485  private boolean onlyWhiteSpaceBeforeCurrent() throws BadLocationException JavaDoc{
1486    String JavaDoc text = this.getText(0, _currentLocation);
1487    //get the text after the previous new line, but before the current location
1488
text = text.substring(text.lastIndexOf("\n")+1);
1489    
1490    //check all positions in the new text to determine if there are any non-whitespace chars
1491
int index = text.length()-1;
1492    char lastChar = ' ';
1493    while(lastChar == ' ' && index >= 0){
1494      lastChar = text.charAt(index);
1495      index--;
1496    }
1497    
1498    if (index < 0) return true;
1499    return false;
1500  }
1501      
1502                          
1503  
1504  /** Sets text between previous newline and first non-whitespace character of line containing pos to tab.
1505   * @param tab String to be placed between previous newline and first
1506   * non-whitespace character
1507   */

1508  public void setTab(String JavaDoc tab, int pos) {
1509    try {
1510      int startPos = getLineStartPos(pos);
1511      int firstNonWSPos = getLineFirstCharPos(pos);
1512      int len = firstNonWSPos - startPos;
1513
1514      // Adjust prefix
1515
boolean onlySpaces = _hasOnlySpaces(tab);
1516      if (!onlySpaces || (len != tab.length())) {
1517
1518        if (onlySpaces) {
1519          // Only add or remove the difference
1520
int diff = tab.length() - len;
1521          if (diff > 0) {
1522            insertString(firstNonWSPos, tab.substring(0, diff), null);
1523          }
1524          else {
1525            remove(firstNonWSPos + diff, -diff);
1526          }
1527        }
1528        else {
1529          // Remove the whole prefix, then add the new one
1530
remove(startPos, len);
1531          insertString(startPos, tab, null);
1532        }
1533      }
1534    }
1535    catch (BadLocationException JavaDoc e) {
1536      // Should never see a bad location
1537
throw new UnexpectedException(e);
1538    }
1539  }
1540  
1541  /** Updates document structure as a result of text insertion. This happens after the text has actually been inserted.
1542   * Here we update the reduced model (using an {@link AbstractDJDocument.InsertCommand InsertCommand}) and store
1543   * information for how to undo/redo the reduced model changes inside the {@link
1544   * javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
1545   *
1546   * @see edu.rice.cs.drjava.model.AbstractDJDocument.InsertCommand
1547   * @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
1548   * @see edu.rice.cs.drjava.model.definitions.DefinitionsDocument.CommandUndoableEdit
1549   */

1550  protected void insertUpdate(AbstractDocument.DefaultDocumentEvent JavaDoc chng, AttributeSet JavaDoc attr) {
1551    // Clear the helper method cache
1552
clearCache();
1553
1554    super.insertUpdate(chng, attr);
1555
1556    try {
1557      final int offset = chng.getOffset();
1558      final int length = chng.getLength();
1559      final String JavaDoc str = getText(offset, length);
1560
1561      InsertCommand doCommand = new InsertCommand(offset, str);
1562      RemoveCommand undoCommand = new RemoveCommand(offset, length);
1563
1564      // add the undo/redo
1565
addUndoRedo(chng,undoCommand,doCommand);
1566      //chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
1567
// actually do the insert
1568
doCommand.run();
1569    }
1570    catch (BadLocationException JavaDoc ble) {
1571      throw new UnexpectedException(ble);
1572    }
1573  }
1574  
1575  /** Updates document structure as a result of text removal. This happens within the swing remove operation before
1576   * the text has actually been removed. Here we update the reduced model (using a {@link AbstractDJDocument.RemoveCommand
1577   * RemoveCommand}) and store information for how to undo/redo the reduced model changes inside the
1578   * {@link javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
1579   * @see AbstractDJDocument.RemoveCommand
1580   * @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
1581   */

1582  protected void removeUpdate(AbstractDocument.DefaultDocumentEvent JavaDoc chng) {
1583    clearCache();
1584
1585    try {
1586      final int offset = chng.getOffset();
1587      final int length = chng.getLength();
1588      final String JavaDoc removedText = getText(offset, length);
1589      super.removeUpdate(chng);
1590
1591      Runnable JavaDoc doCommand = new RemoveCommand(offset, length);
1592      Runnable JavaDoc undoCommand = new InsertCommand(offset, removedText);
1593
1594      // add the undo/redo info
1595
addUndoRedo(chng,undoCommand,doCommand);
1596      // actually do the removal from the reduced model
1597
doCommand.run();
1598    }
1599    catch (BadLocationException JavaDoc e) { throw new UnexpectedException(e); }
1600  }
1601  
1602  
1603  /** Inserts a string of text into the document. Custom processing of the insert is not done here;
1604   * that is done in {@link #insertUpdate}.
1605   */

1606  public void insertString(int offset, String JavaDoc str, AttributeSet JavaDoc a) throws BadLocationException JavaDoc {
1607    
1608    acquireWriteLock();
1609    try {
1610      synchronized(_reduced) { // Prevent updates to the reduced model during this change
1611
clearCache(); // Clear the helper method cache
1612
super.insertString(offset, str, a);
1613      }
1614    }
1615    finally { releaseWriteLock(); }
1616  }
1617  
1618  /** Removes a block of text from the specified location. We don't update the reduced model here; that happens
1619   * in {@link #removeUpdate}.
1620   */

1621  public void remove(final int offset, final int len) throws BadLocationException JavaDoc {
1622    
1623    acquireWriteLock();
1624    try {
1625      synchronized(_reduced) {
1626        clearCache(); // Clear the helper method cache
1627
super.remove(offset, len);
1628      };
1629    }
1630    finally { releaseWriteLock(); }
1631  }
1632    
1633  public String JavaDoc getText() {
1634    acquireReadLock();
1635    try { return getText(0, getLength()); }
1636    catch(BadLocationException JavaDoc e) { throw new UnexpectedException(e); }
1637    finally { releaseReadLock(); }
1638  }
1639  
1640  /** Returns the byte image (as written to a file) of this document. */
1641  public byte[] getBytes() { return getText().getBytes(); }
1642  
1643  public void clear() {
1644    acquireWriteLock();
1645    try { remove(0, getLength()); }
1646    catch(BadLocationException JavaDoc e) { throw new UnexpectedException(e); }
1647    finally { releaseWriteLock(); }
1648  }
1649   
1650  //Two abstract methods to delegate to the undo manager, if one exists.
1651
protected abstract int startCompoundEdit();
1652  protected abstract void endCompoundEdit(int i);
1653  protected abstract void endLastCompoundEdit();
1654  protected abstract void addUndoRedo(AbstractDocument.DefaultDocumentEvent JavaDoc chng, Runnable JavaDoc undoCommand, Runnable JavaDoc doCommand);
1655  
1656  //Checks if the document is closed, and then throws an error if it is.
1657

1658  //-------- INNER CLASSES ------------
1659

1660  protected class InsertCommand implements Runnable JavaDoc {
1661    private final int _offset;
1662    private final String JavaDoc _text;
1663    
1664    public InsertCommand(final int offset, final String JavaDoc text) {
1665      _offset = offset;
1666      _text = text;
1667    }
1668    
1669    public void run() {
1670      // adjust location to the start of the text to input
1671
acquireReadLock();
1672      try {
1673        synchronized(_reduced) {
1674          _reduced.move(_offset - _currentLocation);
1675          int len = _text.length();
1676          // loop over string, inserting characters into reduced model
1677
for (int i = 0; i < len; i++) {
1678            char curChar = _text.charAt(i);
1679            _addCharToReducedModel(curChar);
1680          }
1681          _currentLocation = _offset + len; // current location is at end of inserted string
1682
_styleChanged();
1683        }
1684      }
1685      finally { releaseReadLock(); }
1686    }
1687  }
1688
1689  protected class RemoveCommand implements Runnable JavaDoc {
1690    private final int _offset;
1691    private final int _length;
1692    
1693    public RemoveCommand(final int offset, final int length) {
1694      _offset = offset;
1695      _length = length;
1696    }
1697    
1698    public void run() {
1699      acquireReadLock();
1700      try {
1701        synchronized(_reduced) {
1702          setCurrentLocation(_offset);
1703          _reduced.delete(_length);
1704          _styleChanged();
1705        }
1706      }
1707      finally { releaseReadLock(); }
1708    }
1709  }
1710}
1711
Popular Tags