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