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