52f8defe96648ff1a5b9e2e032965f42781cc1b6
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPPartitionScanner.java
1 /**
2  * This program and the accompanying materials
3  * are made available under the terms of the Common Public License v1.0
4  * which accompanies this distribution, and is available at
5  * http://www.eclipse.org/legal/cpl-v10.html
6  * Created on 05.03.2003
7  *
8  * @author Stefan Langer (musk)
9  * @version $Revision: 1.19 $
10  */
11 package net.sourceforge.phpeclipse.phpeditor.php;
12
13
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.Map;
17
18 import org.eclipse.jface.text.Assert;
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.IDocument;
21 import org.eclipse.jface.text.ITypedRegion;
22 import org.eclipse.jface.text.rules.ICharacterScanner;
23 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
24 import org.eclipse.jface.text.rules.IToken;
25 import org.eclipse.jface.text.rules.Token;
26
27 /**
28  * 
29  */
30 public class PHPPartitionScanner implements IPartitionTokenScanner
31 {
32     private static final boolean DEBUG = false;
33     private boolean fInString = false;
34     private boolean fInDoubString = false;
35     private IDocument fDocument = null;
36     private int fOffset = -1;
37     private String fContentType = IPHPPartitionScannerConstants.HTML;
38     private String fPrevContentType = IPHPPartitionScannerConstants.HTML;
39     private boolean partitionBorder = false;
40     private int fTokenOffset;
41     private int fEnd = -1;
42     private int fLength;
43     private int fCurrentLength;
44     private Map tokens = new HashMap();
45
46     public PHPPartitionScanner()
47     {
48         this.tokens.put(
49             IPHPPartitionScannerConstants.PHP,
50             new Token(IPHPPartitionScannerConstants.PHP));
51         this.tokens.put(
52             IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT,
53             new Token(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT));
54         this.tokens.put(
55             IPHPPartitionScannerConstants.HTML,
56             new Token(IPHPPartitionScannerConstants.HTML));
57         this.tokens.put(
58             IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT,
59             new Token(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT));
60         this.tokens.put(
61             IDocument.DEFAULT_CONTENT_TYPE,
62             new Token(IDocument.DEFAULT_CONTENT_TYPE));
63     }
64
65     private IToken getToken(String type)
66     {
67         fLength = fCurrentLength;
68         if (DEBUG)
69         {
70
71             try
72             {
73                 if (fLength <= 0)
74                 {
75                     int line = fDocument.getLineOfOffset(fOffset);
76                     System.err.println(
77                         "Error at "
78                             + line
79                             + " offset:"
80                             + String.valueOf(
81                                 fOffset - fDocument.getLineOffset(line)));
82                 }
83             }
84             catch (BadLocationException e)
85             {   // should never happen
86                 // TODO Write stacktrace to log
87                 e.printStackTrace();
88             }
89         }
90         Assert.isTrue(fLength > 0, "Partition length <= 0!");
91         fCurrentLength = 0;
92         // String can never cross partition borders so reset string detection
93         fInString = false;
94         fInDoubString = false;
95         IToken token = (IToken) this.tokens.get(type);
96         Assert.isNotNull(token, "Token for type \"" + type + "\" not found!");
97         if (DEBUG)
98         {
99             System.out.println(
100                 "Partition: fTokenOffset="
101                     + fTokenOffset
102                     + " fContentType="
103                     + type
104                     + " fLength="
105                     + fLength);
106         }
107         return token;
108     }
109
110     /* (non-Javadoc)
111      * @see org.eclipse.jface.text.rules.IPartitionTokenScanner#setPartialRange(org.eclipse.jface.text.IDocument, int, int, java.lang.String, int)
112      */
113     public void setPartialRange(
114         IDocument document,
115         int offset,
116         int length,
117         String contentType,
118         int partitionOffset)
119     {
120         if (DEBUG)
121         {
122             System.out.println(
123                 "PartialRange: contentType="
124                     + contentType
125                     + " partitionOffset="
126                     + partitionOffset);
127         }
128
129         try
130         {
131             if (partitionOffset > -1)
132             {
133                 partitionBorder = false;
134                 // because of strings we have to parse the whole partition
135                 this.setRange(
136                     document,
137                     partitionOffset,
138                     offset - partitionOffset + length);
139                 // sometimes we get a wrong partition so we retrieve the partition
140                 // directly from the document
141                 fContentType = fDocument.getContentType(partitionOffset);
142             }
143             else
144                 this.setRange(document, offset, length);
145
146         }
147         catch (BadLocationException e)
148         {
149             // should never happen
150             // TODO print stack trace to log
151             // fall back just scan the whole document again
152             this.setRange(document, 0, fDocument.getLength());
153         }
154
155     }
156
157     /* (non-Javadoc)
158      * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
159      */
160     public int getTokenLength()
161     {
162         return fLength;
163     }
164
165     /* (non-Javadoc)
166      * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
167      */
168     public int getTokenOffset()
169     {
170         return fTokenOffset;
171     }
172
173     /* (non-Javadoc)
174      * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
175      */
176     public IToken nextToken()
177     {
178         int c;
179
180         // check if we are not allready at the end of the
181         // file
182         if ((c = read()) == ICharacterScanner.EOF)
183         {
184             partitionBorder = false;
185             return Token.EOF;
186         }
187         else
188             unread();
189
190         if (partitionBorder)
191         {
192             fTokenOffset = fOffset;
193             partitionBorder = false;
194         }
195
196         while ((c = read()) != ICharacterScanner.EOF)
197         {
198             switch (c)
199             {
200                 case '<' :
201                     if (!isInString(IPHPPartitionScannerConstants.PHP)
202                         && fContentType
203                             != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT
204                         && checkPattern(new char[] { '?', 'p', 'h', 'p' }, true))
205                     {
206                         if (fContentType != IPHPPartitionScannerConstants.PHP
207                             && fCurrentLength > 5)
208                         {
209                             unread(5);
210                             IToken token = getToken(fContentType);
211                             // save previouse contenttype
212                             //TODO build stack for previouse contenttype 
213                             fPrevContentType = fContentType;
214
215                             fContentType = IPHPPartitionScannerConstants.PHP;
216
217                             return token;
218                         }
219                         else
220                             fContentType = IPHPPartitionScannerConstants.PHP;
221
222                         // remember offset of this partition
223                         fTokenOffset = fOffset - 5;
224                         fCurrentLength = 5;
225                     }
226                     else if (
227                         !isInString(IPHPPartitionScannerConstants.PHP)
228                             && fContentType
229                                 != IPHPPartitionScannerConstants
230                                     .PHP_MULTILINE_COMMENT
231                             && checkPattern(new char[] { '?' }, false))
232                     {
233                         if (fContentType != IPHPPartitionScannerConstants.PHP
234                             && fCurrentLength > 2)
235                         {
236                             unread(2);
237                             IToken token = getToken(fContentType);
238                             // save previouse contenttype
239                             fPrevContentType = fContentType;
240                             fContentType = IPHPPartitionScannerConstants.PHP;
241                             return token;
242                         }
243                         else
244                             fContentType = IPHPPartitionScannerConstants.PHP;
245                         // remember offset of this partition
246                         fTokenOffset = fOffset - 2;
247                         fCurrentLength = 2;
248                     }
249                     else if (
250                         !isInString(IPHPPartitionScannerConstants.PHP)
251                             && checkPattern(new char[] { '!', '-', '-' }))
252                     { // return previouse partition
253                         if (fContentType
254                             != IPHPPartitionScannerConstants
255                                 .HTML_MULTILINE_COMMENT
256                             && fCurrentLength > 4)
257                         {
258                             unread(4);
259                             IToken token = getToken(fContentType);
260                             fContentType =
261                                 IPHPPartitionScannerConstants
262                                     .HTML_MULTILINE_COMMENT;
263                             return token;
264                         }
265                         else
266                             fContentType =
267                                 IPHPPartitionScannerConstants
268                                     .HTML_MULTILINE_COMMENT;
269                                     
270                         fTokenOffset = fOffset - 4;
271                         fCurrentLength = 4;
272                     }
273                     break;
274                 case '?' :
275                     if (!isInString(IPHPPartitionScannerConstants.PHP)
276                         && fContentType == IPHPPartitionScannerConstants.PHP)
277                     {
278                         if ((c = read()) == '>')
279                         { 
280                             if (fPrevContentType != null)
281                                 fContentType = fPrevContentType;
282                             else
283                                 fContentType =
284                                     IPHPPartitionScannerConstants.HTML;
285                             partitionBorder = true;
286                             return getToken(IPHPPartitionScannerConstants.PHP);
287                         }
288                         else if (c != ICharacterScanner.EOF)
289                             unread();
290                     }
291                     break;
292                 case '-' :
293                     if (!isInString(IPHPPartitionScannerConstants.PHP)
294                         && fContentType
295                             == IPHPPartitionScannerConstants
296                                 .HTML_MULTILINE_COMMENT
297                         && checkPattern(new char[] { '-', '>' }))
298                     {
299                         fContentType = IPHPPartitionScannerConstants.HTML;
300                         partitionBorder = true;
301                         return getToken(
302                             IPHPPartitionScannerConstants
303                                 .HTML_MULTILINE_COMMENT);
304                     }
305                     break;
306                 case '/' :
307                     if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '*')
308                     { // MULTINE COMMENT JAVASCRIPT, CSS, PHP
309                         if (fContentType == IPHPPartitionScannerConstants.PHP
310                             && fCurrentLength > 2)
311                         {
312                             unread(2);
313                             IToken token = getToken(fContentType);
314                             fContentType =
315                                 IPHPPartitionScannerConstants
316                                     .PHP_MULTILINE_COMMENT;
317                             return token;
318                         }
319                         else if (
320                             fContentType
321                                 == IPHPPartitionScannerConstants
322                                     .PHP_MULTILINE_COMMENT)
323                         {
324
325                             fTokenOffset = fOffset - 2;
326                             fCurrentLength = 2;
327                         }
328
329                     }
330                     else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF)
331                         unread();
332                     break;
333                 case '*' :
334                     if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '/')
335                     {
336                         if (fContentType
337                             == IPHPPartitionScannerConstants
338                                 .PHP_MULTILINE_COMMENT)
339                         {
340                             fContentType = IPHPPartitionScannerConstants.PHP;
341                             partitionBorder = true;
342                             return getToken(
343                                 IPHPPartitionScannerConstants
344                                     .PHP_MULTILINE_COMMENT);
345                         }
346                         else if (
347                             fContentType
348                                 == IPHPPartitionScannerConstants
349                                     .CSS_MULTILINE_COMMENT)
350                         {
351                         }
352                         else if (
353                             fContentType
354                                 == IPHPPartitionScannerConstants
355                                     .JS_MULTILINE_COMMENT)
356                         {
357                         }
358                     }
359                     else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF)
360                         unread();
361                     break;
362                 case '\'' :
363                     if (!fInDoubString)
364                         fInString = !fInString;
365                     break;
366                 case '"' :
367                     // toggle String mode
368                     if (!fInString)
369                         fInDoubString = !fInDoubString;
370                     break;
371             }
372         } // end of file reached but we have to return the
373         // last partition.
374         return getToken(fContentType);
375     }
376     /* (non-Javadoc)
377      * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
378      */
379     public void setRange(IDocument document, int offset, int length)
380     {
381         if (DEBUG)
382         {
383             System.out.println(
384                 "SET RANGE: offset=" + offset + " length=" + length);
385         }
386
387         fDocument = document;
388         fOffset = offset;
389         fTokenOffset = offset;
390         fCurrentLength = 0;
391         fLength = 0;
392         fEnd = fOffset + length;
393         fInString = false;
394         fInDoubString = false;
395         fContentType = IPHPPartitionScannerConstants.HTML;
396 //        String[] prev = getPartitionStack(offset);
397     }
398
399     private int read()
400     {
401         try
402         {
403             if (fOffset < fEnd)
404             {
405                 fCurrentLength++;
406                 return fDocument.getChar(fOffset++);
407             }
408             return ICharacterScanner.EOF;
409         }
410         catch (BadLocationException e)
411         {
412             // should never happen
413             // TODO write stacktrace to log
414             fOffset = fEnd;
415             return ICharacterScanner.EOF;
416         }
417     }
418
419     private void unread()
420     {
421         --fOffset;
422         --fCurrentLength;
423     }
424     
425     private void unread(int num)
426     {
427         fOffset -= num;
428         fCurrentLength -= num;
429     }
430
431     private boolean checkPattern(char[] pattern)
432     {
433         return checkPattern(pattern, false);
434     }
435
436     /**
437      * Check if next character sequence read from document is equals to 
438      * the provided pattern. Pattern is read from left to right until the 
439      * first character read doesn't match. If this happens all read characters are
440      * unread.
441      * @param pattern The pattern to check.
442      * @return <code>true</code> if pattern is equals else returns <code>false</code>.
443      */
444     private boolean checkPattern(char[] pattern, boolean ignoreCase)
445     {
446         int prevOffset = fOffset;
447         int prevLength = fCurrentLength;
448         for (int i = 0; i < pattern.length; i++)
449         {
450             int c = read();
451
452             if (c == ICharacterScanner.EOF
453                 || !letterEquals(c, pattern[i], ignoreCase))
454             {
455                 fOffset = prevOffset;
456                 fCurrentLength = prevLength;
457                 return false;
458             }
459         }
460
461         return true;
462     }
463
464     private boolean letterEquals(int test, char letter, boolean ignoreCase)
465     {
466         if (test == letter)
467             return true;
468         else if (
469             ignoreCase
470                 && Character.isLowerCase(letter)
471                 && test == Character.toUpperCase(letter))
472             return true;
473         else if (
474             ignoreCase
475                 && Character.isUpperCase(letter)
476                 && test == Character.toLowerCase(letter))
477             return true;
478
479         return false;
480     }
481     
482     /**
483      * Checks wether the offset is in a <code>String</code> and the specified 
484      * contenttype is the current content type.
485      * Strings are delimited, mutual exclusive, by a " or by a '.
486      * 
487      * @param contentType The contenttype to check.
488      * @return <code>true</code> if the current offset is in a string else 
489      *                  returns false.
490      */
491     private  boolean isInString(String contentType)
492     {
493         if(fContentType == contentType)
494                 return (fInString || fInDoubString);
495         else
496                 return false;           
497     }
498     
499     /**
500      * Returns the previouse partition stack for the given offset.
501      * 
502      * @param offset The offset to return the previouse partitionstack for.
503      * 
504      * @return The stack as a string array.
505      */
506     private String[] getPartitionStack(int offset)
507     {
508         ArrayList types = new ArrayList();
509         int tmpOffset = 0;
510         try
511         {
512             ITypedRegion region = fDocument.getPartition(offset);
513             tmpOffset = region.getOffset();
514             while(tmpOffset-1 > 0)
515             {
516                 region = fDocument.getPartition(tmpOffset-1);
517                 tmpOffset = region.getOffset();
518                 types.add(0, region.getType());
519             }
520         }
521         catch (BadLocationException e)
522         {
523            if(DEBUG)
524            {
525                         e.printStackTrace();
526            }
527         }
528         
529                 String[] retVal = new String[types.size()];
530         
531         retVal = (String[])types.toArray(retVal);
532         return retVal;
533     }
534     
535 }