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