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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text.folding;
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;
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;
50 import org.eclipse.jface.preference.IPreferenceStore;
51 import org.eclipse.jface.text.Assert;
52 import org.eclipse.jface.text.BadLocationException;
53 import org.eclipse.jface.text.IDocument;
54 import org.eclipse.jface.text.IRegion;
55 import org.eclipse.jface.text.Position;
56 import org.eclipse.jface.text.Region;
57 import org.eclipse.jface.text.source.Annotation;
58 import org.eclipse.jface.text.source.IAnnotationModel;
59 import org.eclipse.jface.text.source.projection.IProjectionListener;
60 import org.eclipse.jface.text.source.projection.IProjectionPosition;
61 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
62 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
63 import org.eclipse.jface.text.source.projection.ProjectionViewer;
64 import org.eclipse.ui.texteditor.IDocumentProvider;
65 import org.eclipse.ui.texteditor.ITextEditor;
68 * Updates the projection model of a class file or compilation unit.
72 public class DefaultJavaFoldingStructureProvider implements
73 IProjectionListener, IJavaFoldingStructureProvider {
75 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
77 private IJavaElement fJavaElement;
79 private boolean fIsComment;
81 public JavaProjectionAnnotation(IJavaElement element,
82 boolean isCollapsed, boolean isComment) {
84 fJavaElement = element;
85 fIsComment = isComment;
88 public IJavaElement getElement() {
92 public void setElement(IJavaElement element) {
93 fJavaElement = element;
96 public boolean isComment() {
100 public void setIsComment(boolean isComment) {
101 fIsComment = isComment;
105 * @see java.lang.Object#toString()
107 public String toString() {
108 return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
109 "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
110 "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
111 "\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
115 private static final class Tuple {
116 JavaProjectionAnnotation annotation;
120 Tuple(JavaProjectionAnnotation annotation, Position position) {
121 this.annotation = annotation;
122 this.position = position;
126 private class ElementChangedListener implements IElementChangedListener {
129 * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
131 public void elementChanged(ElementChangedEvent e) {
132 IJavaElementDelta delta = findElement(fInput, e.getDelta());
137 private IJavaElementDelta findElement(IJavaElement target,
138 IJavaElementDelta delta) {
140 if (delta == null || target == null)
143 IJavaElement element = delta.getElement();
145 if (element.getElementType() > IJavaElement.CLASS_FILE)
148 if (target.equals(element))
151 IJavaElementDelta[] children = delta.getAffectedChildren();
153 for (int i = 0; i < children.length; i++) {
154 IJavaElementDelta d = findElement(target, children[i]);
164 * Projection position that will return two foldable regions: one folding
165 * away the region from after the '/**' to the beginning of the content, the
166 * other from after the first content line until after the comment.
170 private static final class CommentPosition extends Position implements
171 IProjectionPosition {
172 CommentPosition(int offset, int length) {
173 super(offset, length);
177 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
179 public IRegion[] computeProjectionRegions(IDocument document)
180 throws BadLocationException {
181 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
182 document, offset, offset + length);
184 int contentStart = findFirstContent(sequence, prefixEnd);
186 int firstLine = document.getLineOfOffset(offset + prefixEnd);
187 int captionLine = document.getLineOfOffset(offset + contentStart);
188 int lastLine = document.getLineOfOffset(offset + length);
190 Assert.isTrue(firstLine <= captionLine,
191 "first folded line is greater than the caption line"); //$NON-NLS-1$
192 Assert.isTrue(captionLine <= lastLine,
193 "caption line is greater than the last folded line"); //$NON-NLS-1$
196 if (firstLine < captionLine) {
197 // preRegion= new Region(offset + prefixEnd, contentStart -
199 int preOffset = document.getLineOffset(firstLine);
200 IRegion preEndLineInfo = document
201 .getLineInformation(captionLine);
202 int preEnd = preEndLineInfo.getOffset();
203 preRegion = new Region(preOffset, preEnd - preOffset);
208 if (captionLine < lastLine) {
209 int postOffset = document.getLineOffset(captionLine + 1);
210 IRegion postRegion = new Region(postOffset, offset + length
213 if (preRegion == null)
214 return new IRegion[] { postRegion };
216 return new IRegion[] { preRegion, postRegion };
219 if (preRegion != null)
220 return new IRegion[] { preRegion };
226 * Finds the offset of the first identifier part within
227 * <code>content</code>. Returns 0 if none is found.
230 * the content to search
231 * @return the first index of a unicode identifier part, or zero if none
234 private int findFirstContent(final CharSequence content, int prefixEnd) {
235 int lenght = content.length();
236 for (int i = prefixEnd; i < lenght; i++) {
237 if (Character.isUnicodeIdentifierPart(content.charAt(i)))
244 // * Finds the offset of the first identifier part within
245 // <code>content</code>.
246 // * Returns 0 if none is found.
248 // * @param content the content to search
249 // * @return the first index of a unicode identifier part, or zero if
254 // private int findPrefixEnd(final CharSequence content) {
255 // // return the index after the leading '/*' or '/**'
256 // int len= content.length();
258 // while (i < len && isWhiteSpace(content.charAt(i)))
260 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1)
263 // if (len >= i + 3 && content.charAt(i + 2) == '*')
271 // private boolean isWhiteSpace(char c) {
272 // return c == ' ' || c == '\t';
276 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
278 public int computeCaptionOffset(IDocument document) {
280 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
281 document, offset, offset + length);
282 return findFirstContent(sequence, 0);
287 * Projection position that will return two foldable regions: one folding
288 * away the lines before the one containing the simple name of the java
289 * element, one folding away any lines after the caption.
293 private static final class JavaElementPosition extends Position implements
294 IProjectionPosition {
296 private IMember fMember;
298 public JavaElementPosition(int offset, int length, IMember member) {
299 super(offset, length);
300 Assert.isNotNull(member);
304 public void setMember(IMember member) {
305 Assert.isNotNull(member);
310 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
312 public IRegion[] computeProjectionRegions(IDocument document)
313 throws BadLocationException {
314 int nameStart = offset;
317 * The member's name range may not be correct. However,
318 * reconciling would trigger another element delta which would
319 * lead to reentrant situations. Therefore, we optimistically
320 * assume that the name range is correct, but double check the
321 * received lines below.
323 ISourceRange nameRange = fMember.getNameRange();
324 if (nameRange != null)
325 nameStart = nameRange.getOffset();
327 } catch (JavaModelException e) {
328 // ignore and use default
331 int firstLine = document.getLineOfOffset(offset);
332 int captionLine = document.getLineOfOffset(nameStart);
333 int lastLine = document.getLineOfOffset(offset + length);
336 * see comment above - adjust the caption line to be inside the
337 * entire folded region, and rely on later element deltas to correct
340 if (captionLine < firstLine)
341 captionLine = firstLine;
342 if (captionLine > lastLine)
343 captionLine = lastLine;
346 if (firstLine < captionLine) {
347 int preOffset = document.getLineOffset(firstLine);
348 IRegion preEndLineInfo = document
349 .getLineInformation(captionLine);
350 int preEnd = preEndLineInfo.getOffset();
351 preRegion = new Region(preOffset, preEnd - preOffset);
356 if (captionLine < lastLine) {
357 int postOffset = document.getLineOffset(captionLine + 1);
358 IRegion postRegion = new Region(postOffset, offset + length
361 if (preRegion == null)
362 return new IRegion[] { postRegion };
364 return new IRegion[] { preRegion, postRegion };
367 if (preRegion != null)
368 return new IRegion[] { preRegion };
374 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
376 public int computeCaptionOffset(IDocument document)
377 throws BadLocationException {
378 int nameStart = offset;
380 // need a reconcile here?
381 ISourceRange nameRange = fMember.getNameRange();
382 if (nameRange != null)
383 nameStart = nameRange.getOffset();
384 } catch (JavaModelException e) {
385 // ignore and use default
388 return nameStart - offset;
393 private IDocument fCachedDocument;
395 private ProjectionAnnotationModel fCachedModel;
397 private ITextEditor fEditor;
399 private ProjectionViewer fViewer;
401 private IJavaElement fInput;
403 private IElementChangedListener fElementListener;
405 private boolean fAllowCollapsing = false;
407 private boolean fCollapseJavadoc = false;
409 // private boolean fCollapseImportContainer = true;
411 private boolean fCollapseInnerTypes = true;
413 private boolean fCollapseMethods = false;
415 private boolean fCollapseHeaderComments = true;
417 /* caches for header comment extraction. */
418 private IType fFirstType;
420 private boolean fHasHeaderComment;
422 public DefaultJavaFoldingStructureProvider() {
425 public void install(ITextEditor editor, ProjectionViewer viewer) {
426 if (editor instanceof PHPEditor) {
429 fViewer.addProjectionListener(this);
433 public void uninstall() {
435 projectionDisabled();
436 fViewer.removeProjectionListener(this);
442 protected boolean isInstalled() {
443 return fEditor != null;
447 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
449 public void projectionEnabled() {
450 // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
451 // projectionEnabled messages are not always paired with
452 // projectionDisabled
453 // i.e. multiple enabled messages may be sent out.
454 // we have to make sure that we disable first when getting an enable
456 projectionDisabled();
458 if (fEditor instanceof PHPEditor) {
460 fElementListener = new ElementChangedListener();
461 JavaCore.addElementChangedListener(fElementListener);
466 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
468 public void projectionDisabled() {
469 fCachedDocument = null;
470 if (fElementListener != null) {
471 JavaCore.removeElementChangedListener(fElementListener);
472 fElementListener = null;
476 public void initialize() {
481 initializePreferences();
485 IDocumentProvider provider = fEditor.getDocumentProvider();
486 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
487 fAllowCollapsing = true;
490 fHasHeaderComment = false;
492 if (fEditor instanceof PHPUnitEditor) {
493 IWorkingCopyManager manager = WebUI.getDefault()
494 .getWorkingCopyManager();
495 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
497 // else if (fEditor instanceof ClassFileEditor) {
498 // IClassFileEditorInput editorInput= (IClassFileEditorInput)
499 // fEditor.getEditorInput();
500 // fInput= editorInput.getClassFile();
503 if (fInput != null) {
504 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
505 .getAdapter(ProjectionAnnotationModel.class);
507 fCachedModel = model;
508 if (fInput instanceof ICompilationUnit) {
509 ICompilationUnit unit = (ICompilationUnit) fInput;
510 synchronized (unit) {
512 // unit.reconcile(ICompilationUnit.NO_AST,
513 // false, null, null);
515 } catch (JavaModelException x) {
520 Map additions = computeAdditions((IParent) fInput);
522 * Minimize the events being sent out - as this happens in
523 * the UI thread merge everything into one call.
525 List removals = new LinkedList();
526 Iterator existing = model.getAnnotationIterator();
527 while (existing.hasNext())
528 removals.add(existing.next());
529 model.replaceAnnotations((Annotation[]) removals
530 .toArray(new Annotation[removals.size()]),
536 fCachedDocument = null;
538 fAllowCollapsing = false;
541 fHasHeaderComment = false;
545 private void initializePreferences() {
546 IPreferenceStore store = WebUI.getDefault()
547 .getPreferenceStore();
548 fCollapseInnerTypes = store
549 .getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
550 // fCollapseImportContainer =
551 // store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
552 fCollapseJavadoc = store
553 .getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
554 fCollapseMethods = store
555 .getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
556 fCollapseHeaderComments = store
557 .getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
560 private Map computeAdditions(IParent parent) {
561 Map map = new LinkedHashMap(); // use a linked map to maintain ordering
565 computeAdditions(parent.getChildren(), map);
566 } catch (JavaModelException x) {
571 private void computeAdditions(IJavaElement[] elements, Map map)
572 throws JavaModelException {
573 for (int i = 0; i < elements.length; i++) {
574 IJavaElement element = elements[i];
576 computeAdditions(element, map);
578 if (element instanceof IParent) {
579 IParent parent = (IParent) element;
580 computeAdditions(parent.getChildren(), map);
585 private void computeAdditions(IJavaElement element, Map map) {
587 boolean createProjection = false;
589 boolean collapse = false;
590 switch (element.getElementType()) {
592 // case IJavaElement.IMPORT_CONTAINER:
593 // collapse = fAllowCollapsing && fCollapseImportContainer;
594 // createProjection = true;
596 case IJavaElement.TYPE:
597 collapse = fAllowCollapsing;
598 if (isInnerType((IType) element)) {
599 collapse = collapse && fCollapseInnerTypes;
601 collapse = false; // don't allow the most outer type to be
602 // folded, may be changed in future versions
604 createProjection = true;
606 case IJavaElement.METHOD:
607 collapse = fAllowCollapsing && fCollapseMethods;
608 createProjection = true;
612 if (createProjection) {
613 IRegion[] regions = computeProjectionRanges(element);
614 if (regions != null) {
616 for (int i = 0; i < regions.length - 1; i++) {
617 Position position = createProjectionPosition(regions[i],
619 boolean commentCollapse;
620 if (position != null) {
621 if (i == 0 && (regions.length > 2 || fHasHeaderComment)
622 && element == fFirstType) {
623 commentCollapse = fAllowCollapsing
624 && fCollapseHeaderComments;
626 commentCollapse = fAllowCollapsing
629 map.put(new JavaProjectionAnnotation(element,
630 commentCollapse, true), position);
634 Position position = createProjectionPosition(
635 regions[regions.length - 1], element);
636 if (position != null)
637 map.put(new JavaProjectionAnnotation(element, collapse,
643 private boolean isInnerType(IType type) {
646 return type.isMember();
647 } catch (JavaModelException x) {
648 IJavaElement parent = type.getParent();
649 if (parent != null) {
650 int parentType = parent.getElementType();
651 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
659 * Computes the projection ranges for a given <code>IJavaElement</code>.
660 * More than one range may be returned if the element has a leading comment
661 * which gets folded separately. If there are no foldable regions,
662 * <code>null</code> is returned.
665 * the java element that can be folded
666 * @return the regions to be folded, or <code>null</code> if there are
669 private IRegion[] computeProjectionRanges(IJavaElement element) {
672 if (element instanceof ISourceReference) {
673 ISourceReference reference = (ISourceReference) element;
674 ISourceRange range = reference.getSourceRange();
676 String contents = reference.getSource();
677 if (contents == null)
680 List regions = new ArrayList();
681 // now add all comments first to the regions list
682 if (fFirstType == null && element instanceof IType) {
683 fFirstType = (IType) element;
684 IRegion headerComment = computeHeaderComment(fFirstType);
685 if (headerComment != null) {
686 regions.add(headerComment);
687 fHasHeaderComment = true;
691 final int shift = range.getOffset();
693 if (element instanceof IType) {
694 Scanner scanner = ToolFactory.createScanner(true, false,
696 scanner.setSource(contents.toCharArray());
697 scanner.setPHPMode(true);
699 int token = scanner.getNextToken();
700 while (token != ITerminalSymbols.TokenNameEOF) {
702 token = scanner.getNextToken();
703 start = shift + scanner.getCurrentTokenStartPosition();
706 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
707 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
709 + scanner.getCurrentTokenEndPosition() + 1;
710 regions.add(new Region(start, end - start));
712 case ITerminalSymbols.TokenNameCOMMENT_LINE:
717 // at the end add the element region
718 regions.add(new Region(range.getOffset(), range.getLength()));
720 if (regions.size() > 0) {
721 IRegion[] result = new IRegion[regions.size()];
722 regions.toArray(result);
727 } catch (JavaModelException e) {
728 } catch (InvalidInputException e) {
734 private IRegion computeHeaderComment(IType type) throws JavaModelException {
735 if (fCachedDocument == null)
738 // search at most up to the first type
739 ISourceRange range = type.getSourceRange();
743 int end = range.getOffset();
745 if (fInput instanceof ISourceReference) {
748 content = fCachedDocument.get(start, end - start);
749 } catch (BadLocationException e) {
750 return null; // ignore header comment in that case
754 * code adapted from CommentFormattingStrategy: scan the header
755 * content up to the first type. Once a comment is found, accumulate
756 * any additional comments up to the stop condition. The stop
757 * condition is reaching a package declaration, import container, or
758 * the end of the input.
760 IScanner scanner = ToolFactory.createScanner(true, false, false,
762 scanner.setSource(content.toCharArray());
764 int headerStart = -1;
767 boolean foundComment = false;
768 int terminal = scanner.getNextToken();
769 while (terminal != ITerminalSymbols.TokenNameEOF
770 && !(terminal == ITerminalSymbols.TokenNameclass
771 || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
773 if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC
774 || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
775 || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
777 headerStart = scanner
778 .getCurrentTokenStartPosition();
779 headerEnd = scanner.getCurrentTokenEndPosition();
782 terminal = scanner.getNextToken();
785 } catch (InvalidInputException ex) {
789 if (headerEnd != -1) {
790 return new Region(headerStart, headerEnd - headerStart);
796 private Position createProjectionPosition(IRegion region,
797 IJavaElement element) {
799 if (fCachedDocument == null)
804 int start = fCachedDocument.getLineOfOffset(region.getOffset());
805 int end = fCachedDocument.getLineOfOffset(region.getOffset()
806 + region.getLength());
808 int offset = fCachedDocument.getLineOffset(start);
810 if (fCachedDocument.getNumberOfLines() > end + 1)
811 endOffset = fCachedDocument.getLineOffset(end + 1);
812 else if (end > start)
813 endOffset = fCachedDocument.getLineOffset(end)
814 + fCachedDocument.getLineLength(end);
817 if (element instanceof IMember)
818 return new JavaElementPosition(offset, endOffset - offset,
821 return new CommentPosition(offset, endOffset - offset);
824 } catch (BadLocationException x) {
830 protected void processDelta(IJavaElementDelta delta) {
835 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
838 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
839 .getAdapter(ProjectionAnnotationModel.class);
845 IDocumentProvider provider = fEditor.getDocumentProvider();
846 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
847 fCachedModel = model;
848 fAllowCollapsing = false;
851 fHasHeaderComment = false;
853 Map additions = new HashMap();
854 List deletions = new ArrayList();
855 List updates = new ArrayList();
857 Map updated = computeAdditions((IParent) fInput);
858 Map previous = createAnnotationMap(model);
860 Iterator e = updated.keySet().iterator();
861 while (e.hasNext()) {
862 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e
865 Position newPosition = (Position) updated.get(newAnnotation);
866 additions.put(newAnnotation, newPosition);
868 // IJavaElement element = newAnnotation.getElement();
869 // Position newPosition = (Position) updated.get(newAnnotation);
871 // List annotations = (List) previous.get(element);
872 // if (annotations == null) {
874 // additions.put(newAnnotation, newPosition);
877 // Iterator x = annotations.iterator();
878 // boolean matched = false;
879 // while (x.hasNext()) {
880 // Tuple tuple = (Tuple) x.next();
881 // JavaProjectionAnnotation existingAnnotation = tuple.annotation;
882 // Position existingPosition = tuple.position;
883 // if (newAnnotation.isComment() == existingAnnotation
885 // if (existingPosition != null
886 // && (!newPosition.equals(existingPosition))) {
887 // existingPosition.setOffset(newPosition
889 // existingPosition.setLength(newPosition
891 // updates.add(existingAnnotation);
899 // additions.put(newAnnotation, newPosition);
901 // if (annotations.isEmpty())
902 // previous.remove(element);
907 e = previous.values().iterator();
908 while (e.hasNext()) {
909 List list = (List) e.next();
910 int size = list.size();
911 for (int i = 0; i < size; i++)
912 deletions.add(((Tuple) list.get(i)).annotation);
915 match(deletions, additions, updates);
917 Annotation[] removals = new Annotation[deletions.size()];
918 deletions.toArray(removals);
919 Annotation[] changes = new Annotation[updates.size()];
920 updates.toArray(changes);
921 model.modifyAnnotations(removals, additions, changes);
924 fCachedDocument = null;
925 fAllowCollapsing = true;
929 fHasHeaderComment = false;
934 * Matches deleted annotations to changed or added ones. A deleted
935 * annotation/position tuple that has a matching addition / change is
936 * updated and marked as changed. The matching tuple is not added (for
937 * additions) or marked as deletion instead (for changes). The result is
938 * that more annotations are changed and fewer get deleted/re-added.
940 private void match(List deletions, Map additions, List changes) {
941 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
944 List newDeletions = new ArrayList();
945 List newChanges = new ArrayList();
947 Iterator deletionIterator = deletions.iterator();
948 while (deletionIterator.hasNext()) {
949 JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator
951 Position deletedPosition = fCachedModel.getPosition(deleted);
952 if (deletedPosition == null)
955 Tuple deletedTuple = new Tuple(deleted, deletedPosition);
957 Tuple match = findMatch(deletedTuple, changes, null);
958 boolean addToDeletions = true;
960 match = findMatch(deletedTuple, additions.keySet(), additions);
961 addToDeletions = false;
965 IJavaElement element = match.annotation.getElement();
966 deleted.setElement(element);
967 deletedPosition.setLength(match.position.getLength());
968 if (deletedPosition instanceof JavaElementPosition
969 && element instanceof IMember) {
970 JavaElementPosition jep = (JavaElementPosition) deletedPosition;
971 jep.setMember((IMember) element);
974 deletionIterator.remove();
975 newChanges.add(deleted);
978 newDeletions.add(match.annotation);
982 deletions.addAll(newDeletions);
983 changes.addAll(newChanges);
987 * Finds a match for <code>tuple</code> in a collection of annotations.
988 * The positions for the <code>JavaProjectionAnnotation</code> instances
989 * in <code>annotations</code> can be found in the passed
990 * <code>positionMap</code> or <code>fCachedModel</code> if
991 * <code>positionMap</code> is <code>null</code>.
993 * A tuple is said to match another if their annotations have the same
994 * comment flag and their position offsets are equal.
997 * If a match is found, the annotation gets removed from
998 * <code>annotations</code>.
1002 * the tuple for which we want to find a match
1003 * @param annotations
1004 * collection of <code>JavaProjectionAnnotation</code>
1005 * @param positionMap
1006 * a <code>Map<Annotation, Position></code> or
1008 * @return a matching tuple or <code>null</code> for no match
1010 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
1011 Iterator it = annotations.iterator();
1012 while (it.hasNext()) {
1013 JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it
1015 if (tuple.annotation.isComment() == annotation.isComment()) {
1016 Position position = positionMap == null ? fCachedModel
1017 .getPosition(annotation) : (Position) positionMap
1019 if (position == null)
1022 if (tuple.position.getOffset() == position.getOffset()) {
1024 return new Tuple(annotation, position);
1032 private Map createAnnotationMap(IAnnotationModel model) {
1033 Map map = new HashMap();
1034 Iterator e = model.getAnnotationIterator();
1035 while (e.hasNext()) {
1036 Object annotation = e.next();
1037 if (annotation instanceof JavaProjectionAnnotation) {
1038 JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
1039 Position position = model.getPosition(java);
1040 Assert.isNotNull(position);
1041 List list = (List) map.get(java.getElement());
1043 list = new ArrayList(2);
1044 map.put(java.getElement(), list);
1046 list.add(new Tuple(java, position));
1050 Comparator comparator = new Comparator() {
1051 public int compare(Object o1, Object o2) {
1052 return ((Tuple) o1).position.getOffset()
1053 - ((Tuple) o2).position.getOffset();
1056 for (Iterator it = map.values().iterator(); it.hasNext();) {
1057 List list = (List) it.next();
1058 Collections.sort(list, comparator);