Clover coverage report - DrJava Test Coverage (drjava-20110828-r5448)
Coverage timestamp: Sun Aug 28 2011 03:13:33 CDT
file stats: LOC: 1,902   Methods: 88
NCLOC: 957   Classes: 6
 
 Source file Conditionals Statements Methods TOTAL
AbstractDJDocument.java 70.3% 83.9% 90.9% 80.7%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model;
 38   
 39    import edu.rice.cs.drjava.DrJava;
 40    import edu.rice.cs.drjava.config.OptionConstants;
 41    import edu.rice.cs.drjava.config.OptionEvent;
 42    import edu.rice.cs.drjava.config.OptionListener;
 43   
 44    import edu.rice.cs.drjava.model.definitions.indent.Indenter;
 45    import edu.rice.cs.drjava.model.definitions.reducedmodel.BraceInfo;
 46    import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl;
 47    import edu.rice.cs.drjava.model.definitions.reducedmodel.HighlightStatus;
 48    import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState;
 49   
 50    import edu.rice.cs.util.OperationCanceledException;
 51    import edu.rice.cs.util.StringOps;
 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.awt.EventQueue;
 57    import java.util.ArrayList;
 58    import java.util.Set;
 59    import java.util.HashMap;
 60    import java.util.HashSet;
 61    import java.util.LinkedList;
 62    import java.util.List;
 63    import java.util.SortedMap;
 64    import java.util.StringTokenizer;
 65    import java.util.TreeMap;
 66    import javax.swing.ProgressMonitor;
 67    import javax.swing.text.AbstractDocument;
 68    import javax.swing.text.AttributeSet;
 69    import javax.swing.text.BadLocationException;
 70    import javax.swing.text.Position;
 71   
 72    import static edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelStates.*;
 73   
 74    /** This class contains code supporting the concept of a "DJDocument"; it is shared between DefinitionsDocument and
 75    * InteractionsDJDocument. This partial implementation of <code>Document</code> contains a "reduced model". The reduced
 76    * model is automatically kept in sync when this document is updated. Also, that synchronization is maintained even
 77    * across undo/redo -- this is done by making the undo/redo commands know how to restore the reduced model state.
 78    *
 79    * The reduced model is not thread-safe, so it is essential that its methods are only called from the event thread. In
 80    * addition, any information from the reduced model should be obtained through helper methods in this class/subclasses.
 81    *
 82    * @see edu.rice.cs.drjava.model.definitions.reducedmodel.BraceReduction
 83    * @see edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl
 84    * @see edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelComment
 85    * @see edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelBrace
 86    */
 87    public abstract class AbstractDJDocument extends SwingDocument implements DJDocument, OptionConstants {
 88   
 89    /*-------- CONSTANTS ----------*/
 90   
 91    protected static final String delimiters = " \t\n\r{}()[].+-/*;:=!@#$%^&*~<>?,\"`'<>|";
 92    protected static final char newline = '\n';
 93    /** A set of normal endings for lines. */
 94    protected static final HashSet<String> _normEndings = _makeNormEndings();
 95    /** A set of Java keywords. */
 96    protected final HashSet<String> _keywords = new HashSet<String>
 97    (edu.rice.cs.drjava.model.compiler.JavacCompiler.JAVA_KEYWORDS);
 98    /** A set of Java primitive types. */
 99    protected static final HashSet<String> _primTypes = _makePrimTypes();
 100    /** The default indent setting. */
 101    protected volatile int _indent = 2;
 102    /** Initial number of elements in _queryCache (see below). */
 103    private static final int INIT_CACHE_SIZE = 0x10000; // 16**4 = 16384
 104    /** Constant specifying how large pos must be before incremental analysis is applied in posInBlockComment */
 105    public static final int POS_THRESHOLD = 10000;
 106    // /** Constant specifying how large pos must be before incremental analysis is applied in posInParenPhrase */
 107    // public static final int POS_THRESHOLD = 10000;
 108    /** The set of closing braces recognized in most indenting operations. */
 109    public static final char[] CLOSING_BRACES = new char[] {'}', ')'};
 110   
 111    /*-------- FIELDS ----------*/
 112   
 113    // /** Whether a block indent operation is in progress on this document. */
 114    // private volatile boolean _indentInProgress = false;
 115   
 116    /** The reduced model of the document (stored in field _reduced) handles most of the document logic and keeps
 117    * track of state. This field together with _currentLocation function as a virtual object for purposes of
 118    * synchronization. All operations that access or modify this virtual object should be synchronized on _reduced.
 119    */
 120    public final ReducedModelControl _reduced = new ReducedModelControl(); // public only for locking purposes
 121   
 122    /** The absolute character offset in the document. Treated as part of the _reduced (model) for locking
 123    * purposes. */
 124    protected volatile int _currentLocation = 0;
 125   
 126    /* The fields _queryCache, _offsetToQueries, and _cacheModified function as an extension of the reduced model.
 127    * When enabled in blockIndent, this data structure caches calls to the reduced model to speed up indent performance.
 128    * Must be cleared every time the document is changed. Use by calling _checkCache, _storeInCache, and _clearCache.
 129    * When _queryCache = null, the cache is disabled.
 130    */
 131    private volatile HashMap<Query, Object> _queryCache;
 132   
 133    /** Records the set of queries (as a list) for each offset. */
 134    private volatile SortedMap<Integer, List<Query>> _offsetToQueries;
 135   
 136    /** The instance of the indent decision tree used by Definitions documents. */
 137    private volatile Indenter _indenter;
 138   
 139    /* Saved here to allow the listener to be removed easily. This is needed to allow for garbage collection. */
 140    private volatile OptionListener<Integer> _listener1;
 141    private volatile OptionListener<Boolean> _listener2;
 142   
 143    /*-------- CONSTRUCTORS --------*/
 144   
 145    /** Constructor used in super calls from DefinitionsDocument and InteractionsDJDocument. */
 146  759 protected AbstractDJDocument() {
 147  759 this(new Indenter(DrJava.getConfig().getSetting(INDENT_LEVEL).intValue()));
 148    }
 149   
 150    /** Constructor used from anonymous test classes. */
 151  143 protected AbstractDJDocument(int indentLevel) {
 152  143 this(new Indenter(indentLevel));
 153    }
 154   
 155    /** Constructor used to build a new document with an existing indenter. Used in tests and super calls from
 156    * DefinitionsDocument and interactions documents. */
 157  1351 protected AbstractDJDocument(Indenter indenter) {
 158  1351 _indenter = indenter;
 159  1351 _queryCache = null;
 160  1351 _offsetToQueries = null;
 161  1351 _initNewIndenter();
 162    // System.err.println("AbstractDJDocument constructor with indent level " + indenter.getIndentLevel()
 163    // + " invoked on " + this);
 164    }
 165   
 166    //-------- METHODS ---------//
 167   
 168    /** Get the indenter.
 169    * @return the indenter
 170    */
 171  236 private Indenter getIndenter() { return _indenter; }
 172   
 173    /** Get the indent level.
 174    * @return the indent level
 175    */
 176  0 public int getIndent() { return _indent; }
 177   
 178    /** Set the indent to a particular number of spaces.
 179    * @param indent the size of indent that you want for the document
 180    */
 181  11 public void setIndent(final int indent) {
 182  11 DrJava.getConfig().setSetting(INDENT_LEVEL, indent);
 183  11 this._indent = indent;
 184    }
 185   
 186  117 protected void _removeIndenter() {
 187    // System.err.println("REMOVE INDENTER called");
 188  117 DrJava.getConfig().removeOptionListener(INDENT_LEVEL, _listener1);
 189  117 DrJava.getConfig().removeOptionListener(AUTO_CLOSE_COMMENTS, _listener2);
 190    }
 191   
 192    /** Only called from within getIndenter(). */
 193  1351 private void _initNewIndenter() {
 194    // Create the indenter from the config values
 195   
 196  1351 final Indenter indenter = _indenter;
 197    // System.err.println("Installing Indent Option Listener for " + this);
 198  1351 _listener1 = new OptionListener<Integer>() {
 199  124 public void optionChanged(OptionEvent<Integer> oce) {
 200    // System.err.println("Changing INDENT_LEVEL for " + this + " to " + oce.value);
 201  124 indenter.buildTree(oce.value);
 202    }
 203    };
 204   
 205  1351 _listener2 = new OptionListener<Boolean>() {
 206  0 public void optionChanged(OptionEvent<Boolean> oce) {
 207    // System.err.println("Reconfiguring indenter to use AUTO_CLOSE_COMMENTS = " + oce.value);
 208  0 indenter.buildTree(DrJava.getConfig().getSetting(INDENT_LEVEL));
 209    }
 210    };
 211   
 212  1351 DrJava.getConfig().addOptionListener(INDENT_LEVEL, _listener1);
 213  1351 DrJava.getConfig().addOptionListener(AUTO_CLOSE_COMMENTS, _listener2);
 214    }
 215   
 216   
 217    /** Create a set of normal endings, i.e., semi-colons and braces for the purposes of indenting.
 218    * @return the set of normal endings
 219    */
 220  56 protected static HashSet<String> _makeNormEndings() {
 221  56 HashSet<String> normEndings = new HashSet<String>();
 222  56 normEndings.add(";");
 223  56 normEndings.add("{");
 224  56 normEndings.add("}");
 225  56 normEndings.add("(");
 226  56 return normEndings;
 227    }
 228   
 229    /** Set the specified keywords as keywords for syntax highlighting.
 230    * @param keywords keywords to highlight */
 231  361 public void setKeywords(Set<String> keywords) {
 232  361 _keywords.clear();
 233  361 _keywords.addAll(keywords);
 234    }
 235   
 236    /** Create a set of Java/GJ primitive types for special coloring.
 237    * @return the set of primitive types
 238    */
 239  56 protected static HashSet<String> _makePrimTypes() {
 240  56 final String[] words = {
 241    "boolean", "char", "byte", "short", "int", "long", "float", "double", "void",
 242    };
 243  56 HashSet<String> prims = new HashSet<String>();
 244  504 for (String w: words) { prims.add(w); }
 245  56 return prims;
 246    }
 247   
 248    // /** Computes the maximum of x and y. */
 249    // private int max(int x, int y) { return x <= y? y : x; }
 250   
 251    /** Return all highlight status info for text between start and end. This should collapse adjoining blocks with the
 252    * same status into one. ONLY runs in the event thread. Perturbs _currentLocation to improve performance.
 253    */
 254  0 public ArrayList<HighlightStatus> getHighlightStatus(int start, int end) {
 255   
 256  0 assert EventQueue.isDispatchThread();
 257   
 258  0 if (start == end) return new ArrayList<HighlightStatus>(0);
 259  0 ArrayList<HighlightStatus> v;
 260   
 261  0 setCurrentLocation(start);
 262    /* Now ask reduced model for highlight status for chars till end */
 263  0 v = _reduced.getHighlightStatus(start, end - start);
 264   
 265    /* Go through and find any NORMAL blocks. Within them check for keywords. */
 266  0 for (int i = 0; i < v.size(); i++) {
 267  0 HighlightStatus stat = v.get(i);
 268  0 if (stat.getState() == HighlightStatus.NORMAL) i = _highlightKeywords(v, i);
 269    }
 270   
 271    /* bstoler: Previously we moved back to the old location. This implementation choice severely slowed down
 272    * rendering when scrolling because parts are rendered in order. Thus, if old location is 0, but now we've
 273    * scrolled to display 100000-100100, if we keep jumping back to 0 after getting every bit of highlight, it
 274    * slows stuff down incredibly. */
 275    //setCurrentLocation(oldLocation);
 276  0 return v;
 277    }
 278   
 279    /** Distinguishes keywords from normal text in the given HighlightStatus element. Specifically, it looks to see
 280    * if the given text contains a keyword. If it does, it splits the HighlightStatus block into separate blocks
 281    * so that each keyword has its own block. This process identifies all keywords in the given block.
 282    * Note that the given block must have state NORMAL. Only runs in the event thread.
 283    * @param v Vector with highlight info
 284    * @param i Index of the single HighlightStatus to check for keywords in
 285    * @return the index into the vector of the last processed element
 286    */
 287  0 private int _highlightKeywords(ArrayList<HighlightStatus> v, int i) {
 288    // Basically all non-alphanumeric chars are delimiters
 289  0 final HighlightStatus original = v.get(i);
 290  0 final String text;
 291   
 292   
 293  0 try { text = getText(original.getLocation(), original.getLength()); }
 294  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 295   
 296    // Because this text is not quoted or commented, we can use the simpler tokenizer StringTokenizer. We have
 297    // to return delimiters as tokens so we can keep track of positions in the original string.
 298  0 StringTokenizer tokenizer = new StringTokenizer(text, delimiters, true);
 299   
 300    // start and length of the text that has not yet been put back into the vector.
 301  0 int start = original.getLocation();
 302  0 int length = 0;
 303   
 304    // Remove the old element from the vector.
 305  0 v.remove(i);
 306   
 307    // Index where we are in the vector. It's the location we would insert new things into.
 308  0 int index = i;
 309   
 310  0 boolean process;
 311  0 int state = 0;
 312  0 while (tokenizer.hasMoreTokens()) {
 313  0 String token = tokenizer.nextToken();
 314   
 315    //first check to see if we need highlighting
 316  0 process = false;
 317  0 if (_isType(token)) {
 318    //right now keywords incl prim types, so must put this first
 319  0 state = HighlightStatus.TYPE;
 320  0 process = true;
 321    }
 322  0 else if (_keywords.contains(token)) {
 323  0 state = HighlightStatus.KEYWORD;
 324  0 process = true;
 325    }
 326  0 else if (_isNum(token)) {
 327  0 state = HighlightStatus.NUMBER;
 328  0 process = true;
 329    }
 330   
 331  0 if (process) {
 332    // first check if we had any text before the token
 333  0 if (length != 0) {
 334  0 HighlightStatus newStat = new HighlightStatus(start, length, original.getState());
 335  0 v.add(index, newStat);
 336  0 index++;
 337  0 start += length;
 338  0 length = 0;
 339    }
 340   
 341    // Now pull off the keyword
 342  0 int keywordLength = token.length();
 343  0 v.add(index, new HighlightStatus(start, keywordLength, state));
 344  0 index++;
 345    // Move start to the end of the keyword
 346  0 start += keywordLength;
 347    }
 348    else {
 349    // This is not a keyword, so just keep accumulating length
 350  0 length += token.length();
 351    }
 352    }
 353    // Now check if there was any text left after the keywords.
 354  0 if (length != 0) {
 355  0 HighlightStatus newStat = new HighlightStatus(start, length, original.getState());
 356  0 v.add(index, newStat);
 357  0 index++;
 358  0 length = 0;
 359    }
 360    // return one before because we need to point to the last one we inserted
 361  0 return index - 1;
 362    }
 363   
 364    /** Checks to see if the current string is a number
 365    * @return true if x is a parseable number, i.e. either parsable as a double or as a long after chopping off a possible trailing 'L' or 'l'
 366    */
 367  106 static boolean _isNum(String x) {
 368  106 try {
 369  106 Double.parseDouble(x);
 370  46 return true;
 371    }
 372    catch (NumberFormatException e) {
 373  60 int radix = 10;
 374  60 int begin = 0;
 375  60 int end = x.length();
 376  60 int bits = 32;
 377  60 if (end-begin>1) {
 378    // string is not empty
 379  60 char ch = x.charAt(end-1);
 380  60 if ((ch=='l')||(ch=='L')) { // skip trailing 'l' or 'L'
 381  44 --end;
 382  44 bits = 64;
 383    }
 384  60 if (end-begin>1) {
 385    // string is not empty
 386  56 if (x.charAt(0) == '0') { // skip leading '0' of octal or hexadecimal literal
 387  54 ++begin;
 388  54 radix = 8;
 389  54 if (end-begin>1) {
 390    // string is not empty
 391  50 ch = x.charAt(1);
 392  50 if ((ch=='x')||(ch=='X')) { // skip 'x' or 'X' from hexadecimal literal
 393  48 ++begin;
 394  48 radix = 16;
 395    }
 396    }
 397    }
 398    }
 399    }
 400  60 try {
 401    // BigInteger can parse hex numbers representing negative longs; Long can't
 402  60 java.math.BigInteger val = new java.math.BigInteger(x.substring(begin, end), radix);
 403  54 return (val.bitLength() <= bits);
 404    }
 405    catch (NumberFormatException e2) {
 406  6 return false;
 407    }
 408    }
 409    }
 410   
 411    /** Checks to see if the current string is a type. A type is assumed to be a primitive type OR
 412    * anything else that begins with a capitalized character
 413    */
 414  0 private boolean _isType(String x) {
 415  0 if (_primTypes.contains(x)) return true;
 416   
 417  0 try { return Character.isUpperCase(x.charAt(0)); }
 418  0 catch (IndexOutOfBoundsException e) { return false; }
 419    }
 420   
 421    /** Returns whether the given text only has spaces. */
 422  35 public static boolean hasOnlySpaces(String text) { return (text.trim().length() == 0); }
 423   
 424    /** Fire event that styles changed from current location to the end.
 425    * Right now we do this every time there is an insertion or removal.
 426    * Two possible future optimizations:
 427    * <ol>
 428    * <li>Only fire changed event if text other than that which was inserted
 429    * or removed *actually* changed status. If we didn't changed the status
 430    * of other text (by inserting or deleting unmatched pair of quote or
 431    * comment chars), no change need be fired.
 432    * <li>If a change must be fired, we could figure out the exact end
 433    * of what has been changed. Right now we fire the event saying that
 434    * everything changed to the end of the document.
 435    * </ol>
 436    *
 437    * I don't think we'll need to do either one since it's still fast now.
 438    * I think this is because the UI only actually paints the things on the screen anyway.
 439    */
 440    protected abstract void _styleChanged();
 441   
 442    /** Add a character to the underlying reduced model. ASSUMEs _reduced lock is already held!
 443    * @param curChar the character to be added. */
 444  61964 private void _addCharToReducedModel(char curChar) {
 445    // _clearCache(_currentLocation); // redundant; already done in insertUpdate
 446  61964 _reduced.insertChar(curChar);
 447    }
 448   
 449    /** Get the current location of the cursor in the document. Unlike the usual swing document model, which is
 450    * stateless, we maintain a cursor position within our implementation of the reduced model. Can be modified
 451    * by any thread locking _reduced. The returned value may be stale if _reduced lock is not held
 452    * @return where the cursor is as the number of characters into the document
 453    */
 454  2866 public int getCurrentLocation() { return _currentLocation; }
 455   
 456    /** Change the current location of the document. Only runs in the event thread.
 457    * @param loc the new absolute location
 458    */
 459  413262 public void setCurrentLocation(int loc) {
 460  413262 if (loc < 0) {
 461  0 throw new UnexpectedException("Illegal location " + loc); // was loc = 0
 462    }
 463  413262 if (loc > getLength()) {
 464  0 throw new UnexpectedException("Illegal location " + loc); // was loc = getLength();
 465    }
 466  413262 int dist = loc - _currentLocation; // _currentLocation and _reduced can be updated asynchronously
 467  413262 _currentLocation = loc;
 468  413262 _reduced.move(dist); // must call _reduced.move here; this._move changes _currentLocation
 469    // System.err.println("_setCurrentLocation(" + loc + ") executed");
 470    }
 471   
 472    /** Moves _currentLocation the specified distance.
 473    * Identical to _setCurrentLocation, except that input arg is relative rather than absolute and the new location
 474    * is bounds checked. Only runs in the event thread.
 475    * @param dist the distance from the current location to the new location.
 476    */
 477  504 public void move(int dist) {
 478  504 int newLocation = _currentLocation + dist;
 479  504 if (0 <= newLocation && newLocation <= getLength()) {
 480  504 _reduced.move(dist);
 481  504 _currentLocation = newLocation;
 482    }
 483  0 else throw new IllegalArgumentException("AbstractDJDocument.move(" + dist + ") places the cursor at " +
 484    newLocation + " which is out of range");
 485    }
 486   
 487    /** Finds the match for the closing brace immediately to the left, assuming there is such a brace. On failure,
 488    * returns -1. Only runs in the event thread.
 489    * @return the relative distance backwards to the offset before the matching brace.
 490    */
 491  84 public int balanceBackward() {
 492  84 int origPos = _currentLocation;
 493  84 try {
 494  0 if (_currentLocation < 2) return -1;
 495  84 char prevChar = _getText(_currentLocation - 1, 1).charAt(0);
 496    // Utilities.show("_currentLocation = " + _currentLocation + "; prevChar = '" + prevChar + "'");
 497  0 if (prevChar != '}' && prevChar != ')' && prevChar != ']') return -1;
 498  84 return _reduced.balanceBackward();
 499    }
 500  84 finally { setCurrentLocation(origPos); }
 501    }
 502   
 503    /** FindS the match for the open brace immediately to the right, assuming there is such a brace. On failure,
 504    * returns -1. Only runs in event thread.
 505    * @return the relative distance forwards to the offset after the matching brace.
 506    */
 507  3972 public int balanceForward() {
 508  3972 int origPos = _currentLocation;
 509  3972 try {
 510  0 if (_currentLocation == 0) return -1;
 511  3972 char prevChar = _getText(_currentLocation - 1, 1).charAt(0);
 512    // System.err.println("_currentLocation = " + _currentLocation + "; prevChar = '" + prevChar + "'");
 513  0 if (prevChar != '{' && prevChar != '(' && prevChar != '[') return -1;
 514    // System.err.println("Calling _reduced.balanceForward()");
 515  3972 return _reduced.balanceForward() ;
 516    }
 517  3972 finally { setCurrentLocation(origPos); }
 518    }
 519   
 520    /** This method is used ONLY inside of document Read Lock. This method is UNSAFE in any other context!
 521    * @return The reduced model of this document.
 522    */
 523  12 public ReducedModelControl getReduced() { return _reduced; }
 524   
 525    /** Assumes that read lock and reduced lock are already held. */
 526  273 public ReducedModelState stateAtRelLocation(int dist) { return _reduced.moveWalkerGetState(dist); }
 527   
 528    /** Assumes that read lock and reduced lock are already held. */
 529  646 public ReducedModelState getStateAtCurrent() {
 530  646 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 531  646 return _reduced.getStateAtCurrent();
 532    }
 533   
 534    /** Assumes that read lock and reduced lock are already held. */
 535  24 public void resetReducedModelLocation() { _reduced.resetLocation(); }
 536   
 537    /** Searching backwards, finds the position of the enclosing brace of specified type. Ignores comments. Only runs in
 538    * event thread. TODO: implement this method by iterating getEnclosingBrace until brace of specified form is found
 539    * @param pos Position to start from
 540    * @param opening opening brace character
 541    * @param closing closing brace character
 542    * @return position of enclosing brace, or ERROR_INDEX (-1) if beginning
 543    * of document is reached.
 544    */
 545  12210 public int findPrevEnclosingBrace(final int pos, final char opening, final char closing) throws BadLocationException {
 546   
 547    // assert EventQueue.isDispatchThread();
 548    // Check cache
 549  12210 final Query key = new Query.PrevEnclosingBrace(pos, opening, closing);
 550  12210 final Integer cached = (Integer) _checkCache(key);
 551  0 if (cached != null) return cached.intValue();
 552   
 553  2 if (pos >= getLength() || pos <= 0) { return -1; }
 554   
 555  12208 final char[] delims = {opening, closing};
 556  12208 int reducedPos = pos;
 557  12208 int i; // index of for loop below
 558  12208 int braceBalance = 0;
 559   
 560  12208 String text = getText(0, pos);
 561   
 562  12208 final int origPos = _currentLocation;
 563    // Move reduced model to location pos
 564  12208 setCurrentLocation(pos); // reduced model points to pos == reducedPos
 565   
 566    // Walk backwards from specificed position
 567  12208 for (i = pos - 1; i >= 0; i--) {
 568    /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims,
 569    * 0 <= i < reducedPos <= pos */
 570   
 571  3253595 if (match(text.charAt(i), delims)) {
 572    // Move reduced model to walker's location
 573  79941 setCurrentLocation(i); // reduced model points to i
 574  79941 reducedPos = i; // reduced model points to reducedPos
 575   
 576    // Check if matching char should be ignored because it is within a comment,
 577    // quotes, or ignored paren phrase
 578  24 if (isShadowed()) continue; // ignore matching char
 579    else {
 580    // found valid matching char
 581  34899 if (text.charAt(i) == closing) ++braceBalance;
 582    else {
 583  10119 if (braceBalance == 0) break; // found our opening brace
 584  34899 --braceBalance;
 585    }
 586    }
 587    }
 588    }
 589   
 590    /* Invariant: same as for loop except that -1 <= i <= reducedPos <= pos */
 591   
 592  12208 setCurrentLocation(origPos); // Restore the state of the reduced model;
 593   
 594  2089 if (i == -1) reducedPos = -1; // No matching char was found
 595  12208 _storeInCache(key, reducedPos, pos - 1);
 596   
 597    // Return position of matching char or ERROR_INDEX (-1)
 598  12208 return reducedPos;
 599    }
 600   
 601    /** @return true iff _currentLocation is inside comment pr string. */
 602  181721 public boolean isShadowed() { return _reduced.isShadowed(); }
 603   
 604    /** @return true iff specified pos is inside comment pr string. */
 605  70 public boolean isShadowed(int pos) {
 606  70 int origPos = _currentLocation;
 607  70 setCurrentLocation(pos);
 608  70 boolean result = isShadowed();
 609  70 setCurrentLocation(origPos);
 610  70 return result;
 611    }
 612   
 613    /** Searching forward, finds the position of the enclosing brace, which may be a pointy bracket. NB: ignores comments.
 614    * Only runs in event thread.
 615    * @param pos Position to start from
 616    * @param opening opening brace character
 617    * @param closing closing brace character
 618    * @return position of enclosing brace, or ERROR_INDEX (-1) if beginning of document is reached.
 619    */
 620  3958 public int findNextEnclosingBrace(final int pos, final char opening, final char closing) throws BadLocationException {
 621  3958 assert EventQueue.isDispatchThread();
 622   
 623    // Check cache
 624  3958 final Query key = new Query.NextEnclosingBrace(pos, opening, closing);
 625  3958 final Integer cached = (Integer) _checkCache(key);
 626   
 627  0 if (cached != null) return cached.intValue();
 628  0 if (pos >= getLength() - 1) { return -1; }
 629   
 630  3958 final char[] delims = {opening, closing};
 631  3958 int reducedPos = pos;
 632  3958 int i; // index of for loop below
 633  3958 int braceBalance = 0;
 634   
 635  3958 String text = getText();
 636   
 637  3958 final int origPos = _currentLocation;
 638    // Move reduced model to location pos
 639  3958 setCurrentLocation(pos); // reduced model points to pos == reducedPos
 640   
 641    // Walk forward from specificed position
 642  76966 for (i = pos + 1; i < text.length(); i++) {
 643    /* Invariant: reduced model points to reducedPos, text[pos:i-1] contains no valid delims,
 644    * pos <= reducedPos < i <= text.length() */
 645   
 646  76966 if (match(text.charAt(i),delims)) {
 647    // Move reduced model to walker's location
 648  7244 setCurrentLocation(i); // reduced model points to i
 649  7244 reducedPos = i; // reduced model points to reducedPos
 650   
 651    // Check if matching char should be ignored because it is within a comment, quotes, or ignored paren phrase
 652  544 if (isShadowed()) continue; // ignore matching char
 653    else {
 654    // found valid matching char
 655  1371 if (text.charAt(i) == opening) ++braceBalance;
 656    else {
 657  3958 if (braceBalance == 0) break; // found our closing brace
 658  1371 --braceBalance;
 659    }
 660    }
 661    }
 662    }
 663   
 664    /* Invariant: same as for loop except that pos <= reducedPos <= i <= text.length() */
 665   
 666  3958 setCurrentLocation(origPos); // Restore the state of the reduced model;
 667   
 668  0 if (i == text.length()) reducedPos = -1; // No matching char was found
 669  3958 _storeInCache(key, reducedPos, reducedPos);
 670    // Return position of matching char or ERROR_INDEX (-1)
 671  3958 return reducedPos;
 672    }
 673   
 674    /** Searching backwards, finds the position of the first character that is one of the given delimiters. Does
 675    * not look for delimiters inside bracketed phrases (e.g., skips semicolons used inside for statements.).
 676    * Bracketed phrases exclude those ending a delimiter (e.g., '}' if a delimiter).
 677    * NB: ignores comments.
 678    * @param pos Position to start from
 679    * @param delims array of characters to search for
 680    * @return position of first matching delimiter, or ERROR_INDEX (-1) if beginning of document is reached.
 681    */
 682  6529 public int findPrevDelimiter(int pos, char[] delims) throws BadLocationException {
 683  6529 return findPrevDelimiter(pos, delims, true);
 684    }
 685   
 686    /** Searching backwards, finds position of first character that is a given delimiter, skipping over balanced braces
 687    * if so instructed. Does not look for delimiters inside a brace phrase if skipBracePhrases is true. Ignores
 688    * comments. Only runs in event thread.
 689    * @param pos Position to start from
 690    * @param delims array of characters to search for
 691    * @param skipBracePhrases whether to look for delimiters inside brace phrases
 692    * @return position of first matching delimiter, or ERROR_INDEX (-1) if beginning of document is reached.
 693    */
 694  13299 public int findPrevDelimiter(final int pos, final char[] delims, final boolean skipBracePhrases)
 695    throws BadLocationException {
 696   
 697  13299 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 698   
 699    // Check cache
 700  13299 final Query key = new Query.PrevDelimiter(pos, delims, skipBracePhrases);
 701  13299 final Integer cached = (Integer) _checkCache(key);
 702  13299 if (cached != null) {
 703    // System.err.println(cached.intValue() + " found in cache");
 704  152 return cached.intValue();
 705    }
 706   
 707  13147 int reducedPos = pos;
 708  13147 int i; // index for for loop below
 709  13147 int lineStartPos = _getLineStartPos(pos);
 710  0 if (lineStartPos < 0) lineStartPos = 0;
 711   
 712  2470 if (lineStartPos >= pos) i = lineStartPos - 1; // the line containing pos is empty
 713    else {
 714  10677 assert lineStartPos < pos;
 715  10677 String line = getText(lineStartPos, pos - lineStartPos); // the line containing pos
 716  10677 final int origPos = _currentLocation;
 717   
 718    // Walk backwards from specificed position, scanning current line for a delimiter
 719  10677 for (i = pos - 1; i >= lineStartPos; i--) {
 720    /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims,
 721    * 0 <= i < reducedPos <= pos */
 722    // Move reduced model to location pos
 723  94132 int irel = i - lineStartPos;
 724  94132 setCurrentLocation(i); // reduced model points to i
 725  94132 if (isShadowed() || isCommentOpen(line, irel)) {
 726    // System.err.println(text.charAt(i) + " at pos " + i + " is shadowed");
 727  1104 continue;
 728    }
 729  93028 char ch = line.charAt(irel);
 730   
 731  93028 if (match(ch, delims) /* && ! isShadowed() && (! skipParenPhrases || ! posInParenPhrase())*/) {
 732  6493 reducedPos = i; // record valid match
 733  6493 break;
 734    }
 735   
 736  86535 if (skipBracePhrases && match(ch, CLOSING_BRACES) ) { // note that delims have already been matched
 737    // Utilities.show("closing bracket is '" + ch + "' at pos " + i);
 738  24 setCurrentLocation(i + 1); // move cursor immediately to right of ch (a brace)
 739    // Utilities.show("_currentLocation = " + _currentLocation);
 740  24 int dist = balanceBackward(); // bypasses redundant read locking
 741  24 if (dist == -1) { // if braces do not balance, return failure
 742  0 i = -1;
 743    // Utilities.show("dist = " + dist + " No matching brace found");
 744  0 break;
 745    }
 746  24 assert dist > 0;
 747    // Utilities.show("text = '" + getText(i + 1 - dist, dist) + "' dist = " + dist + " matching bracket is '"
 748    // + text.charAt(i) + "' at pos " + i);
 749  24 setCurrentLocation(i + 1 - dist); // skip over balanced brace text, decrementing _currentLocation
 750  24 i = _currentLocation;
 751    // Decrementing i skips over matching brace; could skip back into text preceding current line
 752  24 continue;
 753    }
 754    } // end for
 755   
 756  10677 setCurrentLocation(origPos); // Restore the state of the reduced model;
 757    } // end processing of text on same line as pos
 758   
 759    /* Invariant: same as for loop except that lineStartPos-1 <= i <= reducedPos <= pos && 0 <= reducedPos */
 760   
 761  13147 if (i < lineStartPos) { // No matching char was found on line containing pos; must look at preceding text
 762  154 if (i <= 0) reducedPos = -1; // No preceding text left to search
 763  6500 else reducedPos = findPrevDelimiter(i, delims, skipBracePhrases);
 764    }
 765   
 766  13147 _storeInCache(key, reducedPos, pos - 1);
 767    // Utilities.show("findPrevDelimiter returning " + reducedPos);
 768   
 769    // Return position of matching char or ERROR_INDEX (-1)
 770  13147 return reducedPos;
 771    }
 772   
 773  3565655 private static boolean match(char c, char[] delims) {
 774  115718 for (char d : delims) { if (c == d) return true; } // Found matching delimiter
 775  3449937 return false;
 776    }
 777   
 778    /** This function finds the given character in the same statement as the given position, and before the given
 779    * position. It is used by QuestionExistsCharInStmt and QuestionExistsCharInPrevStmt
 780    */
 781  73 public boolean findCharInStmtBeforePos(char findChar, int position) {
 782   
 783  73 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 784   
 785  73 if (position == -1) {
 786  0 String msg =
 787    "Argument endChar to QuestionExistsCharInStmt must be a char that exists on the current line.";
 788  0 throw new UnexpectedException(new IllegalArgumentException(msg));
 789    }
 790   
 791  73 char[] findCharDelims = {findChar, ';', '{', '}'};
 792  73 int prevFindChar;
 793   
 794    // Find the position of the preceding occurrence findChar position (looking in paren phrases as well)
 795  73 boolean found;
 796   
 797  73 try {
 798  73 prevFindChar = this.findPrevDelimiter(position, findCharDelims, false);
 799   
 800  12 if ((prevFindChar == -1) || (prevFindChar < 0)) return false; // no such char
 801   
 802    // Determine if prevFindChar is findChar or the end of statement delimiter
 803  61 String foundString = getText(prevFindChar, 1);
 804  61 char foundChar = foundString.charAt(0);
 805  61 found = (foundChar == findChar);
 806    }
 807  0 catch (Throwable t) { throw new UnexpectedException(t); }
 808  61 return found;
 809    }
 810   
 811    // /** Finds the position of the first non-whitespace, non-comment character before pos. Skips comments and all
 812    // * whitespace, including newlines.
 813    // * @param pos Position to start from
 814    // * @param whitespace chars considered as white space
 815    // * @return position of first non-whitespace character before pos OR ERROR_INDEX (-1) if no such char
 816    // */
 817    // public int findPrevCharPos(int pos, char[] whitespace) throws BadLocationException {
 818    // return _findPrevCharPos(pos, whitespace); }
 819    // }
 820   
 821    /** Finds the position of the first non-whitespace, non-comment character before pos. Skips comments and all
 822    * whitespace, including newlines.
 823    * @param pos Position to start from
 824    * @param whitespace chars considered as white space
 825    * @return position of first non-whitespace character before pos OR ERROR_INDEX (-1) if no such char
 826    */
 827  6353 public int _findPrevCharPos(final int pos, final char[] whitespace) throws BadLocationException {
 828   
 829  6353 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 830   
 831    // Check cache
 832  6353 final Query key = new Query.PrevCharPos(pos, whitespace);
 833  6353 final Integer cached = (Integer) _checkCache(key);
 834  37 if (cached != null) return cached.intValue();
 835   
 836  6316 int reducedPos = pos;
 837  6316 int i = pos - 1;
 838  6316 String text;
 839  6316 text = getText(0, pos);
 840   
 841  6316 final int oldPos = _currentLocation;
 842    // Move reduced model to location reducedPpos
 843  6316 setCurrentLocation(reducedPos);
 844   
 845    // Walk backward from specified position
 846   
 847  6316 while (i >= 0) {
 848    /* Invariant: reduced model points to reducedPos, 0 <= i < reducedPos <= pos,
 849    * text[i+1:pos-1] contains invalid chars */
 850   
 851  13260 if (match(text.charAt(i), whitespace)) {
 852    // ith char is whitespace
 853  6808 i--;
 854  6808 continue;
 855    }
 856   
 857    // Found a non-whitespace char; move reduced model to location i
 858  6452 setCurrentLocation(i);
 859  6452 reducedPos = i; // reduced model points to i == reducedPos
 860   
 861    // Check if matching char is within a comment (not including opening two characters)
 862  6452 if ((_reduced.getStateAtCurrent().equals(INSIDE_LINE_COMMENT)) ||
 863    (_reduced.getStateAtCurrent().equals(INSIDE_BLOCK_COMMENT))) {
 864  128 i--;
 865  128 continue;
 866    }
 867   
 868  6324 if (i > 0 && _isStartOfComment(text, i - 1)) { /* char i is second character in opening comment marker */
 869    // Move i past the first comment character and continue searching
 870  12 i = i - 2;
 871  12 continue;
 872    }
 873   
 874    // Found valid previous character
 875  6312 break;
 876    }
 877   
 878    /* Exit invariant same as for loop except that i <= reducedPos because at break i = reducedPos */
 879  6316 setCurrentLocation(oldPos);
 880   
 881  6316 int result = reducedPos;
 882  4 if (i < 0) result = -1;
 883  6316 _storeInCache(key, result, pos - 1);
 884  6316 return result;
 885    }
 886   
 887    /** Checks the query cache for a stored value. Returns the value if it has been cached, or null
 888    * otherwise. Calling convention for keys: methodName:arg1:arg2.
 889    * @param key Name of the method and arguments
 890    */
 891  112852 protected Object _checkCache(final Query key) {
 892  106343 if (_queryCache == null) return null;
 893  6509 return _queryCache.get(key);
 894    }
 895   
 896    /** Stores the given result in the helper method cache. Query classes define equality structurally.
 897    * @param query A canonical description of the query
 898    * @param answer The answer returned for the query
 899    * @param offset The offset bounding the right edge of the text on which the query depends; if (0:offset) in
 900    * the document is unchanged, the query should return the same answer.
 901    */
 902  108419 protected void _storeInCache(final Query query, final Object answer, final int offset) {
 903  106180 if (_queryCache == null) return;
 904  2239 _queryCache.put(query, answer);
 905  2239 _addToOffsetsToQueries(query, offset);
 906    }
 907   
 908    /** Clears the memozing cache of queries with offset >= than specified value. Should be called every time the
 909    * document is modified.
 910    */
 911  1839 protected void _clearCache(int offset) {
 912  1704 if (_queryCache == null) return;
 913   
 914  135 if (offset <= 0) {
 915  1 _queryCache.clear();
 916  1 _offsetToQueries.clear();
 917  1 return;
 918    }
 919    // The Integer[] copy of the key set is required to avoid ConcurrentModifiationExceptions. Ugh!
 920  134 Integer[] deadOffsets = _offsetToQueries.tailMap(offset).keySet().toArray(new Integer[0]);
 921  134 for (int i: deadOffsets) {
 922  384 for (Query query: _offsetToQueries.get(i)) _queryCache.remove(query); // remove query entry from cache
 923  308 _offsetToQueries.remove(i); // remove query bucket for i from offsetToQueries table
 924    }
 925    }
 926   
 927    /** Add <query,offset> pair to _offsetToQueries map. Assumes lock on _queryCache is already held. */
 928  2239 private void _addToOffsetsToQueries(final Query query, final int offset) {
 929  2239 List<Query> selectedQueries = _offsetToQueries.get(offset);
 930  2239 if (selectedQueries == null) {
 931  891 selectedQueries = new LinkedList<Query>();
 932  891 _offsetToQueries.put(offset, selectedQueries);
 933    }
 934  2239 selectedQueries.add(query);
 935    }
 936   
 937    /** Default indentation - uses OTHER flag and no progress indicator. Assume write lock is already held.
 938    * @param selStart the offset of the initial character of the region to indent
 939    * @param selEnd the offset of the last character of the region to indent
 940    */
 941  54 public void indentLines(int selStart, int selEnd) {
 942  54 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 943  54 try { indentLines(selStart, selEnd, Indenter.IndentReason.OTHER, null); }
 944    catch (OperationCanceledException oce) {
 945    // Indenting without a ProgressMonitor should never be cancelled!
 946  0 throw new UnexpectedException(oce);
 947    }
 948    }
 949   
 950    /** Parameterized indentation for special-case handling. If selStart == selEnd, then the line containing the
 951    * currentLocation is indented. The values of selStart and selEnd are ignored!
 952    *
 953    * @param selStart the offset of the initial character of the region to indent
 954    * @param selEnd the offset of the last character of the region to indent
 955    * @param reason a flag from {@link Indenter} to indicate the reason for the indent
 956    * (indent logic may vary slightly based on the trigger action)
 957    * @param pm used to display progress, null if no reporting is desired
 958    */
 959  66 public void indentLines(int selStart, int selEnd, Indenter.IndentReason reason, ProgressMonitor pm)
 960    throws OperationCanceledException {
 961   
 962  66 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 963   
 964    // Begins a compound edit.
 965    // int key = startCompoundEdit(); // commented out in connection with the FrenchKeyBoard Fix
 966   
 967  66 try {
 968  66 if (selStart == selEnd) { // single line to indent
 969    // Utilities.showDebug("selStart = " + selStart + " currentLocation = " + _currentLocation);
 970  45 Position oldPosition = createUnwrappedPosition(_currentLocation);
 971  45 int lineStart = _getLineStartPos(selStart);
 972  0 if (lineStart < 0) lineStart = 0; // selStart on first line
 973  45 setCurrentLocation(lineStart);
 974    // Indent, updating current location if necessary.
 975    // Utilities.showDebug("Indenting line at offset " + selStart);
 976  45 if (_indentLine(reason)) {
 977  45 setCurrentLocation(oldPosition.getOffset()); // moves currentLocation back to original offset on line
 978  20 if (onlyWhiteSpaceBeforeCurrent()) move(_getWhiteSpace()); // passes any additional spaces before firstNonWS
 979    }
 980    }
 981  21 else _indentBlock(selStart, selEnd, reason, pm);
 982    }
 983  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 984   
 985    // Ends the compound edit.
 986    //endCompoundEdit(key); //Changed to endLastCompoundEdit in connection with the FrenchKeyBoard Fix
 987  66 endLastCompoundEdit();
 988    }
 989   
 990    /** Indents the lines between and including the lines containing points start and end. Only runs in event thread.
 991    * @param start Position in document to start indenting from
 992    * @param end Position in document to end indenting at
 993    * @param reason a flag from {@link Indenter} to indicate the reason for the indent
 994    * (indent logic may vary slightly based on the trigger action)
 995    * @param pm used to display progress, null if no reporting is desired
 996    */
 997  21 private void _indentBlock(final int start, final int end, Indenter.IndentReason reason, ProgressMonitor pm)
 998    throws OperationCanceledException, BadLocationException {
 999   
 1000    // Set up the query cache;
 1001  21 _queryCache = new HashMap<Query, Object>(INIT_CACHE_SIZE);
 1002  21 _offsetToQueries = new TreeMap<Integer, List<Query>>();
 1003   
 1004    // Keep marker at the end. This Position will be the correct endpoint no matter how we change
 1005    // the doc doing the indentLine calls.
 1006  21 final Position endPos = this.createUnwrappedPosition(end);
 1007    // Iterate, line by line, until we get to/past the end
 1008  21 int walker = start;
 1009    // _indentInProgress = true;
 1010  21 while (walker < endPos.getOffset()) {
 1011  187 setCurrentLocation(walker);
 1012    // Keep pointer to walker position that will stay current regardless of how indentLine changes things
 1013  187 Position walkerPos = this.createUnwrappedPosition(walker);
 1014    // Indent current line
 1015    // We ignore current location info from each line, because it probably doesn't make sense in a block context.
 1016  187 _indentLine(reason); // this operation is atomic
 1017    // Move back to walker spot
 1018  187 setCurrentLocation(walkerPos.getOffset());
 1019  187 walker = walkerPos.getOffset();
 1020   
 1021  187 if (pm != null) {
 1022  0 pm.setProgress(walker); // Update ProgressMonitor.
 1023  0 if (pm.isCanceled()) throw new OperationCanceledException(); // Check for cancel button-press.
 1024    }
 1025   
 1026    // Adding 1 makes us point to the first character AFTER the next newline. We don't actually move the
 1027    // location yet. That happens at the top of the loop, after we check if we're past the end.
 1028  187 walker += _reduced.getDistToNextNewline() + 1;
 1029    // _indentInProgress = false;
 1030    }
 1031   
 1032    // disable the query cache
 1033  21 _queryCache = null;
 1034  21 _offsetToQueries = null;
 1035    }
 1036   
 1037    /** Indents a line using the Indenter. Public ONLY for testing purposes. */
 1038  236 public boolean _indentLine(Indenter.IndentReason reason) { return getIndenter().indent(this, reason); }
 1039   
 1040    /** Returns the "intelligent" beginning of line. If currPos is to the right of the first
 1041    * non-whitespace character, the position of the first non-whitespace character is returned.
 1042    * If currPos is at or to the left of the first non-whitespace character, the beginning of
 1043    * the line is returned.
 1044    * @param currPos A position on the current line
 1045    */
 1046  13 public int getIntelligentBeginLinePos(int currPos) throws BadLocationException {
 1047  13 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1048   
 1049  13 String prefix;
 1050  13 int firstChar;
 1051  13 firstChar = _getLineStartPos(currPos);
 1052  13 prefix = getText(firstChar, currPos-firstChar);
 1053   
 1054    // Walk through string until we find a non-whitespace character
 1055  13 int i;
 1056  13 int len = prefix.length();
 1057   
 1058  7 for (i = 0; i < len; i++ ) { if (! Character.isWhitespace(prefix.charAt(i))) break; }
 1059   
 1060    // If we found a non-WS char left of curr pos, return it
 1061  13 if (i < len) {
 1062  7 int firstRealChar = firstChar + i;
 1063  7 if (firstRealChar < currPos) return firstRealChar;
 1064    }
 1065    // Otherwise, return the beginning of the line
 1066  6 return firstChar;
 1067    }
 1068   
 1069    /** Returns the number of blanks in the indent prefix for the start of the statement identified by pos. Uses a
 1070    * default set of delimiters. (';', '{', '}') and a default set of whitespace characters (' ', '\t', n', ',')
 1071    * @param pos Cursor position
 1072    */
 1073  117 public int _getIndentOfCurrStmt(int pos) {
 1074  117 char[] delims = {';', '{', '}'};
 1075  117 char[] whitespace = {' ', '\t', '\n', ','};
 1076  117 return _getIndentOfCurrStmt(pos, delims, whitespace);
 1077    }
 1078   
 1079    /** Returns the number of blanks in the indent prefix of the start of the statement identified by pos. Uses a
 1080    * default set of whitespace characters: {' ', '\t', '\n', ','}
 1081    * @param pos Cursor position
 1082    */
 1083  59 public int _getIndentOfCurrStmt(int pos, char[] delims) {
 1084  59 char[] whitespace = {' ', '\t', '\n',','};
 1085  59 return _getIndentOfCurrStmt(pos, delims, whitespace);
 1086    }
 1087   
 1088    /** Returns the number of blanks in the indent prefix of the start of the statement identified by pos,
 1089    * assuming that the statement is already properly indented
 1090    * @param pos the position identifying the current statement
 1091    * @param delims delimiter characters denoting end of statement
 1092    * @param whitespace characters to skip when looking for beginning of next statement
 1093    */
 1094  197 public int _getIndentOfCurrStmt(final int pos, final char[] delims, final char[] whitespace) {
 1095  197 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1096   
 1097  197 try {
 1098    // Check cache
 1099  197 int lineStart = _getLineStartPos(pos); // returns 0 for initial line
 1100   
 1101  197 final Query key = new Query.IndentOfCurrStmt(lineStart, delims, whitespace);
 1102  197 final Integer cached = (Integer) _checkCache(key);
 1103  47 if (cached != null) return cached; // relying on auto-unboxing
 1104   
 1105    // Find the previous delimiter (typically an enclosing brace or closing symbol) skipping over balanced braces
 1106    // that are not delims
 1107  150 boolean reachedStart = false;
 1108  150 int prevDelim = findPrevDelimiter(lineStart, delims, /* skipBracePhrases */ true);
 1109   
 1110  73 if (prevDelim == -1) reachedStart = true; // no delimiter found
 1111   
 1112    // From the previous delimiter or start, find the next non-whitespace character (why?)
 1113  150 int nextNonWSChar;
 1114  73 if (reachedStart) nextNonWSChar = getFirstNonWSCharPos(0);
 1115  77 else nextNonWSChar = getFirstNonWSCharPos(prevDelim + 1, whitespace, false);
 1116   
 1117    // If the end of the document was reached
 1118  0 if (nextNonWSChar == -1) nextNonWSChar = getLength();
 1119   
 1120    // The following statement looks right; otherwise, the indenting of the current line depends on how it is indented
 1121    // if (nextNonWSChar >= lineStart) nextNonWSChar = prevDelim;
 1122   
 1123    // Get the start of the line of the non-ws char
 1124  150 int newLineStart = _getLineStartPos(nextNonWSChar);
 1125   
 1126    // Get the position of the first non-ws character on this line (or end of line if no such char
 1127  150 int firstNonWS = _getLineFirstCharPos(newLineStart);
 1128  150 int wSPrefix = firstNonWS - newLineStart;
 1129  150 _storeInCache(key, wSPrefix, firstNonWS); // relying on autoboxing
 1130  150 return wSPrefix;
 1131    }
 1132  0 catch(BadLocationException e) { throw new UnexpectedException(e); }
 1133    // Utilities.show("getIdentCurrStmt(...) call completed");
 1134    }
 1135   
 1136    // Not current used.
 1137    // /** Gets the white space prefix preceding the first non-blank/tab character on the line identified by pos.
 1138    // * Assumes that line has nonWS character.
 1139    // */
 1140    // public String getWSPrefix(int pos) {
 1141    // assert EventQueue.isDispatchThread();
 1142    // try {
 1143    //
 1144    // // Get the start of this line
 1145    // int lineStart = _getLineStartPos(pos);
 1146    // // Get the position of the first non-ws character on this line
 1147    // int firstNonWSPos = _getLineFirstCharPos(pos);
 1148    // return StringOps.getBlankString(firstNonWSPos - lineStart);
 1149    // }
 1150    // catch(BadLocationException e) { throw new UnexpectedException(e); }
 1151    // }
 1152   
 1153    /** Determines if the given character exists on the line where the given cursor position is. Does not search
 1154    * inside quotes or comments. <b>Does not work if character being searched for is a '/' or a '*'</b>. Only
 1155    * read lock is already held.
 1156    * @param pos Cursor position
 1157    * @param findChar Character to search for
 1158    * @return true if this node's rule holds.
 1159    */
 1160  129 public int findCharOnLine(final int pos, final char findChar) {
 1161   
 1162  129 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread(); // violated in some unit tests
 1163   
 1164    // Check cache
 1165  129 final Query key = new Query.CharOnLine(pos, findChar);
 1166  129 final Integer cached = (Integer) _checkCache(key);
 1167  5 if (cached != null) return cached.intValue();
 1168   
 1169  124 int i;
 1170  124 int matchIndex; // absolute index of matching character
 1171   
 1172  124 try {
 1173  124 final int oldPos = _currentLocation;
 1174  124 int lineStart = _getLineStartPos(pos);
 1175  124 int lineEnd = _getLineEndPos(pos);
 1176  124 String lineText = getText(lineStart, lineEnd - lineStart);
 1177  124 i = lineText.indexOf(findChar, 0);
 1178  124 matchIndex = i + lineStart;
 1179   
 1180  124 while (i != -1) { // match found
 1181    /* Invariant: reduced model points to original location (here), lineText[0:i-1] does not contain valid
 1182    * findChar, lineText[i] == findChar which may or may not be valid. */
 1183   
 1184    // Move reduced model to location of ith char
 1185  26 setCurrentLocation(matchIndex); // move reduced model to location matchIndex
 1186   
 1187    // Check if matching char is in comment or quotes
 1188  22 if (_reduced.getStateAtCurrent().equals(FREE)) break; // found matching char
 1189   
 1190    // matching character is not valid, try again
 1191  4 i = lineText.indexOf(findChar, i+1);
 1192    }
 1193  124 setCurrentLocation(oldPos); // restore old position
 1194   
 1195  102 if (i == -1) matchIndex = -1;
 1196  124 _storeInCache(key, matchIndex, Math.max(pos - 1, matchIndex));
 1197    }
 1198  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 1199   
 1200  124 return matchIndex;
 1201    }
 1202   
 1203    /** Returns the absolute position of the beginning of the current line. (Just after most recent newline, or 0.)
 1204    * Doesn't ignore comments.
 1205    * @param pos Any position on the current line
 1206    * @return position of the beginning of this line
 1207    */
 1208  17527 public int _getLineStartPos(final int pos) {
 1209   
 1210  17527 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1211   
 1212  0 if (pos < 0 || pos > getLength()) return -1;
 1213    // Check cache
 1214  17527 final Query key = new Query.LineStartPos(pos);
 1215  17527 final Integer cached = (Integer) _checkCache(key);
 1216  2390 if (cached != null) return cached.intValue();
 1217   
 1218  15137 int dist;
 1219   
 1220  15137 final int oldPos = _currentLocation;
 1221  15137 setCurrentLocation(pos);
 1222  15137 dist = _reduced.getDistToStart(0);
 1223  15137 setCurrentLocation(oldPos);
 1224   
 1225  15137 int newPos = 0;
 1226  12322 if (dist >= 0) newPos = pos - dist;
 1227  15137 _storeInCache(key, newPos, pos - 1);
 1228  15137 return newPos; // may equal 0
 1229    }
 1230   
 1231    /** Returns the absolute position of the end of the current line. (At the next newline, or the end of the document.)
 1232    * @param pos Any position on the current line
 1233    * @return position of the end of this line
 1234    */
 1235  25168 public int _getLineEndPos(final int pos) {
 1236   
 1237  25168 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1238   
 1239  0 if (pos < 0 || pos > getLength()) return -1;
 1240   
 1241    // Check cache
 1242  25168 final Query key = new Query.LineEndPos(pos);
 1243  25168 final Integer cached = (Integer) _checkCache(key);
 1244  626 if (cached != null) return cached.intValue();
 1245   
 1246  24542 int dist, newPos;
 1247   
 1248  24542 final int oldPos = _currentLocation;
 1249  24542 setCurrentLocation(pos);
 1250  24542 dist = _reduced.getDistToNextNewline();
 1251  24542 setCurrentLocation(oldPos);
 1252   
 1253  24542 newPos = pos + dist;
 1254  24542 assert newPos == getLength() || _getText(newPos, 1).charAt(0) == newline;
 1255  24542 _storeInCache(key, newPos, newPos);
 1256  24542 return newPos;
 1257    }
 1258   
 1259    /** Returns the absolute position of the first non-blank/tab character on the current line including comment text or
 1260    * the end of the line if no non-blank/tab character is found.
 1261    * TODO: get rid of tab character references in AbstractDJDocument and related files and prevent insertion of tabs
 1262    * @param pos position on the line
 1263    * @return position of first non-blank/tab character on this line, or the end of the line if no non-blank/tab
 1264    * character is found.
 1265    */
 1266  930 public int _getLineFirstCharPos(final int pos) {
 1267   
 1268  930 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1269   
 1270    // Check cache
 1271  930 final Query key = new Query.LineFirstCharPos(pos);
 1272  930 final Integer cached = (Integer) _checkCache(key);
 1273  250 if (cached != null) return cached.intValue();
 1274   
 1275  680 final int startLinePos = _getLineStartPos(pos);
 1276  680 final int endLinePos = _getLineEndPos(pos);
 1277  680 int nonWSPos = endLinePos;
 1278   
 1279    // Get all text on this line and search for first nonWS char
 1280  680 String text = _getText(startLinePos, endLinePos - startLinePos);
 1281  680 int walker = 0;
 1282  680 while (walker < text.length()) {
 1283  1086 if (text.charAt(walker) == ' ' || text.charAt(walker) == '\t') walker++;
 1284    else {
 1285  604 nonWSPos = startLinePos + walker;
 1286  604 break;
 1287    }
 1288    }
 1289  680 _storeInCache(key, nonWSPos, Math.max(pos - 1, nonWSPos));
 1290  680 return nonWSPos; // may equal lineEndPos
 1291    }
 1292   
 1293    /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace,
 1294    * including newlines.
 1295    * @param pos Position to start from
 1296    * @return position of first non-whitespace character after pos, or ERROR_INDEX (-1) if end of document is reached
 1297    */
 1298  23418 public int getFirstNonWSCharPos(int pos) throws BadLocationException {
 1299  23418 char[] whitespace = {' ', '\t', '\n'};
 1300  23418 return getFirstNonWSCharPos(pos, whitespace, false);
 1301    }
 1302   
 1303    /** Similar to the single-argument version, but allows including comments.
 1304    * @param pos Position to start from
 1305    * @param acceptComments if true, find non-whitespace chars in comments
 1306    * @return position of first non-whitespace character after pos,
 1307    * or ERROR_INDEX (-1) if end of document is reached
 1308    */
 1309  23 public int getFirstNonWSCharPos(int pos, boolean acceptComments) throws BadLocationException {
 1310  23 char[] whitespace = {' ', '\t', '\n'};
 1311  23 return getFirstNonWSCharPos(pos, whitespace, acceptComments);
 1312    }
 1313   
 1314    /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace,
 1315    * including newlines.
 1316    * @param pos Position to start from
 1317    * @param whitespace array of whitespace chars to ignore
 1318    * @param acceptComments if true, find non-whitespace chars in comments
 1319    * @return position of first non-whitespace character after pos, or ERROR_INDEX (-1) if end of document is reached
 1320    */
 1321  23965 public int getFirstNonWSCharPos(final int pos, final char[] whitespace, final boolean acceptComments) throws
 1322    BadLocationException {
 1323   
 1324  23965 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1325   
 1326    // Check cache
 1327  23965 final Query key = new Query.FirstNonWSCharPos(pos, whitespace, acceptComments);
 1328  23965 final Integer cached = (Integer) _checkCache(key);
 1329  88 if (cached != null) return cached.intValue();
 1330   
 1331  23877 final int docLen = getLength();
 1332  23877 final int origPos = _currentLocation;
 1333  23877 final int endPos = _getLineEndPos(pos);
 1334   
 1335  23877 String line = getText(pos, endPos - pos); // Get text from pos to end of line
 1336  23877 setCurrentLocation(pos); // Move reduced model to location pos
 1337  23877 try {
 1338  23877 int i = pos;
 1339  23877 int reducedPos = pos;
 1340    // Walk forward from specificed position
 1341  23877 while (i < endPos) {
 1342   
 1343    // Check if character is whitespace
 1344  43784 if (match(line.charAt(i-pos), whitespace)) {
 1345  15208 i++;
 1346  15208 continue;
 1347    }
 1348    // Found a non whitespace character
 1349    // Move reduced model to walker's location for subsequent processing
 1350  28576 setCurrentLocation(i); // reduced model points to location i
 1351  28576 reducedPos = i;
 1352   
 1353    // Check if non-ws char is within comment and if we want to ignore them.
 1354  28576 if (! acceptComments &&
 1355    ((_reduced.getStateAtCurrent().equals(INSIDE_LINE_COMMENT)) ||
 1356    (_reduced.getStateAtCurrent().equals(INSIDE_BLOCK_COMMENT)))) {
 1357  5075 i++; // TODO: increment i to skip over entire comment
 1358  5075 continue;
 1359    }
 1360   
 1361    // Check if non-ws char is part of comment opening bracket and if we want to ignore them
 1362  23501 if (! acceptComments && _isStartOfComment(line, i - pos)) {
 1363    // ith char is first char in comment open market; skip past this marker and continue searching
 1364  95 i = i + 2; // TODO: increment i to skip over entire comment
 1365  95 continue;
 1366    }
 1367   
 1368    // Return position of matching char
 1369  23406 _storeInCache(key, reducedPos, reducedPos); // Cached answer depends only on text(0:reducedPos]
 1370    // _setCurrentLocation(origPos);
 1371  23406 return reducedPos;
 1372    }
 1373   
 1374    // No matching char found on this line
 1375  471 if (endPos + 1 >= docLen) { // No matching char found in doc
 1376  24 _storeInCache(key, -1, Integer.MAX_VALUE); // Any change to the document invalidates this result!
 1377    // _setCurrentLocation(origPos);
 1378  24 return -1;
 1379    }
 1380    }
 1381  23877 finally { setCurrentLocation(origPos); } // restore _currentLocation
 1382   
 1383    // Search through remaining lines of document; recursion depth is bounded by number of blank lines following pos
 1384  447 return getFirstNonWSCharPos(endPos + 1, whitespace, acceptComments);
 1385    }
 1386   
 1387  6305 public int _findPrevNonWSCharPos(int pos) throws BadLocationException {
 1388  6305 char[] whitespace = {' ', '\t', '\n'};
 1389  6305 return _findPrevCharPos(pos, whitespace);
 1390    }
 1391   
 1392    /** Helper method for getFirstNonWSCharPos Determines whether the current character is the start of a comment:
 1393    * "/*" or "//"
 1394    */
 1395  63422 protected static boolean _isStartOfComment(String text, int pos) {
 1396  63422 char currChar = text.charAt(pos);
 1397  63422 if (currChar == '/') {
 1398  453 try {
 1399  453 char afterCurrChar = text.charAt(pos + 1);
 1400  107 if ((afterCurrChar == '/') || (afterCurrChar == '*')) return true;
 1401    } catch (StringIndexOutOfBoundsException e) { }
 1402    }
 1403  63315 return false;
 1404    }
 1405   
 1406    // Never used
 1407    // /** Determines if _currentLocation is the start of a comment. */
 1408    // private boolean _isStartOfComment(int pos) { return _isStartOfComment(getText(), pos); }
 1409   
 1410    // /** Helper method for findPrevNonWSCharPos. Determines whether the current character is the start of a comment
 1411    // * encountered from the end: '/' or '*' preceded by a '/'.
 1412    // * @return true if (pos-1,pos) == '/*' or '//'
 1413    // */
 1414    // protected static boolean _isOneCharPastStartOfComment(String text, int pos) {
 1415    // char currChar = text.charAt(pos);
 1416    // if (currChar == '/' || currChar == '*') {
 1417    // try {
 1418    // char beforeCurrChar = text.charAt(pos - 1);
 1419    // if (beforeCurrChar == '/') return true;
 1420    // } catch (StringIndexOutOfBoundsException e) { /* do nothing */ }
 1421    // }
 1422    // return false;
 1423    // }
 1424   
 1425   
 1426    /** Returns true if the given position is inside a paren phrase.
 1427    * @param pos the position we're looking at
 1428    * @return true if pos is immediately inside parentheses
 1429    */
 1430  2 public boolean _inParenPhrase(final int pos) {
 1431   
 1432  2 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1433   
 1434    // Check cache
 1435  2 final Query key = new Query.PosInParenPhrase(pos);
 1436  2 Boolean cached = (Boolean) _checkCache(key);
 1437  0 if (cached != null) return cached.booleanValue();
 1438   
 1439  2 boolean _inParenPhrase;
 1440   
 1441  2 final int oldPos = _currentLocation;
 1442    // assert pos == here if read lock and reduced already held before call
 1443  2 setCurrentLocation(pos);
 1444  2 _inParenPhrase = _inParenPhrase();
 1445  2 setCurrentLocation(oldPos);
 1446  2 _storeInCache(key, _inParenPhrase, pos - 1);
 1447   
 1448  2 return _inParenPhrase;
 1449    }
 1450   
 1451    /** Cached version of _reduced.getLineEnclosingBrace(). Assumes that read lock and reduced lock are already held. */
 1452  1210 public BraceInfo _getLineEnclosingBrace() {
 1453   
 1454  1210 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1455   
 1456    // Check cache
 1457  1210 final int lineStart = _getLineStartPos(_currentLocation);
 1458    // System.err.println("_currentLocation = " + origPos + " lineStart = " + lineStart);
 1459  0 if (lineStart < 0) return BraceInfo.NULL;
 1460  1210 final int keyPos = lineStart;
 1461  1210 final Query key = new Query.LineEnclosingBrace(keyPos);
 1462  1210 final BraceInfo cached = (BraceInfo) _checkCache(key);
 1463  389 if (cached != null) return cached;
 1464   
 1465    // BraceInfo b = _reduced.getLineEnclosingBrace(lineStart); // optimized version to be developed
 1466  821 BraceInfo b = _reduced._getLineEnclosingBrace();
 1467   
 1468  821 _storeInCache(key, b, keyPos - 1);
 1469  821 return b;
 1470    }
 1471   
 1472    /** Cached version of _reduced.getEnclosingBrace(). Assumes that read lock and reduced lock are already held. */
 1473  28 public BraceInfo _getEnclosingBrace() {
 1474  28 int pos = _currentLocation;
 1475    // Check cache
 1476  28 final Query key = new Query.EnclosingBrace(pos);
 1477  28 final BraceInfo cached = (BraceInfo) _checkCache(key);
 1478  0 if (cached != null) return cached;
 1479  28 BraceInfo b = _reduced._getEnclosingBrace();
 1480  28 _storeInCache(key, b, pos - 1);
 1481  28 return b;
 1482    }
 1483   
 1484    /** Returns true if the reduced model's current position is inside a paren phrase. Only runs in the event thread.
 1485    * @return true if pos is immediately inside parentheses
 1486    */
 1487  2 private boolean _inParenPhrase() {
 1488   
 1489  2 BraceInfo info = _reduced._getEnclosingBrace();
 1490  2 return info.braceType().equals(BraceInfo.OPEN_PAREN);
 1491    // return _getLineEnclosingBrace(_currentLocation).braceType().equals(IndentInfo.openParen);
 1492    }
 1493   
 1494    // /** @return true if the start of the current line is inside a block comment. Assumes that write lock or read lock
 1495    // * and reduced lock are already held. */
 1496    // public boolean posInBlockComment() {
 1497    // int pos = _currentLocation;
 1498    // final int lineStart = getLineStartPos(pos);
 1499    // if (lineStart < POS_THRESHOLD) return posInBlockComment(lineStart);
 1500    // return cachedPosInBlockComment(lineStart);
 1501    // }
 1502   
 1503    // /** Returns true if given position is inside a block comment using cached information. Only runs in event thread/
 1504    // * @param pos a position at the beginning of a line.
 1505    // * @return true if pos is immediately inside a block comment.
 1506    // */
 1507    // private boolean cachedPosInBlockComment(final int pos) {
 1508    //
 1509    // // Check cache
 1510    // final Query key = new Query.PosInBlockComment(pos);
 1511    // final Boolean cached = (Boolean) _checkCache(key);
 1512    // if (cached != null) return cached.booleanValue();
 1513    //
 1514    // boolean result;
 1515    //
 1516    // final int startPrevLine = getLineStartPos(pos - 1);
 1517    // final Query prevLineKey = new Query.PosInBlockComment(startPrevLine);
 1518    // final Boolean cachedPrevLine = (Boolean) _checkCache(prevLineKey);
 1519    //
 1520    // if (cachedPrevLine != null) result = posInBlockComment(cachedPrevLine, startPrevLine, pos - startPrevLine);
 1521    // else result = posInBlockComment(pos);
 1522    //
 1523    // _storeInCache(key, result, pos - 1);
 1524    // return result;
 1525    // }
 1526   
 1527    /** Determines if pos lies within a block comment using the reduced model (ignoring the cache). Assumes that read
 1528    * lock and reduced lock are already held.
 1529    */
 1530  249 public boolean _inBlockComment(final int pos) {
 1531  249 final int here = _currentLocation;
 1532  249 final int distToStart = here - _getLineStartPos(here);
 1533  249 _reduced.resetLocation();
 1534  249 ReducedModelState state = stateAtRelLocation(-distToStart);
 1535   
 1536  249 return (state.equals(INSIDE_BLOCK_COMMENT));
 1537    }
 1538   
 1539    // /** Determines if pos lies within a block comment using the cached result for previous line. Assumes that read lock
 1540    // * and _reduced lock are already held.
 1541    // * @param resultPrevLine whether the start of the previous line is inside a block comment
 1542    // * @param startPrevLine the document offset of the start of the previous line
 1543    // * @param the len length of the previous line
 1544    // */
 1545    // private boolean posInBlockComment(final boolean resultPrevLine, final int startPrevLine, final int len) {
 1546    // try {
 1547    // final String text = getText(startPrevLine, len); // text of previous line
 1548    // if (resultPrevLine) return text.indexOf("*/") < 0; // inside a block comment unless "*/" found
 1549    // int startLineComment = text.indexOf("//");
 1550    // int startBlockComment = text.indexOf("/*");
 1551    // /* inside a block comment if "/*" found and it precedes "//" (if present) */
 1552    // return startBlockComment >= 0 && (startLineComment == -1 || startLineComment > startBlockComment);
 1553    // }
 1554    // catch(BadLocationException e) { throw new UnexpectedException(e); }
 1555    // }
 1556   
 1557    /** Returns true if the given position is not inside a paren/brace/etc phrase. Assumes that read lock and reduced
 1558    * lock are already held.
 1559    * @param pos the position we're looking at
 1560    * @return true if pos is immediately inside a paren/brace/etc
 1561    */
 1562  333 protected boolean notInBlock(final int pos) {
 1563    // Check cache
 1564  333 final Query key = new Query.PosNotInBlock(pos);
 1565  333 final Boolean cached = (Boolean) _checkCache(key);
 1566  0 if (cached != null) return cached.booleanValue();
 1567   
 1568  333 final int oldPos = _currentLocation;
 1569  333 setCurrentLocation(pos);
 1570  333 final BraceInfo info = _reduced._getEnclosingBrace();
 1571  333 final boolean notInParenPhrase = info.braceType().equals(BraceInfo.NONE);
 1572  333 setCurrentLocation(oldPos);
 1573  333 _storeInCache(key, notInParenPhrase, pos - 1);
 1574  333 return notInParenPhrase;
 1575    }
 1576   
 1577    /** Returns true if the current line has only blanks before the current location. Serves as a check so that
 1578    * indentation will only move the caret when it is at or before the "smart" beginning of a line (i.e. the first
 1579    * non-blank character). Only runs in the event thread.
 1580    * @return true if there are only blank characters before the current location on the current line.
 1581    */
 1582  45 private boolean onlyWhiteSpaceBeforeCurrent() throws BadLocationException{
 1583   
 1584  45 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1585   
 1586  45 int lineStart = _getLineStartPos(_currentLocation);
 1587  0 if (lineStart < 0) lineStart = 0; // _currentLocation on first line
 1588  45 int prefixSize = _currentLocation - lineStart;
 1589   
 1590    // get prefix of _currentLocation (the text after the previous new line, but before the current location)
 1591  45 String prefix = getText(lineStart, prefixSize);
 1592   
 1593    //check all positions in the prefix to determine if there are any blank chars
 1594  45 int pos = prefixSize - 1;
 1595  48 while (pos >= 0 && prefix.charAt(pos) == ' ') pos--;
 1596  45 return (pos < 0);
 1597    }
 1598   
 1599    /** Gets the number of blank characters between the current location and the first non-blank character or the end of
 1600    * the document, whichever comes first. TODO: cache it.
 1601    * (The method is misnamed.)
 1602    * @return the number of whitespace characters
 1603    */
 1604  20 private int _getWhiteSpace() throws BadLocationException {
 1605   
 1606  20 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1607   
 1608  20 int lineEnd = _getLineEndPos(_currentLocation); // index of next '\n' char or end of document
 1609  20 int lineLen = lineEnd - _currentLocation;
 1610  20 String line = getText(_currentLocation, lineLen);
 1611  20 int i;
 1612  20 for (i = 0; i < lineLen && line.charAt(i) == ' '; i++) ;
 1613  20 return i;
 1614    }
 1615   
 1616    /** Returns the size of the white space prefix before the current location. If the prefix contains any
 1617    * non white space chars, returns 0. Use definition of white space in String.trim()
 1618    * Assumes that the read lock is already held.
 1619    * @return true if there are only blank characters before the current location on the current line.
 1620    */
 1621  0 private int _getWhiteSpacePrefix() throws BadLocationException {
 1622   
 1623    // System.err.println("lockState = " + _lockState);
 1624   
 1625  0 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1626   
 1627  0 int lineStart = _getLineStartPos(_currentLocation);
 1628  0 if (lineStart < 0) lineStart = 0; // _currentLocation on first line
 1629  0 int prefixSize = _currentLocation - lineStart;
 1630   
 1631    // get prefix of _currentLocation (the text after the previous new line, but before the current location)
 1632  0 String prefix = getText(lineStart, prefixSize);
 1633   
 1634    //check all positions in the prefix to determine if there are any blank chars
 1635  0 int pos = prefixSize - 1;
 1636  0 while (pos >= 0 && prefix.charAt(pos) == ' ') pos--;
 1637  0 return (pos < 0) ? prefixSize : 0;
 1638    }
 1639   
 1640    /** Inserts the number of blanks specified as the whitespace prefix for the line identified by pos. The prefix
 1641    * replaces the prefix is already there. Assumes that the prefix consists of blanks. ASSUMES write lock is
 1642    * already held.g1
 1643    * @param tab The string to be placed between previous newline and first non-whitespace character
 1644    */
 1645  270 public void setTab(int tab, int pos) {
 1646   
 1647  270 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1648   
 1649  270 try {
 1650  270 int startPos = _getLineStartPos(pos);
 1651  270 int firstNonWSPos = _getLineFirstCharPos(pos);
 1652  270 int len = firstNonWSPos - startPos;
 1653   
 1654    // Adjust prefix
 1655  270 if (len != tab) {
 1656    // Only add or remove the difference
 1657  207 int diff = tab - len;
 1658  179 if (diff > 0) insertString(firstNonWSPos, StringOps.getBlankString(diff), null);
 1659  28 else remove(firstNonWSPos + diff, -diff);
 1660    }
 1661    /* else do nothing */
 1662    }
 1663    catch (BadLocationException e) {
 1664    // Should never see a bad location
 1665  0 throw new UnexpectedException(e);
 1666    }
 1667    }
 1668   
 1669    /** Inserts the string specified by tab at the beginning of the line identified by pos. ASSUMES write lock is
 1670    * already held.
 1671    * @param tab The string to be placed between previous newline and first non-whitespace character
 1672    */
 1673  27 public void setTab(String tab, int pos) {
 1674   
 1675  27 /* */ assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1676   
 1677  27 try {
 1678  27 int startPos = _getLineStartPos(pos);
 1679  27 int firstNonWSPos = _getLineFirstCharPos(pos);
 1680  27 int len = firstNonWSPos - startPos;
 1681   
 1682    // Remove the whole prefix, then add the new one
 1683  27 remove(startPos, len);
 1684  27 insertString(startPos, tab, null);
 1685    }
 1686    catch (BadLocationException e) {
 1687    // Should never see a bad location
 1688  0 throw new UnexpectedException(e);
 1689    }
 1690    }
 1691   
 1692    /** Updates document structure as a result of text insertion. This happens after the text has actually been inserted.
 1693    * Here we update the reduced model (using an {@link AbstractDJDocument.InsertCommand InsertCommand}) and store
 1694    * information for how to undo/redo the reduced model changes inside the {@link
 1695    * javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
 1696    * NOTE: an exclusive read lock on the document is already held when this code runs.
 1697    * @see edu.rice.cs.drjava.model.AbstractDJDocument.InsertCommand
 1698    * @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
 1699    * @see edu.rice.cs.drjava.model.definitions.DefinitionsDocument.CommandUndoableEdit
 1700    */
 1701  1565 protected void insertUpdate(AbstractDocument.DefaultDocumentEvent chng, AttributeSet attr) {
 1702   
 1703  1565 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1704   
 1705  1565 super.insertUpdate(chng, attr);
 1706   
 1707  1565 try {
 1708  1565 final int offset = chng.getOffset();
 1709  1565 final int length = chng.getLength();
 1710  1565 final String str = getText(offset, length);
 1711   
 1712  1565 if (length > 0) _clearCache(offset); // Selectively clear the query cache
 1713   
 1714  1565 Runnable doCommand =
 1715  1565 (length == 1) ? new CharInsertCommand(offset, str.charAt(0)) : new InsertCommand(offset, str);
 1716  1565 RemoveCommand undoCommand = new UninsertCommand(offset, length, str);
 1717   
 1718    // add the undo/redo
 1719  1565 addUndoRedo(chng, undoCommand, doCommand);
 1720    //chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
 1721    // actually do the insert
 1722  1565 doCommand.run(); // This method runs in the updating thread with exclusive access to the updated document
 1723    }
 1724  0 catch (BadLocationException ble) { throw new UnexpectedException(ble); }
 1725    }
 1726   
 1727    /** Updates document structure as a result of text removal. This happens within the swing remove operation before
 1728    * the text has actually been removed. Updates the reduced model (using a {@link AbstractDJDocument.RemoveCommand
 1729    * RemoveCommand}) and store information for how to undo/redo the reduced model changes inside the
 1730    * {@link javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
 1731    * NOTE: an exclusive read lock on the document is already held when this code runs.
 1732    * @see AbstractDJDocument.RemoveCommand
 1733    * @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
 1734    */
 1735  274 protected void removeUpdate(AbstractDocument.DefaultDocumentEvent chng) {
 1736   
 1737  274 assert Utilities.TEST_MODE || EventQueue.isDispatchThread();
 1738   
 1739  274 try {
 1740  274 final int offset = chng.getOffset();
 1741  274 final int length = chng.getLength();
 1742   
 1743  274 final String removedText = getText(offset, length);
 1744  274 super.removeUpdate(chng);
 1745   
 1746  274 if (length > 0) _clearCache(offset); // Selectively clear the query cache
 1747   
 1748  274 Runnable doCommand = new RemoveCommand(offset, length, removedText);
 1749  274 Runnable undoCommand = new UnremoveCommand(offset, removedText);
 1750   
 1751    // add the undo/redo info
 1752  274 addUndoRedo(chng, undoCommand, doCommand);
 1753    // actually do the removal from the reduced model
 1754  274 doCommand.run();
 1755    }
 1756  0 catch (BadLocationException e) { throw new UnexpectedException(e); }
 1757    }
 1758   
 1759    /** Returns the byte image (as written to a file) of this document. */
 1760  0 public byte[] getBytes() { return getText().getBytes(); }
 1761   
 1762  348 public void clear() {
 1763  348 try { remove(0, getLength()); }
 1764  0 catch(BadLocationException e) { throw new UnexpectedException(e); }
 1765    }
 1766   
 1767    /** @return true if pos is the position of one of the chars in an occurrence of "//" or "/*" in text. */
 1768  93176 private static boolean isCommentOpen(String text, int pos) {
 1769  93176 int len = text.length();
 1770  112 if (len < 2) return false;
 1771  10463 if (pos == len - 1) return isCommentStart(text, pos - 1);
 1772  4139 if (pos == 0) return isCommentStart(text, 0);
 1773  78462 return isCommentStart(text, pos - 1) || isCommentStart(text, pos);
 1774    }
 1775   
 1776    /** @return true if pos is index of string "//" or "/*" in text. Assumes pos < text.length() - 1 */
 1777  171461 private static boolean isCommentStart(String text, int pos) {
 1778  171461 char ch1 = text.charAt(pos);
 1779  171461 char ch2 = text.charAt(pos + 1);
 1780  171461 return ch1 == '/' && (ch2 == '/' || ch2 == '*');
 1781    }
 1782   
 1783    //Two abstract methods to delegate to the undo manager, if one exists.
 1784    protected abstract int startCompoundEdit();
 1785    protected abstract void endCompoundEdit(int i);
 1786    protected abstract void endLastCompoundEdit();
 1787    protected abstract void addUndoRedo(AbstractDocument.DefaultDocumentEvent chng, Runnable undoCommand,
 1788    Runnable doCommand);
 1789   
 1790    //Checks if the document is closed, and then throws an error if it is.
 1791   
 1792    //-------- INNER CLASSES ------------
 1793   
 1794    //--- Fields set only by InsertCommand, CharInsertCommand, and RemoveCommand
 1795   
 1796    /** Offset marking where line number changes begin due to an insertion or deletion. */
 1797    private volatile int _numLinesChangedAfter = -1;
 1798   
 1799