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