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;
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;
70 * Updates the projection model of a class file or compilation unit.
74 public class DefaultJavaFoldingStructureProvider implements
75 IProjectionListener, IJavaFoldingStructureProvider {
77 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
79 private IJavaElement fJavaElement;
81 private boolean fIsComment;
83 public JavaProjectionAnnotation(IJavaElement element,
84 boolean isCollapsed, boolean isComment) {
86 fJavaElement = element;
87 fIsComment = isComment;
90 public IJavaElement getElement() {
94 public void setElement(IJavaElement element) {
95 fJavaElement = element;
98 public boolean isComment() {
102 public void setIsComment(boolean isComment) {
103 fIsComment = isComment;
107 * @see java.lang.Object#toString()
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$
117 private static final class Tuple {
118 JavaProjectionAnnotation annotation;
122 Tuple(JavaProjectionAnnotation annotation, Position position) {
123 this.annotation = annotation;
124 this.position = position;
128 private class ElementChangedListener implements IElementChangedListener {
131 * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
133 public void elementChanged(ElementChangedEvent e) {
134 IJavaElementDelta delta = findElement(fInput, e.getDelta());
139 private IJavaElementDelta findElement(IJavaElement target,
140 IJavaElementDelta delta) {
142 if (delta == null || target == null)
145 IJavaElement element = delta.getElement();
147 if (element.getElementType() > IJavaElement.CLASS_FILE)
150 if (target.equals(element))
153 IJavaElementDelta[] children = delta.getAffectedChildren();
155 for (int i = 0; i < children.length; i++) {
156 IJavaElementDelta d = findElement(target, children[i]);
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.
172 private static final class CommentPosition extends Position implements
173 IProjectionPosition {
174 CommentPosition(int offset, int length) {
175 super(offset, length);
179 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
181 public IRegion[] computeProjectionRegions(IDocument document)
182 throws BadLocationException {
183 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
184 document, offset, offset + length);
186 int contentStart = findFirstContent(sequence, prefixEnd);
188 int firstLine = document.getLineOfOffset(offset + prefixEnd);
189 int captionLine = document.getLineOfOffset(offset + contentStart);
190 int lastLine = document.getLineOfOffset(offset + length);
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$
198 if (firstLine < captionLine) {
199 // preRegion= new Region(offset + prefixEnd, contentStart -
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);
210 if (captionLine < lastLine) {
211 int postOffset = document.getLineOffset(captionLine + 1);
212 IRegion postRegion = new Region(postOffset, offset + length
215 if (preRegion == null)
216 return new IRegion[] { postRegion };
218 return new IRegion[] { preRegion, postRegion };
221 if (preRegion != null)
222 return new IRegion[] { preRegion };
228 * Finds the offset of the first identifier part within
229 * <code>content</code>. Returns 0 if none is found.
232 * the content to search
233 * @return the first index of a unicode identifier part, or zero if none
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)))
246 // * Finds the offset of the first identifier part within
247 // <code>content</code>.
248 // * Returns 0 if none is found.
250 // * @param content the content to search
251 // * @return the first index of a unicode identifier part, or zero if
256 // private int findPrefixEnd(final CharSequence content) {
257 // // return the index after the leading '/*' or '/**'
258 // int len= content.length();
260 // while (i < len && isWhiteSpace(content.charAt(i)))
262 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1)
265 // if (len >= i + 3 && content.charAt(i + 2) == '*')
273 // private boolean isWhiteSpace(char c) {
274 // return c == ' ' || c == '\t';
278 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
280 public int computeCaptionOffset(IDocument document) {
282 DocumentCharacterIterator sequence = new DocumentCharacterIterator(
283 document, offset, offset + length);
284 return findFirstContent(sequence, 0);
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.
295 private static final class JavaElementPosition extends Position implements
296 IProjectionPosition {
298 private IMember fMember;
300 public JavaElementPosition(int offset, int length, IMember member) {
301 super(offset, length);
302 Assert.isNotNull(member);
306 public void setMember(IMember member) {
307 Assert.isNotNull(member);
312 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
314 public IRegion[] computeProjectionRegions(IDocument document)
315 throws BadLocationException {
316 int nameStart = offset;
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.
325 ISourceRange nameRange = fMember.getNameRange();
326 if (nameRange != null)
327 nameStart = nameRange.getOffset();
329 } catch (JavaModelException e) {
330 // ignore and use default
333 int firstLine = document.getLineOfOffset(offset);
334 int captionLine = document.getLineOfOffset(nameStart);
335 int lastLine = document.getLineOfOffset(offset + length);
338 * see comment above - adjust the caption line to be inside the
339 * entire folded region, and rely on later element deltas to correct
342 if (captionLine < firstLine)
343 captionLine = firstLine;
344 if (captionLine > lastLine)
345 captionLine = lastLine;
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);
358 if (captionLine < lastLine) {
359 int postOffset = document.getLineOffset(captionLine + 1);
360 IRegion postRegion = new Region(postOffset, offset + length
363 if (preRegion == null)
364 return new IRegion[] { postRegion };
366 return new IRegion[] { preRegion, postRegion };
369 if (preRegion != null)
370 return new IRegion[] { preRegion };
376 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
378 public int computeCaptionOffset(IDocument document)
379 throws BadLocationException {
380 int nameStart = offset;
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
390 return nameStart - offset;
395 private IDocument fCachedDocument;
397 private ProjectionAnnotationModel fCachedModel;
399 private ITextEditor fEditor;
401 private ProjectionViewer fViewer;
403 private IJavaElement fInput;
405 private IElementChangedListener fElementListener;
407 private boolean fAllowCollapsing = false;
409 private boolean fCollapseJavadoc = false;
411 // private boolean fCollapseImportContainer = true;
413 private boolean fCollapseInnerTypes = true;
415 private boolean fCollapseMethods = false;
417 private boolean fCollapseHeaderComments = true;
419 /* caches for header comment extraction. */
420 private IType fFirstType;
422 private boolean fHasHeaderComment;
424 public DefaultJavaFoldingStructureProvider() {
427 public void install(ITextEditor editor, ProjectionViewer viewer) {
428 if (editor instanceof PHPEditor) {
431 fViewer.addProjectionListener(this);
435 public void uninstall() {
437 projectionDisabled();
438 fViewer.removeProjectionListener(this);
444 protected boolean isInstalled() {
445 return fEditor != null;
449 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
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
458 projectionDisabled();
460 if (fEditor instanceof PHPEditor) {
462 fElementListener = new ElementChangedListener();
463 JavaCore.addElementChangedListener(fElementListener);
468 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
470 public void projectionDisabled() {
471 fCachedDocument = null;
472 if (fElementListener != null) {
473 JavaCore.removeElementChangedListener(fElementListener);
474 fElementListener = null;
478 public void initialize() {
483 initializePreferences();
487 IDocumentProvider provider = fEditor.getDocumentProvider();
488 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
489 fAllowCollapsing = true;
492 fHasHeaderComment = false;
494 if (fEditor instanceof PHPUnitEditor) {
495 IWorkingCopyManager manager = WebUI.getDefault()
496 .getWorkingCopyManager();
497 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
499 // else if (fEditor instanceof ClassFileEditor) {
500 // IClassFileEditorInput editorInput= (IClassFileEditorInput)
501 // fEditor.getEditorInput();
502 // fInput= editorInput.getClassFile();
505 if (fInput != null) {
506 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
507 .getAdapter(ProjectionAnnotationModel.class);
509 fCachedModel = model;
510 if (fInput instanceof ICompilationUnit) {
511 ICompilationUnit unit = (ICompilationUnit) fInput;
512 synchronized (unit) {
514 // unit.reconcile(ICompilationUnit.NO_AST,
515 // false, null, null);
517 } catch (JavaModelException x) {
522 Map additions = computeAdditions((IParent) fInput);
524 * Minimize the events being sent out - as this happens in
525 * the UI thread merge everything into one call.
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()]),
538 fCachedDocument = null;
540 fAllowCollapsing = false;
543 fHasHeaderComment = false;
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);
562 private Map computeAdditions(IParent parent) {
563 Map map = new LinkedHashMap(); // use a linked map to maintain ordering
567 computeAdditions(parent.getChildren(), map);
568 } catch (JavaModelException x) {
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];
578 computeAdditions(element, map);
580 if (element instanceof IParent) {
581 IParent parent = (IParent) element;
582 computeAdditions(parent.getChildren(), map);
587 private void computeAdditions(IJavaElement element, Map map) {
589 boolean createProjection = false;
591 boolean collapse = false;
592 switch (element.getElementType()) {
594 // case IJavaElement.IMPORT_CONTAINER:
595 // collapse = fAllowCollapsing && fCollapseImportContainer;
596 // createProjection = true;
598 case IJavaElement.TYPE:
599 collapse = fAllowCollapsing;
600 if (isInnerType((IType) element)) {
601 collapse = collapse && fCollapseInnerTypes;
603 collapse = false; // don't allow the most outer type to be
604 // folded, may be changed in future versions
606 createProjection = true;
608 case IJavaElement.METHOD:
609 collapse = fAllowCollapsing && fCollapseMethods;
610 createProjection = true;
614 if (createProjection) {
615 IRegion[] regions = computeProjectionRanges(element);
616 if (regions != null) {
618 for (int i = 0; i < regions.length - 1; i++) {
619 Position position = createProjectionPosition(regions[i],
621 boolean commentCollapse;
622 if (position != null) {
623 if (i == 0 && (regions.length > 2 || fHasHeaderComment)
624 && element == fFirstType) {
625 commentCollapse = fAllowCollapsing
626 && fCollapseHeaderComments;
628 commentCollapse = fAllowCollapsing
631 map.put(new JavaProjectionAnnotation(element,
632 commentCollapse, true), position);
636 Position position = createProjectionPosition(
637 regions[regions.length - 1], element);
638 if (position != null)
639 map.put(new JavaProjectionAnnotation(element, collapse,
645 private boolean isInnerType(IType type) {
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);
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.
667 * the java element that can be folded
668 * @return the regions to be folded, or <code>null</code> if there are
671 private IRegion[] computeProjectionRanges(IJavaElement element) {
674 if (element instanceof ISourceReference) {
675 ISourceReference reference = (ISourceReference) element;
676 ISourceRange range = reference.getSourceRange();
678 String contents = reference.getSource();
679 if (contents == null)
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;
693 final int shift = range.getOffset();
695 if (element instanceof IType) {
696 Scanner scanner = ToolFactory.createScanner(true, false,
698 scanner.setSource(contents.toCharArray());
699 scanner.setPHPMode(true);
701 int token = scanner.getNextToken();
702 while (token != ITerminalSymbols.TokenNameEOF) {
704 token = scanner.getNextToken();
705 start = shift + scanner.getCurrentTokenStartPosition();
708 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
709 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
711 + scanner.getCurrentTokenEndPosition() + 1;
712 regions.add(new Region(start, end - start));
714 case ITerminalSymbols.TokenNameCOMMENT_LINE:
719 // at the end add the element region
720 regions.add(new Region(range.getOffset(), range.getLength()));
722 if (regions.size() > 0) {
723 IRegion[] result = new IRegion[regions.size()];
724 regions.toArray(result);
729 } catch (JavaModelException e) {
730 } catch (InvalidInputException e) {
736 private IRegion computeHeaderComment(IType type) throws JavaModelException {
737 if (fCachedDocument == null)
740 // search at most up to the first type
741 ISourceRange range = type.getSourceRange();
745 int end = range.getOffset();
747 if (fInput instanceof ISourceReference) {
750 content = fCachedDocument.get(start, end - start);
751 } catch (BadLocationException e) {
752 return null; // ignore header comment in that case
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.
762 IScanner scanner = ToolFactory.createScanner(true, false, false,
764 scanner.setSource(content.toCharArray());
766 int headerStart = -1;
769 boolean foundComment = false;
770 int terminal = scanner.getNextToken();
771 while (terminal != ITerminalSymbols.TokenNameEOF
772 && !(terminal == ITerminalSymbols.TokenNameclass
773 || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
775 if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC
776 || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
777 || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
779 headerStart = scanner
780 .getCurrentTokenStartPosition();
781 headerEnd = scanner.getCurrentTokenEndPosition();
784 terminal = scanner.getNextToken();
787 } catch (InvalidInputException ex) {
791 if (headerEnd != -1) {
792 return new Region(headerStart, headerEnd - headerStart);
798 private Position createProjectionPosition(IRegion region,
799 IJavaElement element) {
801 if (fCachedDocument == null)
806 int start = fCachedDocument.getLineOfOffset(region.getOffset());
807 int end = fCachedDocument.getLineOfOffset(region.getOffset()
808 + region.getLength());
810 int offset = fCachedDocument.getLineOffset(start);
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);
819 if (element instanceof IMember)
820 return new JavaElementPosition(offset, endOffset - offset,
823 return new CommentPosition(offset, endOffset - offset);
826 } catch (BadLocationException x) {
832 protected void processDelta(IJavaElementDelta delta) {
837 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
840 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor
841 .getAdapter(ProjectionAnnotationModel.class);
847 IDocumentProvider provider = fEditor.getDocumentProvider();
848 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
849 fCachedModel = model;
850 fAllowCollapsing = false;
853 fHasHeaderComment = false;
855 Map additions = new HashMap();
856 List deletions = new ArrayList();
857 List updates = new ArrayList();
859 Map updated = computeAdditions((IParent) fInput);
860 Map previous = createAnnotationMap(model);
862 Iterator e = updated.keySet().iterator();
863 while (e.hasNext()) {
864 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e
867 Position newPosition = (Position) updated.get(newAnnotation);
868 additions.put(newAnnotation, newPosition);
870 // IJavaElement element = newAnnotation.getElement();
871 // Position newPosition = (Position) updated.get(newAnnotation);
873 // List annotations = (List) previous.get(element);
874 // if (annotations == null) {
876 // additions.put(newAnnotation, newPosition);
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
887 // if (existingPosition != null
888 // && (!newPosition.equals(existingPosition))) {
889 // existingPosition.setOffset(newPosition
891 // existingPosition.setLength(newPosition
893 // updates.add(existingAnnotation);
901 // additions.put(newAnnotation, newPosition);
903 // if (annotations.isEmpty())
904 // previous.remove(element);
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);
917 match(deletions, additions, updates);
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);
926 fCachedDocument = null;
927 fAllowCollapsing = true;
931 fHasHeaderComment = false;
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.
942 private void match(List deletions, Map additions, List changes) {
943 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
946 List newDeletions = new ArrayList();
947 List newChanges = new ArrayList();
949 Iterator deletionIterator = deletions.iterator();
950 while (deletionIterator.hasNext()) {
951 JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator
953 Position deletedPosition = fCachedModel.getPosition(deleted);
954 if (deletedPosition == null)
957 Tuple deletedTuple = new Tuple(deleted, deletedPosition);
959 Tuple match = findMatch(deletedTuple, changes, null);
960 boolean addToDeletions = true;
962 match = findMatch(deletedTuple, additions.keySet(), additions);
963 addToDeletions = false;
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);
976 deletionIterator.remove();
977 newChanges.add(deleted);
980 newDeletions.add(match.annotation);
984 deletions.addAll(newDeletions);
985 changes.addAll(newChanges);
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>.
995 * A tuple is said to match another if their annotations have the same
996 * comment flag and their position offsets are equal.
999 * If a match is found, the annotation gets removed from
1000 * <code>annotations</code>.
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<Annotation, Position></code> or
1010 * @return a matching tuple or <code>null</code> for no match
1012 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
1013 Iterator it = annotations.iterator();
1014 while (it.hasNext()) {
1015 JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it
1017 if (tuple.annotation.isComment() == annotation.isComment()) {
1018 Position position = positionMap == null ? fCachedModel
1019 .getPosition(annotation) : (Position) positionMap
1021 if (position == null)
1024 if (tuple.position.getOffset() == position.getOffset()) {
1026 return new Tuple(annotation, position);
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());
1045 list = new ArrayList(2);
1046 map.put(java.getElement(), list);
1048 list.add(new Tuple(java, position));
1052 Comparator comparator = new Comparator() {
1053 public int compare(Object o1, Object o2) {
1054 return ((Tuple) o1).position.getOffset()
1055 - ((Tuple) o2).position.getOffset();
1058 for (Iterator it = map.values().iterator(); it.hasNext();) {
1059 List list = (List) it.next();
1060 Collections.sort(list, comparator);