Source Code Cross Referenced for AbstractDJDocument.java in  » IDE » DrJava » edu » rice » cs » drjava » model » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE » DrJava » edu.rice.cs.drjava.model 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*BEGIN_COPYRIGHT_BLOCK
0002:         *
0003:         * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
0004:         * All rights reserved.
0005:         * 
0006:         * Redistribution and use in source and binary forms, with or without
0007:         * modification, are permitted provided that the following conditions are met:
0008:         *    * Redistributions of source code must retain the above copyright
0009:         *      notice, this list of conditions and the following disclaimer.
0010:         *    * Redistributions in binary form must reproduce the above copyright
0011:         *      notice, this list of conditions and the following disclaimer in the
0012:         *      documentation and/or other materials provided with the distribution.
0013:         *    * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
0014:         *      names of its contributors may be used to endorse or promote products
0015:         *      derived from this software without specific prior written permission.
0016:         * 
0017:         * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0018:         * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0019:         * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0020:         * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0021:         * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0022:         * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0023:         * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0024:         * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0025:         * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0026:         * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0027:         * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028:         *
0029:         * This software is Open Source Initiative approved Open Source Software.
0030:         * Open Source Initative Approved is a trademark of the Open Source Initiative.
0031:         * 
0032:         * This file is part of DrJava.  Download the current version of this project
0033:         * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
0034:         * 
0035:         * END_COPYRIGHT_BLOCK*/
0036:
0037:        package edu.rice.cs.drjava.model;
0038:
0039:        import edu.rice.cs.drjava.DrJava;
0040:        import edu.rice.cs.drjava.config.OptionConstants;
0041:        import edu.rice.cs.drjava.config.OptionEvent;
0042:        import edu.rice.cs.drjava.config.OptionListener;
0043:
0044:        import edu.rice.cs.drjava.model.definitions.DefinitionsDocument;
0045:        import edu.rice.cs.drjava.model.definitions.indent.Indenter;
0046:        import edu.rice.cs.drjava.model.definitions.reducedmodel.BraceReduction;
0047:        import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl;
0048:        import edu.rice.cs.drjava.model.definitions.reducedmodel.HighlightStatus;
0049:        import edu.rice.cs.drjava.model.definitions.reducedmodel.IndentInfo;
0050:        import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState;
0051:        import edu.rice.cs.drjava.model.definitions.ClassNameNotFoundException;
0052:
0053:        import edu.rice.cs.util.OperationCanceledException;
0054:        import edu.rice.cs.util.UnexpectedException;
0055:        import edu.rice.cs.util.swing.Utilities;
0056:        import edu.rice.cs.util.text.SwingDocument;
0057:
0058:        import java.util.HashSet;
0059:        import java.util.Hashtable;
0060:        import java.util.StringTokenizer;
0061:        import java.util.Vector;
0062:        import javax.swing.ProgressMonitor;
0063:        import javax.swing.text.AbstractDocument;
0064:        import javax.swing.text.AttributeSet;
0065:        import javax.swing.text.BadLocationException;
0066:        import javax.swing.text.Position;
0067:
0068:        /** Class containing code shared between the DefinitionsDocument and the InteractionsDJDocument. */
0069:        public abstract class AbstractDJDocument extends SwingDocument
0070:                implements  DJDocument, OptionConstants {
0071:
0072:            /*-------- FIELDS ----------*/
0073:
0074:            /** A set of normal endings for lines. */
0075:            protected static final HashSet<String> _normEndings = _makeNormEndings();
0076:            /** A set of Java keywords. */
0077:            protected static final HashSet<String> _keywords = _makeKeywords();
0078:            /** A set of Java keywords. */
0079:            protected static final HashSet<String> _primTypes = _makePrimTypes();
0080:            /** The default indent setting. */
0081:            protected volatile int _indent = 2;
0082:
0083:            /** The reduced model of the document (stored in field _reduced) handles most of the document logic and keeps 
0084:             *  track of state.  This field together with _currentLocation function as a virtual object for purposes of 
0085:             *  synchronization.  All operations that access or modify this virtual object should be synchronized on _reduced.
0086:             */
0087:            public final BraceReduction _reduced = new ReducedModelControl(); // public only for locking purposes
0088:
0089:            /** The absolute character offset in the document. */
0090:            protected volatile int _currentLocation = 0;
0091:
0092:            /* The fields _helperCache, _helperCacheHistory, and _cacheInUse function as a virtual object that is synchronized
0093:             * on operations that access or modify any of these fields.  The _helperCache object serves as the lock. 
0094:             */
0095:
0096:            /** Caches calls to the reduced model to speed up indent performance. Must be cleared every time 
0097:             *  the document is changed.  Use by calling _checkCache and _storeInCache.
0098:             */
0099:            private final Hashtable<String, Object> _helperCache = new Hashtable<String, Object>();
0100:
0101:            /** Keeps track of the order of elements added to the helper method cache, so that the oldest elements can be 
0102:             *  removed when the maximum size is reached.  A true LRU cache might be more effective, though I'm not sure
0103:             *  what the exact pattern of helper method reuse is-- this should be sufficient without significantly decreasing 
0104:             *  the effectiveness of the cache.
0105:             */
0106:            private final Vector<String> _helperCacheHistory = new Vector<String>();
0107:
0108:            /** Whether anything is stored in the cache.  It is used to avoid clearing the table
0109:             *  unnecessarily on every change to the document.
0110:             */
0111:            protected volatile boolean _cacheInUse;
0112:
0113:            /** Maximum number of elements to allow in the helper method cache.  Only encountered when indenting 
0114:             *  very large blocks, since the cache is cleared after each change to the document.
0115:             */
0116:            private static final int MAX_CACHE_SIZE = 10000;
0117:
0118:            /** Constant for starting position of document. */
0119:            public static final int DOCSTART = 0;
0120:
0121:            /** Constant used by helper methods to indicate an error. */
0122:            public static final int ERROR_INDEX = -1;
0123:
0124:            /** The instance of the indent decision tree used by Definitions documents. */
0125:            private volatile Indenter _indenter;
0126:
0127:            /* Saved here to allow the listener to be removed easily. This is needed to allow for garbage collection. */
0128:            private volatile OptionListener<Integer> _listener1;
0129:            private volatile OptionListener<Boolean> _listener2;
0130:
0131:            /*-------- CONSTRUCTORS --------*/
0132:
0133:            /** Standard default constructor; required because a unary constructor is defined. */
0134:            protected AbstractDJDocument() {
0135:                //    int ind = DrJava.getConfig().getSetting(INDENT_LEVEL).intValue();
0136:                //    _indenter = makeNewIndenter(ind); //new Indenter(ind);
0137:                //    _initNewIndenter();
0138:            }
0139:
0140:            /** Constructor used to build a new document with an existing indenter. Only used in tests. */
0141:            protected AbstractDJDocument(Indenter indent) {
0142:                _indenter = indent;
0143:            }
0144:
0145:            //-------- METHODS ---------//
0146:
0147:            /* acquireReadLock, releaseReadLock, acquireWriteLock, releaseWriteLock are inherited from SwingDocument. */
0148:
0149:            /** Returns a new indenter.  Assumes writeLock is held. */
0150:            protected abstract Indenter makeNewIndenter(int indentLevel);
0151:
0152:            /** Get the indenter.  Assumes writeLock is already held.
0153:             * @return the indenter
0154:             */
0155:            private Indenter getIndenter() {
0156:                if (_indenter == null) {
0157:                    int ind = DrJava.getConfig().getSetting(INDENT_LEVEL)
0158:                            .intValue();
0159:                    _indenter = makeNewIndenter(ind); //new Indenter(ind);
0160:                    _initNewIndenter();
0161:                }
0162:                return _indenter;
0163:            }
0164:
0165:            /** Get the indent level.
0166:             * @return the indent level
0167:             */
0168:            public int getIndent() {
0169:                return _indent;
0170:            }
0171:
0172:            /** Set the indent to a particular number of spaces.
0173:             * @param indent the size of indent that you want for the document
0174:             */
0175:            public void setIndent(final int indent) {
0176:                DrJava.getConfig().setSetting(INDENT_LEVEL, indent);
0177:                this ._indent = indent;
0178:            }
0179:
0180:            protected void _removeIndenter() {
0181:                DrJava.getConfig().removeOptionListener(INDENT_LEVEL,
0182:                        _listener1);
0183:                DrJava.getConfig().removeOptionListener(AUTO_CLOSE_COMMENTS,
0184:                        _listener2);
0185:            }
0186:
0187:            /** Only called from within getIndenter(). */
0188:            private void _initNewIndenter() {
0189:                // Create the indenter from the config values
0190:
0191:                final Indenter indenter = _indenter;
0192:
0193:                _listener1 = new OptionListener<Integer>() {
0194:                    public void optionChanged(OptionEvent<Integer> oce) {
0195:                        indenter.buildTree(oce.value.intValue());
0196:                    }
0197:                };
0198:
0199:                _listener2 = new OptionListener<Boolean>() {
0200:                    public void optionChanged(OptionEvent<Boolean> oce) {
0201:                        indenter.buildTree(DrJava.getConfig().getSetting(
0202:                                INDENT_LEVEL).intValue());
0203:                    }
0204:                };
0205:
0206:                DrJava.getConfig().addOptionListener(INDENT_LEVEL, _listener1);
0207:                DrJava.getConfig().addOptionListener(AUTO_CLOSE_COMMENTS,
0208:                        _listener2);
0209:            }
0210:
0211:            /** Create a set of normal endings, i.e., semi-colons and braces for the purposes of indenting.
0212:             *  @return the set of normal endings
0213:             */
0214:            protected static HashSet<String> _makeNormEndings() {
0215:                HashSet<String> normEndings = new HashSet<String>();
0216:                normEndings.add(";");
0217:                normEndings.add("{");
0218:                normEndings.add("}");
0219:                normEndings.add("(");
0220:                return normEndings;
0221:            }
0222:
0223:            /** Create a set of Java/GJ keywords for special coloring.
0224:             * @return the set of keywords
0225:             */
0226:            protected static HashSet<String> _makeKeywords() {
0227:                final String[] words = { "import", "native", "package", "goto",
0228:                        "const", "if", "else", "switch", "while", "for", "do",
0229:                        "true", "false", "null", "this", "super", "new",
0230:                        "instanceof", "return", "static", "synchronized",
0231:                        "transient", "volatile", "final", "strictfp", "throw",
0232:                        "try", "catch", "finally", "throws", "extends",
0233:                        "implements", "interface", "class", "break",
0234:                        "continue", "public", "protected", "private",
0235:                        "abstract", "case", "default", "assert", "enum" };
0236:                HashSet<String> keywords = new HashSet<String>();
0237:                for (int i = 0; i < words.length; i++) {
0238:                    keywords.add(words[i]);
0239:                }
0240:                return keywords;
0241:            }
0242:
0243:            /** Create a set of Java/GJ primitive types for special coloring.
0244:             * @return the set of primitive types
0245:             */
0246:            protected static HashSet<String> _makePrimTypes() {
0247:                final String[] words = { "boolean", "char", "byte", "short",
0248:                        "int", "long", "float", "double", "void", };
0249:                HashSet<String> prims = new HashSet<String>();
0250:                for (String w : words) {
0251:                    prims.add(w);
0252:                }
0253:                return prims;
0254:            }
0255:
0256:            /** Return all highlight status info for text between start and end. This should collapse adjoining blocks 
0257:             * with the same status into one.
0258:             */
0259:            public Vector<HighlightStatus> getHighlightStatus(int start, int end) {
0260:
0261:                if (start == end)
0262:                    return new Vector<HighlightStatus>(0);
0263:                Vector<HighlightStatus> v;
0264:
0265:                acquireReadLock();
0266:                try {
0267:                    synchronized (_reduced) {
0268:                        _setCurrentLocation(start);
0269:                        /* Now ask reduced model for highlight status for chars till end */
0270:                        v = _reduced.getHighlightStatus(start, end - start);
0271:
0272:                        /* Go through and find any NORMAL blocks. Within them check for keywords. */
0273:                        for (int i = 0; i < v.size(); i++) {
0274:                            HighlightStatus stat = v.get(i);
0275:                            if (stat.getState() == HighlightStatus.NORMAL)
0276:                                i = _highlightKeywords(v, i);
0277:                        }
0278:                    }
0279:                } finally {
0280:                    releaseReadLock();
0281:                }
0282:
0283:                // bstoler: Previously we moved back to the old location. This was
0284:                // very bad and severly slowed down rendering when scrolling.
0285:                // This is because parts are rendered in order. Thus, if old location is
0286:                // 0, but now we've scrolled to display 100000-100100, if we keep
0287:                // jumping back to 0 after getting every bit of highlight, it slows
0288:                // stuff down incredibly.
0289:                //setCurrentLocation(oldLocation);
0290:                return v;
0291:            }
0292:
0293:            /** Distinguishes keywords from normal text in the given HighlightStatus element. Specifically, it looks to see
0294:             * if the given text contains a keyword. If it does, it splits the HighlightStatus into separate blocks
0295:             * so that each keyword has its own block. This process identifies all keywords in the given block.
0296:             * Note that the given block must have state NORMAL.  Assumes that readLock is ALREADY HELD.
0297:             *
0298:             * @param v Vector with highlight info
0299:             * @param i Index of the single HighlightStatus to check for keywords in
0300:             * @return the index into the vector of the last processed element
0301:             */
0302:            private int _highlightKeywords(Vector<HighlightStatus> v, int i) {
0303:                // Basically all non-alphanumeric chars are delimiters
0304:                final String delimiters = " \t\n\r{}()[].+-/*;:=!@#$%^&*~<>?,\"`'<>|";
0305:                final HighlightStatus original = v.get(i);
0306:                final String text;
0307:
0308:                try {
0309:                    text = getText(original.getLocation(), original.getLength());
0310:                } catch (BadLocationException e) {
0311:                    throw new UnexpectedException(e);
0312:                }
0313:
0314:                // Because this text is not quoted or commented, we can use the simpler tokenizer StringTokenizer. We have 
0315:                // to return delimiters as tokens so we can keep track of positions in the original string.
0316:                StringTokenizer tokenizer = new StringTokenizer(text,
0317:                        delimiters, true);
0318:
0319:                // start and length of the text that has not yet been put back into the vector.
0320:                int start = original.getLocation();
0321:                int length = 0;
0322:
0323:                // Remove the old element from the vector.
0324:                v.remove(i);
0325:
0326:                // Index where we are in the vector. It's the location we would insert new things into.
0327:                int index = i;
0328:
0329:                boolean process;
0330:                int state = 0;
0331:                while (tokenizer.hasMoreTokens()) {
0332:                    String token = tokenizer.nextToken();
0333:
0334:                    //first check to see if we need highlighting
0335:                    process = false;
0336:                    if (_isType(token)) {
0337:                        //right now keywords incl prim types, so must put this first
0338:                        state = HighlightStatus.TYPE;
0339:                        process = true;
0340:                    } else if (_keywords.contains(token)) {
0341:                        state = HighlightStatus.KEYWORD;
0342:                        process = true;
0343:                    } else if (_isNum(token)) {
0344:                        state = HighlightStatus.NUMBER;
0345:                        process = true;
0346:                    }
0347:
0348:                    if (process) {
0349:                        // first check if we had any text before the token
0350:                        if (length != 0) {
0351:                            HighlightStatus newStat = new HighlightStatus(
0352:                                    start, length, original.getState());
0353:                            v.add(index, newStat);
0354:                            index++;
0355:                            start += length;
0356:                            length = 0;
0357:                        }
0358:
0359:                        // Now pull off the keyword
0360:                        int keywordLength = token.length();
0361:                        v.add(index, new HighlightStatus(start, keywordLength,
0362:                                state));
0363:                        index++;
0364:                        // Move start to the end of the keyword
0365:                        start += keywordLength;
0366:                    } else {
0367:                        // This is not a keyword, so just keep accumulating length
0368:                        length += token.length();
0369:                    }
0370:                }
0371:                // Now check if there was any text left after the keywords.
0372:                if (length != 0) {
0373:                    HighlightStatus newStat = new HighlightStatus(start,
0374:                            length, original.getState());
0375:                    v.add(index, newStat);
0376:                    index++;
0377:                    length = 0;
0378:                }
0379:                // return one before because we need to point to the last one we inserted
0380:                return index - 1;
0381:            }
0382:
0383:            /** Checks to see if the current string is a number
0384:             *  @return true if x is a parseable number
0385:             */
0386:            private boolean _isNum(String x) {
0387:                try {
0388:                    Double.parseDouble(x);
0389:                    return true;
0390:                } catch (NumberFormatException e) {
0391:                    return false;
0392:                }
0393:            }
0394:
0395:            /** Checks to see if the current string is a type. A type is assumed to be a primitive type OR
0396:             *  anything else that begins with a capitalized character
0397:             */
0398:            private boolean _isType(String x) {
0399:                if (_primTypes.contains(x))
0400:                    return true;
0401:
0402:                try {
0403:                    return Character.isUpperCase(x.charAt(0));
0404:                } catch (IndexOutOfBoundsException e) {
0405:                    return false;
0406:                }
0407:            }
0408:
0409:            /** Returns whether the given text only has spaces. */
0410:            private boolean _hasOnlySpaces(String text) {
0411:                return (text.trim().length() == 0);
0412:            }
0413:
0414:            /** Fire event that styles changed from current location to the end.
0415:             *  Right now we do this every time there is an insertion or removal.
0416:             *  Two possible future optimizations:
0417:             *  <ol>
0418:             *  <li>Only fire changed event if text other than that which was inserted
0419:             *     or removed *actually* changed status. If we didn't changed the status
0420:             *     of other text (by inserting or deleting unmatched pair of quote or
0421:             *     comment chars), no change need be fired.
0422:             *  <li>If a change must be fired, we could figure out the exact end
0423:             *     of what has been changed. Right now we fire the event saying that
0424:             *     everything changed to the end of the document.
0425:             *  </ol>
0426:             *
0427:             *  I don't think we'll need to do either one since it's still fast now.
0428:             *  I think this is because the UI only actually paints the things on the screen anyway.
0429:             */
0430:            protected abstract void _styleChanged();
0431:
0432:            /** Clears the memoizing cache for read operations on the document.  This 
0433:             * operation must be done before the document is modified since the contents 
0434:             * of this cache are invalidated by any modification to the document.
0435:             */
0436:            protected void clearCache() {
0437:                synchronized (_helperCache) {
0438:                    if (_cacheInUse)
0439:                        _clearCache();
0440:                }
0441:            }
0442:
0443:            /** Clears the helper method cache.  Should be called every time the document is modified. */
0444:            private void _clearCache() {
0445:                _helperCache.clear();
0446:                _helperCacheHistory.clear();
0447:                _cacheInUse = false;
0448:            }
0449:
0450:            /** Add a character to the underlying reduced model. ONLY called from _reduced synchronized code!
0451:             *  @param curChar the character to be added. */
0452:            private void _addCharToReducedModel(char curChar) {
0453:                clearCache();
0454:                _reduced.insertChar(curChar);
0455:            }
0456:
0457:            /** Get the current location of the cursor in the document.  Unlike the usual swing document model, 
0458:             *  which is stateless, because of our implementation of the underlying reduced model, we need to 
0459:             *  keep track of the current location.
0460:             * @return where the cursor is as the number of characters into the document 
0461:             */
0462:            public int getCurrentLocation() {
0463:                return _currentLocation;
0464:            }
0465:
0466:            /** Change the current location of the document
0467:             * @param loc the new absolute location 
0468:             */
0469:            public void setCurrentLocation(int loc) {
0470:                acquireReadLock();
0471:                try {
0472:                    _setCurrentLocation(loc);
0473:                } finally {
0474:                    releaseReadLock();
0475:                }
0476:            }
0477:
0478:            /** Change the current location of the document assuming that ReadLock is already held
0479:             * @param loc the new absolute location 
0480:             */
0481:            private void _setCurrentLocation(int loc) {
0482:                synchronized (_reduced) {
0483:                    int dist = loc - _currentLocation; // _currentLocation and _reduced can be updated asynchronously
0484:                    _currentLocation = loc;
0485:                    _reduced.move(dist);
0486:                }
0487:            }
0488:
0489:            /** The actual cursor movement logic.  Helper for setCurrentLocation(int).
0490:             *  @param dist the distance from the current location to the new location.
0491:             */
0492:            public void move(int dist) {
0493:                acquireReadLock();
0494:                try {
0495:                    synchronized (_reduced) {
0496:                        int newLoc = _currentLocation + dist;
0497:                        //        // location is set asynchronously when caret is moved so the following adjustment is necessary
0498:                        //        // should no longer be true
0499:                        //        if (newLoc < 0) {
0500:                        //          assert false; // should never get here
0501:                        //          dist -= newLoc; // increase dist by error in newLoc
0502:                        //          newLoc = 0;
0503:                        //        }
0504:                        //        else {
0505:                        //          int len = getLength();
0506:                        //          if (newLoc > len) {
0507:                        //            assert false; // should never get here
0508:                        //            dist -= (newLoc - len); // decrease dist by error in newLoc
0509:                        //            newLoc = len;
0510:                        //          }
0511:                        //        }
0512:                        _currentLocation = newLoc;
0513:                        _reduced.move(dist);
0514:                    }
0515:                } finally {
0516:                    releaseReadLock();
0517:                }
0518:            }
0519:
0520:            /** Forwarding method to find the match for the closing brace immediately to the left, assuming 
0521:             *  there is such a brace.
0522:             *  @return the relative distance backwards to the offset before the matching brace.
0523:             */
0524:            public int balanceBackward() {
0525:                acquireReadLock();
0526:                try {
0527:                    synchronized (_reduced) {
0528:                        return _reduced.balanceBackward();
0529:                    }
0530:                } finally {
0531:                    releaseReadLock();
0532:                }
0533:            }
0534:
0535:            /** Forwarding method to find the match for the open brace immediately to the right, assuming there 
0536:             * is such a brace.
0537:             * @return the relative distance forwards to the offset after the matching brace.
0538:             */
0539:            public int balanceForward() {
0540:                acquireReadLock();
0541:                try {
0542:                    synchronized (_reduced) {
0543:                        return _reduced.balanceForward();
0544:                    }
0545:                } finally {
0546:                    releaseReadLock();
0547:                }
0548:            }
0549:
0550:            /** This method is used ONLY for testing.  This method is UNSAFE in any other context!
0551:             * @return The reduced model of this document.
0552:             */
0553:            public BraceReduction getReduced() {
0554:                return _reduced;
0555:            }
0556:
0557:            /** Returns the indent information for the current location. */
0558:            public IndentInfo getIndentInformation() {
0559:                // Check cache
0560:                String key = "getIndentInformation:" + _currentLocation;
0561:
0562:                IndentInfo cached = (IndentInfo) _checkCache(key);
0563:                if (cached != null)
0564:                    return cached;
0565:
0566:                IndentInfo info;
0567:                acquireReadLock();
0568:                try {
0569:                    synchronized (_reduced) {
0570:                        info = _reduced.getIndentInformation();
0571:                    }
0572:                    _storeInCache(key, info);
0573:                } finally {
0574:                    releaseReadLock();
0575:                }
0576:
0577:                return info;
0578:            }
0579:
0580:            public ReducedModelState stateAtRelLocation(int dist) {
0581:                acquireReadLock();
0582:                try {
0583:                    synchronized (_reduced) {
0584:                        return _reduced.moveWalkerGetState(dist);
0585:                    }
0586:                } finally {
0587:                    releaseReadLock();
0588:                }
0589:            }
0590:
0591:            public ReducedModelState getStateAtCurrent() {
0592:                acquireReadLock();
0593:                try {
0594:                    synchronized (_reduced) {
0595:                        return _reduced.getStateAtCurrent();
0596:                    }
0597:                } finally {
0598:                    releaseReadLock();
0599:                }
0600:            }
0601:
0602:            public void resetReducedModelLocation() {
0603:                acquireReadLock();
0604:                try {
0605:                    synchronized (_reduced) {
0606:                        _reduced.resetLocation();
0607:                    }
0608:                } finally {
0609:                    releaseReadLock();
0610:                }
0611:            }
0612:
0613:            /** Searching backwards, finds the position of the enclosing brace.  NB: ignores comments.
0614:             * @param pos Position to start from
0615:             * @param opening opening brace character
0616:             * @param closing closing brace character
0617:             * @return position of enclosing squiggly brace, or ERROR_INDEX if beginning
0618:             * of document is reached.
0619:             */
0620:            public int findPrevEnclosingBrace(int pos, char opening,
0621:                    char closing) throws BadLocationException {
0622:                // Check cache
0623:                final StringBuilder keyBuf = new StringBuilder(
0624:                        "findPrevEnclosingBrace:").append(opening).append(':')
0625:                        .append(closing).append(':').append(pos);
0626:                final String key = keyBuf.toString();
0627:                final Integer cached = (Integer) _checkCache(key);
0628:                if (cached != null)
0629:                    return cached.intValue();
0630:
0631:                if ((pos >= getLength()) || (pos == DOCSTART)) {
0632:                    return ERROR_INDEX;
0633:                }
0634:
0635:                final char[] delims = { opening, closing };
0636:                int reducedPos = pos;
0637:                int i; // index of for loop below
0638:                int braceBalance = 0;
0639:
0640:                acquireReadLock();
0641:                try {
0642:                    String text = getText(DOCSTART, pos);
0643:
0644:                    synchronized (_reduced) {
0645:                        final int origLocation = _currentLocation;
0646:                        // Move reduced model to location pos
0647:                        _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0648:
0649:                        // Walk backwards from specificed position
0650:                        for (i = pos - 1; i >= DOCSTART; i--) {
0651:                            /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims, 
0652:                             * DOCSTART <= i < reducedPos <= pos */
0653:
0654:                            if (match(text.charAt(i), delims)) {
0655:                                // Move reduced model to walker's location
0656:                                _reduced.move(i - reducedPos); // reduced model points to i
0657:                                reducedPos = i; // reduced model points to reducedPos
0658:
0659:                                // Check if matching char should be ignored because it is within a comment, 
0660:                                // quotes, or ignored paren phrase
0661:                                ReducedModelState state = _reduced
0662:                                        .getStateAtCurrent();
0663:                                if (!state.equals(ReducedModelState.FREE)
0664:                                        || _isStartOfComment(text, i)
0665:                                        || ((i > 0) && _isStartOfComment(text,
0666:                                                i - 1)))
0667:                                    continue; // ignore matching char 
0668:                                else {
0669:                                    // found valid matching char
0670:                                    if (text.charAt(i) == closing)
0671:                                        ++braceBalance;
0672:                                    else {
0673:                                        if (braceBalance == 0)
0674:                                            break; // found our opening brace
0675:                                        --braceBalance;
0676:                                    }
0677:                                }
0678:                            }
0679:                        }
0680:
0681:                        /* Invariant: same as for loop except that DOCSTART-1 <= i <= reducedPos <= pos */
0682:
0683:                        _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
0684:                    } // end synchronized
0685:
0686:                    if (i == DOCSTART - 1)
0687:                        reducedPos = ERROR_INDEX; // No matching char was found
0688:                    _storeInCache(key, reducedPos);
0689:                } finally {
0690:                    releaseReadLock();
0691:                }
0692:
0693:                // Return position of matching char or ERROR_INDEX 
0694:                return reducedPos;
0695:            }
0696:
0697:            /** Searching forward, finds the position of the enclosing squiggly brace. NB: ignores comments.
0698:             *  @param pos Position to start from
0699:             *  @param opening opening brace character
0700:             *  @param closing closing brace character
0701:             *  @return position of enclosing squiggly brace, or ERROR_INDEX if beginning of document is reached.
0702:             */
0703:            public int findNextEnclosingBrace(int pos, char opening,
0704:                    char closing) throws BadLocationException {
0705:                // Check cache
0706:                final StringBuilder keyBuf = new StringBuilder(
0707:                        "findNextEnclosingBrace:").append(opening).append(':')
0708:                        .append(closing).append(':').append(pos);
0709:                final String key = keyBuf.toString();
0710:                final Integer cached = (Integer) _checkCache(key);
0711:
0712:                if (cached != null)
0713:                    return cached.intValue();
0714:                if (pos >= getLength() - 1) {
0715:                    return ERROR_INDEX;
0716:                }
0717:
0718:                final char[] delims = { opening, closing };
0719:                int reducedPos = pos;
0720:                int i; // index of for loop below
0721:                int braceBalance = 0;
0722:
0723:                acquireReadLock();
0724:                String text = getText();
0725:                try {
0726:                    synchronized (_reduced) {
0727:                        final int origLocation = _currentLocation;
0728:                        // Move reduced model to location pos
0729:                        _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0730:
0731:                        // Walk forward from specificed position
0732:                        for (i = pos + 1; i < text.length(); i++) {
0733:                            /* Invariant: reduced model points to reducedPos, text[pos:i-1] contains no valid delims, 
0734:                             * pos <= reducedPos < i <= text.length() */
0735:
0736:                            if (match(text.charAt(i), delims)) {
0737:                                // Move reduced model to walker's location
0738:                                _reduced.move(i - reducedPos); // reduced model points to i
0739:                                reducedPos = i; // reduced model points to reducedPos
0740:
0741:                                // Check if matching char should be ignored because it is within a comment, 
0742:                                // quotes, or ignored paren phrase
0743:                                ReducedModelState state = _reduced
0744:                                        .getStateAtCurrent();
0745:                                if (!state.equals(ReducedModelState.FREE)
0746:                                        || _isStartOfComment(text, i)
0747:                                        || ((i > 0) && _isStartOfComment(text,
0748:                                                i - 1)))
0749:                                    continue; // ignore matching char 
0750:                                else {
0751:                                    // found valid matching char
0752:                                    if (text.charAt(i) == opening) {
0753:                                        ++braceBalance;
0754:                                    } else {
0755:                                        if (braceBalance == 0)
0756:                                            break; // found our closing brace
0757:                                        --braceBalance;
0758:                                    }
0759:                                }
0760:                            }
0761:                        }
0762:
0763:                        /* Invariant: same as for loop except that pos <= reducedPos <= i <= text.length() */
0764:
0765:                        _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
0766:                    } // end synchronized
0767:
0768:                    if (i == text.length())
0769:                        reducedPos = ERROR_INDEX; // No matching char was found
0770:                    _storeInCache(key, reducedPos);
0771:                } finally {
0772:                    releaseReadLock();
0773:                }
0774:
0775:                // Return position of matching char or ERROR_INDEX     
0776:                return reducedPos;
0777:            }
0778:
0779:            /** Searching backwards, finds the position of the first character that is one of the given delimiters.  Does
0780:             *  not look for delimiters inside paren phrases (e.g., skips semicolons used inside for statements.)  
0781:             *  NB: ignores comments.
0782:             *  @param pos Position to start from
0783:             *  @param delims array of characters to search for
0784:             *  @return position of first matching delimiter, or ERROR_INDEX if beginning of document is reached.
0785:             */
0786:            public int findPrevDelimiter(int pos, char[] delims)
0787:                    throws BadLocationException {
0788:                return findPrevDelimiter(pos, delims, true);
0789:            }
0790:
0791:            /** Searching backwards, finds the position of the first character that is one of the given delimiters.  
0792:             *  Will not look for delimiters inside a paren phrase if skipParenPhrases is true. NB: ignores comments.
0793:             *  @param pos Position to start from
0794:             *  @param delims array of characters to search for
0795:             *  @param skipParenPhrases whether to look for delimiters inside paren phrases
0796:             *  @return position of first matching delimiter, or ERROR_INDEX if beginning of document is reached.
0797:             */
0798:            public int findPrevDelimiter(final int pos, final char[] delims,
0799:                    final boolean skipParenPhrases) throws BadLocationException {
0800:                // Check cache
0801:                final StringBuilder keyBuf = new StringBuilder(
0802:                        "findPrevDelimiter:").append(pos);
0803:                for (char ch : delims) {
0804:                    keyBuf.append(':').append(ch);
0805:                }
0806:                keyBuf.append(':').append(skipParenPhrases);
0807:                final String key = keyBuf.toString();
0808:                final Integer cached = (Integer) _checkCache(key);
0809:                if (cached != null)
0810:                    return cached.intValue();
0811:
0812:                int reducedPos = pos;
0813:                int i; // index of for loop below
0814:                acquireReadLock();
0815:                try {
0816:                    String text = getText(DOCSTART, pos);
0817:
0818:                    synchronized (_reduced) {
0819:                        final int origLocation = _currentLocation;
0820:                        // Move reduced model to location pos
0821:                        _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0822:
0823:                        // Walk backwards from specificed position
0824:                        for (i = pos - 1; i >= DOCSTART; i--) {
0825:                            /* Invariant: reduced model points to reducedPos, text[i+1:pos] contains no valid delims, 
0826:                             * DOCSTART <= i < reducedPos <= pos */
0827:
0828:                            if (match(text.charAt(i), delims)) {
0829:                                // Move reduced model to walker's location
0830:                                _reduced.move(i - reducedPos); // reduced model points to i
0831:                                reducedPos = i; // reduced model points to reducedPos
0832:
0833:                                // Check if matching char should be ignored because it is within a comment, quotes, or ignored paren phrase
0834:                                ReducedModelState state = _reduced
0835:                                        .getStateAtCurrent();
0836:                                if (!state.equals(ReducedModelState.FREE)
0837:                                        || _isStartOfComment(text, i)
0838:                                        || ((i > 0) && _isStartOfComment(text,
0839:                                                i - 1))
0840:                                        || (skipParenPhrases && posInParenPhrase()))
0841:                                    continue; // ignore matching char 
0842:                                else
0843:                                    break; // found valid matching char
0844:                            }
0845:                        }
0846:
0847:                        /* Invariant: same as for loop except that DOCSTART-1 <= i <= reducedPos <= pos */
0848:
0849:                        _reduced.move(origLocation - reducedPos); // Restore the state of the reduced model;
0850:                    } // end synchronized
0851:
0852:                    if (i == DOCSTART - 1)
0853:                        reducedPos = ERROR_INDEX; // No matching char was found
0854:                    _storeInCache(key, reducedPos);
0855:                } finally {
0856:                    releaseReadLock();
0857:                }
0858:
0859:                // Return position of matching char or ERROR_INDEX 
0860:                return reducedPos;
0861:            }
0862:
0863:            private static boolean match(char c, char[] delims) {
0864:                for (char d : delims) {
0865:                    if (c == d)
0866:                        return true;
0867:                } // Found matching delimiter
0868:                return false;
0869:            }
0870:
0871:            /** This function finds the given character in the same statement as the given position, and before the given
0872:             *  position.  It is used by QuestionExistsCharInStmt and QuestionExistsCharInPrevStmt
0873:             */
0874:            public boolean findCharInStmtBeforePos(char findChar, int position) {
0875:                if (position == DefinitionsDocument.ERROR_INDEX) {
0876:                    String mesg = "Argument endChar to QuestionExistsCharInStmt must be a char that exists on the current line.";
0877:                    throw new UnexpectedException(new IllegalArgumentException(
0878:                            mesg));
0879:                }
0880:
0881:                char[] findCharDelims = { findChar, ';', '{', '}' };
0882:                int prevFindChar;
0883:
0884:                // Find the position of the preceding occurrence findChar position (looking in paren phrases as well)
0885:                boolean found;
0886:
0887:                acquireReadLock();
0888:                try {
0889:                    prevFindChar = this .findPrevDelimiter(position,
0890:                            findCharDelims, false);
0891:
0892:                    if ((prevFindChar == DefinitionsDocument.ERROR_INDEX)
0893:                            || (prevFindChar < 0))
0894:                        return false; // no such char
0895:
0896:                    // Determine if prevFindChar is findChar or the end of statement delimiter
0897:                    String foundString = this .getText(prevFindChar, 1);
0898:                    char foundChar = foundString.charAt(0);
0899:                    found = (foundChar == findChar);
0900:                } catch (Throwable t) {
0901:                    throw new UnexpectedException(t);
0902:                } finally {
0903:                    releaseReadLock();
0904:                }
0905:                return found;
0906:            }
0907:
0908:            /** Finds the position of the first non-whitespace, non-comment character before pos.
0909:             *  Skips comments and all whitespace, including newlines.
0910:             *  @param pos Position to start from
0911:             *  @param whitespace chars considered as white space
0912:             *  @return position of first non-whitespace character before pos OR ERROR_INDEX if no such char
0913:             */
0914:            public int findPrevCharPos(int pos, char[] whitespace)
0915:                    throws BadLocationException {
0916:                // Check cache
0917:                final StringBuilder keyBuf = new StringBuilder(
0918:                        "findPrevCharPos:").append(pos);
0919:                for (char ch : whitespace) {
0920:                    keyBuf.append(':').append(ch);
0921:                }
0922:                final String key = keyBuf.toString();
0923:                final Integer cached = (Integer) _checkCache(key);
0924:                if (cached != null)
0925:                    return cached.intValue();
0926:
0927:                int reducedPos = pos;
0928:                int i = pos - 1;
0929:                String text;
0930:                acquireReadLock();
0931:                try {
0932:                    text = getText(0, pos);
0933:
0934:                    synchronized (_reduced) {
0935:
0936:                        final int origLocation = _currentLocation;
0937:                        // Move reduced model to location pos
0938:                        _reduced.move(pos - origLocation); // reduced model points to pos == reducedPos
0939:
0940:                        // Walk backward from specified position
0941:
0942:                        while (i >= 0) {
0943:                            /* Invariant: reduced model points to reducedPos, i < reducedPos <= pos, 
0944:                             * text[i+1:pos-1] contains invalid chars */
0945:
0946:                            if (match(text.charAt(i), whitespace)) {
0947:                                // ith char is whitespace
0948:                                i--;
0949:                                continue;
0950:                            }
0951:
0952:                            // Found a non-whitespace char;  move reduced model to location i
0953:                            _reduced.move(i - reducedPos);
0954:                            reducedPos = i; // reduced model points to i == reducedPos
0955:
0956:                            // Check if matching char is within a comment (not including opening two characters)
0957:                            if ((_reduced.getStateAtCurrent()
0958:                                    .equals(ReducedModelState.INSIDE_LINE_COMMENT))
0959:                                    || (_reduced.getStateAtCurrent()
0960:                                            .equals(ReducedModelState.INSIDE_BLOCK_COMMENT))) {
0961:                                i--;
0962:                                continue;
0963:                            }
0964:
0965:                            if (_isReversteStartOfComment(text, i)) { /* char is second character in opening comment marker */
0966:                                // Move i past the first comment character and continue searching
0967:                                i = i - 2;
0968:                                continue;
0969:                            }
0970:
0971:                            // Found valid previous character
0972:                            break;
0973:                        }
0974:
0975:                        /* Exit invariant same as for loop except that i <= reducedPos because at break i = reducedPos */
0976:                        _reduced.move(origLocation - reducedPos);
0977:                    }
0978:
0979:                    int result = reducedPos;
0980:                    if (i < 0)
0981:                        result = ERROR_INDEX;
0982:                    _storeInCache(key, result);
0983:                    return result;
0984:                } finally {
0985:                    releaseReadLock();
0986:                }
0987:
0988:            }
0989:
0990:            /** Checks the helper method cache for a stored value.  Returns the value if it has been cached, or null 
0991:             *  otherwise. Calling convention for keys: methodName:arg1:arg2
0992:             *  @param key Name of the method and arguments
0993:             */
0994:            protected Object _checkCache(String key) {
0995:                //_helperCache.put(key+"|time", new Long(System.currentTimeMillis()));
0996:                Object result = _helperCache.get(key); /* already synchronized by Hashtable */
0997:                //if (result != null) DrJava.consoleOut().println("Using cache for " + key);
0998:                return result;
0999:            }
1000:
1001:            /** Stores the given result in the helper method cache. Calling convention for keys: methodName:arg1:arg2
1002:             *  @param key Name of method and arguments
1003:             *  @param result Result of the method call
1004:             */
1005:            protected void _storeInCache(String key, Object result) {
1006:                synchronized (_helperCache) {
1007:                    _cacheInUse = true;
1008:
1009:                    // Prevent going over max size
1010:                    if (_helperCache.size() >= MAX_CACHE_SIZE) {
1011:                        if (_helperCacheHistory.size() > 0) {
1012:                            _helperCache.remove(_helperCacheHistory.get(0));
1013:                            _helperCacheHistory.remove(0);
1014:                        } else { // Should not happen
1015:                            throw new RuntimeException(
1016:                                    "Cache larger than cache history!");
1017:                        }
1018:                    }
1019:                    Object prev = _helperCache.put(key, result);
1020:                    // Add to history if the insert increased the size of the table
1021:                    if (prev == null)
1022:                        _helperCacheHistory.add(key);
1023:                }
1024:            }
1025:
1026:            /** Default indentation - uses OTHER flag and no progress indicator.
1027:             *  @param selStart the offset of the initial character of the region to indent
1028:             *  @param selEnd the offset of the last character of the region to indent
1029:             */
1030:            public void indentLines(int selStart, int selEnd) {
1031:                try {
1032:                    indentLines(selStart, selEnd, Indenter.IndentReason.OTHER,
1033:                            null);
1034:                } catch (OperationCanceledException oce) {
1035:                    // Indenting without a ProgressMonitor should never be cancelled!
1036:                    throw new UnexpectedException(oce);
1037:                }
1038:            }
1039:
1040:            /** Parameterized indentation for special-case handling.  If selStart == selEnd, then the line containing the
1041:             * _currentLocation is indented.  The values of selStart and selEnd are ignored!
1042:             * @param selStart the offset of the initial character of the region to indent
1043:             * @param selEnd the offset of the last character of the region to indent
1044:             * @param reason a flag from {@link Indenter} to indicate the reason for the indent
1045:             *        (indent logic may vary slightly based on the trigger action)
1046:             * @param pm used to display progress, null if no reporting is desired
1047:             */
1048:            public void indentLines(int selStart, int selEnd,
1049:                    Indenter.IndentReason reason, ProgressMonitor pm)
1050:                    throws OperationCanceledException {
1051:
1052:                // Begins a compound edit.
1053:                // int key = startCompoundEdit(); // commented out in connection with the FrenchKeyBoard Fix
1054:
1055:                acquireWriteLock();
1056:                try {
1057:                    synchronized (_reduced) {
1058:                        if (selStart == selEnd) { // single line to indent
1059:                        //          Utilities.showDebug("selStart = " + selStart + " currentLocation = " + _currentLocation);
1060:                            Position oldCurrentPosition = createUnwrappedPosition(_currentLocation);
1061:
1062:                            // Indent, updating current location if necessary.
1063:                            //          Utilities.showDebug("Indenting line at offset " + selStart);
1064:                            if (_indentLine(reason)) {
1065:                                _setCurrentLocation(oldCurrentPosition
1066:                                        .getOffset());
1067:                                if (onlyWhiteSpaceBeforeCurrent()) {
1068:                                    int space = getWhiteSpace();
1069:                                    move(space);
1070:                                }
1071:                            }
1072:                        } else
1073:                            _indentBlock(selStart, selEnd, reason, pm);
1074:                    }
1075:                } catch (Throwable t) {
1076:                    throw new UnexpectedException(t);
1077:                } finally {
1078:                    releaseWriteLock();
1079:                }
1080:
1081:                // Ends the compound edit.
1082:                //endCompoundEdit(key);   //Changed to endLastCompoundEdit in connection with the FrenchKeyBoard Fix
1083:                endLastCompoundEdit();
1084:            }
1085:
1086:            /** Indents the lines between and including the lines containing points start and end.  Assumes that writeLock
1087:             *  and _reduced locks are already held.
1088:             *  @param start Position in document to start indenting from
1089:             *  @param end Position in document to end indenting at
1090:             *  @param reason a flag from {@link Indenter} to indicate the reason for the indent
1091:             *        (indent logic may vary slightly based on the trigger action)
1092:             *  @param pm used to display progress, null if no reporting is desired
1093:             */
1094:            private void _indentBlock(final int start, final int end,
1095:                    Indenter.IndentReason reason, ProgressMonitor pm)
1096:                    throws OperationCanceledException, BadLocationException {
1097:
1098:                // Keep marker at the end. This Position will be the correct endpoint no matter how we change 
1099:                // the doc doing the indentLine calls.
1100:                final Position endPos = this .createUnwrappedPosition(end);
1101:                // Iterate, line by line, until we get to/past the end
1102:                int walker = start;
1103:                while (walker < endPos.getOffset()) {
1104:                    _setCurrentLocation(walker);
1105:                    // Keep pointer to walker position that will stay current
1106:                    // regardless of how indentLine changes things
1107:                    Position walkerPos = this .createUnwrappedPosition(walker);
1108:                    // Indent current line
1109:                    // We ignore current location info from each line, because it probably doesn't make sense in a block context.
1110:                    _indentLine(reason); // this operation is atomic
1111:                    // Move back to walker spot
1112:                    _setCurrentLocation(walkerPos.getOffset());
1113:                    walker = walkerPos.getOffset();
1114:
1115:                    if (pm != null) {
1116:                        pm.setProgress(walker); // Update ProgressMonitor.
1117:                        if (pm.isCanceled())
1118:                            throw new OperationCanceledException(); // Check for cancel button-press.
1119:                    }
1120:
1121:                    // Adding 1 makes us point to the first character AFTER the next newline. We don't actually move the
1122:                    // location yet. That happens at the top of the loop, after we check if we're past the end. 
1123:                    walker += _reduced.getDistToNextNewline() + 1;
1124:                }
1125:            }
1126:
1127:            /** Indents a line using the Indenter.  Public ONLY for testing purposes. Assumes writeLock is already held.*/
1128:            public boolean _indentLine(Indenter.IndentReason reason) {
1129:                return getIndenter().indent(this , reason);
1130:            }
1131:
1132:            /** Returns the "intelligent" beginning of line.  If currPos is to the right of the first 
1133:             *  non-whitespace character, the position of the first non-whitespace character is returned.  
1134:             *  If currPos is at or to the left of the first non-whitespace character, the beginning of
1135:             *  the line is returned.
1136:             *  @param currPos A position on the current line
1137:             */
1138:            public int getIntelligentBeginLinePos(int currPos)
1139:                    throws BadLocationException {
1140:                String prefix;
1141:                int firstChar;
1142:                acquireReadLock();
1143:                try {
1144:                    firstChar = getLineStartPos(currPos);
1145:                    prefix = getText(firstChar, currPos - firstChar);
1146:                } finally {
1147:                    releaseReadLock();
1148:                }
1149:
1150:                // Walk through string until we find a non-whitespace character
1151:                int i;
1152:                int len = prefix.length();
1153:
1154:                for (i = 0; i < len; i++) {
1155:                    if (!Character.isWhitespace(prefix.charAt(i)))
1156:                        break;
1157:                }
1158:
1159:                // If we found a non-WS char left of curr pos, return it
1160:                if (i < len) {
1161:                    int firstRealChar = firstChar + i;
1162:                    if (firstRealChar < currPos)
1163:                        return firstRealChar;
1164:                }
1165:                // Otherwise, return the beginning of the line
1166:                return firstChar;
1167:            }
1168:
1169:            /** Returns the indent level of the start of the statement that the cursor is on.  Uses a default 
1170:             *  set of delimiters. (';', '{', '}') and a default set of whitespace characters (' ', '\t', n', ',')
1171:             *  @param pos Cursor position
1172:             */
1173:            public String getIndentOfCurrStmt(int pos)
1174:                    throws BadLocationException {
1175:                char[] delims = { ';', '{', '}' };
1176:                char[] whitespace = { ' ', '\t', '\n', ',' };
1177:                return getIndentOfCurrStmt(pos, delims, whitespace);
1178:            }
1179:
1180:            /** Returns the indent level of the start of the statement that the cursor is on.  Uses a default
1181:             *  set of whitespace characters: {' ', '\t', '\n', ','}
1182:             *  @param pos Cursor position
1183:             */
1184:            public String getIndentOfCurrStmt(int pos, char[] delims)
1185:                    throws BadLocationException {
1186:                char[] whitespace = { ' ', '\t', '\n', ',' };
1187:                return getIndentOfCurrStmt(pos, delims, whitespace);
1188:            }
1189:
1190:            /** Returns the indent level of the start of the statement that the cursor is on.
1191:             *  @param pos Cursor position
1192:             *  @param delims Delimiter characters denoting end of statement
1193:             *  @param whitespace characters to skip when looking for beginning of next statement
1194:             */
1195:            public String getIndentOfCurrStmt(int pos, char[] delims,
1196:                    char[] whitespace) throws BadLocationException {
1197:                // Check cache
1198:                final StringBuilder keyBuf = new StringBuilder(
1199:                        "getIndentOfCurrStmt:").append(pos);
1200:                for (char ch : delims) {
1201:                    keyBuf.append(':').append(ch);
1202:                }
1203:                final String key = keyBuf.toString();
1204:                final String cached = (String) _checkCache(key);
1205:                if (cached != null)
1206:                    return cached;
1207:
1208:                String lineText;
1209:
1210:                acquireReadLock();
1211:                try {
1212:                    synchronized (_reduced) {
1213:                        // Get the start of the current line
1214:                        int lineStart = getLineStartPos(pos);
1215:
1216:                        // Find the previous delimiter that closes a statement
1217:                        boolean reachedStart = false;
1218:                        int prevDelim = lineStart;
1219:                        boolean ignoreParens = posInParenPhrase(prevDelim);
1220:
1221:                        do {
1222:                            prevDelim = findPrevDelimiter(prevDelim, delims,
1223:                                    ignoreParens);
1224:                            if (prevDelim > 0 && prevDelim < getLength()
1225:                                    && getText(prevDelim, 1).charAt(0) == '{')
1226:                                break;
1227:                            if (prevDelim == ERROR_INDEX) { // no delimiter found
1228:                                reachedStart = true;
1229:                                break;
1230:                            }
1231:                            ignoreParens = posInParenPhrase(prevDelim);
1232:                        } while (ignoreParens);
1233:
1234:                        // From the previous delimiter, find the next non-whitespace character
1235:                        int nextNonWSChar;
1236:                        if (reachedStart)
1237:                            nextNonWSChar = getFirstNonWSCharPos(DOCSTART);
1238:                        else
1239:                            nextNonWSChar = getFirstNonWSCharPos(prevDelim + 1,
1240:                                    whitespace, false);
1241:
1242:                        // If the end of the document was reached
1243:                        if (nextNonWSChar == ERROR_INDEX)
1244:                            nextNonWSChar = getLength();
1245:
1246:                        // Get the start of the line of the non-ws char
1247:                        int lineStartStmt = getLineStartPos(nextNonWSChar);
1248:
1249:                        // Get the position of the first non-ws character on this line
1250:                        int lineFirstNonWS = getLineFirstCharPos(lineStartStmt);
1251:                        lineText = getText(lineStartStmt, lineFirstNonWS
1252:                                - lineStartStmt);
1253:                        _storeInCache(key, lineText);
1254:                    }
1255:                } catch (Exception e) {
1256:                    throw new UnexpectedException(e);
1257:                } finally {
1258:                    releaseReadLock();
1259:                }
1260:
1261:                return lineText;
1262:            }
1263:
1264:            /** Determines if the given character exists on the line where the given cursor position is.  Does not 
1265:             *  search in quotes or comments. <b>Does not work if character being searched for is a '/' or a '*'</b>.
1266:             *  @param pos Cursor position
1267:             *  @param findChar Character to search for
1268:             *  @return true if this node's rule holds.
1269:             */
1270:            public int findCharOnLine(int pos, char findChar) {
1271:                // Check cache
1272:                String key = "findCharOnLine:" + pos + ":" + findChar;
1273:                Integer cached = (Integer) _checkCache(key);
1274:                if (cached != null)
1275:                    return cached.intValue();
1276:
1277:                int i;
1278:                int matchIndex; // absolute index of matching character 
1279:
1280:                acquireReadLock();
1281:                try {
1282:                    synchronized (_reduced) {
1283:                        int here = _currentLocation;
1284:                        int lineStart = getLineStartPos(pos);
1285:                        int lineEnd = getLineEndPos(pos);
1286:                        String lineText = getText(lineStart, lineEnd
1287:                                - lineStart);
1288:                        i = lineText.indexOf(findChar, 0);
1289:                        matchIndex = i + lineStart;
1290:
1291:                        while (i != -1) { // match found
1292:                            /* Invariant: reduced model points to original location (here), lineText[0:i-1] does not contain valid 
1293:                             *            findChar, lineText[i] == findChar which may or may not be valid. */
1294:
1295:                            // Move reduced model to location of ith char
1296:                            _reduced.move(matchIndex - here); // move reduced model to location matchIndex
1297:
1298:                            // Check if matching char is in comment or quotes
1299:                            if (_reduced.getStateAtCurrent().equals(
1300:                                    ReducedModelState.FREE)) {
1301:                                // Found matching char
1302:                                _reduced.move(here - matchIndex); // Restore reduced model
1303:                                break;
1304:                            }
1305:
1306:                            // matching character is not valid, try again
1307:                            _reduced.move(here - matchIndex); // Restore reduced model
1308:                            i = lineText.indexOf(findChar, i + 1);
1309:                        }
1310:                    }
1311:
1312:                    if (i == -1)
1313:                        matchIndex = ERROR_INDEX;
1314:                    _storeInCache(key, matchIndex);
1315:                } catch (Throwable t) {
1316:                    throw new UnexpectedException(t);
1317:                } finally {
1318:                    releaseReadLock();
1319:                }
1320:
1321:                return matchIndex;
1322:            }
1323:
1324:            /** Returns the absolute position of the beginning of the current line.  (Just after most recent newline, or DOCSTART)
1325:             *  Doesn't ignore comments.
1326:             *  @param pos Any position on the current line
1327:             *  @return position of the beginning of this line
1328:             */
1329:            public int getLineStartPos(final int pos) {
1330:                if (pos < 0 || pos > getLength())
1331:                    return -1;
1332:                // Check cache
1333:                String key = "getLineStartPos:" + pos;
1334:                Integer cached = (Integer) _checkCache(key);
1335:                if (cached != null)
1336:                    return cached.intValue();
1337:
1338:                int dist;
1339:                acquireReadLock();
1340:                try {
1341:                    synchronized (_reduced) {
1342:                        int location = _currentLocation;
1343:                        _reduced.move(pos - location);
1344:                        dist = _reduced.getDistToPreviousNewline(0);
1345:                        _reduced.move(location - pos);
1346:                    }
1347:
1348:                    if (dist == -1) {
1349:                        // No previous newline was found; return DOCSTART
1350:                        _storeInCache(key, DOCSTART);
1351:                        return DOCSTART;
1352:                    }
1353:                    _storeInCache(key, pos - dist);
1354:                } finally {
1355:                    releaseReadLock();
1356:                }
1357:
1358:                return pos - dist;
1359:            }
1360:
1361:            /** Returns the absolute position of the end of the current line.  (At the next newline, or the end of the document.)
1362:             *  @param pos Any position on the current line
1363:             *  @return position of the end of this line
1364:             */
1365:            public int getLineEndPos(final int pos) {
1366:                if (pos < 0 || pos > getLength())
1367:                    return -1;
1368:
1369:                // Check cache
1370:                String key = "getLineEndPos:" + pos;
1371:                Integer cached = (Integer) _checkCache(key);
1372:                if (cached != null)
1373:                    return cached.intValue();
1374:
1375:                int dist;
1376:                acquireReadLock();
1377:                try {
1378:                    synchronized (_reduced) {
1379:                        int location = _currentLocation;
1380:                        _reduced.move(pos - location);
1381:                        dist = _reduced.getDistToNextNewline();
1382:                        _reduced.move(location - pos);
1383:                    }
1384:                    _storeInCache(key, pos + dist);
1385:                } finally {
1386:                    releaseReadLock();
1387:                }
1388:
1389:                return pos + dist;
1390:            }
1391:
1392:            /** Returns the absolute position of the first non-whitespace character on the current line.
1393:             *  NB: Doesn't ignore comments.
1394:             *  @param pos position on the line
1395:             *  @return position of first non-whitespace character on this line, or the end
1396:             *  of the line if no non-whitespace character is found.
1397:             */
1398:            public int getLineFirstCharPos(int pos) throws BadLocationException {
1399:                // Check cache
1400:                String key = "getLineFirstCharPos:" + pos;
1401:                Integer cached = (Integer) _checkCache(key);
1402:                if (cached != null)
1403:                    return cached.intValue();
1404:
1405:                acquireReadLock();
1406:                try {
1407:                    int startLinePos = getLineStartPos(pos);
1408:                    int endLinePos = getLineEndPos(pos);
1409:
1410:                    // Get all text on this line
1411:                    String text = this .getText(startLinePos, endLinePos
1412:                            - startLinePos);
1413:                    int walker = 0;
1414:                    while (walker < text.length()) {
1415:                        if (text.charAt(walker) == ' '
1416:                                || text.charAt(walker) == '\t')
1417:                            walker++;
1418:                        else {
1419:                            _storeInCache(key, startLinePos + walker);
1420:                            return startLinePos + walker;
1421:                        }
1422:                    }
1423:                    // No non-WS char found, so return last position on line
1424:                    _storeInCache(key, endLinePos);
1425:                    return endLinePos;
1426:                } finally {
1427:                    releaseReadLock();
1428:                }
1429:            }
1430:
1431:            /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace, 
1432:             * including newlines
1433:             * @param pos Position to start from
1434:             * @return position of first non-whitespace character after pos, or ERROR_INDEX if end of document is reached
1435:             */
1436:            public int getFirstNonWSCharPos(int pos)
1437:                    throws BadLocationException {
1438:                char[] whitespace = { ' ', '\t', '\n' };
1439:                return getFirstNonWSCharPos(pos, whitespace, false);
1440:            }
1441:
1442:            /** Similar to the single-argument version, but allows including comments.
1443:             * @param pos Position to start from
1444:             * @param acceptComments if true, find non-whitespace chars in comments
1445:             * @return position of first non-whitespace character after pos,
1446:             * or ERROR_INDEX if end of document is reached
1447:             */
1448:            public int getFirstNonWSCharPos(int pos, boolean acceptComments)
1449:                    throws BadLocationException {
1450:                char[] whitespace = { ' ', '\t', '\n' };
1451:                return getFirstNonWSCharPos(pos, whitespace, acceptComments);
1452:            }
1453:
1454:            /** Finds the position of the first non-whitespace character after pos. NB: Skips comments and all whitespace, 
1455:             *  including newlines.
1456:             *  @param pos Position to start from
1457:             *  @param whitespace array of whitespace chars to ignore
1458:             *  @param acceptComments if true, find non-whitespace chars in comments
1459:             *  @return position of first non-whitespace character after pos, or ERROR_INDEX if end of document is reached
1460:             */
1461:            public int getFirstNonWSCharPos(int pos, char[] whitespace,
1462:                    boolean acceptComments) throws BadLocationException {
1463:                // Check cache
1464:                final StringBuilder keyBuf = new StringBuilder(
1465:                        "getFirstNonWSCharPos:").append(pos);
1466:                for (char ch : whitespace) {
1467:                    keyBuf.append(':').append(ch);
1468:                }
1469:                final String key = keyBuf.toString();
1470:
1471:                final Integer cached = (Integer) _checkCache(key);
1472:                if (cached != null)
1473:                    return cached.intValue();
1474:
1475:                int result = ERROR_INDEX; // variable used to hold result to be returned
1476:
1477:                acquireReadLock();
1478:                try {
1479:
1480:                    int i = pos;
1481:                    int endPos = getLength();
1482:
1483:                    // Get text from pos to end of document
1484:                    String text = getText(pos, endPos - pos);
1485:
1486:                    final int origLocation = _currentLocation;
1487:                    // Move reduced model to location pos
1488:                    synchronized (_reduced) {
1489:                        _reduced.move(pos - origLocation);
1490:                        int reducedPos = pos;
1491:
1492:                        //int iter = 0;
1493:
1494:                        // Walk forward from specificed position
1495:                        while (i < endPos) {
1496:
1497:                            // Check if character is whitespace
1498:                            if (match(text.charAt(i - pos), whitespace)) {
1499:                                i++;
1500:                                continue;
1501:                            }
1502:                            // Found a non whitespace character
1503:                            // Move reduced model to walker's location
1504:                            _reduced.move(i - reducedPos); // reduced model points to location i
1505:                            reducedPos = i; // reduced mdoel points to location reducedPos
1506:
1507:                            // Check if non-ws char is within comment and if we want to ignore them.
1508:                            if (!acceptComments
1509:                                    && ((_reduced.getStateAtCurrent()
1510:                                            .equals(ReducedModelState.INSIDE_LINE_COMMENT)) || (_reduced
1511:                                            .getStateAtCurrent()
1512:                                            .equals(ReducedModelState.INSIDE_BLOCK_COMMENT)))) {
1513:                                i++;
1514:                                continue;
1515:                            }
1516:
1517:                            // Check if non-ws char is part of comment opening market and if we want to ignore them
1518:                            if (!acceptComments
1519:                                    && _isStartOfComment(text, i - pos)) {
1520:                                // ith char is first char in comment open market; skip past this marker
1521:                                // and continue searching
1522:                                i = i + 2;
1523:                                continue;
1524:                            }
1525:
1526:                            // Return position of matching char
1527:                            break;
1528:                        }
1529:                        _reduced.move(origLocation - reducedPos);
1530:
1531:                        result = reducedPos;
1532:                        if (i == endPos)
1533:                            result = ERROR_INDEX;
1534:                    }
1535:                    _storeInCache(key, result);
1536:                } finally {
1537:                    releaseReadLock();
1538:                }
1539:
1540:                return result;
1541:            }
1542:
1543:            public int findPrevNonWSCharPos(int pos)
1544:                    throws BadLocationException {
1545:                char[] whitespace = { ' ', '\t', '\n' };
1546:                return findPrevCharPos(pos, whitespace);
1547:            }
1548:
1549:            /** Helper method for getFirstNonWSCharPos Determines whether the current character is the start of a comment: 
1550:             *  "/*" or "//"
1551:             */
1552:            protected static boolean _isStartOfComment(String text, int pos) {
1553:                char currChar = text.charAt(pos);
1554:                if (currChar == '/') {
1555:                    try {
1556:                        char afterCurrChar = text.charAt(pos + 1);
1557:                        if ((afterCurrChar == '/') || (afterCurrChar == '*'))
1558:                            return true;
1559:                    } catch (StringIndexOutOfBoundsException e) {
1560:                    }
1561:                }
1562:                return false;
1563:            }
1564:
1565:            /** Helper method for findPrevNonWSCharPos. Determines whether the current character is the start of a comment
1566:             *  encountered from the end: '/' or '*' preceded by a '/'.
1567:             *  @return true if (pos-1,pos) == '/*' or '//'
1568:             */
1569:            protected static boolean _isReversteStartOfComment(String text,
1570:                    int pos) {
1571:                char currChar = text.charAt(pos);
1572:                if ((currChar == '/') || (currChar == '*')) {
1573:                    try {
1574:                        char beforeCurrChar = text.charAt(pos - 1);
1575:                        if (beforeCurrChar == '/')
1576:                            return true;
1577:                    } catch (StringIndexOutOfBoundsException e) { /* do nothing */
1578:                    }
1579:                }
1580:                return false;
1581:            }
1582:
1583:            /** Returns true if the given position is inside a paren phrase.
1584:             *  @param pos the position we're looking at
1585:             *  @return true if pos is immediately inside parentheses
1586:             */
1587:            public boolean posInParenPhrase(int pos) {
1588:                // Check cache
1589:                String key = "posInParenPhrase:" + pos;
1590:                Boolean cached = (Boolean) _checkCache(key);
1591:                if (cached != null)
1592:                    return cached.booleanValue();
1593:
1594:                boolean inParenPhrase;
1595:
1596:                acquireReadLock();
1597:                try {
1598:                    synchronized (_reduced) {
1599:                        int here = _currentLocation;
1600:                        _reduced.move(pos - here);
1601:                        inParenPhrase = posInParenPhrase();
1602:                        _reduced.move(here - pos);
1603:                    }
1604:                    _storeInCache(key, Boolean.valueOf(inParenPhrase));
1605:                } finally {
1606:                    releaseReadLock();
1607:                }
1608:
1609:                return inParenPhrase;
1610:            }
1611:
1612:            /**
1613:             * Returns true if the reduced model's current position is inside a paren phrase.
1614:             * @return true if pos is immediately inside parentheses
1615:             */
1616:            public boolean posInParenPhrase() {
1617:                IndentInfo info;
1618:                acquireReadLock();
1619:                try {
1620:                    synchronized (_reduced) {
1621:                        info = _reduced.getIndentInformation();
1622:                    }
1623:                } finally {
1624:                    releaseReadLock();
1625:                }
1626:                return info.braceTypeCurrent.equals(IndentInfo.openParen);
1627:            }
1628:
1629:            /** Returns true if the given position is not inside a paren/brace/etc phrase.  Assumes that read lock is ALREADY HELD.
1630:             *  @param pos the position we're looking at
1631:             *  @return true if pos is immediately inside a paren/brace/etc
1632:             */
1633:            protected boolean posNotInBlock(int pos) {
1634:                // Check cache
1635:                String key = "posNotInBlock:" + pos;
1636:                Boolean cached = (Boolean) _checkCache(key);
1637:                if (cached != null)
1638:                    return cached.booleanValue();
1639:
1640:                boolean notInParenPhrase;
1641:
1642:                synchronized (_reduced) {
1643:                    int here = _currentLocation;
1644:                    _reduced.move(pos - here);
1645:                    IndentInfo info = _reduced.getIndentInformation();
1646:                    notInParenPhrase = info.braceTypeCurrent
1647:                            .equals(IndentInfo.noBrace);
1648:                    _reduced.move(here - pos);
1649:                }
1650:                _storeInCache(key, Boolean.valueOf(notInParenPhrase));
1651:                return notInParenPhrase;
1652:            }
1653:
1654:            /** Gets the number of whitespace characters between the current location and the end of
1655:             *  the document or the first non-whitespace character, whichever comes first.
1656:             *  @return the number of whitespace characters
1657:             */
1658:            public int getWhiteSpace() {
1659:                try {
1660:                    return getWhiteSpaceBetween(0, getLength()
1661:                            - _currentLocation);
1662:                } catch (BadLocationException e) {
1663:                    e.printStackTrace();
1664:                }
1665:                return -1;
1666:            }
1667:
1668:            /** Starts at start and gets whitespace starting at relStart and either stopping at relEnd or at the first 
1669:             *  non-white space char.
1670:             *  NOTE: relStart and relEnd are relative to where we are in the document relStart must be <= _currentLocation
1671:             *  @exception BadLocationException
1672:             */
1673:            private int getWhiteSpaceBetween(int relStart, int relEnd)
1674:                    throws BadLocationException {
1675:                String text = this .getText(_currentLocation - relStart, Math
1676:                        .abs(relStart - relEnd));
1677:                int i = 0;
1678:                int length = text.length();
1679:                while ((i < length) && (text.charAt(i) == ' '))
1680:                    i++;
1681:                return i;
1682:            }
1683:
1684:            /** Returns true if the current line has only white space before the current location. Serves as a check so that
1685:             * indentation will only move the caret when it is at or before the "smart" beginning of a line (i.e. the first
1686:             * non-whitespace character
1687:             * @return true if there are only whitespace characters before the current location on the current line.
1688:             */
1689:            private boolean onlyWhiteSpaceBeforeCurrent()
1690:                    throws BadLocationException {
1691:                String text = this .getText(0, _currentLocation);
1692:                //get the text after the previous new line, but before the current location
1693:                text = text.substring(text.lastIndexOf("\n") + 1);
1694:
1695:                //check all positions in the new text to determine if there are any non-whitespace chars
1696:                int index = text.length() - 1;
1697:                char lastChar = ' ';
1698:                while (lastChar == ' ' && index >= 0) {
1699:                    lastChar = text.charAt(index);
1700:                    index--;
1701:                }
1702:
1703:                if (index < 0)
1704:                    return true;
1705:                return false;
1706:            }
1707:
1708:            /** Sets text between previous newline and first non-whitespace character of line containing pos to tab.
1709:             *  @param tab String to be placed between previous newline and first
1710:             *  non-whitespace character
1711:             */
1712:            public void setTab(String tab, int pos) {
1713:                try {
1714:                    int startPos = getLineStartPos(pos);
1715:                    int firstNonWSPos = getLineFirstCharPos(pos);
1716:                    int len = firstNonWSPos - startPos;
1717:
1718:                    // Adjust prefix
1719:                    boolean onlySpaces = _hasOnlySpaces(tab);
1720:                    if (!onlySpaces || (len != tab.length())) {
1721:
1722:                        if (onlySpaces) {
1723:                            // Only add or remove the difference
1724:                            int diff = tab.length() - len;
1725:                            if (diff > 0) {
1726:                                insertString(firstNonWSPos, tab.substring(0,
1727:                                        diff), null);
1728:                            } else {
1729:                                remove(firstNonWSPos + diff, -diff);
1730:                            }
1731:                        } else {
1732:                            // Remove the whole prefix, then add the new one
1733:                            remove(startPos, len);
1734:                            insertString(startPos, tab, null);
1735:                        }
1736:                    }
1737:                } catch (BadLocationException e) {
1738:                    // Should never see a bad location
1739:                    throw new UnexpectedException(e);
1740:                }
1741:            }
1742:
1743:            /** Updates document structure as a result of text insertion. This happens after the text has actually been inserted.
1744:             *  Here we update the reduced model (using an {@link AbstractDJDocument.InsertCommand InsertCommand}) and store 
1745:             *  information for how to undo/redo the reduced model changes inside the {@link 
1746:             *  javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
1747:             *
1748:             *  @see edu.rice.cs.drjava.model.AbstractDJDocument.InsertCommand
1749:             *  @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
1750:             *  @see edu.rice.cs.drjava.model.definitions.DefinitionsDocument.CommandUndoableEdit
1751:             */
1752:            protected void insertUpdate(
1753:                    AbstractDocument.DefaultDocumentEvent chng,
1754:                    AttributeSet attr) {
1755:                // Clear the helper method cache
1756:                clearCache();
1757:
1758:                super .insertUpdate(chng, attr);
1759:
1760:                try {
1761:                    final int offset = chng.getOffset();
1762:                    final int length = chng.getLength();
1763:                    final String str = getText(offset, length);
1764:
1765:                    InsertCommand doCommand = new InsertCommand(offset, str);
1766:                    RemoveCommand undoCommand = new RemoveCommand(offset,
1767:                            length);
1768:
1769:                    // add the undo/redo
1770:                    addUndoRedo(chng, undoCommand, doCommand);
1771:                    //chng.addEdit(new CommandUndoableEdit(undoCommand, doCommand));
1772:                    // actually do the insert
1773:                    doCommand.run();
1774:                } catch (BadLocationException ble) {
1775:                    throw new UnexpectedException(ble);
1776:                }
1777:            }
1778:
1779:            /** Updates document structure as a result of text removal. This happens within the swing remove operation before
1780:             *  the text has actually been removed. Here we update the reduced model (using a {@link AbstractDJDocument.RemoveCommand
1781:             *  RemoveCommand}) and store information for how to undo/redo the reduced model changes inside the 
1782:             *  {@link javax.swing.text.AbstractDocument.DefaultDocumentEvent DefaultDocumentEvent}.
1783:             *  @see AbstractDJDocument.RemoveCommand
1784:             *  @see javax.swing.text.AbstractDocument.DefaultDocumentEvent
1785:             */
1786:            protected void removeUpdate(
1787:                    AbstractDocument.DefaultDocumentEvent chng) {
1788:                clearCache();
1789:
1790:                try {
1791:                    final int offset = chng.getOffset();
1792:                    final int length = chng.getLength();
1793:                    final String removedText = getText(offset, length);
1794:                    super .removeUpdate(chng);
1795:
1796:                    Runnable doCommand = new RemoveCommand(offset, length);
1797:                    Runnable undoCommand = new InsertCommand(offset,
1798:                            removedText);
1799:
1800:                    // add the undo/redo info
1801:                    addUndoRedo(chng, undoCommand, doCommand);
1802:                    // actually do the removal from the reduced model
1803:                    doCommand.run();
1804:                } catch (BadLocationException e) {
1805:                    throw new UnexpectedException(e);
1806:                }
1807:            }
1808:
1809:            /** Inserts a string of text into the document.  Custom processing of the insert is not done here;
1810:             *  that is done in {@link #insertUpdate}.
1811:             */
1812:            public void insertString(int offset, String str, AttributeSet a)
1813:                    throws BadLocationException {
1814:
1815:                acquireWriteLock();
1816:                try {
1817:                    synchronized (_reduced) { // Prevent updates to the reduced model during this change
1818:                        clearCache(); // Clear the helper method cache
1819:                        super .insertString(offset, str, a);
1820:                    }
1821:                } finally {
1822:                    releaseWriteLock();
1823:                }
1824:            }
1825:
1826:            /** Removes a block of text from the specified location.  We don't update the reduced model here; that happens
1827:             *  in {@link #removeUpdate}.
1828:             */
1829:            public void remove(final int offset, final int len)
1830:                    throws BadLocationException {
1831:
1832:                acquireWriteLock();
1833:                try {
1834:                    synchronized (_reduced) {
1835:                        clearCache(); // Clear the helper method cache
1836:                        super .remove(offset, len);
1837:                    }
1838:                    ;
1839:                } finally {
1840:                    releaseWriteLock();
1841:                }
1842:            }
1843:
1844:            public String getText() {
1845:                acquireReadLock();
1846:                try {
1847:                    return getText(0, getLength());
1848:                } catch (BadLocationException e) {
1849:                    throw new UnexpectedException(e);
1850:                } finally {
1851:                    releaseReadLock();
1852:                }
1853:            }
1854:
1855:            /** Returns the byte image (as written to a file) of this document. */
1856:            public byte[] getBytes() {
1857:                return getText().getBytes();
1858:            }
1859:
1860:            public void clear() {
1861:                acquireWriteLock();
1862:                try {
1863:                    remove(0, getLength());
1864:                } catch (BadLocationException e) {
1865:                    throw new UnexpectedException(e);
1866:                } finally {
1867:                    releaseWriteLock();
1868:                }
1869:            }
1870:
1871:            //Two abstract methods to delegate to the undo manager, if one exists.
1872:            protected abstract int startCompoundEdit();
1873:
1874:            protected abstract void endCompoundEdit(int i);
1875:
1876:            protected abstract void endLastCompoundEdit();
1877:
1878:            protected abstract void addUndoRedo(
1879:                    AbstractDocument.DefaultDocumentEvent chng,
1880:                    Runnable undoCommand, Runnable doCommand);
1881:
1882:            //Checks if the document is closed, and then throws an error if it is.
1883:
1884:            //-------- INNER CLASSES ------------
1885:
1886:            protected class InsertCommand implements  Runnable {
1887:                private final int _offset;
1888:                private final String _text;
1889:
1890:                public InsertCommand(final int offset, final String text) {
1891:                    _offset = offset;
1892:                    _text = text;
1893:                }
1894:
1895:                public void run() {
1896:                    // adjust location to the start of the text to input
1897:                    acquireReadLock();
1898:                    try {
1899:                        synchronized (_reduced) {
1900:                            _reduced.move(_offset - _currentLocation);
1901:                            int len = _text.length();
1902:                            // loop over string, inserting characters into reduced model
1903:                            for (int i = 0; i < len; i++) {
1904:                                char curChar = _text.charAt(i);
1905:                                _addCharToReducedModel(curChar);
1906:                            }
1907:                            _currentLocation = _offset + len; // current location is at end of inserted string
1908:                            _styleChanged();
1909:                        }
1910:                    } finally {
1911:                        releaseReadLock();
1912:                    }
1913:                }
1914:            }
1915:
1916:            protected class RemoveCommand implements  Runnable {
1917:                private final int _offset;
1918:                private final int _length;
1919:
1920:                public RemoveCommand(final int offset, final int length) {
1921:                    _offset = offset;
1922:                    _length = length;
1923:                }
1924:
1925:                public void run() {
1926:                    acquireReadLock();
1927:                    try {
1928:                        synchronized (_reduced) {
1929:                            _setCurrentLocation(_offset);
1930:                            _reduced.delete(_length);
1931:                            _styleChanged();
1932:                        }
1933:                    } finally {
1934:                        releaseReadLock();
1935:                    }
1936:                }
1937:            }
1938:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.