1) Fixed '$' preceding of PHP variables (this file was missed in the previous commit).
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpeclipse / ui / text / rules / AbstractPartitioner.java
1 /*
2  * Copyright (c) 2002-2004 Widespace, OU 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://solareclipse.sourceforge.net/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     Igor Malinin - initial contribution
10  * 
11  * $Id: AbstractPartitioner.java,v 1.4 2006-10-21 23:13:53 pombredanne Exp $
12  */
13
14 package net.sourceforge.phpeclipse.ui.text.rules;
15
16 import java.util.ArrayList;
17 import java.util.List;
18
19 //incastrix
20 //import org.eclipse.jface.text.Assert;
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.jface.text.DocumentEvent;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IDocumentPartitioner;
25 import org.eclipse.jface.text.IDocumentPartitionerExtension;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITypedRegion;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TypedRegion;
30 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
31 import org.eclipse.jface.text.rules.IToken;
32
33 /**
34  * Advanced partitioner which maintains partitions as views to connected
35  * document. Views have own partitioners themselves. This class is designed as a
36  * base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
37  * 
38  * @author Igor Malinin
39  */
40 public abstract class AbstractPartitioner implements IDocumentPartitioner,
41                 IDocumentPartitionerExtension {
42         public final static boolean DEBUG = false;
43
44         /** Partition scanner */
45         protected IPartitionTokenScanner scanner;
46
47         /** Connected document */
48         protected IDocument document;
49
50         /** Flat structure of the document */
51         protected List nodes = new ArrayList();
52
53         /** The offset at which the first changed partition starts */
54         protected int regionStart;
55
56         /** The offset at which the last changed partition ends */
57         protected int regionEnd;
58
59         public AbstractPartitioner(IPartitionTokenScanner scanner) {
60                 this.scanner = scanner;
61         }
62
63         protected FlatNode createNode(String type, int offset, int length) {
64                 if (DEBUG) {
65                         Assert.isTrue(offset >= 0, Integer.toString(offset));
66                 }
67                 FlatNode node = new FlatNode(type);
68                 node.offset = offset;
69                 node.length = length;
70                 return node;
71         }
72
73         protected void addInnerRegion(FlatNode position) {
74                 nodes.add(computeFlatNodeIndex(position.offset), position);
75         }
76
77         protected void removeInnerRegion(FlatNode position) {
78                 nodes.remove(position); // TODO: Indexed remove?
79         }
80
81         protected void deleteInnerRegion(FlatNode position) {
82                 nodes.remove(position); // TODO: Indexed remove?
83         }
84
85         protected void resizeInnerRegion(FlatNode position) {
86         }
87
88         /*
89          * @see org.eclipse.jface.text.IDocumentPartitioner#connect(IDocument)
90          */
91         public void connect(IDocument document) {
92                 this.document = document;
93
94                 initialize();
95         }
96
97         /*
98          * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
99          */
100         public void disconnect() {
101                 nodes.clear();
102                 document = null;
103         }
104
105         /**
106          * Performs the initial partitioning of the partitioner's document.
107          */
108         protected void initialize() {
109                 scanner.setRange(document, 0, document.getLength());
110                 // axelcl start
111                 nodes.clear();
112                 // axelcl end
113                 IToken token = scanner.nextToken();
114                 while (!token.isEOF()) {
115                         String contentType = getTokenContentType(token);
116
117                         if (isSupportedContentType(contentType)) {
118                                 addInnerRegion(createNode(contentType,
119                                                 scanner.getTokenOffset(), scanner.getTokenLength()));
120                         }
121
122                         token = scanner.nextToken();
123                 }
124         }
125
126         /*
127          * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
128          */
129         public void documentAboutToBeChanged(DocumentEvent event) {
130                 regionStart = regionEnd = -1;
131         }
132
133         /*
134          * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
135          */
136         public boolean documentChanged(DocumentEvent event) {
137                 return (documentChanged2(event) != null);
138         }
139
140         /*
141          * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
142          */
143         public IRegion documentChanged2(DocumentEvent event) {
144                 int first = fixupPartitions(event);
145
146                 FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes
147                                 .size()]);
148
149                 // repartition changed region
150
151                 String contentType = IDocument.DEFAULT_CONTENT_TYPE;
152
153                 int offset;
154
155                 if (first == 0) {
156                         offset = 0; // Bug #697414: first offset
157                 } else {
158                         offset = event.getOffset();
159
160                         FlatNode partition = category[first - 1];
161                         if (partition.includes(offset)) {
162                                 offset = partition.offset;
163                                 contentType = partition.type;
164                                 --first;
165                         } else if (offset == partition.offset + partition.length) {
166                                 offset = partition.offset;
167                                 contentType = partition.type;
168                                 --first;
169                         } else {
170                                 offset = partition.offset + partition.length;
171                         }
172                 }
173
174                 // should not be changed since last conversion
175                 // category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
176                 if (DEBUG) {
177                         Assert.isTrue(offset >= 0, Integer.toString(offset));
178                 }
179                 scanner.setPartialRange(document, offset, document.getLength(),
180                                 contentType, offset);
181
182                 int lastScannedPosition = offset;
183                 IToken token = scanner.nextToken();
184                 while (!token.isEOF()) {
185                         contentType = getTokenContentType(token);
186
187                         if (!isSupportedContentType(contentType)) {
188                                 token = scanner.nextToken();
189                                 continue;
190                         }
191
192                         offset = scanner.getTokenOffset();
193                         if (DEBUG) {
194                                 Assert.isTrue(offset >= 0, scanner.toString());
195                         }
196                         int length = scanner.getTokenLength();
197
198                         lastScannedPosition = offset + length;
199
200                         // remove all affected positions
201                         while (first < category.length) {
202                                 FlatNode p = category[first];
203                                 if (p.offset + p.length < lastScannedPosition
204                                                 || (p.overlapsWith(offset, length) && (!containsPosition(
205                                                                 offset, length) || !contentType.equals(p.type)))) {
206                                         removeInnerRegion(p);
207                                         rememberRegion(p.offset, p.length);
208                                         ++first;
209                                 } else {
210                                         break;
211                                 }
212                         }
213
214                         // if position already exists we are done
215                         if (containsPosition(offset, length)) {
216                                 if (lastScannedPosition > event.getOffset()) {
217                                         // TODO: optional repartition till end of doc
218                                         return createRegion();
219                                 }
220
221                                 ++first;
222                         } else {
223                                 // insert the new type position
224                                 addInnerRegion(createNode(contentType, offset, length));
225                                 rememberRegion(offset, length);
226                         }
227                         // try {
228                         token = scanner.nextToken();
229                         // } catch (ArrayIndexOutOfBoundsException e) {
230                         // System.out.println(this.getClass().toString());
231                         // throw e;
232                         // }
233                 }
234
235                 // remove all positions behind lastScannedPosition
236                 // since there aren't any further types
237
238                 // Do not need to recalculate (lost remove events)!
239                 // first = computeIndexInInnerDocuments(lastScannedPosition);
240                 while (first < category.length) {
241                         FlatNode p = category[first++];
242                         removeInnerRegion(p);
243                         rememberRegion(p.offset, p.length);
244                 }
245
246                 return createRegion();
247         }
248
249         protected int fixupPartitions(DocumentEvent event) {
250                 int offset = event.getOffset();
251                 int length = event.getLength();
252                 int end = offset + length;
253
254                 // fixup flat nodes laying on change boundaries
255
256                 int first = computeFlatNodeIndex(offset);
257                 if (first > 0) {
258                         FlatNode p = (FlatNode) nodes.get(first - 1);
259
260                         int right = p.offset + p.length;
261                         if (offset < right) {
262                                 // change overlaps with partition
263                                 if (end < right) {
264                                         // cahnge completely inside partition
265                                         String text = event.getText();
266                                         p.length -= length;
267                                         if (text != null) {
268                                                 p.length += text.length();
269                                         }
270                                 } else {
271                                         // cut partition at right
272                                         int cut = p.offset + p.length - offset;
273                                         p.length -= cut;
274                                 }
275                         }
276                 }
277
278                 int last = computeFlatNodeIndex(end);
279                 if (first < last) {
280                         FlatNode p = (FlatNode) nodes.get(last - 1);
281
282                         int right = p.offset + p.length;
283                         if (end < right) {
284                                 // cut partition at left
285                                 int cut = end - p.offset;
286                                 p.length -= cut;
287                                 p.offset = offset;
288
289                                 String text = event.getText();
290                                 if (text != null) {
291                                         p.offset += text.length();
292                                 }
293
294                                 --last;
295                         }
296                 }
297
298                 // fixup flat nodes laying afrer change
299
300                 String text = event.getText();
301                 if (text != null) {
302                         length -= text.length();
303                 }
304
305                 for (int i = last, size = nodes.size(); i < size; i++) {
306                         ((FlatNode) nodes.get(i)).offset -= length;
307                 }
308
309                 // delete flat nodes laying completely inside change boundaries
310
311                 if (first < last) {
312                         do {
313                                 deleteInnerRegion((FlatNode) nodes.get(--last));
314                         } while (first < last);
315
316                         rememberRegion(offset, 0);
317                 }
318
319                 return first;
320         }
321
322         /**
323          * Returns whether the given type is one of the legal content types.
324          * 
325          * @param contentType
326          *            the content type to check
327          * @return <code>true</code> if the content type is a legal content type
328          */
329         protected boolean isSupportedContentType(String contentType) {
330                 /* TODO: implementation */
331                 // if (contentType != null) {
332                 // for (int i= 0; i < fLegalContentTypes.length; i++) {
333                 // if (fLegalContentTypes[i].equals(contentType)) {
334                 // return true;
335                 // }
336                 // }
337                 // }
338                 // return false;
339                 return (contentType != null);
340         }
341
342         /**
343          * Returns a content type encoded in the given token. If the token's data is
344          * not <code>null</code> and a string it is assumed that it is the encoded
345          * content type.
346          * 
347          * @param token
348          *            the token whose content type is to be determined
349          * @return the token's content type
350          */
351         protected String getTokenContentType(IToken token) {
352                 Object data = token.getData();
353                 if (data instanceof String) {
354                         return (String) data;
355                 }
356
357                 return null;
358         }
359
360         /*
361          * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
362          */
363         public String[] getLegalContentTypes() {
364                 // TODO: implementation
365                 return null;
366         }
367
368         /*
369          * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
370          */
371         public String getContentType(int offset) {
372                 return getPartition(offset).getType();
373         }
374
375         /*
376          * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
377          */
378         public ITypedRegion getPartition(int offset) {
379                 if (nodes.size() == 0) {
380                         return new TypedRegion(0, document.getLength(),
381                                         IDocument.DEFAULT_CONTENT_TYPE);
382                 }
383
384                 int index = computeFlatNodeIndex(offset);
385                 if (index < nodes.size()) {
386                         FlatNode next = (FlatNode) nodes.get(index);
387
388                         if (offset == next.offset) {
389                                 return new TypedRegion(next.offset, next.length, next.type);
390                         }
391
392                         if (index == 0) {
393                                 return new TypedRegion(0, next.offset,
394                                                 IDocument.DEFAULT_CONTENT_TYPE);
395                         }
396
397                         FlatNode prev = (FlatNode) nodes.get(index - 1);
398
399                         if (prev.includes(offset)) {
400                                 return new TypedRegion(prev.offset, prev.length, prev.type);
401                         }
402
403                         int end = prev.offset + prev.length;
404                         return new TypedRegion(end, next.offset - end,
405                                         IDocument.DEFAULT_CONTENT_TYPE);
406                 }
407
408                 FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
409
410                 if (prev.includes(offset)) {
411                         return new TypedRegion(prev.offset, prev.length, prev.type);
412                 }
413
414                 int end = prev.offset + prev.length;
415
416                 return new TypedRegion(end, document.getLength() - end,
417                                 IDocument.DEFAULT_CONTENT_TYPE);
418         }
419
420         /*
421          * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int,
422          *      int)
423          */
424         public ITypedRegion[] computePartitioning(int offset, int length) {
425                 List list = new ArrayList();
426
427                 int end = offset + length;
428
429                 int index = computeFlatNodeIndex(offset);
430                 while (true) {
431                         FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1)
432                                         : null;
433
434                         if (prev != null) {
435                                 if (prev.overlapsWith(offset, length)) {
436                                         list.add(new TypedRegion(prev.offset, prev.length,
437                                                         prev.type));
438                                 }
439
440                                 if (end <= prev.offset + prev.length) {
441                                         break;
442                                 }
443                         }
444
445                         FlatNode next = (index < nodes.size()) ? (FlatNode) nodes
446                                         .get(index) : null;
447
448                         if (next == null || offset < next.offset) {
449                                 int off0 = offset;
450                                 int off1 = offset + length;
451
452                                 if (prev != null && off0 < prev.offset + prev.length) {
453                                         off0 = prev.offset + prev.length;
454                                 }
455
456                                 if (next != null && next.offset < off1) {
457                                         off1 = next.offset;
458                                 }
459
460                                 if (off0 < off1) {
461                                         list.add(new TypedRegion(off0, off1 - off0,
462                                                         IDocument.DEFAULT_CONTENT_TYPE));
463                                 }
464                         }
465
466                         if (next == null) {
467                                 break;
468                         }
469
470                         ++index;
471                 }
472
473                 return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
474         }
475
476         /**
477          * Computes the index in the list of flat nodes at which an flat node with
478          * the given offset would be inserted. The flat node is supposed to become
479          * the first in this list of all flat nodes with the same offset.
480          * 
481          * @param offset
482          *            the offset for which the index is computed
483          * @return the computed index
484          */
485         protected int computeFlatNodeIndex(int offset) {
486                 if (nodes.size() == 0) {
487                         return 0;
488                 }
489
490                 int left = 0, mid = 0;
491                 int right = nodes.size() - 1;
492
493                 FlatNode p = null;
494
495                 while (left < right) {
496                         mid = (left + right) / 2;
497
498                         p = (FlatNode) nodes.get(mid);
499
500                         if (offset < p.offset) {
501                                 right = (left == mid) ? left : mid - 1;
502                         } else if (offset > p.offset) {
503                                 left = (right == mid) ? right : mid + 1;
504                         } else if (offset == p.offset) {
505                                 left = right = mid;
506                         }
507                 }
508
509                 int pos = left;
510                 p = (FlatNode) nodes.get(pos);
511                 if (offset > p.offset) {
512                         // append to the end
513                         pos++;
514                 } else {
515                         // entry will became the first of all entries with the same offset
516                         do {
517                                 --pos;
518                                 if (pos < 0) {
519                                         break;
520                                 }
521                                 p = (FlatNode) nodes.get(pos);
522                         } while (offset == p.offset);
523                         ++pos;
524                 }
525
526                 return pos;
527         }
528
529         public boolean containsPosition(int offset, int length) {
530                 int size = nodes.size();
531                 if (size == 0) {
532                         return false;
533                 }
534
535                 int index = computeFlatNodeIndex(offset);
536                 if (index < size) {
537                         FlatNode p = (FlatNode) nodes.get(index);
538
539                         while (p.offset == offset) {
540                                 if (p.length == length) {
541                                         return true;
542                                 }
543
544                                 if (++index < size) {
545                                         p = (FlatNode) nodes.get(index);
546                                 } else {
547                                         break;
548                                 }
549                         }
550                 }
551
552                 return false;
553         }
554
555         /**
556          * Helper method for tracking the minimal region containg all partition
557          * changes. If <code>offset</code> is smaller than the remembered offset,
558          * <code>offset</code> will from now on be remembered. If
559          * <code>offset + length</code> is greater than the remembered end offset,
560          * it will be remembered from now on.
561          * 
562          * @param offset
563          *            the offset
564          * @param length
565          *            the length
566          */
567         protected final void rememberRegion(int offset, int length) {
568                 // remember start offset
569                 if (regionStart == -1) {
570                         regionStart = offset;
571                 } else if (offset < regionStart) {
572                         regionStart = offset;
573                 }
574
575                 // remember end offset
576                 int endOffset = offset + length;
577
578                 if (regionEnd == -1) {
579                         regionEnd = endOffset;
580                 } else if (endOffset > regionEnd) {
581                         regionEnd = endOffset;
582                 }
583         }
584
585         /**
586          * Creates the minimal region containing all partition changes using the
587          * remembered offsets.
588          * 
589          * @return the minimal region containing all the partition changes
590          */
591         protected final IRegion createRegion() {
592                 if (regionStart == -1 || regionEnd == -1) {
593                         return null;
594                 }
595
596                 return new Region(regionStart, regionEnd - regionStart);
597         }
598 }