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 IProjectionListener, IJavaFoldingStructureProvider {
73 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
75 private IJavaElement fJavaElement;
77 private boolean fIsComment;
79 public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) {
81 fJavaElement = element;
82 fIsComment = isComment;
85 public IJavaElement getElement() {
89 public void setElement(IJavaElement element) {
90 fJavaElement = element;
93 public boolean isComment() {
97 public void setIsComment(boolean isComment) {
98 fIsComment = isComment;
102 * @see java.lang.Object#toString()
104 public String toString() {
105 return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
106 "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
107 "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
108 "\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
112 private static final class Tuple {
113 JavaProjectionAnnotation annotation;
117 Tuple(JavaProjectionAnnotation annotation, Position position) {
118 this.annotation = annotation;
119 this.position = position;
123 private class ElementChangedListener implements IElementChangedListener {
126 * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
128 public void elementChanged(ElementChangedEvent e) {
129 IJavaElementDelta delta = findElement(fInput, e.getDelta());
134 private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {
136 if (delta == null || target == null)
139 IJavaElement element = delta.getElement();
141 if (element.getElementType() > IJavaElement.CLASS_FILE)
144 if (target.equals(element))
147 IJavaElementDelta[] children = delta.getAffectedChildren();
149 for (int i = 0; i < children.length; i++) {
150 IJavaElementDelta d = findElement(target, children[i]);
160 * Projection position that will return two foldable regions: one folding away
161 * the region from after the '/**' to the beginning of the content, the other
162 * from after the first content line until after the comment.
166 private static final class CommentPosition extends Position implements IProjectionPosition {
167 CommentPosition(int offset, int length) {
168 super(offset, length);
172 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
174 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
175 DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
177 int contentStart = findFirstContent(sequence, prefixEnd);
179 int firstLine = document.getLineOfOffset(offset + prefixEnd);
180 int captionLine = document.getLineOfOffset(offset + contentStart);
181 int lastLine = document.getLineOfOffset(offset + length);
183 Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$
184 Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
187 if (firstLine < captionLine) {
188 // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
189 int preOffset = document.getLineOffset(firstLine);
190 IRegion preEndLineInfo = document.getLineInformation(captionLine);
191 int preEnd = preEndLineInfo.getOffset();
192 preRegion = new Region(preOffset, preEnd - preOffset);
197 if (captionLine < lastLine) {
198 int postOffset = document.getLineOffset(captionLine + 1);
199 IRegion postRegion = new Region(postOffset, offset + length - postOffset);
201 if (preRegion == null)
202 return new IRegion[] { postRegion };
204 return new IRegion[] { preRegion, postRegion };
207 if (preRegion != null)
208 return new IRegion[] { preRegion };
214 * Finds the offset of the first identifier part within <code>content</code>.
215 * Returns 0 if none is found.
218 * the content to search
219 * @return the first index of a unicode identifier part, or zero if none can
222 private int findFirstContent(final CharSequence content, int prefixEnd) {
223 int lenght = content.length();
224 for (int i = prefixEnd; i < lenght; i++) {
225 if (Character.isUnicodeIdentifierPart(content.charAt(i)))
232 // * Finds the offset of the first identifier part within
233 // <code>content</code>.
234 // * Returns 0 if none is found.
236 // * @param content the content to search
237 // * @return the first index of a unicode identifier part, or zero if none
241 // private int findPrefixEnd(final CharSequence content) {
242 // // return the index after the leading '/*' or '/**'
243 // int len= content.length();
245 // while (i < len && isWhiteSpace(content.charAt(i)))
247 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) ==
249 // if (len >= i + 3 && content.charAt(i + 2) == '*')
257 // private boolean isWhiteSpace(char c) {
258 // return c == ' ' || c == '\t';
262 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
264 public int computeCaptionOffset(IDocument document) {
266 DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
267 return findFirstContent(sequence, 0);
272 * Projection position that will return two foldable regions: one folding away
273 * the lines before the one containing the simple name of the java element,
274 * one folding away any lines after the caption.
278 private static final class JavaElementPosition extends Position implements IProjectionPosition {
280 private IMember fMember;
282 public JavaElementPosition(int offset, int length, IMember member) {
283 super(offset, length);
284 Assert.isNotNull(member);
288 public void setMember(IMember member) {
289 Assert.isNotNull(member);
294 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
296 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
297 int nameStart = offset;
300 * The member's name range may not be correct. However, reconciling
301 * would trigger another element delta which would lead to reentrant
302 * situations. Therefore, we optimistically assume that the name range
303 * is correct, but double check the received lines below.
305 ISourceRange nameRange = fMember.getNameRange();
306 if (nameRange != null)
307 nameStart = nameRange.getOffset();
309 } catch (JavaModelException e) {
310 // ignore and use default
313 int firstLine = document.getLineOfOffset(offset);
314 int captionLine = document.getLineOfOffset(nameStart);
315 int lastLine = document.getLineOfOffset(offset + length);
318 * see comment above - adjust the caption line to be inside the entire
319 * folded region, and rely on later element deltas to correct the name
322 if (captionLine < firstLine)
323 captionLine = firstLine;
324 if (captionLine > lastLine)
325 captionLine = lastLine;
328 if (firstLine < captionLine) {
329 int preOffset = document.getLineOffset(firstLine);
330 IRegion preEndLineInfo = document.getLineInformation(captionLine);
331 int preEnd = preEndLineInfo.getOffset();
332 preRegion = new Region(preOffset, preEnd - preOffset);
337 if (captionLine < lastLine) {
338 int postOffset = document.getLineOffset(captionLine + 1);
339 IRegion postRegion = new Region(postOffset, offset + length - postOffset);
341 if (preRegion == null)
342 return new IRegion[] { postRegion };
344 return new IRegion[] { preRegion, postRegion };
347 if (preRegion != null)
348 return new IRegion[] { preRegion };
354 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
356 public int computeCaptionOffset(IDocument document) throws BadLocationException {
357 int nameStart = offset;
359 // need a reconcile here?
360 ISourceRange nameRange = fMember.getNameRange();
361 if (nameRange != null)
362 nameStart = nameRange.getOffset();
363 } catch (JavaModelException e) {
364 // ignore and use default
367 return nameStart - offset;
372 private IDocument fCachedDocument;
374 private ProjectionAnnotationModel fCachedModel;
376 private ITextEditor fEditor;
378 private ProjectionViewer fViewer;
380 private IJavaElement fInput;
382 private IElementChangedListener fElementListener;
384 private boolean fAllowCollapsing = false;
386 private boolean fCollapseJavadoc = false;
388 // private boolean fCollapseImportContainer = true;
390 private boolean fCollapseInnerTypes = true;
392 private boolean fCollapseMethods = false;
394 private boolean fCollapseHeaderComments = true;
396 /* caches for header comment extraction. */
397 private IType fFirstType;
399 private boolean fHasHeaderComment;
401 public DefaultJavaFoldingStructureProvider() {
404 public void install(ITextEditor editor, ProjectionViewer viewer) {
405 if (editor instanceof PHPEditor) {
408 fViewer.addProjectionListener(this);
412 public void uninstall() {
414 projectionDisabled();
415 fViewer.removeProjectionListener(this);
421 protected boolean isInstalled() {
422 return fEditor != null;
426 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
428 public void projectionEnabled() {
429 // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
430 // projectionEnabled messages are not always paired with projectionDisabled
431 // i.e. multiple enabled messages may be sent out.
432 // we have to make sure that we disable first when getting an enable
434 projectionDisabled();
436 if (fEditor instanceof PHPEditor) {
438 fElementListener = new ElementChangedListener();
439 JavaCore.addElementChangedListener(fElementListener);
444 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
446 public void projectionDisabled() {
447 fCachedDocument = null;
448 if (fElementListener != null) {
449 JavaCore.removeElementChangedListener(fElementListener);
450 fElementListener = null;
454 public void initialize() {
459 initializePreferences();
463 IDocumentProvider provider = fEditor.getDocumentProvider();
464 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
465 fAllowCollapsing = true;
468 fHasHeaderComment = false;
470 if (fEditor instanceof PHPUnitEditor) {
471 IWorkingCopyManager manager = PHPeclipsePlugin.getDefault().getWorkingCopyManager();
472 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
474 // else if (fEditor instanceof ClassFileEditor) {
475 // IClassFileEditorInput editorInput= (IClassFileEditorInput)
476 // fEditor.getEditorInput();
477 // fInput= editorInput.getClassFile();
480 if (fInput != null) {
481 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
483 fCachedModel = model;
484 if (fInput instanceof ICompilationUnit) {
485 ICompilationUnit unit = (ICompilationUnit) fInput;
486 synchronized (unit) {
488 // unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
490 } catch (JavaModelException x) {
495 Map additions = computeAdditions((IParent) fInput);
497 * Minimize the events being sent out - as this happens in the UI
498 * thread merge everything into one call.
500 List removals = new LinkedList();
501 Iterator existing = model.getAnnotationIterator();
502 while (existing.hasNext())
503 removals.add(existing.next());
504 model.replaceAnnotations((Annotation[]) removals.toArray(new Annotation[removals.size()]), additions);
509 fCachedDocument = null;
511 fAllowCollapsing = false;
514 fHasHeaderComment = false;
518 private void initializePreferences() {
519 IPreferenceStore store = PHPeclipsePlugin.getDefault().getPreferenceStore();
520 fCollapseInnerTypes = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
521 // fCollapseImportContainer =
522 // store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
523 fCollapseJavadoc = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
524 fCollapseMethods = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
525 fCollapseHeaderComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
528 private Map computeAdditions(IParent parent) {
529 Map map = new LinkedHashMap(); // use a linked map to maintain ordering of
532 computeAdditions(parent.getChildren(), map);
533 } catch (JavaModelException x) {
538 private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException {
539 for (int i = 0; i < elements.length; i++) {
540 IJavaElement element = elements[i];
542 computeAdditions(element, map);
544 if (element instanceof IParent) {
545 IParent parent = (IParent) element;
546 computeAdditions(parent.getChildren(), map);
551 private void computeAdditions(IJavaElement element, Map map) {
553 boolean createProjection = false;
555 boolean collapse = false;
556 switch (element.getElementType()) {
558 // case IJavaElement.IMPORT_CONTAINER:
559 // collapse = fAllowCollapsing && fCollapseImportContainer;
560 // createProjection = true;
562 case IJavaElement.TYPE:
563 collapse = fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element);
564 createProjection = true;
566 case IJavaElement.METHOD:
567 collapse = fAllowCollapsing && fCollapseMethods;
568 createProjection = true;
572 if (createProjection) {
573 IRegion[] regions = computeProjectionRanges(element);
574 if (regions != null) {
576 for (int i = 0; i < regions.length - 1; i++) {
577 Position position = createProjectionPosition(regions[i], null);
578 boolean commentCollapse;
579 if (position != null) {
580 if (i == 0 && (regions.length > 2 || fHasHeaderComment) && element == fFirstType) {
581 commentCollapse = fAllowCollapsing && fCollapseHeaderComments;
583 commentCollapse = fAllowCollapsing && fCollapseJavadoc;
585 map.put(new JavaProjectionAnnotation(element, commentCollapse, true), position);
589 Position position = createProjectionPosition(regions[regions.length - 1], element);
590 if (position != null)
591 map.put(new JavaProjectionAnnotation(element, collapse, false), position);
596 private boolean isInnerType(IType type) {
599 return type.isMember();
600 } catch (JavaModelException x) {
601 IJavaElement parent = type.getParent();
602 if (parent != null) {
603 int parentType = parent.getElementType();
604 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
612 * Computes the projection ranges for a given <code>IJavaElement</code>.
613 * More than one range may be returned if the element has a leading comment
614 * which gets folded separately. If there are no foldable regions,
615 * <code>null</code> is returned.
618 * the java element that can be folded
619 * @return the regions to be folded, or <code>null</code> if there are none
621 private IRegion[] computeProjectionRanges(IJavaElement element) {
624 if (element instanceof ISourceReference) {
625 ISourceReference reference = (ISourceReference) element;
626 ISourceRange range = reference.getSourceRange();
628 String contents = reference.getSource();
629 if (contents == null)
632 List regions = new ArrayList();
633 // now add all comments first to the regions list
634 if (fFirstType == null && element instanceof IType) {
635 fFirstType = (IType) element;
636 IRegion headerComment = computeHeaderComment(fFirstType);
637 if (headerComment != null) {
638 regions.add(headerComment);
639 fHasHeaderComment = true;
643 final int shift = range.getOffset();
645 if (element instanceof IType) {
646 Scanner scanner = ToolFactory.createScanner(true, false, false, false);
647 scanner.setSource(contents.toCharArray());
648 scanner.setPHPMode(true);
650 int token = scanner.getNextToken();
651 while (token != ITerminalSymbols.TokenNameEOF) {
653 token = scanner.getNextToken();
654 start = shift + scanner.getCurrentTokenStartPosition();
657 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
658 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
659 int end = shift + scanner.getCurrentTokenEndPosition() + 1;
660 regions.add(new Region(start, end - start));
662 case ITerminalSymbols.TokenNameCOMMENT_LINE:
667 // at the end add the element region
668 regions.add(new Region(range.getOffset(), range.getLength()));
670 if (regions.size() > 0) {
671 IRegion[] result = new IRegion[regions.size()];
672 regions.toArray(result);
677 } catch (JavaModelException e) {
678 } catch (InvalidInputException e) {
684 private IRegion computeHeaderComment(IType type) throws JavaModelException {
685 if (fCachedDocument == null)
688 // search at most up to the first type
689 ISourceRange range = type.getSourceRange();
693 int end = range.getOffset();
695 if (fInput instanceof ISourceReference) {
698 content = fCachedDocument.get(start, end - start);
699 } catch (BadLocationException e) {
700 return null; // ignore header comment in that case
704 * code adapted from CommentFormattingStrategy: scan the header content up
705 * to the first type. Once a comment is found, accumulate any additional
706 * comments up to the stop condition. The stop condition is reaching a
707 * package declaration, import container, or the end of the input.
709 IScanner scanner = ToolFactory.createScanner(true, false, false, false);
710 scanner.setSource(content.toCharArray());
712 int headerStart = -1;
715 boolean foundComment = false;
716 int terminal = scanner.getNextToken();
717 while (terminal != ITerminalSymbols.TokenNameEOF
718 && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
720 if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
721 || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
723 headerStart = scanner.getCurrentTokenStartPosition();
724 headerEnd = scanner.getCurrentTokenEndPosition();
727 terminal = scanner.getNextToken();
730 } catch (InvalidInputException ex) {
734 if (headerEnd != -1) {
735 return new Region(headerStart, headerEnd - headerStart);
741 private Position createProjectionPosition(IRegion region, IJavaElement element) {
743 if (fCachedDocument == null)
748 int start = fCachedDocument.getLineOfOffset(region.getOffset());
749 int end = fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
751 int offset = fCachedDocument.getLineOffset(start);
753 if (fCachedDocument.getNumberOfLines() > end + 1)
754 endOffset = fCachedDocument.getLineOffset(end + 1);
755 else if (end > start)
756 endOffset = fCachedDocument.getLineOffset(end) + fCachedDocument.getLineLength(end);
759 if (element instanceof IMember)
760 return new JavaElementPosition(offset, endOffset - offset, (IMember) element);
762 return new CommentPosition(offset, endOffset - offset);
765 } catch (BadLocationException x) {
771 protected void processDelta(IJavaElementDelta delta) {
776 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
779 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
785 IDocumentProvider provider = fEditor.getDocumentProvider();
786 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
787 fCachedModel = model;
788 fAllowCollapsing = false;
791 fHasHeaderComment = false;
793 Map additions = new HashMap();
794 List deletions = new ArrayList();
795 List updates = new ArrayList();
797 Map updated = computeAdditions((IParent) fInput);
798 Map previous = createAnnotationMap(model);
800 Iterator e = updated.keySet().iterator();
801 while (e.hasNext()) {
802 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e.next();
803 IJavaElement element = newAnnotation.getElement();
804 Position newPosition = (Position) updated.get(newAnnotation);
806 List annotations = (List) previous.get(element);
807 if (annotations == null) {
809 additions.put(newAnnotation, newPosition);
812 Iterator x = annotations.iterator();
813 boolean matched = false;
814 while (x.hasNext()) {
815 Tuple tuple = (Tuple) x.next();
816 JavaProjectionAnnotation existingAnnotation = tuple.annotation;
817 Position existingPosition = tuple.position;
818 if (newAnnotation.isComment() == existingAnnotation.isComment()) {
819 if (existingPosition != null && (!newPosition.equals(existingPosition))) {
820 existingPosition.setOffset(newPosition.getOffset());
821 existingPosition.setLength(newPosition.getLength());
822 updates.add(existingAnnotation);
830 additions.put(newAnnotation, newPosition);
832 if (annotations.isEmpty())
833 previous.remove(element);
837 e = previous.values().iterator();
838 while (e.hasNext()) {
839 List list = (List) e.next();
840 int size = list.size();
841 for (int i = 0; i < size; i++)
842 deletions.add(((Tuple) list.get(i)).annotation);
845 match(deletions, additions, updates);
847 Annotation[] removals = new Annotation[deletions.size()];
848 deletions.toArray(removals);
849 Annotation[] changes = new Annotation[updates.size()];
850 updates.toArray(changes);
851 model.modifyAnnotations(removals, additions, changes);
854 fCachedDocument = null;
855 fAllowCollapsing = true;
859 fHasHeaderComment = false;
864 * Matches deleted annotations to changed or added ones. A deleted
865 * annotation/position tuple that has a matching addition / change is updated
866 * and marked as changed. The matching tuple is not added (for additions) or
867 * marked as deletion instead (for changes). The result is that more
868 * annotations are changed and fewer get deleted/re-added.
870 private void match(List deletions, Map additions, List changes) {
871 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
874 List newDeletions = new ArrayList();
875 List newChanges = new ArrayList();
877 Iterator deletionIterator = deletions.iterator();
878 while (deletionIterator.hasNext()) {
879 JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator.next();
880 Position deletedPosition = fCachedModel.getPosition(deleted);
881 if (deletedPosition == null)
884 Tuple deletedTuple = new Tuple(deleted, deletedPosition);
886 Tuple match = findMatch(deletedTuple, changes, null);
887 boolean addToDeletions = true;
889 match = findMatch(deletedTuple, additions.keySet(), additions);
890 addToDeletions = false;
894 IJavaElement element = match.annotation.getElement();
895 deleted.setElement(element);
896 deletedPosition.setLength(match.position.getLength());
897 if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) {
898 JavaElementPosition jep = (JavaElementPosition) deletedPosition;
899 jep.setMember((IMember) element);
902 deletionIterator.remove();
903 newChanges.add(deleted);
906 newDeletions.add(match.annotation);
910 deletions.addAll(newDeletions);
911 changes.addAll(newChanges);
915 * Finds a match for <code>tuple</code> in a collection of annotations. The
916 * positions for the <code>JavaProjectionAnnotation</code> instances in
917 * <code>annotations</code> can be found in the passed
918 * <code>positionMap</code> or <code>fCachedModel</code> if
919 * <code>positionMap</code> is <code>null</code>.
921 * A tuple is said to match another if their annotations have the same comment
922 * flag and their position offsets are equal.
925 * If a match is found, the annotation gets removed from
926 * <code>annotations</code>.
930 * the tuple for which we want to find a match
932 * collection of <code>JavaProjectionAnnotation</code>
934 * a <code>Map<Annotation, Position></code> or
936 * @return a matching tuple or <code>null</code> for no match
938 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
939 Iterator it = annotations.iterator();
940 while (it.hasNext()) {
941 JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it.next();
942 if (tuple.annotation.isComment() == annotation.isComment()) {
943 Position position = positionMap == null ? fCachedModel.getPosition(annotation) : (Position) positionMap.get(annotation);
944 if (position == null)
947 if (tuple.position.getOffset() == position.getOffset()) {
949 return new Tuple(annotation, position);
957 private Map createAnnotationMap(IAnnotationModel model) {
958 Map map = new HashMap();
959 Iterator e = model.getAnnotationIterator();
960 while (e.hasNext()) {
961 Object annotation = e.next();
962 if (annotation instanceof JavaProjectionAnnotation) {
963 JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
964 Position position = model.getPosition(java);
965 Assert.isNotNull(position);
966 List list = (List) map.get(java.getElement());
968 list = new ArrayList(2);
969 map.put(java.getElement(), list);
971 list.add(new Tuple(java, position));
975 Comparator comparator = new Comparator() {
976 public int compare(Object o1, Object o2) {
977 return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset();
980 for (Iterator it = map.values().iterator(); it.hasNext();) {
981 List list = (List) it.next();
982 Collections.sort(list, comparator);