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