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