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