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;
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;
67 * Updates the projection model of a class file or compilation unit.
71 public class DefaultJavaFoldingStructureProvider implements
72 IProjectionListener, IJavaFoldingStructureProvider {
74 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
76 private IJavaElement fJavaElement;
78 private boolean fIsComment;
80 public JavaProjectionAnnotation(IJavaElement element,
81 boolean isCollapsed, boolean isComment) {
83 fJavaElement = element;
84 fIsComment = isComment;
87 public IJavaElement getElement() {
91 public void setElement(IJavaElement element) {
92 fJavaElement = element;
95 public boolean isComment() {
99 public void setIsComment(boolean isComment) {
100 fIsComment = isComment;
104 * @see java.lang.Object#toString()
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$
114 private static final class Tuple {
115 JavaProjectionAnnotation annotation;
119 Tuple(JavaProjectionAnnotation annotation, Position position) {
120 this.annotation = annotation;
121 this.position = position;
125 private class ElementChangedListener implements IElementChangedListener {
128 * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
130 public void elementChanged(ElementChangedEvent e) {
131 IJavaElementDelta delta = findElement(fInput, e.getDelta());
136 private IJavaElementDelta findElement(IJavaElement target,
137 IJavaElementDelta delta) {
139 if (delta == null || target == null)
142 IJavaElement element = delta.getElement();
144 if (element.getElementType() > IJavaElement.CLASS_FILE)
147 if (target.equals(element))
150 IJavaElementDelta[] children = delta.getAffectedChildren();
152 for (int i = 0; i < children.length; i++) {
153 IJavaElementDelta d = findElement(target, children[i]);
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.
169 private static final class CommentPosition extends Position implements
170 IProjectionPosition {
171 CommentPosition(int offset, int length) {
172 super(offset, length);
176 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
178 public IRegion[] computeProjectionRegions(IDocument document)
179 throws BadLocationException {
180 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
181 document, offset, offset + length);
183 int contentStart = findFirstContent(sequence, prefixEnd);
185 int firstLine = document.getLineOfOffset(offset + prefixEnd);
186 int captionLine = document.getLineOfOffset(offset + contentStart);
187 int lastLine = document.getLineOfOffset(offset + length);
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$
195 if (firstLine < captionLine) {
196 // preRegion= new Region(offset + prefixEnd, contentStart -
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);
207 if (captionLine < lastLine) {
208 int postOffset = document.getLineOffset(captionLine + 1);
209 IRegion postRegion = new Region(postOffset, offset + length
212 if (preRegion == null)
213 return new IRegion[] { postRegion };
215 return new IRegion[] { preRegion, postRegion };
218 if (preRegion != null)
219 return new IRegion[] { preRegion };
225 * Finds the offset of the first identifier part within
226 * <code>content</code>. Returns 0 if none is found.
229 * the content to search
230 * @return the first index of a unicode identifier part, or zero if none
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)))
243 // * Finds the offset of the first identifier part within
244 // <code>content</code>.
245 // * Returns 0 if none is found.
247 // * @param content the content to search
248 // * @return the first index of a unicode identifier part, or zero if
253 // private int findPrefixEnd(final CharSequence content) {
254 // // return the index after the leading '/*' or '/**'
255 // int len= content.length();
257 // while (i < len && isWhiteSpace(content.charAt(i)))
259 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1)
262 // if (len >= i + 3 && content.charAt(i + 2) == '*')
270 // private boolean isWhiteSpace(char c) {
271 // return c == ' ' || c == '\t';
275 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
277 public int computeCaptionOffset(IDocument document) {
279 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
280 document, offset, offset + length);
281 return findFirstContent(sequence, 0);
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.
292 private static final class JavaElementPosition extends Position implements
293 IProjectionPosition {
295 private IMember fMember;
297 public JavaElementPosition(int offset, int length, IMember member) {
298 super(offset, length);
299 Assert.isNotNull(member);
303 public void setMember(IMember member) {
304 Assert.isNotNull(member);
309 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
311 public IRegion[] computeProjectionRegions(IDocument document)
312 throws BadLocationException {
313 int nameStart = offset;
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.
322 ISourceRange nameRange = fMember.getNameRange();
323 if (nameRange != null)
324 nameStart = nameRange.getOffset();
326 } catch (JavaModelException e) {
327 // ignore and use default
330 int firstLine = document.getLineOfOffset(offset);
331 int captionLine = document.getLineOfOffset(nameStart);
332 int lastLine = document.getLineOfOffset(offset + length);
335 * see comment above - adjust the caption line to be inside the
336 * entire folded region, and rely on later element deltas to correct
339 if (captionLine < firstLine)
340 captionLine = firstLine;
341 if (captionLine > lastLine)
342 captionLine = lastLine;
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);
355 if (captionLine < lastLine) {
356 int postOffset = document.getLineOffset(captionLine + 1);
357 IRegion postRegion = new Region(postOffset, offset + length
360 if (preRegion == null)
361 return new IRegion[] { postRegion };
363 return new IRegion[] { preRegion, postRegion };
366 if (preRegion != null)
367 return new IRegion[] { preRegion };
373 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
375 public int computeCaptionOffset(IDocument document)
376 throws BadLocationException {
377 int nameStart = offset;
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
387 return nameStart - offset;
392 private IDocument fCachedDocument;
394 private ProjectionAnnotationModel fCachedModel;
396 private ITextEditor fEditor;
398 private ProjectionViewer fViewer;
400 private IJavaElement fInput;
402 private IElementChangedListener fElementListener;
404 private boolean fAllowCollapsing = false;
406 private boolean fCollapseJavadoc = false;
408 // private boolean fCollapseImportContainer = true;
410 private boolean fCollapseInnerTypes = true;
412 private boolean fCollapseMethods = false;
414 private boolean fCollapseHeaderComments = true;
416 /* caches for header comment extraction. */
417 private IType fFirstType;
419 private boolean fHasHeaderComment;
421 public DefaultJavaFoldingStructureProvider() {
424 public void install(ITextEditor editor, ProjectionViewer viewer) {
425 if (editor instanceof PHPEditor) {
428 fViewer.addProjectionListener(this);
432 public void uninstall() {
434 projectionDisabled();
435 fViewer.removeProjectionListener(this);
441 protected boolean isInstalled() {
442 return fEditor != null;
446 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
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
455 projectionDisabled();
457 if (fEditor instanceof PHPEditor) {
459 fElementListener = new ElementChangedListener();
460 JavaCore.addElementChangedListener(fElementListener);
465 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
467 public void projectionDisabled() {
468 fCachedDocument = null;
469 if (fElementListener != null) {
470 JavaCore.removeElementChangedListener(fElementListener);
471 fElementListener = null;
475 public void initialize() {
480 initializePreferences();
484 IDocumentProvider provider = fEditor.getDocumentProvider();
485 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
486 fAllowCollapsing = true;
489 fHasHeaderComment = false;
491 if (fEditor instanceof PHPUnitEditor) {
492 IWorkingCopyManager manager = PHPeclipsePlugin.getDefault()
493 .getWorkingCopyManager();
494 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
496 // else if (fEditor instanceof ClassFileEditor) {
497 // IClassFileEditorInput editorInput= (IClassFileEditorInput)
498 // fEditor.getEditorInput();
499 // fInput= editorInput.getClassFile();
502 if (fInput != null) {
503 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
504 .getAdapter(ProjectionAnnotationModel.class);
506 fCachedModel = model;
507 if (fInput instanceof ICompilationUnit) {
508 ICompilationUnit unit = (ICompilationUnit) fInput;
509 synchronized (unit) {
511 // unit.reconcile(ICompilationUnit.NO_AST,
512 // false, null, null);
514 } catch (JavaModelException x) {
519 Map additions = computeAdditions((IParent) fInput);
521 * Minimize the events being sent out - as this happens in
522 * the UI thread merge everything into one call.
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()]),
535 fCachedDocument = null;
537 fAllowCollapsing = false;
540 fHasHeaderComment = false;
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);
559 private Map computeAdditions(IParent parent) {
560 Map map = new LinkedHashMap(); // use a linked map to maintain ordering
564 computeAdditions(parent.getChildren(), map);
565 } catch (JavaModelException x) {
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];
575 computeAdditions(element, map);
577 if (element instanceof IParent) {
578 IParent parent = (IParent) element;
579 computeAdditions(parent.getChildren(), map);
584 private void computeAdditions(IJavaElement element, Map map) {
586 boolean createProjection = false;
588 boolean collapse = false;
589 switch (element.getElementType()) {
591 // case IJavaElement.IMPORT_CONTAINER:
592 // collapse = fAllowCollapsing && fCollapseImportContainer;
593 // createProjection = true;
595 case IJavaElement.TYPE:
596 collapse = fAllowCollapsing;
597 if (isInnerType((IType) element)) {
598 collapse = collapse && fCollapseInnerTypes;
600 collapse = false; // don't allow the most outer type to be
601 // folded, may be changed in future versions
603 createProjection = true;
605 case IJavaElement.METHOD:
606 collapse = fAllowCollapsing && fCollapseMethods;
607 createProjection = true;
611 if (createProjection) {
612 IRegion[] regions = computeProjectionRanges(element);
613 if (regions != null) {
615 for (int i = 0; i < regions.length - 1; i++) {
616 Position position = createProjectionPosition(regions[i],
618 boolean commentCollapse;
619 if (position != null) {
620 if (i == 0 && (regions.length > 2 || fHasHeaderComment)
621 && element == fFirstType) {
622 commentCollapse = fAllowCollapsing
623 && fCollapseHeaderComments;
625 commentCollapse = fAllowCollapsing
628 map.put(new JavaProjectionAnnotation(element,
629 commentCollapse, true), position);
633 Position position = createProjectionPosition(
634 regions[regions.length - 1], element);
635 if (position != null)
636 map.put(new JavaProjectionAnnotation(element, collapse,
642 private boolean isInnerType(IType type) {
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);
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.
664 * the java element that can be folded
665 * @return the regions to be folded, or <code>null</code> if there are
668 private IRegion[] computeProjectionRanges(IJavaElement element) {
671 if (element instanceof ISourceReference) {
672 ISourceReference reference = (ISourceReference) element;
673 ISourceRange range = reference.getSourceRange();
675 String contents = reference.getSource();
676 if (contents == null)
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;
690 final int shift = range.getOffset();
692 if (element instanceof IType) {
693 Scanner scanner = ToolFactory.createScanner(true, false,
695 scanner.setSource(contents.toCharArray());
696 scanner.setPHPMode(true);
698 int token = scanner.getNextToken();
699 while (token != ITerminalSymbols.TokenNameEOF) {
701 token = scanner.getNextToken();
702 start = shift + scanner.getCurrentTokenStartPosition();
705 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
706 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
708 + scanner.getCurrentTokenEndPosition() + 1;
709 regions.add(new Region(start, end - start));
711 case ITerminalSymbols.TokenNameCOMMENT_LINE:
716 // at the end add the element region
717 regions.add(new Region(range.getOffset(), range.getLength()));
719 if (regions.size() > 0) {
720 IRegion[] result = new IRegion[regions.size()];
721 regions.toArray(result);
726 } catch (JavaModelException e) {
727 } catch (InvalidInputException e) {
733 private IRegion computeHeaderComment(IType type) throws JavaModelException {
734 if (fCachedDocument == null)
737 // search at most up to the first type
738 ISourceRange range = type.getSourceRange();
742 int end = range.getOffset();
744 if (fInput instanceof ISourceReference) {
747 content = fCachedDocument.get(start, end - start);
748 } catch (BadLocationException e) {
749 return null; // ignore header comment in that case
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.
759 IScanner scanner = ToolFactory.createScanner(true, false, false,
761 scanner.setSource(content.toCharArray());
763 int headerStart = -1;
766 boolean foundComment = false;
767 int terminal = scanner.getNextToken();
768 while (terminal != ITerminalSymbols.TokenNameEOF
769 && !(terminal == ITerminalSymbols.TokenNameclass
770 || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
772 if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC
773 || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
774 || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
776 headerStart = scanner
777 .getCurrentTokenStartPosition();
778 headerEnd = scanner.getCurrentTokenEndPosition();
781 terminal = scanner.getNextToken();
784 } catch (InvalidInputException ex) {
788 if (headerEnd != -1) {
789 return new Region(headerStart, headerEnd - headerStart);
795 private Position createProjectionPosition(IRegion region,
796 IJavaElement element) {
798 if (fCachedDocument == null)
803 int start = fCachedDocument.getLineOfOffset(region.getOffset());
804 int end = fCachedDocument.getLineOfOffset(region.getOffset()
805 + region.getLength());
807 int offset = fCachedDocument.getLineOffset(start);
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);
816 if (element instanceof IMember)
817 return new JavaElementPosition(offset, endOffset - offset,
820 return new CommentPosition(offset, endOffset - offset);
823 } catch (BadLocationException x) {
829 protected void processDelta(IJavaElementDelta delta) {
834 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
837 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
838 .getAdapter(ProjectionAnnotationModel.class);
844 IDocumentProvider provider = fEditor.getDocumentProvider();
845 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
846 fCachedModel = model;
847 fAllowCollapsing = false;
850 fHasHeaderComment = false;
852 Map additions = new HashMap();
853 List deletions = new ArrayList();
854 List updates = new ArrayList();
856 Map updated = computeAdditions((IParent) fInput);
857 Map previous = createAnnotationMap(model);
859 Iterator e = updated.keySet().iterator();
860 while (e.hasNext()) {
861 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e
863 IJavaElement element = newAnnotation.getElement();
864 Position newPosition = (Position) updated.get(newAnnotation);
866 List annotations = (List) previous.get(element);
867 if (annotations == null) {
869 additions.put(newAnnotation, newPosition);
872 Iterator x = annotations.iterator();
873 boolean matched = false;
874 while (x.hasNext()) {
875 Tuple tuple = (Tuple) x.next();
876 JavaProjectionAnnotation existingAnnotation = tuple.annotation;
877 Position existingPosition = tuple.position;
878 if (newAnnotation.isComment() == existingAnnotation
880 if (existingPosition != null
881 && (!newPosition.equals(existingPosition))) {
882 existingPosition.setOffset(newPosition
884 existingPosition.setLength(newPosition
886 updates.add(existingAnnotation);
894 additions.put(newAnnotation, newPosition);
896 if (annotations.isEmpty())
897 previous.remove(element);
901 e = previous.values().iterator();
902 while (e.hasNext()) {
903 List list = (List) e.next();
904 int size = list.size();
905 for (int i = 0; i < size; i++)
906 deletions.add(((Tuple) list.get(i)).annotation);
909 match(deletions, additions, updates);
911 Annotation[] removals = new Annotation[deletions.size()];
912 deletions.toArray(removals);
913 Annotation[] changes = new Annotation[updates.size()];
914 updates.toArray(changes);
915 model.modifyAnnotations(removals, additions, changes);
918 fCachedDocument = null;
919 fAllowCollapsing = true;
923 fHasHeaderComment = false;
928 * Matches deleted annotations to changed or added ones. A deleted
929 * annotation/position tuple that has a matching addition / change is
930 * updated and marked as changed. The matching tuple is not added (for
931 * additions) or marked as deletion instead (for changes). The result is
932 * that more annotations are changed and fewer get deleted/re-added.
934 private void match(List deletions, Map additions, List changes) {
935 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
938 List newDeletions = new ArrayList();
939 List newChanges = new ArrayList();
941 Iterator deletionIterator = deletions.iterator();
942 while (deletionIterator.hasNext()) {
943 JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator
945 Position deletedPosition = fCachedModel.getPosition(deleted);
946 if (deletedPosition == null)
949 Tuple deletedTuple = new Tuple(deleted, deletedPosition);
951 Tuple match = findMatch(deletedTuple, changes, null);
952 boolean addToDeletions = true;
954 match = findMatch(deletedTuple, additions.keySet(), additions);
955 addToDeletions = false;
959 IJavaElement element = match.annotation.getElement();
960 deleted.setElement(element);
961 deletedPosition.setLength(match.position.getLength());
962 if (deletedPosition instanceof JavaElementPosition
963 && element instanceof IMember) {
964 JavaElementPosition jep = (JavaElementPosition) deletedPosition;
965 jep.setMember((IMember) element);
968 deletionIterator.remove();
969 newChanges.add(deleted);
972 newDeletions.add(match.annotation);
976 deletions.addAll(newDeletions);
977 changes.addAll(newChanges);
981 * Finds a match for <code>tuple</code> in a collection of annotations.
982 * The positions for the <code>JavaProjectionAnnotation</code> instances
983 * in <code>annotations</code> can be found in the passed
984 * <code>positionMap</code> or <code>fCachedModel</code> if
985 * <code>positionMap</code> is <code>null</code>.
987 * A tuple is said to match another if their annotations have the same
988 * comment flag and their position offsets are equal.
991 * If a match is found, the annotation gets removed from
992 * <code>annotations</code>.
996 * the tuple for which we want to find a match
998 * collection of <code>JavaProjectionAnnotation</code>
1000 * a <code>Map<Annotation, Position></code> or
1002 * @return a matching tuple or <code>null</code> for no match
1004 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
1005 Iterator it = annotations.iterator();
1006 while (it.hasNext()) {
1007 JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it
1009 if (tuple.annotation.isComment() == annotation.isComment()) {
1010 Position position = positionMap == null ? fCachedModel
1011 .getPosition(annotation) : (Position) positionMap
1013 if (position == null)
1016 if (tuple.position.getOffset() == position.getOffset()) {
1018 return new Tuple(annotation, position);
1026 private Map createAnnotationMap(IAnnotationModel model) {
1027 Map map = new HashMap();
1028 Iterator e = model.getAnnotationIterator();
1029 while (e.hasNext()) {
1030 Object annotation = e.next();
1031 if (annotation instanceof JavaProjectionAnnotation) {
1032 JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
1033 Position position = model.getPosition(java);
1034 Assert.isNotNull(position);
1035 List list = (List) map.get(java.getElement());
1037 list = new ArrayList(2);
1038 map.put(java.getElement(), list);
1040 list.add(new Tuple(java, position));
1044 Comparator comparator = new Comparator() {
1045 public int compare(Object o1, Object o2) {
1046 return ((Tuple) o1).position.getOffset()
1047 - ((Tuple) o2).position.getOffset();
1050 for (Iterator it = map.values().iterator(); it.hasNext();) {
1051 List list = (List) it.next();
1052 Collections.sort(list, comparator);