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);