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.ui.text.DocumentCharacterIterator;
41 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
42 import net.sourceforge.phpdt.ui.PreferenceConstants;
43 import net.sourceforge.phpdt.ui.text.folding.IJavaFoldingStructureProvider;
44 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
45 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
46 import net.sourceforge.phpeclipse.phpeditor.PHPUnitEditor;
48 import org.eclipse.jface.preference.IPreferenceStore;
49 import org.eclipse.jface.text.Assert;
50 import org.eclipse.jface.text.BadLocationException;
51 import org.eclipse.jface.text.IDocument;
52 import org.eclipse.jface.text.IRegion;
53 import org.eclipse.jface.text.Position;
54 import org.eclipse.jface.text.Region;
55 import org.eclipse.jface.text.source.Annotation;
56 import org.eclipse.jface.text.source.IAnnotationModel;
57 import org.eclipse.jface.text.source.projection.IProjectionListener;
58 import org.eclipse.jface.text.source.projection.IProjectionPosition;
59 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
60 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
61 import org.eclipse.jface.text.source.projection.ProjectionViewer;
62 import org.eclipse.ui.texteditor.IDocumentProvider;
63 import org.eclipse.ui.texteditor.ITextEditor;
66 * Updates the projection model of a class file or compilation unit.
70 public class DefaultJavaFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider {
72 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
74 private IJavaElement fJavaElement;
76 private boolean fIsComment;
78 public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) {
80 fJavaElement = element;
81 fIsComment = isComment;
84 public IJavaElement getElement() {
88 public void setElement(IJavaElement element) {
89 fJavaElement = element;
92 public boolean isComment() {
96 public void setIsComment(boolean isComment) {
97 fIsComment = isComment;
101 * @see java.lang.Object#toString()
103 public String toString() {
104 return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
105 "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
106 "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
107 "\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
111 private static final class Tuple {
112 JavaProjectionAnnotation annotation;
116 Tuple(JavaProjectionAnnotation annotation, Position position) {
117 this.annotation = annotation;
118 this.position = position;
122 private class ElementChangedListener implements IElementChangedListener {
125 * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
127 public void elementChanged(ElementChangedEvent e) {
128 IJavaElementDelta delta = findElement(fInput, e.getDelta());
133 private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {
135 if (delta == null || target == null)
138 IJavaElement element = delta.getElement();
140 if (element.getElementType() > IJavaElement.CLASS_FILE)
143 if (target.equals(element))
146 IJavaElementDelta[] children = delta.getAffectedChildren();
148 for (int i = 0; i < children.length; i++) {
149 IJavaElementDelta d = findElement(target, children[i]);
159 * Projection position that will return two foldable regions: one folding away
160 * the region from after the '/**' to the beginning of the content, the other
161 * from after the first content line until after the comment.
165 private static final class CommentPosition extends Position implements IProjectionPosition {
166 CommentPosition(int offset, int length) {
167 super(offset, length);
171 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
173 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
174 DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
176 int contentStart = findFirstContent(sequence, prefixEnd);
178 int firstLine = document.getLineOfOffset(offset + prefixEnd);
179 int captionLine = document.getLineOfOffset(offset + contentStart);
180 int lastLine = document.getLineOfOffset(offset + length);
182 Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$
183 Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
186 if (firstLine < captionLine) {
187 // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
188 int preOffset = document.getLineOffset(firstLine);
189 IRegion preEndLineInfo = document.getLineInformation(captionLine);
190 int preEnd = preEndLineInfo.getOffset();
191 preRegion = new Region(preOffset, preEnd - preOffset);
196 if (captionLine < lastLine) {
197 int postOffset = document.getLineOffset(captionLine + 1);
198 IRegion postRegion = new Region(postOffset, offset + length - postOffset);
200 if (preRegion == null)
201 return new IRegion[] { postRegion };
203 return new IRegion[] { preRegion, postRegion };
206 if (preRegion != null)
207 return new IRegion[] { preRegion };
213 * Finds the offset of the first identifier part within <code>content</code>.
214 * Returns 0 if none is found.
217 * the content to search
218 * @return the first index of a unicode identifier part, or zero if none can
221 private int findFirstContent(final CharSequence content, int prefixEnd) {
222 int lenght = content.length();
223 for (int i = prefixEnd; i < lenght; i++) {
224 if (Character.isUnicodeIdentifierPart(content.charAt(i)))
231 // * Finds the offset of the first identifier part within
232 // <code>content</code>.
233 // * Returns 0 if none is found.
235 // * @param content the content to search
236 // * @return the first index of a unicode identifier part, or zero if none
240 // private int findPrefixEnd(final CharSequence content) {
241 // // return the index after the leading '/*' or '/**'
242 // int len= content.length();
244 // while (i < len && isWhiteSpace(content.charAt(i)))
246 // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) ==
248 // if (len >= i + 3 && content.charAt(i + 2) == '*')
256 // private boolean isWhiteSpace(char c) {
257 // return c == ' ' || c == '\t';
261 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
263 public int computeCaptionOffset(IDocument document) {
265 DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
266 return findFirstContent(sequence, 0);
271 * Projection position that will return two foldable regions: one folding away
272 * the lines before the one containing the simple name of the java element,
273 * one folding away any lines after the caption.
277 private static final class JavaElementPosition extends Position implements IProjectionPosition {
279 private IMember fMember;
281 public JavaElementPosition(int offset, int length, IMember member) {
282 super(offset, length);
283 Assert.isNotNull(member);
287 public void setMember(IMember member) {
288 Assert.isNotNull(member);
293 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
295 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
296 int nameStart = offset;
299 * The member's name range may not be correct. However, reconciling
300 * would trigger another element delta which would lead to reentrant
301 * situations. Therefore, we optimistically assume that the name range
302 * is correct, but double check the received lines below.
304 ISourceRange nameRange = fMember.getNameRange();
305 if (nameRange != null)
306 nameStart = nameRange.getOffset();
308 } catch (JavaModelException e) {
309 // ignore and use default
312 int firstLine = document.getLineOfOffset(offset);
313 int captionLine = document.getLineOfOffset(nameStart);
314 int lastLine = document.getLineOfOffset(offset + length);
317 * see comment above - adjust the caption line to be inside the entire
318 * folded region, and rely on later element deltas to correct the name
321 if (captionLine < firstLine)
322 captionLine = firstLine;
323 if (captionLine > lastLine)
324 captionLine = lastLine;
327 if (firstLine < captionLine) {
328 int preOffset = document.getLineOffset(firstLine);
329 IRegion preEndLineInfo = document.getLineInformation(captionLine);
330 int preEnd = preEndLineInfo.getOffset();
331 preRegion = new Region(preOffset, preEnd - preOffset);
336 if (captionLine < lastLine) {
337 int postOffset = document.getLineOffset(captionLine + 1);
338 IRegion postRegion = new Region(postOffset, offset + length - postOffset);
340 if (preRegion == null)
341 return new IRegion[] { postRegion };
343 return new IRegion[] { preRegion, postRegion };
346 if (preRegion != null)
347 return new IRegion[] { preRegion };
353 * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
355 public int computeCaptionOffset(IDocument document) throws BadLocationException {
356 int nameStart = offset;
358 // need a reconcile here?
359 ISourceRange nameRange = fMember.getNameRange();
360 if (nameRange != null)
361 nameStart = nameRange.getOffset();
362 } catch (JavaModelException e) {
363 // ignore and use default
366 return nameStart - offset;
371 private IDocument fCachedDocument;
373 private ProjectionAnnotationModel fCachedModel;
375 private ITextEditor fEditor;
377 private ProjectionViewer fViewer;
379 private IJavaElement fInput;
381 private IElementChangedListener fElementListener;
383 private boolean fAllowCollapsing = false;
385 private boolean fCollapseJavadoc = false;
387 // private boolean fCollapseImportContainer = true;
389 private boolean fCollapseInnerTypes = true;
391 private boolean fCollapseMethods = false;
393 private boolean fCollapseHeaderComments = true;
395 /* caches for header comment extraction. */
396 private IType fFirstType;
398 private boolean fHasHeaderComment;
400 public DefaultJavaFoldingStructureProvider() {
403 public void install(ITextEditor editor, ProjectionViewer viewer) {
404 if (editor instanceof PHPEditor) {
407 fViewer.addProjectionListener(this);
411 public void uninstall() {
413 projectionDisabled();
414 fViewer.removeProjectionListener(this);
420 protected boolean isInstalled() {
421 return fEditor != null;
425 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
427 public void projectionEnabled() {
428 // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
429 // projectionEnabled messages are not always paired with projectionDisabled
430 // i.e. multiple enabled messages may be sent out.
431 // we have to make sure that we disable first when getting an enable
433 projectionDisabled();
435 if (fEditor instanceof PHPEditor) {
437 fElementListener = new ElementChangedListener();
438 JavaCore.addElementChangedListener(fElementListener);
443 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
445 public void projectionDisabled() {
446 fCachedDocument = null;
447 if (fElementListener != null) {
448 JavaCore.removeElementChangedListener(fElementListener);
449 fElementListener = null;
453 public void initialize() {
458 initializePreferences();
462 IDocumentProvider provider = fEditor.getDocumentProvider();
463 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
464 fAllowCollapsing = true;
467 fHasHeaderComment = false;
469 if (fEditor instanceof PHPUnitEditor) {
470 IWorkingCopyManager manager = PHPeclipsePlugin.getDefault().getWorkingCopyManager();
471 fInput = manager.getWorkingCopy(fEditor.getEditorInput());
473 // else if (fEditor instanceof ClassFileEditor) {
474 // IClassFileEditorInput editorInput= (IClassFileEditorInput)
475 // fEditor.getEditorInput();
476 // fInput= editorInput.getClassFile();
479 if (fInput != null) {
480 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
482 fCachedModel = model;
483 if (fInput instanceof ICompilationUnit) {
484 ICompilationUnit unit = (ICompilationUnit) fInput;
485 synchronized (unit) {
487 // unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
489 } catch (JavaModelException x) {
494 Map additions = computeAdditions((IParent) fInput);
496 * Minimize the events being sent out - as this happens in the UI
497 * thread merge everything into one call.
499 List removals = new LinkedList();
500 Iterator existing = model.getAnnotationIterator();
501 while (existing.hasNext())
502 removals.add(existing.next());
503 model.replaceAnnotations((Annotation[]) removals.toArray(new Annotation[removals.size()]), additions);
508 fCachedDocument = null;
510 fAllowCollapsing = false;
513 fHasHeaderComment = false;
517 private void initializePreferences() {
518 IPreferenceStore store = PHPeclipsePlugin.getDefault().getPreferenceStore();
519 fCollapseInnerTypes = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
520 // fCollapseImportContainer = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
521 fCollapseJavadoc = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
522 fCollapseMethods = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
523 fCollapseHeaderComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
526 private Map computeAdditions(IParent parent) {
527 Map map = new LinkedHashMap(); // use a linked map to maintain ordering of
530 computeAdditions(parent.getChildren(), map);
531 } catch (JavaModelException x) {
536 private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException {
537 for (int i = 0; i < elements.length; i++) {
538 IJavaElement element = elements[i];
540 computeAdditions(element, map);
542 if (element instanceof IParent) {
543 IParent parent = (IParent) element;
544 computeAdditions(parent.getChildren(), map);
549 private void computeAdditions(IJavaElement element, Map map) {
551 boolean createProjection = false;
553 boolean collapse = false;
554 switch (element.getElementType()) {
556 // case IJavaElement.IMPORT_CONTAINER:
557 // collapse = fAllowCollapsing && fCollapseImportContainer;
558 // createProjection = true;
560 case IJavaElement.TYPE:
561 collapse = fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element);
562 createProjection = true;
564 case IJavaElement.METHOD:
565 collapse = fAllowCollapsing && fCollapseMethods;
566 createProjection = true;
570 if (createProjection) {
571 IRegion[] regions = computeProjectionRanges(element);
572 if (regions != null) {
574 for (int i = 0; i < regions.length - 1; i++) {
575 Position position = createProjectionPosition(regions[i], null);
576 boolean commentCollapse;
577 if (position != null) {
578 if (i == 0 && (regions.length > 2 || fHasHeaderComment) && element == fFirstType) {
579 commentCollapse = fAllowCollapsing && fCollapseHeaderComments;
581 commentCollapse = fAllowCollapsing && fCollapseJavadoc;
583 map.put(new JavaProjectionAnnotation(element, commentCollapse, true), position);
587 Position position = createProjectionPosition(regions[regions.length - 1], element);
588 if (position != null)
589 map.put(new JavaProjectionAnnotation(element, collapse, false), position);
594 private boolean isInnerType(IType type) {
597 return type.isMember();
598 } catch (JavaModelException x) {
599 IJavaElement parent = type.getParent();
600 if (parent != null) {
601 int parentType = parent.getElementType();
602 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
610 * Computes the projection ranges for a given <code>IJavaElement</code>.
611 * More than one range may be returned if the element has a leading comment
612 * which gets folded separately. If there are no foldable regions,
613 * <code>null</code> is returned.
616 * the java element that can be folded
617 * @return the regions to be folded, or <code>null</code> if there are none
619 private IRegion[] computeProjectionRanges(IJavaElement element) {
622 if (element instanceof ISourceReference) {
623 ISourceReference reference = (ISourceReference) element;
624 ISourceRange range = reference.getSourceRange();
626 String contents = reference.getSource();
627 if (contents == null)
630 List regions = new ArrayList();
631 if (fFirstType == null && element instanceof IType) {
632 fFirstType = (IType) element;
633 IRegion headerComment = computeHeaderComment(fFirstType);
634 if (headerComment != null) {
635 regions.add(headerComment);
636 fHasHeaderComment = true;
640 IScanner scanner = ToolFactory.createScanner(true, false, false, false);
641 scanner.setSource(contents.toCharArray());
642 final int shift = range.getOffset();
646 int token = scanner.getNextToken();
647 start = shift + scanner.getCurrentTokenStartPosition();
650 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC:
651 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
652 int end = shift + scanner.getCurrentTokenEndPosition() + 1;
653 regions.add(new Region(start, end - start));
655 case ITerminalSymbols.TokenNameCOMMENT_LINE:
662 regions.add(new Region(start, shift + range.getLength() - start));
664 if (regions.size() > 0) {
665 IRegion[] result = new IRegion[regions.size()];
666 regions.toArray(result);
670 } catch (JavaModelException e) {
671 } catch (InvalidInputException e) {
677 private IRegion computeHeaderComment(IType type) throws JavaModelException {
678 if (fCachedDocument == null)
681 // search at most up to the first type
682 ISourceRange range = type.getSourceRange();
686 int end = range.getOffset();
688 if (fInput instanceof ISourceReference) {
691 content = fCachedDocument.get(start, end - start);
692 } catch (BadLocationException e) {
693 return null; // ignore header comment in that case
697 * code adapted from CommentFormattingStrategy: scan the header content up
698 * to the first type. Once a comment is found, accumulate any additional
699 * comments up to the stop condition. The stop condition is reaching a
700 * package declaration, import container, or the end of the input.
702 IScanner scanner = ToolFactory.createScanner(true, false, false, false);
703 scanner.setSource(content.toCharArray());
705 int headerStart = -1;
708 boolean foundComment = false;
709 int terminal = scanner.getNextToken();
710 while (terminal != ITerminalSymbols.TokenNameEOF
711 && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || foundComment)) {
713 if (terminal == ITerminalSymbols.TokenNameCOMMENT_PHPDOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
714 || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
716 headerStart = scanner.getCurrentTokenStartPosition();
717 headerEnd = scanner.getCurrentTokenEndPosition();
720 terminal = scanner.getNextToken();
723 } catch (InvalidInputException ex) {
727 if (headerEnd != -1) {
728 return new Region(headerStart, headerEnd - headerStart);
734 private Position createProjectionPosition(IRegion region, IJavaElement element) {
736 if (fCachedDocument == null)
741 int start = fCachedDocument.getLineOfOffset(region.getOffset());
742 int end = fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
744 int offset = fCachedDocument.getLineOffset(start);
746 if (fCachedDocument.getNumberOfLines() > end + 1)
747 endOffset = fCachedDocument.getLineOffset(end + 1);
748 else if (end > start)
749 endOffset = fCachedDocument.getLineOffset(end) + fCachedDocument.getLineLength(end);
752 if (element instanceof IMember)
753 return new JavaElementPosition(offset, endOffset - offset, (IMember) element);
755 return new CommentPosition(offset, endOffset - offset);
758 } catch (BadLocationException x) {
764 protected void processDelta(IJavaElementDelta delta) {
769 if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
772 ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
778 IDocumentProvider provider = fEditor.getDocumentProvider();
779 fCachedDocument = provider.getDocument(fEditor.getEditorInput());
780 fCachedModel = model;
781 fAllowCollapsing = false;
784 fHasHeaderComment = false;
786 Map additions = new HashMap();
787 List deletions = new ArrayList();
788 List updates = new ArrayList();
790 Map updated = computeAdditions((IParent) fInput);
791 Map previous = createAnnotationMap(model);
793 Iterator e = updated.keySet().iterator();
794 while (e.hasNext()) {
795 JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e.next();
796 IJavaElement element = newAnnotation.getElement();
797 Position newPosition = (Position) updated.get(newAnnotation);
799 List annotations = (List) previous.get(element);
800 if (annotations == null) {
802 additions.put(newAnnotation, newPosition);
805 Iterator x = annotations.iterator();
806 boolean matched = false;
807 while (x.hasNext()) {
808 Tuple tuple = (Tuple) x.next();
809 JavaProjectionAnnotation existingAnnotation = tuple.annotation;
810 Position existingPosition = tuple.position;
811 if (newAnnotation.isComment() == existingAnnotation.isComment()) {
812 if (existingPosition != null && (!newPosition.equals(existingPosition))) {
813 existingPosition.setOffset(newPosition.getOffset());
814 existingPosition.setLength(newPosition.getLength());
815 updates.add(existingAnnotation);
823 additions.put(newAnnotation, newPosition);
825 if (annotations.isEmpty())
826 previous.remove(element);
830 e = previous.values().iterator();
831 while (e.hasNext()) {
832 List list = (List) e.next();
833 int size = list.size();
834 for (int i = 0; i < size; i++)
835 deletions.add(((Tuple) list.get(i)).annotation);
838 match(deletions, additions, updates);
840 Annotation[] removals = new Annotation[deletions.size()];
841 deletions.toArray(removals);
842 Annotation[] changes = new Annotation[updates.size()];
843 updates.toArray(changes);
844 model.modifyAnnotations(removals, additions, changes);
847 fCachedDocument = null;
848 fAllowCollapsing = true;
852 fHasHeaderComment = false;
857 * Matches deleted annotations to changed or added ones. A deleted
858 * annotation/position tuple that has a matching addition / change is updated
859 * and marked as changed. The matching tuple is not added (for additions) or
860 * marked as deletion instead (for changes). The result is that more
861 * annotations are changed and fewer get deleted/re-added.
863 private void match(List deletions, Map additions, List changes) {
864 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
867 List newDeletions = new ArrayList();
868 List newChanges = new ArrayList();
870 Iterator deletionIterator = deletions.iterator();
871 while (deletionIterator.hasNext()) {
872 JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator.next();
873 Position deletedPosition = fCachedModel.getPosition(deleted);
874 if (deletedPosition == null)
877 Tuple deletedTuple = new Tuple(deleted, deletedPosition);
879 Tuple match = findMatch(deletedTuple, changes, null);
880 boolean addToDeletions = true;
882 match = findMatch(deletedTuple, additions.keySet(), additions);
883 addToDeletions = false;
887 IJavaElement element = match.annotation.getElement();
888 deleted.setElement(element);
889 deletedPosition.setLength(match.position.getLength());
890 if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) {
891 JavaElementPosition jep = (JavaElementPosition) deletedPosition;
892 jep.setMember((IMember) element);
895 deletionIterator.remove();
896 newChanges.add(deleted);
899 newDeletions.add(match.annotation);
903 deletions.addAll(newDeletions);
904 changes.addAll(newChanges);
908 * Finds a match for <code>tuple</code> in a collection of annotations. The
909 * positions for the <code>JavaProjectionAnnotation</code> instances in
910 * <code>annotations</code> can be found in the passed
911 * <code>positionMap</code> or <code>fCachedModel</code> if
912 * <code>positionMap</code> is <code>null</code>.
914 * A tuple is said to match another if their annotations have the same comment
915 * flag and their position offsets are equal.
918 * If a match is found, the annotation gets removed from
919 * <code>annotations</code>.
923 * the tuple for which we want to find a match
925 * collection of <code>JavaProjectionAnnotation</code>
927 * a <code>Map<Annotation, Position></code> or
929 * @return a matching tuple or <code>null</code> for no match
931 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap) {
932 Iterator it = annotations.iterator();
933 while (it.hasNext()) {
934 JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it.next();
935 if (tuple.annotation.isComment() == annotation.isComment()) {
936 Position position = positionMap == null ? fCachedModel.getPosition(annotation) : (Position) positionMap.get(annotation);
937 if (position == null)
940 if (tuple.position.getOffset() == position.getOffset()) {
942 return new Tuple(annotation, position);
950 private Map createAnnotationMap(IAnnotationModel model) {
951 Map map = new HashMap();
952 Iterator e = model.getAnnotationIterator();
953 while (e.hasNext()) {
954 Object annotation = e.next();
955 if (annotation instanceof JavaProjectionAnnotation) {
956 JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
957 Position position = model.getPosition(java);
958 Assert.isNotNull(position);
959 List list = (List) map.get(java.getElement());
961 list = new ArrayList(2);
962 map.put(java.getElement(), list);
964 list.add(new Tuple(java, position));
968 Comparator comparator = new Comparator() {
969 public int compare(Object o1, Object o2) {
970 return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset();
973 for (Iterator it = map.values().iterator(); it.hasNext();) {
974 List list = (List) it.next();
975 Collections.sort(list, comparator);