Fixed: malfunctioned "Remove trailing spaces on editor save"
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / JavaHeuristicScanner.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text;
12
13 import java.util.Arrays;
14
15 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
16
17 import org.eclipse.jface.text.Assert;
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.IDocument;
20 import org.eclipse.jface.text.IRegion;
21 import org.eclipse.jface.text.ITypedRegion;
22 import org.eclipse.jface.text.Region;
23 import org.eclipse.jface.text.TextUtilities;
24
25 /**
26  * Utility methods for heuristic based Java manipulations in an incomplete Java
27  * source file.
28  * 
29  * <p>
30  * An instance holds some internal position in the document and is therefore not
31  * threadsafe.
32  * </p>
33  * 
34  * @since 3.0
35  */
36 public class JavaHeuristicScanner implements Symbols {
37         /**
38          * Returned by all methods when the requested position could not be found,
39          * or if a {@link BadLocationException} was thrown while scanning.
40          */
41         public static final int NOT_FOUND = -1;
42
43         /**
44          * Special bound parameter that means either -1 (backward scanning) or
45          * <code>fDocument.getLength()</code> (forward scanning).
46          */
47         public static final int UNBOUND = -2;
48
49         /* character constants */
50         private static final char LBRACE = '{';
51
52         private static final char RBRACE = '}';
53
54         private static final char LPAREN = '(';
55
56         private static final char RPAREN = ')';
57
58         private static final char SEMICOLON = ';';
59
60         private static final char COLON = ':';
61
62         private static final char COMMA = ',';
63
64         private static final char LBRACKET = '[';
65
66         private static final char RBRACKET = ']';
67
68         private static final char QUESTIONMARK = '?';
69
70         private static final char EQUAL = '=';
71
72         /**
73          * Specifies the stop condition, upon which the <code>scanXXX</code>
74          * methods will decide whether to keep scanning or not. This interface may
75          * implemented by clients.
76          */
77         public interface StopCondition {
78                 /**
79                  * Instructs the scanner to return the current position.
80                  * 
81                  * @param ch
82                  *            the char at the current position
83                  * @param position
84                  *            the current position
85                  * @param forward
86                  *            the iteration direction
87                  * @return <code>true</code> if the stop condition is met.
88                  */
89                 boolean stop(char ch, int position, boolean forward);
90         }
91
92         /**
93          * Stops upon a non-whitespace (as defined by
94          * {@link Character#isWhitespace(char)}) character.
95          */
96         private static class NonWhitespace implements StopCondition {
97                 /*
98                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
99                  */
100                 public boolean stop(char ch, int position, boolean forward) {
101                         return !Character.isWhitespace(ch);
102                 }
103         }
104
105         /**
106          * Stops upon a non-whitespace character in the default partition.
107          * 
108          * @see NonWhitespace
109          */
110         private class NonWhitespaceDefaultPartition extends NonWhitespace {
111                 /*
112                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
113                  */
114                 public boolean stop(char ch, int position, boolean forward) {
115                         return super.stop(ch, position, true)
116                                         && isDefaultPartition(position);
117                 }
118         }
119
120         /**
121          * Stops upon a non-java identifier (as defined by
122          * {@link Scanner#isPHPIdentifierPart(char)}) character.
123          */
124         private static class NonJavaIdentifierPart implements StopCondition {
125                 /*
126                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
127                  */
128                 public boolean stop(char ch, int position, boolean forward) {
129                         return !Scanner.isPHPIdentifierPart(ch);
130                 }
131         }
132
133         /**
134          * Stops upon a non-java identifier character in the default partition.
135          * 
136          * @see NonJavaIdentifierPart
137          */
138         private class NonJavaIdentifierPartDefaultPartition extends
139                         NonJavaIdentifierPart {
140                 /*
141                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
142                  */
143                 public boolean stop(char ch, int position, boolean forward) {
144                         return super.stop(ch, position, true)
145                                         || !isDefaultPartition(position);
146                 }
147         }
148
149         /**
150          * Stops upon a character in the default partition that matches the given
151          * character list.
152          */
153         private class CharacterMatch implements StopCondition {
154                 private final char[] fChars;
155
156                 /**
157                  * Creates a new instance.
158                  * 
159                  * @param ch
160                  *            the single character to match
161                  */
162                 public CharacterMatch(char ch) {
163                         this(new char[] { ch });
164                 }
165
166                 /**
167                  * Creates a new instance.
168                  * 
169                  * @param chars
170                  *            the chars to match.
171                  */
172                 public CharacterMatch(char[] chars) {
173                         Assert.isNotNull(chars);
174                         Assert.isTrue(chars.length > 0);
175                         fChars = chars;
176                         Arrays.sort(chars);
177                 }
178
179                 /*
180                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char,
181                  *      int)
182                  */
183                 public boolean stop(char ch, int position, boolean forward) {
184                         return Arrays.binarySearch(fChars, ch) >= 0
185                                         && isDefaultPartition(position);
186                 }
187         }
188
189         /**
190          * Acts like character match, but skips all scopes introduced by
191          * parenthesis, brackets, and braces.
192          */
193         protected class SkippingScopeMatch extends CharacterMatch {
194                 private char fOpening, fClosing;
195
196                 private int fDepth = 0;
197
198                 /**
199                  * Creates a new instance.
200                  * 
201                  * @param ch
202                  *            the single character to match
203                  */
204                 public SkippingScopeMatch(char ch) {
205                         super(ch);
206                 }
207
208                 /**
209                  * Creates a new instance.
210                  * 
211                  * @param chars
212                  *            the chars to match.
213                  */
214                 public SkippingScopeMatch(char[] chars) {
215                         super(chars);
216                 }
217
218                 /*
219                  * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char,
220                  *      int)
221                  */
222                 public boolean stop(char ch, int position, boolean forward) {
223
224                         if (fDepth == 0 && super.stop(ch, position, true))
225                                 return true;
226                         else if (ch == fOpening)
227                                 fDepth++;
228                         else if (ch == fClosing) {
229                                 fDepth--;
230                                 if (fDepth == 0) {
231                                         fOpening = 0;
232                                         fClosing = 0;
233                                 }
234                         } else if (fDepth == 0) {
235                                 fDepth = 1;
236                                 if (forward) {
237
238                                         switch (ch) {
239                                         case LBRACE:
240                                                 fOpening = LBRACE;
241                                                 fClosing = RBRACE;
242                                                 break;
243                                         case LPAREN:
244                                                 fOpening = LPAREN;
245                                                 fClosing = RPAREN;
246                                                 break;
247                                         case LBRACKET:
248                                                 fOpening = LBRACKET;
249                                                 fClosing = RBRACKET;
250                                                 break;
251                                         }
252
253                                 } else {
254                                         switch (ch) {
255                                         case RBRACE:
256                                                 fOpening = RBRACE;
257                                                 fClosing = LBRACE;
258                                                 break;
259                                         case RPAREN:
260                                                 fOpening = RPAREN;
261                                                 fClosing = LPAREN;
262                                                 break;
263                                         case RBRACKET:
264                                                 fOpening = RBRACKET;
265                                                 fClosing = LBRACKET;
266                                                 break;
267                                         }
268
269                                 }
270                         }
271
272                         return false;
273
274                 }
275
276         }
277
278         /** The document being scanned. */
279         private IDocument fDocument;
280
281         /** The partitioning being used for scanning. */
282         private String fPartitioning;
283
284         /** The partition to scan in. */
285         private String fPartition;
286
287         /* internal scan state */
288
289         /** the most recently read character. */
290         private char fChar;
291
292         /** the most recently read position. */
293         private int fPos;
294
295         /* preset stop conditions */
296         private final StopCondition fNonWSDefaultPart = new NonWhitespaceDefaultPartition();
297
298         private final static StopCondition fNonWS = new NonWhitespace();
299
300         private final StopCondition fNonIdent = new NonJavaIdentifierPartDefaultPartition();
301
302         /**
303          * Creates a new instance.
304          * 
305          * @param document
306          *            the document to scan
307          * @param partitioning
308          *            the partitioning to use for scanning
309          * @param partition
310          *            the partition to scan in
311          */
312         public JavaHeuristicScanner(IDocument document, String partitioning,
313                         String partition) {
314                 Assert.isNotNull(document);
315                 Assert.isNotNull(partitioning);
316                 Assert.isNotNull(partition);
317                 fDocument = document;
318                 fPartitioning = partitioning;
319                 fPartition = partition;
320         }
321
322         /**
323          * Calls
324          * <code>this(document, IJavaPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE)</code>.
325          * 
326          * @param document
327          *            the document to scan.
328          */
329         public JavaHeuristicScanner(IDocument document) {
330                 this(document, IPHPPartitions.PHP_PARTITIONING,
331                                 IDocument.DEFAULT_CONTENT_TYPE);
332         }
333
334         /**
335          * Returns the most recent internal scan position.
336          * 
337          * @return the most recent internal scan position.
338          */
339         public int getPosition() {
340                 return fPos;
341         }
342
343         /**
344          * Returns the next token in forward direction, starting at
345          * <code>start</code>, and not extending further than <code>bound</code>.
346          * The return value is one of the constants defined in {@link Symbols}.
347          * After a call, {@link #getPosition()} will return the position just after
348          * the scanned token (i.e. the next position that will be scanned).
349          * 
350          * @param start
351          *            the first character position in the document to consider
352          * @param bound
353          *            the first position not to consider any more
354          * @return a constant from {@link Symbols} describing the next token
355          */
356         public int nextToken(int start, int bound) {
357                 int pos = scanForward(start, bound, fNonWSDefaultPart);
358                 if (pos == NOT_FOUND)
359                         return TokenEOF;
360
361                 fPos++;
362
363                 switch (fChar) {
364                 case LBRACE:
365                         return TokenLBRACE;
366                 case RBRACE:
367                         return TokenRBRACE;
368                 case LBRACKET:
369                         return TokenLBRACKET;
370                 case RBRACKET:
371                         return TokenRBRACKET;
372                 case LPAREN:
373                         return TokenLPAREN;
374                 case RPAREN:
375                         return TokenRPAREN;
376                 case SEMICOLON:
377                         return TokenSEMICOLON;
378                 case COMMA:
379                         return TokenCOMMA;
380                 case QUESTIONMARK:
381                         return TokenQUESTIONMARK;
382                 case EQUAL:
383                         return TokenEQUAL;
384                 }
385
386                 // else
387                 if (Scanner.isPHPIdentifierPart(fChar)) {
388                         // assume an ident or keyword
389                         int from = pos, to;
390                         pos = scanForward(pos + 1, bound, fNonIdent);
391                         if (pos == NOT_FOUND)
392                                 to = bound == UNBOUND ? fDocument.getLength() : bound;
393                         else
394                                 to = pos;
395
396                         String identOrKeyword;
397                         try {
398                                 identOrKeyword = fDocument.get(from, to - from);
399                         } catch (BadLocationException e) {
400                                 return TokenEOF;
401                         }
402
403                         return getToken(identOrKeyword);
404
405                 } else {
406                         // operators, number literals etc
407                         return TokenOTHER;
408                 }
409         }
410
411         /**
412          * Returns the next token in backward direction, starting at
413          * <code>start</code>, and not extending further than <code>bound</code>.
414          * The return value is one of the constants defined in {@link Symbols}.
415          * After a call, {@link #getPosition()} will return the position just before
416          * the scanned token starts (i.e. the next position that will be scanned).
417          * 
418          * @param start
419          *            the first character position in the document to consider
420          * @param bound
421          *            the first position not to consider any more
422          * @return a constant from {@link Symbols} describing the previous token
423          */
424         public int previousToken(int start, int bound) {
425                 int pos = scanBackward(start, bound, fNonWSDefaultPart);
426                 if (pos == NOT_FOUND)
427                         return TokenEOF;
428
429                 fPos--;
430
431                 switch (fChar) {
432                 case LBRACE:
433                         return TokenLBRACE;
434                 case RBRACE:
435                         return TokenRBRACE;
436                 case LBRACKET:
437                         return TokenLBRACKET;
438                 case RBRACKET:
439                         return TokenRBRACKET;
440                 case LPAREN:
441                         return TokenLPAREN;
442                 case RPAREN:
443                         return TokenRPAREN;
444                 case SEMICOLON:
445                         return TokenSEMICOLON;
446                 case COLON:
447                         return TokenCOLON;
448                 case COMMA:
449                         return TokenCOMMA;
450                 case QUESTIONMARK:
451                         return TokenQUESTIONMARK;
452                 case EQUAL:
453                         return TokenEQUAL;
454                 }
455
456                 // else
457                 if (Scanner.isPHPIdentifierPart(fChar)) {
458                         // assume an ident or keyword
459                         int from, to = pos + 1;
460                         pos = scanBackward(pos - 1, bound, fNonIdent);
461                         if (pos == NOT_FOUND)
462                                 from = bound == UNBOUND ? 0 : bound + 1;
463                         else
464                                 from = pos + 1;
465
466                         String identOrKeyword;
467                         try {
468                                 identOrKeyword = fDocument.get(from, to - from);
469                         } catch (BadLocationException e) {
470                                 return TokenEOF;
471                         }
472
473                         return getToken(identOrKeyword);
474
475                 } else {
476                         // operators, number literals etc
477                         return TokenOTHER;
478                 }
479
480         }
481
482         /**
483          * Returns one of the keyword constants or <code>TokenIDENT</code> for a
484          * scanned identifier.
485          * 
486          * @param s
487          *            a scanned identifier
488          * @return one of the constants defined in {@link Symbols}
489          */
490         private int getToken(String s) {
491                 Assert.isNotNull(s);
492
493                 switch (s.length()) {
494                 case 2:
495                         if ("if".equals(s)) //$NON-NLS-1$
496                                 return TokenIF;
497                         if ("do".equals(s)) //$NON-NLS-1$
498                                 return TokenDO;
499                         break;
500                 case 3:
501                         if ("for".equals(s)) //$NON-NLS-1$
502                                 return TokenFOR;
503                         if ("try".equals(s)) //$NON-NLS-1$
504                                 return TokenTRY;
505                         if ("new".equals(s)) //$NON-NLS-1$
506                                 return TokenNEW;
507                         break;
508                 case 4:
509                         if ("case".equals(s)) //$NON-NLS-1$
510                                 return TokenCASE;
511                         if ("else".equals(s)) //$NON-NLS-1$
512                                 return TokenELSE;
513                         if ("goto".equals(s)) //$NON-NLS-1$
514                                 return TokenGOTO;
515                         break;
516                 case 5:
517                         if ("break".equals(s)) //$NON-NLS-1$
518                                 return TokenBREAK;
519                         if ("catch".equals(s)) //$NON-NLS-1$
520                                 return TokenCATCH;
521                         if ("while".equals(s)) //$NON-NLS-1$
522                                 return TokenWHILE;
523                         break;
524                 case 6:
525                         if ("return".equals(s)) //$NON-NLS-1$
526                                 return TokenRETURN;
527                         if ("static".equals(s)) //$NON-NLS-1$
528                                 return TokenSTATIC;
529                         if ("switch".equals(s)) //$NON-NLS-1$
530                                 return TokenSWITCH;
531                         break;
532                 case 7:
533                         if ("default".equals(s)) //$NON-NLS-1$
534                                 return TokenDEFAULT;
535                         if ("finally".equals(s)) //$NON-NLS-1$
536                                 return TokenFINALLY;
537                         break;
538                 case 12:
539                         if ("synchronized".equals(s)) //$NON-NLS-1$
540                                 return TokenSYNCHRONIZED;
541                         break;
542                 }
543                 return TokenIDENT;
544         }
545
546         /**
547          * Returns the position of the closing peer character (forward search). Any
548          * scopes introduced by opening peers are skipped. All peers accounted for
549          * must reside in the default partition.
550          * 
551          * <p>
552          * Note that <code>start</code> must not point to the opening peer, but to
553          * the first character being searched.
554          * </p>
555          * 
556          * @param start
557          *            the start position
558          * @param openingPeer
559          *            the opening peer character (e.g. '{')
560          * @param closingPeer
561          *            the closing peer character (e.g. '}')
562          * @return the matching peer character position, or <code>NOT_FOUND</code>
563          */
564         public int findClosingPeer(int start, final char openingPeer,
565                         final char closingPeer) {
566                 Assert.isNotNull(fDocument);
567                 Assert.isTrue(start >= 0);
568
569                 try {
570                         int depth = 1;
571                         start -= 1;
572                         while (true) {
573                                 start = scanForward(start + 1, UNBOUND, new CharacterMatch(
574                                                 new char[] { openingPeer, closingPeer }));
575                                 if (start == NOT_FOUND)
576                                         return NOT_FOUND;
577
578                                 if (fDocument.getChar(start) == openingPeer)
579                                         depth++;
580                                 else
581                                         depth--;
582
583                                 if (depth == 0)
584                                         return start;
585                         }
586
587                 } catch (BadLocationException e) {
588                         return NOT_FOUND;
589                 }
590         }
591
592         /**
593          * Returns the position of the opening peer character (backward search). Any
594          * scopes introduced by closing peers are skipped. All peers accounted for
595          * must reside in the default partition.
596          * 
597          * <p>
598          * Note that <code>start</code> must not point to the closing peer, but to
599          * the first character being searched.
600          * </p>
601          * 
602          * @param start
603          *            the start position
604          * @param openingPeer
605          *            the opening peer character (e.g. '{')
606          * @param closingPeer
607          *            the closing peer character (e.g. '}')
608          * @return the matching peer character position, or <code>NOT_FOUND</code>
609          */
610         public int findOpeningPeer(int start, char openingPeer, char closingPeer) {
611                 Assert.isTrue(start < fDocument.getLength());
612
613                 try {
614                         int depth = 1;
615                         start += 1;
616                         while (true) {
617                                 start = scanBackward(start - 1, UNBOUND, new CharacterMatch(
618                                                 new char[] { openingPeer, closingPeer }));
619                                 if (start == NOT_FOUND)
620                                         return NOT_FOUND;
621
622                                 if (fDocument.getChar(start) == closingPeer)
623                                         depth++;
624                                 else
625                                         depth--;
626
627                                 if (depth == 0)
628                                         return start;
629                         }
630
631                 } catch (BadLocationException e) {
632                         return NOT_FOUND;
633                 }
634         }
635
636         /**
637          * Computes the surrounding block around <code>offset</code>. The search
638          * is started at the beginning of <code>offset</code>, i.e. an opening
639          * brace at <code>offset</code> will not be part of the surrounding block,
640          * but a closing brace will.
641          * 
642          * @param offset
643          *            the offset for which the surrounding block is computed
644          * @return a region describing the surrounding block, or <code>null</code>
645          *         if none can be found
646          */
647         public IRegion findSurroundingBlock(int offset) {
648                 if (offset < 1 || offset >= fDocument.getLength())
649                         return null;
650
651                 int begin = findOpeningPeer(offset - 1, LBRACE, RBRACE);
652                 int end = findClosingPeer(offset, LBRACE, RBRACE);
653                 if (begin == NOT_FOUND || end == NOT_FOUND)
654                         return null;
655                 return new Region(begin, end + 1 - begin);
656         }
657
658         /**
659          * Finds the smallest position in <code>fDocument</code> such that the
660          * position is &gt;= <code>position</code> and &lt; <code>bound</code>
661          * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
662          * evaluates to <code>false</code> and the position is in the default
663          * partition.
664          * 
665          * @param position
666          *            the first character position in <code>fDocument</code> to be
667          *            considered
668          * @param bound
669          *            the first position in <code>fDocument</code> to not consider
670          *            any more, with <code>bound</code> &gt; <code>position</code>,
671          *            or <code>UNBOUND</code>
672          * @return the smallest position of a non-whitespace character in [<code>position</code>,
673          *         <code>bound</code>) that resides in a Java partition, or
674          *         <code>NOT_FOUND</code> if none can be found
675          */
676         public int findNonWhitespaceForward(int position, int bound) {
677                 return scanForward(position, bound, fNonWSDefaultPart);
678         }
679
680         /**
681          * Finds the smallest position in <code>fDocument</code> such that the
682          * position is &gt;= <code>position</code> and &lt; <code>bound</code>
683          * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
684          * evaluates to <code>false</code>.
685          * 
686          * @param position
687          *            the first character position in <code>fDocument</code> to be
688          *            considered
689          * @param bound
690          *            the first position in <code>fDocument</code> to not consider
691          *            any more, with <code>bound</code> &gt; <code>position</code>,
692          *            or <code>UNBOUND</code>
693          * @return the smallest position of a non-whitespace character in [<code>position</code>,
694          *         <code>bound</code>), or <code>NOT_FOUND</code> if none can
695          *         be found
696          */
697         public int findNonWhitespaceForwardInAnyPartition(int position, int bound) {
698                 return scanForward(position, bound, fNonWS);
699         }
700
701         /**
702          * Finds the highest position in <code>fDocument</code> such that the
703          * position is &lt;= <code>position</code> and &gt; <code>bound</code>
704          * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
705          * evaluates to <code>false</code> and the position is in the default
706          * partition.
707          * 
708          * @param position
709          *            the first character position in <code>fDocument</code> to be
710          *            considered
711          * @param bound
712          *            the first position in <code>fDocument</code> to not consider
713          *            any more, with <code>bound</code> &lt; <code>position</code>,
714          *            or <code>UNBOUND</code>
715          * @return the highest position of a non-whitespace character in (<code>bound</code>,
716          *         <code>position</code>] that resides in a Java partition, or
717          *         <code>NOT_FOUND</code> if none can be found
718          */
719         public int findNonWhitespaceBackward(int position, int bound) {
720                 return scanBackward(position, bound, fNonWSDefaultPart);
721         }
722
723         /**
724          * Finds the lowest position <code>p</code> in <code>fDocument</code>
725          * such that <code>start</code> &lt;= p &lt; <code>bound</code> and
726          * <code>condition.stop(fDocument.getChar(p), p)</code> evaluates to
727          * <code>true</code>.
728          * 
729          * @param start
730          *            the first character position in <code>fDocument</code> to be
731          *            considered
732          * @param bound
733          *            the first position in <code>fDocument</code> to not consider
734          *            any more, with <code>bound</code> &gt; <code>start</code>,
735          *            or <code>UNBOUND</code>
736          * @param condition
737          *            the <code>StopCondition</code> to check
738          * @return the lowest position in [<code>start</code>,
739          *         <code>bound</code>) for which <code>condition</code> holds,
740          *         or <code>NOT_FOUND</code> if none can be found
741          */
742         public int scanForward(int start, int bound, StopCondition condition) {
743                 Assert.isTrue(start >= 0);
744
745                 if (bound == UNBOUND)
746                         bound = fDocument.getLength();
747
748                 Assert.isTrue(bound <= fDocument.getLength());
749
750                 try {
751                         fPos = start;
752                         while (fPos < bound) {
753
754                                 fChar = fDocument.getChar(fPos);
755                                 if (condition.stop(fChar, fPos, true))
756                                         return fPos;
757
758                                 fPos++;
759                         }
760                 } catch (BadLocationException e) {
761                 }
762                 return NOT_FOUND;
763         }
764
765         /**
766          * Finds the lowest position in <code>fDocument</code> such that the
767          * position is &gt;= <code>position</code> and &lt; <code>bound</code>
768          * and <code>fDocument.getChar(position) == ch</code> evaluates to
769          * <code>true</code> and the position is in the default partition.
770          * 
771          * @param position
772          *            the first character position in <code>fDocument</code> to be
773          *            considered
774          * @param bound
775          *            the first position in <code>fDocument</code> to not consider
776          *            any more, with <code>bound</code> &gt; <code>position</code>,
777          *            or <code>UNBOUND</code>
778          * @param ch
779          *            the <code>char</code> to search for
780          * @return the lowest position of <code>ch</code> in (<code>bound</code>,
781          *         <code>position</code>] that resides in a Java partition, or
782          *         <code>NOT_FOUND</code> if none can be found
783          */
784         public int scanForward(int position, int bound, char ch) {
785                 return scanForward(position, bound, new CharacterMatch(ch));
786         }
787
788         /**
789          * Finds the lowest position in <code>fDocument</code> such that the
790          * position is &gt;= <code>position</code> and &lt; <code>bound</code>
791          * and <code>fDocument.getChar(position) == ch</code> evaluates to
792          * <code>true</code> for at least one ch in <code>chars</code> and the
793          * position is in the default partition.
794          * 
795          * @param position
796          *            the first character position in <code>fDocument</code> to be
797          *            considered
798          * @param bound
799          *            the first position in <code>fDocument</code> to not consider
800          *            any more, with <code>bound</code> &gt; <code>position</code>,
801          *            or <code>UNBOUND</code>
802          * @param chars
803          *            an array of <code>char</code> to search for
804          * @return the lowest position of a non-whitespace character in [<code>position</code>,
805          *         <code>bound</code>) that resides in a Java partition, or
806          *         <code>NOT_FOUND</code> if none can be found
807          */
808         public int scanForward(int position, int bound, char[] chars) {
809                 return scanForward(position, bound, new CharacterMatch(chars));
810         }
811
812         /**
813          * Finds the highest position <code>p</code> in <code>fDocument</code>
814          * such that <code>bound</code> &lt; <code>p</code> &lt;=
815          * <code>start</code> and
816          * <code>condition.stop(fDocument.getChar(p), p)</code> evaluates to
817          * <code>true</code>.
818          * 
819          * @param start
820          *            the first character position in <code>fDocument</code> to be
821          *            considered
822          * @param bound
823          *            the first position in <code>fDocument</code> to not consider
824          *            any more, with <code>bound</code> &lt; <code>start</code>,
825          *            or <code>UNBOUND</code>
826          * @param condition
827          *            the <code>StopCondition</code> to check
828          * @return the highest position in (<code>bound</code>,
829          *         <code>start</code> for which <code>condition</code> holds, or
830          *         <code>NOT_FOUND</code> if none can be found
831          */
832         public int scanBackward(int start, int bound, StopCondition condition) {
833                 if (bound == UNBOUND)
834                         bound = -1;
835
836                 Assert.isTrue(bound >= -1);
837                 Assert.isTrue(start < fDocument.getLength());
838
839                 try {
840                         fPos = start;
841                         while (fPos > bound) {
842
843                                 fChar = fDocument.getChar(fPos);
844                                 if (condition.stop(fChar, fPos, false))
845                                         return fPos;
846
847                                 fPos--;
848                         }
849                 } catch (BadLocationException e) {
850                 }
851                 return NOT_FOUND;
852         }
853
854         /**
855          * Finds the highest position in <code>fDocument</code> such that the
856          * position is &lt;= <code>position</code> and &gt; <code>bound</code>
857          * and <code>fDocument.getChar(position) == ch</code> evaluates to
858          * <code>true</code> for at least one ch in <code>chars</code> and the
859          * position is in the default partition.
860          * 
861          * @param position
862          *            the first character position in <code>fDocument</code> to be
863          *            considered
864          * @param bound
865          *            the first position in <code>fDocument</code> to not consider
866          *            any more, with <code>bound</code> &lt; <code>position</code>,
867          *            or <code>UNBOUND</code>
868          * @param ch
869          *            the <code>char</code> to search for
870          * @return the highest position of one element in <code>chars</code> in (<code>bound</code>,
871          *         <code>position</code>] that resides in a Java partition, or
872          *         <code>NOT_FOUND</code> if none can be found
873          */
874         public int scanBackward(int position, int bound, char ch) {
875                 return scanBackward(position, bound, new CharacterMatch(ch));
876         }
877
878         /**
879          * Finds the highest position in <code>fDocument</code> such that the
880          * position is &lt;= <code>position</code> and &gt; <code>bound</code>
881          * and <code>fDocument.getChar(position) == ch</code> evaluates to
882          * <code>true</code> for at least one ch in <code>chars</code> and the
883          * position is in the default partition.
884          * 
885          * @param position
886          *            the first character position in <code>fDocument</code> to be
887          *            considered
888          * @param bound
889          *            the first position in <code>fDocument</code> to not consider
890          *            any more, with <code>bound</code> &lt; <code>position</code>,
891          *            or <code>UNBOUND</code>
892          * @param chars
893          *            an array of <code>char</code> to search for
894          * @return the highest position of one element in <code>chars</code> in (<code>bound</code>,
895          *         <code>position</code>] that resides in a Java partition, or
896          *         <code>NOT_FOUND</code> if none can be found
897          */
898         public int scanBackward(int position, int bound, char[] chars) {
899                 return scanBackward(position, bound, new CharacterMatch(chars));
900         }
901
902         /**
903          * Checks whether <code>position</code> resides in a default (Java)
904          * partition of <code>fDocument</code>.
905          * 
906          * @param position
907          *            the position to be checked
908          * @return <code>true</code> if <code>position</code> is in the default
909          *         partition of <code>fDocument</code>, <code>false</code>
910          *         otherwise
911          */
912         public boolean isDefaultPartition(int position) {
913                 Assert.isTrue(position >= 0);
914                 Assert.isTrue(position <= fDocument.getLength());
915
916                 try {
917                         ITypedRegion region = TextUtilities.getPartition(fDocument,
918                                         fPartitioning, position, false);
919                         return region.getType().equals(fPartition);
920
921                 } catch (BadLocationException e) {
922                 }
923
924                 return false;
925         }
926
927         /**
928          * Checks if the line seems to be an open condition not followed by a block
929          * (i.e. an if, while, or for statement with just one following statement,
930          * see example below).
931          * 
932          * <pre>
933          * if (condition)
934          *      doStuff();
935          * </pre>
936          * 
937          * <p>
938          * Algorithm: if the last non-WS, non-Comment code on the line is an if
939          * (condition), while (condition), for( expression), do, else, and there is
940          * no statement after that
941          * </p>
942          * 
943          * @param position
944          *            the insert position of the new character
945          * @param bound
946          *            the lowest position to consider
947          * @return <code>true</code> if the code is a conditional statement or
948          *         loop without a block, <code>false</code> otherwise
949          */
950         public boolean isBracelessBlockStart(int position, int bound) {
951                 if (position < 1)
952                         return false;
953
954                 switch (previousToken(position, bound)) {
955                 case TokenDO:
956                 case TokenELSE:
957                         return true;
958                 case TokenRPAREN:
959                         position = findOpeningPeer(fPos, LPAREN, RPAREN);
960                         if (position > 0) {
961                                 switch (previousToken(position - 1, bound)) {
962                                 case TokenIF:
963                                 case TokenFOR:
964                                 case TokenWHILE:
965                                         return true;
966                                 }
967                         }
968                 }
969
970                 return false;
971         }
972 }