refactory: added UI removed from core plugin.
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpdt / internal / ui / text / folding / DefaultJavaFoldingStructureProvider.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 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.folding;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.LinkedHashMap;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23
24 import net.sourceforge.phpdt.core.ElementChangedEvent;
25 import net.sourceforge.phpdt.core.ICompilationUnit;
26 import net.sourceforge.phpdt.core.IElementChangedListener;
27 import net.sourceforge.phpdt.core.IJavaElement;
28 import net.sourceforge.phpdt.core.IJavaElementDelta;
29 import net.sourceforge.phpdt.core.IMember;
30 import net.sourceforge.phpdt.core.IParent;
31 import net.sourceforge.phpdt.core.ISourceRange;
32 import net.sourceforge.phpdt.core.ISourceReference;
33 import net.sourceforge.phpdt.core.IType;
34 import net.sourceforge.phpdt.core.JavaCore;
35 import net.sourceforge.phpdt.core.JavaModelException;
36 import net.sourceforge.phpdt.core.ToolFactory;
37 import net.sourceforge.phpdt.core.compiler.IScanner;
38 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
39 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
40 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
41 import net.sourceforge.phpdt.internal.ui.text.DocumentCharacterIterator;
42 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
43 import net.sourceforge.phpdt.ui.PreferenceConstants;
44 import net.sourceforge.phpdt.ui.text.folding.IJavaFoldingStructureProvider;
45 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
46 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
47 import net.sourceforge.phpeclipse.phpeditor.PHPUnitEditor;
48 import net.sourceforge.phpeclipse.ui.WebUI;
49
50 import org.eclipse.jface.preference.IPreferenceStore;
51 import org.eclipse.jface.text.Assert;
52 import org.eclipse.jface.text.BadLocationException;
53 import org.eclipse.jface.text.IDocument;
54 import org.eclipse.jface.text.IRegion;
55 import org.eclipse.jface.text.Position;
56 import org.eclipse.jface.text.Region;
57 import org.eclipse.jface.text.source.Annotation;
58 import org.eclipse.jface.text.source.IAnnotationModel;
59 import org.eclipse.jface.text.source.projection.IProjectionListener;
60 import org.eclipse.jface.text.source.projection.IProjectionPosition;
61 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
62 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
63 import org.eclipse.jface.text.source.projection.ProjectionViewer;
64 import org.eclipse.ui.texteditor.IDocumentProvider;
65 import org.eclipse.ui.texteditor.ITextEditor;
66
67 /**
68  * Updates the projection model of a class file or compilation unit.
69  * 
70  * @since 3.0
71  */
72 public class DefaultJavaFoldingStructureProvider implements
73                 IProjectionListener, IJavaFoldingStructureProvider {
74
75         private static class JavaProjectionAnnotation extends ProjectionAnnotation {
76
77                 private IJavaElement fJavaElement;
78
79                 private boolean fIsComment;
80
81                 public JavaProjectionAnnotation(IJavaElement element,
82                                 boolean isCollapsed, boolean isComment) {
83                         super(isCollapsed);
84                         fJavaElement = element;
85                         fIsComment = isComment;
86                 }
87
88                 public IJavaElement getElement() {
89                         return fJavaElement;
90                 }
91
92                 public void setElement(IJavaElement element) {
93                         fJavaElement = element;
94                 }
95
96                 public boolean isComment() {
97                         return fIsComment;
98                 }
99
100                 public void setIsComment(boolean isComment) {
101                         fIsComment = isComment;
102                 }
103
104                 /*
105                  * @see java.lang.Object#toString()
106                  */
107                 public String toString() {
108                         return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
109                                         "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
110                                         "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
111                                         "\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
112                 }
113         }
114
115         private static final class Tuple {
116                 JavaProjectionAnnotation annotation;
117
118                 Position position;
119
120                 Tuple(JavaProjectionAnnotation annotation, Position position) {
121                         this.annotation = annotation;
122                         this.position = position;
123                 }
124         }
125
126         private class ElementChangedListener implements IElementChangedListener {
127
128                 /*
129                  * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
130                  */
131                 public void elementChanged(ElementChangedEvent e) {
132                         IJavaElementDelta delta = findElement(fInput, e.getDelta());
133                         if (delta != null)
134                                 processDelta(delta);
135                 }
136
137                 private IJavaElementDelta findElement(IJavaElement target,
138                                 IJavaElementDelta delta) {
139
140                         if (delta == null || target == null)
141                                 return null;
142
143                         IJavaElement element = delta.getElement();
144
145                         if (element.getElementType() > IJavaElement.CLASS_FILE)
146                                 return null;
147
148                         if (target.equals(element))
149                                 return delta;
150
151                         IJavaElementDelta[] children = delta.getAffectedChildren();
152
153                         for (int i = 0; i < children.length; i++) {
154                                 IJavaElementDelta d = findElement(target, children[i]);
155                                 if (d != null)
156                                         return d;
157                         }
158
159                         return null;
160                 }
161         }
162
163         /**
164          * Projection position that will return two foldable regions: one folding
165          * away the region from after the '/**' to the beginning of the content, the
166          * other from after the first content line until after the comment.
167          * 
168          * @since 3.1
169          */
170         private static final class CommentPosition extends Position implements
171                         IProjectionPosition {
172                 CommentPosition(int offset, int length) {
173                         super(offset, length);
174                 }
175
176                 /*
177                  * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
178                  */
179                 public IRegion[] computeProjectionRegions(IDocument document)
180                                 throws BadLocationException {
181                         DocumentCharacterIterator sequence = new DocumentCharacterIterator(
182                                         document, offset, offset + length);
183                         int prefixEnd = 0;
184                         int contentStart = findFirstContent(sequence, prefixEnd);
185
186                         int firstLine = document.getLineOfOffset(offset + prefixEnd);
187                         int captionLine = document.getLineOfOffset(offset + contentStart);
188                         int lastLine = document.getLineOfOffset(offset + length);
189
190                         Assert.isTrue(firstLine <= captionLine,
191                                         "first folded line is greater than the caption line"); //$NON-NLS-1$
192                         Assert.isTrue(captionLine <= lastLine,
193                                         "caption line is greater than the last folded line"); //$NON-NLS-1$
194
195                         IRegion preRegion;
196                         if (firstLine < captionLine) {
197                                 // preRegion= new Region(offset + prefixEnd, contentStart -
198                                 // prefixEnd);
199                                 int preOffset = document.getLineOffset(firstLine);
200                                 IRegion preEndLineInfo = document
201                                                 .getLineInformation(captionLine);
202                                 int preEnd = preEndLineInfo.getOffset();
203                                 preRegion = new Region(preOffset, preEnd - preOffset);
204                         } else {
205                                 preRegion = null;
206                         }
207
208                         if (captionLine < lastLine) {
209                                 int postOffset = document.getLineOffset(captionLine + 1);
210                                 IRegion postRegion = new Region(postOffset, offset + length
211                                                 - postOffset);
212
213                                 if (preRegion == null)
214                                         return new IRegion[] { postRegion };
215
216                                 return new IRegion[] { preRegion, postRegion };
217                         }
218
219                         if (preRegion != null)
220                                 return new IRegion[] { preRegion };
221
222                         return null;
223                 }
224
225                 /**
226                  * Finds the offset of the first identifier part within
227                  * <code>content</code>. Returns 0 if none is found.
228                  * 
229                  * @param content
230                  *            the content to search
231                  * @return the first index of a unicode identifier part, or zero if none
232                  *         can be found
233                  */
234                 private int findFirstContent(final CharSequence content, int prefixEnd) {
235                         int lenght = content.length();
236                         for (int i = prefixEnd; i < lenght; i++) {
237                                 if (Character.isUnicodeIdentifierPart(content.charAt(i)))
238                                         return i;
239                         }
240                         return 0;
241                 }
242
243                 // /**
244                 // * Finds the offset of the first identifier part within
245                 // <code>content</code>.
246                 // * Returns 0 if none is found.
247                 // *
248                 // * @param content the content to search
249                 // * @return the first index of a unicode identifier part, or zero if
250                 // none
251                 // can
252                 // * be found
253                 // */
254                 // private int findPrefixEnd(final CharSequence content) {
255                 // // return the index after the leading '/*' or '/**'
256                 // int len= content.length();
257                 // int i= 0;
258                 // while (i < len && isWhiteSpace(content.charAt(i)))
259                 // i++;
260                 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1)
261                 // ==
262                 // '*')
263                 // if (len >= i + 3 && content.charAt(i + 2) == '*')
264                 // return i + 3;
265                 // else
266                 // return i + 2;
267                 // else
268                 // return i;
269                 // }
270                 //
271                 // private boolean isWhiteSpace(char c) {
272                 // return c == ' ' || c == '\t';
273                 // }
274
275                 /*
276                  * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
277                  */
278                 public int computeCaptionOffset(IDocument document) {
279                         // return 0;
280                         DocumentCharacterIterator sequence = new DocumentCharacterIterator(
281                                         document, offset, offset + length);
282                         return findFirstContent(sequence, 0);
283                 }
284         }
285
286         /**
287          * Projection position that will return two foldable regions: one folding
288          * away the lines before the one containing the simple name of the java
289          * element, one folding away any lines after the caption.
290          * 
291          * @since 3.1
292          */
293         private static final class JavaElementPosition extends Position implements
294                         IProjectionPosition {
295
296                 private IMember fMember;
297
298                 public JavaElementPosition(int offset, int length, IMember member) {
299                         super(offset, length);
300                         Assert.isNotNull(member);
301                         fMember = member;
302                 }
303
304                 public void setMember(IMember member) {
305                         Assert.isNotNull(member);
306                         fMember = member;
307                 }
308
309                 /*
310                  * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
311                  */
312                 public IRegion[] computeProjectionRegions(IDocument document)
313                                 throws BadLocationException {
314                         int nameStart = offset;
315                         try {
316                                 /*
317                                  * The member's name range may not be correct. However,
318                                  * reconciling would trigger another element delta which would
319                                  * lead to reentrant situations. Therefore, we optimistically
320                                  * assume that the name range is correct, but double check the
321                                  * received lines below.
322                                  */
323                                 ISourceRange nameRange = fMember.getNameRange();
324                                 if (nameRange != null)
325                                         nameStart = nameRange.getOffset();
326
327                         } catch (JavaModelException e) {
328                                 // ignore and use default
329                         }
330
331                         int firstLine = document.getLineOfOffset(offset);
332                         int captionLine = document.getLineOfOffset(nameStart);
333                         int lastLine = document.getLineOfOffset(offset + length);
334
335                         /*
336                          * see comment above - adjust the caption line to be inside the
337                          * entire folded region, and rely on later element deltas to correct
338                          * the name range.
339                          */
340                         if (captionLine < firstLine)
341                                 captionLine = firstLine;
342                         if (captionLine > lastLine)
343                                 captionLine = lastLine;
344
345                         IRegion preRegion;
346                         if (firstLine < captionLine) {
347                                 int preOffset = document.getLineOffset(firstLine);
348                                 IRegion preEndLineInfo = document
349                                                 .getLineInformation(captionLine);
350                                 int preEnd = preEndLineInfo.getOffset();
351                                 preRegion = new Region(preOffset, preEnd - preOffset);
352                         } else {
353                                 preRegion = null;
354                         }
355
356                         if (captionLine < lastLine) {
357                                 int postOffset = document.getLineOffset(captionLine + 1);
358                                 IRegion postRegion = new Region(postOffset, offset + length
359                                                 - postOffset);
360
361                                 if (preRegion == null)
362                                         return new IRegion[] { postRegion };
363
364                                 return new IRegion[] { preRegion, postRegion };
365                         }
366
367                         if (preRegion != null)
368                                 return new IRegion[] { preRegion };
369
370                         return null;
371                 }
372
373                 /*
374                  * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
375                  */
376                 public int computeCaptionOffset(IDocument document)
377                                 throws BadLocationException {
378                         int nameStart = offset;
379                         try {
380                                 // need a reconcile here?
381                                 ISourceRange nameRange = fMember.getNameRange();
382                                 if (nameRange != null)
383                                         nameStart = nameRange.getOffset();
384                         } catch (JavaModelException e) {
385                                 // ignore and use default
386                         }
387
388                         return nameStart - offset;
389                 }
390
391         }
392
393         private IDocument fCachedDocument;
394
395         private ProjectionAnnotationModel fCachedModel;
396
397         private ITextEditor fEditor;
398
399         private ProjectionViewer fViewer;
400
401         private IJavaElement fInput;
402
403         private IElementChangedListener fElementListener;
404
405         private boolean fAllowCollapsing = false;
406
407         private boolean fCollapseJavadoc = false;
408
409         // private boolean fCollapseImportContainer = true;
410
411         private boolean fCollapseInnerTypes = true;
412
413         private boolean fCollapseMethods = false;
414
415         private boolean fCollapseHeaderComments = true;
416
417         /* caches for header comment extraction. */
418         private IType fFirstType;
419
420         private boolean fHasHeaderComment;
421
422         public DefaultJavaFoldingStructureProvider() {
423         }
424
425         public void install(ITextEditor editor, ProjectionViewer viewer) {
426                 if (editor instanceof PHPEditor) {
427                         fEditor = editor;
428                         fViewer = viewer;
429                         fViewer.addProjectionListener(this);
430                 }
431         }
432
433         public void uninstall() {
434                 if (isInstalled()) {
435                         projectionDisabled();
436                         fViewer.removeProjectionListener(this);
437                         fViewer = null;
438                         fEditor = null;
439                 }
440         }
441
442         protected boolean isInstalled() {
443                 return fEditor != null;
444         }
445
446         /*
447          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
448          */
449         public void projectionEnabled() {
450                 // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
451                 // projectionEnabled messages are not always paired with
452                 // projectionDisabled
453                 // i.e. multiple enabled messages may be sent out.
454                 // we have to make sure that we disable first when getting an enable
455                 // message.
456                 projectionDisabled();
457
458                 if (fEditor instanceof PHPEditor) {
459                         initialize();
460                         fElementListener = new ElementChangedListener();
461                         JavaCore.addElementChangedListener(fElementListener);
462                 }
463         }
464
465         /*
466          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
467          */
468         public void projectionDisabled() {
469                 fCachedDocument = null;
470                 if (fElementListener != null) {
471                         JavaCore.removeElementChangedListener(fElementListener);
472                         fElementListener = null;
473                 }
474         }
475
476         public void initialize() {
477
478                 if (!isInstalled())
479                         return;
480
481                 initializePreferences();
482
483                 try {
484
485                         IDocumentProvider provider = fEditor.getDocumentProvider();
486                         fCachedDocument = provider.getDocument(fEditor.getEditorInput());
487                         fAllowCollapsing = true;
488
489                         fFirstType = null;
490                         fHasHeaderComment = false;
491
492                         if (fEditor instanceof PHPUnitEditor) {
493                                 IWorkingCopyManager manager = WebUI.getDefault()
494                                                 .getWorkingCopyManager();
495                                 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
496                         }
497                         // else if (fEditor instanceof ClassFileEditor) {
498                         // IClassFileEditorInput editorInput= (IClassFileEditorInput)
499                         // fEditor.getEditorInput();
500                         // fInput= editorInput.getClassFile();
501                         // }
502
503                         if (fInput != null) {
504                                 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
505                                                 .getAdapter(ProjectionAnnotationModel.class);
506                                 if (model != null) {
507                                         fCachedModel = model;
508                                         if (fInput instanceof ICompilationUnit) {
509                                                 ICompilationUnit unit = (ICompilationUnit) fInput;
510                                                 synchronized (unit) {
511                                                         try {
512                                                                 // unit.reconcile(ICompilationUnit.NO_AST,
513                                                                 // false, null, null);
514                                                                 unit.reconcile();
515                                                         } catch (JavaModelException x) {
516                                                         }
517                                                 }
518                                         }
519
520                                         Map additions = computeAdditions((IParent) fInput);
521                                         /*
522                                          * Minimize the events being sent out - as this happens in
523                                          * the UI thread merge everything into one call.
524                                          */
525                                         List removals = new LinkedList();
526                                         Iterator existing = model.getAnnotationIterator();
527                                         while (existing.hasNext())
528                                                 removals.add(existing.next());
529                                         model.replaceAnnotations((Annotation[]) removals
530                                                         .toArray(new Annotation[removals.size()]),
531                                                         additions);
532                                 }
533                         }
534
535                 } finally {
536                         fCachedDocument = null;
537                         fCachedModel = null;
538                         fAllowCollapsing = false;
539
540                         fFirstType = null;
541                         fHasHeaderComment = false;
542                 }
543         }
544
545         private void initializePreferences() {
546                 IPreferenceStore store = WebUI.getDefault()
547                                 .getPreferenceStore();
548                 fCollapseInnerTypes = store
549                                 .getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
550                 // fCollapseImportContainer =
551                 // store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
552                 fCollapseJavadoc = store
553                                 .getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
554                 fCollapseMethods = store
555                                 .getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
556                 fCollapseHeaderComments = store
557                                 .getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
558         }
559
560         private Map computeAdditions(IParent parent) {
561                 Map map = new LinkedHashMap(); // use a linked map to maintain ordering
562                                                                                 // of
563                 // comments
564                 try {
565                         computeAdditions(parent.getChildren(), map);
566                 } catch (JavaModelException x) {
567                 }
568                 return map;
569         }
570
571         private void computeAdditions(IJavaElement[] elements, Map map)
572                         throws JavaModelException {
573                 for (int i = 0; i < elements.length; i++) {
574                         IJavaElement element = elements[i];
575
576                         computeAdditions(element, map);
577
578                         if (element instanceof IParent) {
579                                 IParent parent = (IParent) element;
580                                 computeAdditions(parent.getChildren(), map);
581                         }
582                 }
583         }
584
585         private void computeAdditions(IJavaElement element, Map map) {
586
587                 boolean createProjection = false;
588
589                 boolean collapse = false;
590                 switch (element.getElementType()) {
591
592                 // case IJavaElement.IMPORT_CONTAINER:
593                 // collapse = fAllowCollapsing && fCollapseImportContainer;
594                 // createProjection = true;
595                 // break;
596                 case IJavaElement.TYPE:
597                         collapse = fAllowCollapsing;
598                         if (isInnerType((IType) element)) {
599                                 collapse = collapse && fCollapseInnerTypes;
600                         } else {
601                                 collapse = false; // don't allow the most outer type to be
602                                                                         // folded, may be changed in future versions
603                         }
604                         createProjection = true;
605                         break;
606                 case IJavaElement.METHOD:
607                         collapse = fAllowCollapsing && fCollapseMethods;
608                         createProjection = true;
609                         break;
610                 }
611
612                 if (createProjection) {
613                         IRegion[] regions = computeProjectionRanges(element);
614                         if (regions != null) {
615                                 // comments
616                                 for (int i = 0; i < regions.length - 1; i++) {
617                                         Position position = createProjectionPosition(regions[i],
618                                                         null);
619                                         boolean commentCollapse;
620                                         if (position != null) {
621                                                 if (i == 0 && (regions.length > 2 || fHasHeaderComment)
622                                                                 && element == fFirstType) {
623                                                         commentCollapse = fAllowCollapsing
624                                                                         && fCollapseHeaderComments;
625                                                 } else {
626                                                         commentCollapse = fAllowCollapsing
627                                                                         && fCollapseJavadoc;
628                                                 }
629                                                 map.put(new JavaProjectionAnnotation(element,
630                                                                 commentCollapse, true), position);
631                                         }
632                                 }
633                                 // code
634                                 Position position = createProjectionPosition(
635                                                 regions[regions.length - 1], element);
636                                 if (position != null)
637                                         map.put(new JavaProjectionAnnotation(element, collapse,
638                                                         false), position);
639                         }
640                 }
641         }
642
643         private boolean isInnerType(IType type) {
644
645                 try {
646                         return type.isMember();
647                 } catch (JavaModelException x) {
648                         IJavaElement parent = type.getParent();
649                         if (parent != null) {
650                                 int parentType = parent.getElementType();
651                                 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
652                         }
653                 }
654
655                 return false;
656         }
657
658         /**
659          * Computes the projection ranges for a given <code>IJavaElement</code>.
660          * More than one range may be returned if the element has a leading comment
661          * which gets folded separately. If there are no foldable regions,
662          * <code>null</code> is returned.
663          * 
664          * @param element
665          *            the java element that can be folded
666          * @return the regions to be folded, or <code>null</code> if there are
667          *         none
668          */
669         private IRegion[] computeProjectionRanges(IJavaElement element) {
670
671                 try {
672                         if (element instanceof ISourceReference) {
673                                 ISourceReference reference = (ISourceReference) element;
674                                 ISourceRange range = reference.getSourceRange();
675
676                                 String contents = reference.getSource();
677                                 if (contents == null)
678                                         return null;
679
680                                 List regions = new ArrayList();
681                                 // now add all comments first to the regions list
682                                 if (fFirstType == null && element instanceof IType) {
683                                         fFirstType = (IType) element;
684                                         IRegion headerComment = computeHeaderComment(fFirstType);
685                                         if (headerComment != null) {
686                                                 regions.add(headerComment);
687                                                 fHasHeaderComment = true;
688                                         }
689                                 }
690
691                                 final int shift = range.getOffset();
692                                 int start = shift;
693                                 if (element instanceof IType) {
694                                         Scanner scanner = ToolFactory.createScanner(true, false,
695                                                         false, false);
696                                         scanner.setSource(contents.toCharArray());
697                                         scanner.setPHPMode(true);
698
699                                         int token = scanner.getNextToken();
700                                         while (token != ITerminalSymbols.TokenNameEOF) {
701
702                                                 token = scanner.getNextToken();
703                                                 start = shift + scanner.getCurrentTokenStartPosition();
704
705                                                 switch (token) {
706                                                 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
707                                                 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
708                                                         int end = shift
709                                                                         + scanner.getCurrentTokenEndPosition() + 1;
710                                                         regions.add(new Region(start, end - start));
711                                                 }
712                                                 case ITerminalSymbols.TokenNameCOMMENT_LINE:
713                                                         continue;
714                                                 }
715                                         }
716                                 }
717                                 // at the end add the element region
718                                 regions.add(new Region(range.getOffset(), range.getLength()));
719
720                                 if (regions.size() > 0) {
721                                         IRegion[] result = new IRegion[regions.size()];
722                                         regions.toArray(result);
723                                         return result;
724                                 }
725
726                         }
727                 } catch (JavaModelException e) {
728                 } catch (InvalidInputException e) {
729                 }
730
731                 return null;
732         }
733
734         private IRegion computeHeaderComment(IType type) throws JavaModelException {
735                 if (fCachedDocument == null)
736                         return null;
737
738                 // search at most up to the first type
739                 ISourceRange range = type.getSourceRange();
740                 if (range == null)
741                         return null;
742                 int start = 0;
743                 int end = range.getOffset();
744
745                 if (fInput instanceof ISourceReference) {
746                         String content;
747                         try {
748                                 content = fCachedDocument.get(start, end - start);
749                         } catch (BadLocationException e) {
750                                 return null; // ignore header comment in that case
751                         }
752
753                         /*
754                          * code adapted from CommentFormattingStrategy: scan the header
755                          * content up to the first type. Once a comment is found, accumulate
756                          * any additional comments up to the stop condition. The stop
757                          * condition is reaching a package declaration, import container, or
758                          * the end of the input.
759                          */
760                         IScanner scanner = ToolFactory.createScanner(true, false, false,
761                                         false);
762                         scanner.setSource(content.toCharArray());
763
764                         int headerStart = -1;
765                         int headerEnd = -1;
766                         try {
767                                 boolean foundComment = false;
768                                 int terminal = scanner.getNextToken();
769                                 while (terminal != ITerminalSymbols.TokenNameEOF
770                                                 && !(terminal == ITerminalSymbols.TokenNameclass
771                                                                 || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
772
773                                         if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC
774                                                         || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
775                                                         || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
776                                                 if (!foundComment)
777                                                         headerStart = scanner
778                                                                         .getCurrentTokenStartPosition();
779                                                 headerEnd = scanner.getCurrentTokenEndPosition();
780                                                 foundComment = true;
781                                         }
782                                         terminal = scanner.getNextToken();
783                                 }
784
785                         } catch (InvalidInputException ex) {
786                                 return null;
787                         }
788
789                         if (headerEnd != -1) {
790                                 return new Region(headerStart, headerEnd - headerStart);
791                         }
792                 }
793                 return null;
794         }
795
796         private Position createProjectionPosition(IRegion region,
797                         IJavaElement element) {
798
799                 if (fCachedDocument == null)
800                         return null;
801
802                 try {
803
804                         int start = fCachedDocument.getLineOfOffset(region.getOffset());
805                         int end = fCachedDocument.getLineOfOffset(region.getOffset()
806                                         + region.getLength());
807                         if (start != end) {
808                                 int offset = fCachedDocument.getLineOffset(start);
809                                 int endOffset;
810                                 if (fCachedDocument.getNumberOfLines() > end + 1)
811                                         endOffset = fCachedDocument.getLineOffset(end + 1);
812                                 else if (end > start)
813                                         endOffset = fCachedDocument.getLineOffset(end)
814                                                         + fCachedDocument.getLineLength(end);
815                                 else
816                                         return null;
817                                 if (element instanceof IMember)
818                                         return new JavaElementPosition(offset, endOffset - offset,
819                                                         (IMember) element);
820                                 else
821                                         return new CommentPosition(offset, endOffset - offset);
822                         }
823
824                 } catch (BadLocationException x) {
825                 }
826
827                 return null;
828         }
829
830         protected void processDelta(IJavaElementDelta delta) {
831
832                 if (!isInstalled())
833                         return;
834
835                 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
836                         return;
837
838                 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
839                                 .getAdapter(ProjectionAnnotationModel.class);
840                 if (model == null)
841                         return;
842
843                 try {
844
845                         IDocumentProvider provider = fEditor.getDocumentProvider();
846                         fCachedDocument = provider.getDocument(fEditor.getEditorInput());
847                         fCachedModel = model;
848                         fAllowCollapsing = false;
849
850                         fFirstType = null;
851                         fHasHeaderComment = false;
852
853                         Map additions = new HashMap();
854                         List deletions = new ArrayList();
855                         List updates = new ArrayList();
856
857                         Map updated = computeAdditions((IParent) fInput);
858                         Map previous = createAnnotationMap(model);
859
860                         Iterator e = updated.keySet().iterator();
861                         while (e.hasNext()) {
862                                 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e
863                                                 .next();
864 //+
865                                 Position newPosition = (Position) updated.get(newAnnotation);
866                                 additions.put(newAnnotation, newPosition);
867 //-
868 //                              IJavaElement element = newAnnotation.getElement();
869 //                              Position newPosition = (Position) updated.get(newAnnotation);
870 //
871 //                              List annotations = (List) previous.get(element);
872 //                              if (annotations == null) {
873 //
874 //                                      additions.put(newAnnotation, newPosition);
875 //
876 //                              } else {
877 //                                      Iterator x = annotations.iterator();
878 //                                      boolean matched = false;
879 //                                      while (x.hasNext()) {
880 //                                              Tuple tuple = (Tuple) x.next();
881 //                                              JavaProjectionAnnotation existingAnnotation = tuple.annotation;
882 //                                              Position existingPosition = tuple.position;
883 //                                              if (newAnnotation.isComment() == existingAnnotation
884 //                                                              .isComment()) {
885 //                                                      if (existingPosition != null
886 //                                                                      && (!newPosition.equals(existingPosition))) {
887 //                                                              existingPosition.setOffset(newPosition
888 //                                                                              .getOffset());
889 //                                                              existingPosition.setLength(newPosition
890 //                                                                              .getLength());
891 //                                                              updates.add(existingAnnotation);
892 //                                                      }
893 //                                                      matched = true;
894 //                                                      x.remove();
895 //                                                      break;
896 //                                              }
897 //                                      }
898 //                                      if (!matched)
899 //                                              additions.put(newAnnotation, newPosition);
900 //
901 //                                      if (annotations.isEmpty())
902 //                                              previous.remove(element);
903 //                              }
904 //-
905                         }
906
907                         e = previous.values().iterator();
908                         while (e.hasNext()) {
909                                 List list = (List) e.next();
910                                 int size = list.size();
911                                 for (int i = 0; i < size; i++)
912                                         deletions.add(((Tuple) list.get(i)).annotation);
913                         }
914
915                         match(deletions, additions, updates);
916
917                         Annotation[] removals = new Annotation[deletions.size()];
918                         deletions.toArray(removals);
919                         Annotation[] changes = new Annotation[updates.size()];
920                         updates.toArray(changes);
921                         model.modifyAnnotations(removals, additions, changes);
922
923                 } finally {
924                         fCachedDocument = null;
925                         fAllowCollapsing = true;
926                         fCachedModel = null;
927
928                         fFirstType = null;
929                         fHasHeaderComment = false;
930                 }
931         }
932
933         /**
934          * Matches deleted annotations to changed or added ones. A deleted
935          * annotation/position tuple that has a matching addition / change is
936          * updated and marked as changed. The matching tuple is not added (for
937          * additions) or marked as deletion instead (for changes). The result is
938          * that more annotations are changed and fewer get deleted/re-added.
939          */
940         private void match(List deletions, Map additions, List changes) {
941                 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
942                         return;
943
944                 List newDeletions = new ArrayList();
945                 List newChanges = new ArrayList();
946
947                 Iterator deletionIterator = deletions.iterator();
948                 while (deletionIterator.hasNext()) {
949                         JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator
950                                         .next();
951                         Position deletedPosition = fCachedModel.getPosition(deleted);
952                         if (deletedPosition == null)
953                                 continue;
954
955                         Tuple deletedTuple = new Tuple(deleted, deletedPosition);
956
957                         Tuple match = findMatch(deletedTuple, changes, null);
958                         boolean addToDeletions = true;
959                         if (match == null) {
960                                 match = findMatch(deletedTuple, additions.keySet(), additions);
961                                 addToDeletions = false;
962                         }
963
964                         if (match != null) {
965                                 IJavaElement element = match.annotation.getElement();
966                                 deleted.setElement(element);
967                                 deletedPosition.setLength(match.position.getLength());
968                                 if (deletedPosition instanceof JavaElementPosition
969                                                 && element instanceof IMember) {
970                                         JavaElementPosition jep = (JavaElementPosition) deletedPosition;
971                                         jep.setMember((IMember) element);
972                                 }
973
974                                 deletionIterator.remove();
975                                 newChanges.add(deleted);
976
977                                 if (addToDeletions)
978                                         newDeletions.add(match.annotation);
979                         }
980                 }
981
982                 deletions.addAll(newDeletions);
983                 changes.addAll(newChanges);
984         }
985
986         /**
987          * Finds a match for <code>tuple</code> in a collection of annotations.
988          * The positions for the <code>JavaProjectionAnnotation</code> instances
989          * in <code>annotations</code> can be found in the passed
990          * <code>positionMap</code> or <code>fCachedModel</code> if
991          * <code>positionMap</code> is <code>null</code>.
992          * <p>
993          * A tuple is said to match another if their annotations have the same
994          * comment flag and their position offsets are equal.
995          * </p>
996          * <p>
997          * If a match is found, the annotation gets removed from
998          * <code>annotations</code>.
999          * </p>
1000          * 
1001          * @param tuple
1002          *            the tuple for which we want to find a match
1003          * @param annotations
1004          *            collection of <code>JavaProjectionAnnotation</code>
1005          * @param positionMap
1006          *            a <code>Map&lt;Annotation, Position&gt;</code> or
1007          *            <code>null</code>
1008          * @return a matching tuple or <code>null</code> for no match
1009          */
1010         private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
1011                 Iterator it = annotations.iterator();
1012                 while (it.hasNext()) {
1013                         JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it
1014                                         .next();
1015                         if (tuple.annotation.isComment() == annotation.isComment()) {
1016                                 Position position = positionMap == null ? fCachedModel
1017                                                 .getPosition(annotation) : (Position) positionMap
1018                                                 .get(annotation);
1019                                 if (position == null)
1020                                         continue;
1021
1022                                 if (tuple.position.getOffset() == position.getOffset()) {
1023                                         it.remove();
1024                                         return new Tuple(annotation, position);
1025                                 }
1026                         }
1027                 }
1028
1029                 return null;
1030         }
1031
1032         private Map createAnnotationMap(IAnnotationModel model) {
1033                 Map map = new HashMap();
1034                 Iterator e = model.getAnnotationIterator();
1035                 while (e.hasNext()) {
1036                         Object annotation = e.next();
1037                         if (annotation instanceof JavaProjectionAnnotation) {
1038                                 JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
1039                                 Position position = model.getPosition(java);
1040                                 Assert.isNotNull(position);
1041                                 List list = (List) map.get(java.getElement());
1042                                 if (list == null) {
1043                                         list = new ArrayList(2);
1044                                         map.put(java.getElement(), list);
1045                                 }
1046                                 list.add(new Tuple(java, position));
1047                         }
1048                 }
1049
1050                 Comparator comparator = new Comparator() {
1051                         public int compare(Object o1, Object o2) {
1052                                 return ((Tuple) o1).position.getOffset()
1053                                                 - ((Tuple) o2).position.getOffset();
1054                         }
1055                 };
1056                 for (Iterator it = map.values().iterator(); it.hasNext();) {
1057                         List list = (List) it.next();
1058                         Collections.sort(list, comparator);
1059                 }
1060                 return map;
1061         }
1062 }