0001: /*******************************************************************************
0002: * Copyright (c) 2006, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.jdt.ui.text.folding;
0011:
0012: import java.util.ArrayList;
0013: import java.util.Arrays;
0014: import java.util.Collection;
0015: import java.util.Collections;
0016: import java.util.Comparator;
0017: import java.util.HashMap;
0018: import java.util.HashSet;
0019: import java.util.Iterator;
0020: import java.util.LinkedHashMap;
0021: import java.util.List;
0022: import java.util.Map;
0023: import java.util.Set;
0024:
0025: import org.eclipse.core.runtime.Assert;
0026:
0027: import org.eclipse.jface.preference.IPreferenceStore;
0028:
0029: import org.eclipse.jface.text.BadLocationException;
0030: import org.eclipse.jface.text.IDocument;
0031: import org.eclipse.jface.text.IRegion;
0032: import org.eclipse.jface.text.Position;
0033: import org.eclipse.jface.text.Region;
0034: import org.eclipse.jface.text.TextSelection;
0035: import org.eclipse.jface.text.source.Annotation;
0036: import org.eclipse.jface.text.source.projection.IProjectionListener;
0037: import org.eclipse.jface.text.source.projection.IProjectionPosition;
0038: import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
0039: import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
0040: import org.eclipse.jface.text.source.projection.ProjectionViewer;
0041:
0042: import org.eclipse.ui.texteditor.IDocumentProvider;
0043: import org.eclipse.ui.texteditor.ITextEditor;
0044:
0045: import org.eclipse.jdt.core.ElementChangedEvent;
0046: import org.eclipse.jdt.core.IClassFile;
0047: import org.eclipse.jdt.core.ICompilationUnit;
0048: import org.eclipse.jdt.core.IElementChangedListener;
0049: import org.eclipse.jdt.core.IImportContainer;
0050: import org.eclipse.jdt.core.IImportDeclaration;
0051: import org.eclipse.jdt.core.IJavaElement;
0052: import org.eclipse.jdt.core.IJavaElementDelta;
0053: import org.eclipse.jdt.core.IMember;
0054: import org.eclipse.jdt.core.IParent;
0055: import org.eclipse.jdt.core.ISourceRange;
0056: import org.eclipse.jdt.core.ISourceReference;
0057: import org.eclipse.jdt.core.IType;
0058: import org.eclipse.jdt.core.JavaCore;
0059: import org.eclipse.jdt.core.JavaModelException;
0060: import org.eclipse.jdt.core.ToolFactory;
0061: import org.eclipse.jdt.core.compiler.IProblem;
0062: import org.eclipse.jdt.core.compiler.IScanner;
0063: import org.eclipse.jdt.core.compiler.ITerminalSymbols;
0064: import org.eclipse.jdt.core.compiler.InvalidInputException;
0065: import org.eclipse.jdt.core.dom.CompilationUnit;
0066:
0067: import org.eclipse.jdt.internal.corext.SourceRange;
0068:
0069: import org.eclipse.jdt.ui.PreferenceConstants;
0070:
0071: import org.eclipse.jdt.internal.ui.JavaPlugin;
0072: import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
0073: import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
0074: import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
0075: import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator;
0076:
0077: /**
0078: * Updates the projection model of a class file or compilation unit.
0079: * <p>
0080: * Clients may instantiate or subclass. Subclasses must make sure to always call the superclass'
0081: * code when overriding methods that are marked with "subclasses may extend".
0082: * </p>
0083: *
0084: * @since 3.0 (internal)
0085: * @since 3.2 (API)
0086: */
0087: public class DefaultJavaFoldingStructureProvider implements
0088: IJavaFoldingStructureProvider,
0089: IJavaFoldingStructureProviderExtension {
0090: /**
0091: * A context that contains the information needed to compute the folding structure of an
0092: * {@link ICompilationUnit} or an {@link IClassFile}. Computed folding regions are collected
0093: * via
0094: * {@linkplain #addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) addProjectionRange}.
0095: */
0096: protected final class FoldingStructureComputationContext {
0097: private final ProjectionAnnotationModel fModel;
0098: private final IDocument fDocument;
0099:
0100: private final boolean fAllowCollapsing;
0101:
0102: private IType fFirstType;
0103: private boolean fHasHeaderComment;
0104: private LinkedHashMap fMap = new LinkedHashMap();
0105: private IScanner fScanner;
0106:
0107: private FoldingStructureComputationContext(IDocument document,
0108: ProjectionAnnotationModel model,
0109: boolean allowCollapsing, IScanner scanner) {
0110: Assert.isNotNull(document);
0111: Assert.isNotNull(model);
0112: fDocument = document;
0113: fModel = model;
0114: fAllowCollapsing = allowCollapsing;
0115: fScanner = scanner;
0116: }
0117:
0118: private void setFirstType(IType type) {
0119: if (hasFirstType())
0120: throw new IllegalStateException();
0121: fFirstType = type;
0122: }
0123:
0124: boolean hasFirstType() {
0125: return fFirstType != null;
0126: }
0127:
0128: private IType getFirstType() {
0129: return fFirstType;
0130: }
0131:
0132: private boolean hasHeaderComment() {
0133: return fHasHeaderComment;
0134: }
0135:
0136: private void setHasHeaderComment() {
0137: fHasHeaderComment = true;
0138: }
0139:
0140: /**
0141: * Returns <code>true</code> if newly created folding regions may be collapsed,
0142: * <code>false</code> if not. This is usually <code>false</code> when updating the
0143: * folding structure while typing; it may be <code>true</code> when computing or restoring
0144: * the initial folding structure.
0145: *
0146: * @return <code>true</code> if newly created folding regions may be collapsed,
0147: * <code>false</code> if not
0148: */
0149: public boolean allowCollapsing() {
0150: return fAllowCollapsing;
0151: }
0152:
0153: /**
0154: * Returns the document which contains the code being folded.
0155: *
0156: * @return the document which contains the code being folded
0157: */
0158: private IDocument getDocument() {
0159: return fDocument;
0160: }
0161:
0162: private ProjectionAnnotationModel getModel() {
0163: return fModel;
0164: }
0165:
0166: private IScanner getScanner() {
0167: if (fScanner == null)
0168: fScanner = ToolFactory.createScanner(true, false,
0169: false, false);
0170: return fScanner;
0171: }
0172:
0173: /**
0174: * Adds a projection (folding) region to this context. The created annotation / position
0175: * pair will be added to the {@link ProjectionAnnotationModel} of the
0176: * {@link ProjectionViewer} of the editor.
0177: *
0178: * @param annotation the annotation to add
0179: * @param position the corresponding position
0180: */
0181: public void addProjectionRange(
0182: JavaProjectionAnnotation annotation, Position position) {
0183: fMap.put(annotation, position);
0184: }
0185:
0186: /**
0187: * Returns <code>true</code> if header comments should be collapsed.
0188: *
0189: * @return <code>true</code> if header comments should be collapsed
0190: */
0191: public boolean collapseHeaderComments() {
0192: return fAllowCollapsing && fCollapseHeaderComments;
0193: }
0194:
0195: /**
0196: * Returns <code>true</code> if import containers should be collapsed.
0197: *
0198: * @return <code>true</code> if import containers should be collapsed
0199: */
0200: public boolean collapseImportContainer() {
0201: return fAllowCollapsing && fCollapseImportContainer;
0202: }
0203:
0204: /**
0205: * Returns <code>true</code> if inner types should be collapsed.
0206: *
0207: * @return <code>true</code> if inner types should be collapsed
0208: */
0209: public boolean collapseInnerTypes() {
0210: return fAllowCollapsing && fCollapseInnerTypes;
0211: }
0212:
0213: /**
0214: * Returns <code>true</code> if javadoc comments should be collapsed.
0215: *
0216: * @return <code>true</code> if javadoc comments should be collapsed
0217: */
0218: public boolean collapseJavadoc() {
0219: return fAllowCollapsing && fCollapseJavadoc;
0220: }
0221:
0222: /**
0223: * Returns <code>true</code> if methods should be collapsed.
0224: *
0225: * @return <code>true</code> if methods should be collapsed
0226: */
0227: public boolean collapseMembers() {
0228: return fAllowCollapsing && fCollapseMembers;
0229: }
0230: }
0231:
0232: /**
0233: * A {@link ProjectionAnnotation} for java code.
0234: */
0235: protected static final class JavaProjectionAnnotation extends
0236: ProjectionAnnotation {
0237:
0238: private IJavaElement fJavaElement;
0239: private boolean fIsComment;
0240:
0241: /**
0242: * Creates a new projection annotation.
0243: *
0244: * @param isCollapsed <code>true</code> to set the initial state to collapsed,
0245: * <code>false</code> to set it to expanded
0246: * @param element the java element this annotation refers to
0247: * @param isComment <code>true</code> for a foldable comment, <code>false</code> for a
0248: * foldable code element
0249: */
0250: public JavaProjectionAnnotation(boolean isCollapsed,
0251: IJavaElement element, boolean isComment) {
0252: super (isCollapsed);
0253: fJavaElement = element;
0254: fIsComment = isComment;
0255: }
0256:
0257: IJavaElement getElement() {
0258: return fJavaElement;
0259: }
0260:
0261: void setElement(IJavaElement element) {
0262: fJavaElement = element;
0263: }
0264:
0265: boolean isComment() {
0266: return fIsComment;
0267: }
0268:
0269: void setIsComment(boolean isComment) {
0270: fIsComment = isComment;
0271: }
0272:
0273: /*
0274: * @see java.lang.Object#toString()
0275: */
0276: public String toString() {
0277: return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
0278: "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
0279: "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
0280: "\tcomment: \t" + isComment() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
0281: }
0282: }
0283:
0284: private static final class Tuple {
0285: JavaProjectionAnnotation annotation;
0286: Position position;
0287:
0288: Tuple(JavaProjectionAnnotation annotation, Position position) {
0289: this .annotation = annotation;
0290: this .position = position;
0291: }
0292: }
0293:
0294: /**
0295: * Filter for annotations.
0296: */
0297: private static interface Filter {
0298: boolean match(JavaProjectionAnnotation annotation);
0299: }
0300:
0301: /**
0302: * Matches comments.
0303: */
0304: private static final class CommentFilter implements Filter {
0305: public boolean match(JavaProjectionAnnotation annotation) {
0306: if (annotation.isComment() && !annotation.isMarkedDeleted()) {
0307: return true;
0308: }
0309: return false;
0310: }
0311: }
0312:
0313: /**
0314: * Matches members.
0315: */
0316: private static final class MemberFilter implements Filter {
0317: public boolean match(JavaProjectionAnnotation annotation) {
0318: if (!annotation.isComment()
0319: && !annotation.isMarkedDeleted()) {
0320: IJavaElement element = annotation.getElement();
0321: if (element instanceof IMember) {
0322: if (element.getElementType() != IJavaElement.TYPE
0323: || ((IMember) element).getDeclaringType() != null) {
0324: return true;
0325: }
0326: }
0327: }
0328: return false;
0329: }
0330: }
0331:
0332: /**
0333: * Matches java elements contained in a certain set.
0334: */
0335: private static final class JavaElementSetFilter implements Filter {
0336: private final Set/*<? extends IJavaElement>*/fSet;
0337: private final boolean fMatchCollapsed;
0338:
0339: private JavaElementSetFilter(
0340: Set/*<? extends IJavaElement>*/set,
0341: boolean matchCollapsed) {
0342: fSet = set;
0343: fMatchCollapsed = matchCollapsed;
0344: }
0345:
0346: public boolean match(JavaProjectionAnnotation annotation) {
0347: boolean stateMatch = fMatchCollapsed == annotation
0348: .isCollapsed();
0349: if (stateMatch && !annotation.isComment()
0350: && !annotation.isMarkedDeleted()) {
0351: IJavaElement element = annotation.getElement();
0352: if (fSet.contains(element)) {
0353: return true;
0354: }
0355: }
0356: return false;
0357: }
0358: }
0359:
0360: private class ElementChangedListener implements
0361: IElementChangedListener {
0362:
0363: /*
0364: * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
0365: */
0366: public void elementChanged(ElementChangedEvent e) {
0367: IJavaElementDelta delta = findElement(fInput, e.getDelta());
0368: if (delta != null
0369: && (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) {
0370:
0371: if (shouldIgnoreDelta(e.getDelta()
0372: .getCompilationUnitAST(), delta))
0373: return;
0374:
0375: fUpdatingCount++;
0376: try {
0377: update(createContext(false));
0378: } finally {
0379: fUpdatingCount--;
0380: }
0381: }
0382: }
0383:
0384: /**
0385: * Ignore the delta if there are errors on the caret line.
0386: * <p>
0387: * We don't ignore the delta if an import is added and the
0388: * caret isn't inside the import container.
0389: * </p>
0390: *
0391: * @param ast the compilation unit AST
0392: * @param delta the Java element delta for the given AST element
0393: * @return <code>true</code> if the delta should be ignored
0394: * @since 3.3
0395: */
0396: private boolean shouldIgnoreDelta(CompilationUnit ast,
0397: IJavaElementDelta delta) {
0398: if (ast == null)
0399: return false; // can't compute
0400:
0401: IDocument document = getDocument();
0402: if (document == null)
0403: return false; // can't compute
0404:
0405: JavaEditor editor = fEditor;
0406: if (editor == null
0407: || editor.getCachedSelectedRange() == null)
0408: return false; // can't compute
0409:
0410: try {
0411: if (delta.getAffectedChildren().length == 1
0412: && delta.getAffectedChildren()[0].getElement() instanceof IImportContainer) {
0413: IJavaElement elem = SelectionConverter
0414: .getElementAtOffset(
0415: ast.getJavaElement(),
0416: new TextSelection(
0417: editor
0418: .getCachedSelectedRange().x,
0419: editor
0420: .getCachedSelectedRange().y));
0421: if (!(elem instanceof IImportDeclaration))
0422: return false;
0423:
0424: }
0425: } catch (JavaModelException e) {
0426: return false; // can't compute
0427: }
0428:
0429: int caretLine = 0;
0430: try {
0431: caretLine = document.getLineOfOffset(editor
0432: .getCachedSelectedRange().x) + 1;
0433: } catch (BadLocationException x) {
0434: return false; // can't compute
0435: }
0436:
0437: if (caretLine > 0 && ast != null) {
0438: IProblem[] problems = ast.getProblems();
0439: for (int i = 0; i < problems.length; i++) {
0440: if (problems[i].isError()
0441: && caretLine == problems[i]
0442: .getSourceLineNumber())
0443: return true;
0444: }
0445: }
0446:
0447: return false;
0448: }
0449:
0450: private IJavaElementDelta findElement(IJavaElement target,
0451: IJavaElementDelta delta) {
0452:
0453: if (delta == null || target == null)
0454: return null;
0455:
0456: IJavaElement element = delta.getElement();
0457:
0458: if (element.getElementType() > IJavaElement.CLASS_FILE)
0459: return null;
0460:
0461: if (target.equals(element))
0462: return delta;
0463:
0464: IJavaElementDelta[] children = delta.getAffectedChildren();
0465:
0466: for (int i = 0; i < children.length; i++) {
0467: IJavaElementDelta d = findElement(target, children[i]);
0468: if (d != null)
0469: return d;
0470: }
0471:
0472: return null;
0473: }
0474: }
0475:
0476: /**
0477: * Projection position that will return two foldable regions: one folding away
0478: * the region from after the '/**' to the beginning of the content, the other
0479: * from after the first content line until after the comment.
0480: */
0481: private static final class CommentPosition extends Position
0482: implements IProjectionPosition {
0483: CommentPosition(int offset, int length) {
0484: super (offset, length);
0485: }
0486:
0487: /*
0488: * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
0489: */
0490: public IRegion[] computeProjectionRegions(IDocument document)
0491: throws BadLocationException {
0492: DocumentCharacterIterator sequence = new DocumentCharacterIterator(
0493: document, offset, offset + length);
0494: int prefixEnd = 0;
0495: int contentStart = findFirstContent(sequence, prefixEnd);
0496:
0497: int firstLine = document
0498: .getLineOfOffset(offset + prefixEnd);
0499: int captionLine = document.getLineOfOffset(offset
0500: + contentStart);
0501: int lastLine = document.getLineOfOffset(offset + length);
0502:
0503: Assert
0504: .isTrue(firstLine <= captionLine,
0505: "first folded line is greater than the caption line"); //$NON-NLS-1$
0506: Assert
0507: .isTrue(captionLine <= lastLine,
0508: "caption line is greater than the last folded line"); //$NON-NLS-1$
0509:
0510: IRegion preRegion;
0511: if (firstLine < captionLine) {
0512: // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
0513: int preOffset = document.getLineOffset(firstLine);
0514: IRegion preEndLineInfo = document
0515: .getLineInformation(captionLine);
0516: int preEnd = preEndLineInfo.getOffset();
0517: preRegion = new Region(preOffset, preEnd - preOffset);
0518: } else {
0519: preRegion = null;
0520: }
0521:
0522: if (captionLine < lastLine) {
0523: int postOffset = document
0524: .getLineOffset(captionLine + 1);
0525: IRegion postRegion = new Region(postOffset, offset
0526: + length - postOffset);
0527:
0528: if (preRegion == null)
0529: return new IRegion[] { postRegion };
0530:
0531: return new IRegion[] { preRegion, postRegion };
0532: }
0533:
0534: if (preRegion != null)
0535: return new IRegion[] { preRegion };
0536:
0537: return null;
0538: }
0539:
0540: /**
0541: * Finds the offset of the first identifier part within <code>content</code>.
0542: * Returns 0 if none is found.
0543: *
0544: * @param content the content to search
0545: * @param prefixEnd the end of the prefix
0546: * @return the first index of a unicode identifier part, or zero if none can
0547: * be found
0548: */
0549: private int findFirstContent(final CharSequence content,
0550: int prefixEnd) {
0551: int lenght = content.length();
0552: for (int i = prefixEnd; i < lenght; i++) {
0553: if (Character
0554: .isUnicodeIdentifierPart(content.charAt(i)))
0555: return i;
0556: }
0557: return 0;
0558: }
0559:
0560: // /**
0561: // * Finds the offset of the first identifier part within <code>content</code>.
0562: // * Returns 0 if none is found.
0563: // *
0564: // * @param content the content to search
0565: // * @return the first index of a unicode identifier part, or zero if none can
0566: // * be found
0567: // */
0568: // private int findPrefixEnd(final CharSequence content) {
0569: // // return the index after the leading '/*' or '/**'
0570: // int len= content.length();
0571: // int i= 0;
0572: // while (i < len && isWhiteSpace(content.charAt(i)))
0573: // i++;
0574: // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*')
0575: // if (len >= i + 3 && content.charAt(i + 2) == '*')
0576: // return i + 3;
0577: // else
0578: // return i + 2;
0579: // else
0580: // return i;
0581: // }
0582: //
0583: // private boolean isWhiteSpace(char c) {
0584: // return c == ' ' || c == '\t';
0585: // }
0586:
0587: /*
0588: * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
0589: */
0590: public int computeCaptionOffset(IDocument document) {
0591: // return 0;
0592: DocumentCharacterIterator sequence = new DocumentCharacterIterator(
0593: document, offset, offset + length);
0594: return findFirstContent(sequence, 0);
0595: }
0596: }
0597:
0598: /**
0599: * Projection position that will return two foldable regions: one folding away
0600: * the lines before the one containing the simple name of the java element, one
0601: * folding away any lines after the caption.
0602: */
0603: private static final class JavaElementPosition extends Position
0604: implements IProjectionPosition {
0605:
0606: private IMember fMember;
0607:
0608: public JavaElementPosition(int offset, int length,
0609: IMember member) {
0610: super (offset, length);
0611: Assert.isNotNull(member);
0612: fMember = member;
0613: }
0614:
0615: public void setMember(IMember member) {
0616: Assert.isNotNull(member);
0617: fMember = member;
0618: }
0619:
0620: /*
0621: * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
0622: */
0623: public IRegion[] computeProjectionRegions(IDocument document)
0624: throws BadLocationException {
0625: int nameStart = offset;
0626: try {
0627: /* The member's name range may not be correct. However,
0628: * reconciling would trigger another element delta which would
0629: * lead to reentrant situations. Therefore, we optimistically
0630: * assume that the name range is correct, but double check the
0631: * received lines below. */
0632: ISourceRange nameRange = fMember.getNameRange();
0633: if (nameRange != null)
0634: nameStart = nameRange.getOffset();
0635:
0636: } catch (JavaModelException e) {
0637: // ignore and use default
0638: }
0639:
0640: int firstLine = document.getLineOfOffset(offset);
0641: int captionLine = document.getLineOfOffset(nameStart);
0642: int lastLine = document.getLineOfOffset(offset + length);
0643:
0644: /* see comment above - adjust the caption line to be inside the
0645: * entire folded region, and rely on later element deltas to correct
0646: * the name range. */
0647: if (captionLine < firstLine)
0648: captionLine = firstLine;
0649: if (captionLine > lastLine)
0650: captionLine = lastLine;
0651:
0652: IRegion preRegion;
0653: if (firstLine < captionLine) {
0654: int preOffset = document.getLineOffset(firstLine);
0655: IRegion preEndLineInfo = document
0656: .getLineInformation(captionLine);
0657: int preEnd = preEndLineInfo.getOffset();
0658: preRegion = new Region(preOffset, preEnd - preOffset);
0659: } else {
0660: preRegion = null;
0661: }
0662:
0663: if (captionLine < lastLine) {
0664: int postOffset = document
0665: .getLineOffset(captionLine + 1);
0666: IRegion postRegion = new Region(postOffset, offset
0667: + length - postOffset);
0668:
0669: if (preRegion == null)
0670: return new IRegion[] { postRegion };
0671:
0672: return new IRegion[] { preRegion, postRegion };
0673: }
0674:
0675: if (preRegion != null)
0676: return new IRegion[] { preRegion };
0677:
0678: return null;
0679: }
0680:
0681: /*
0682: * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
0683: */
0684: public int computeCaptionOffset(IDocument document)
0685: throws BadLocationException {
0686: int nameStart = offset;
0687: try {
0688: // need a reconcile here?
0689: ISourceRange nameRange = fMember.getNameRange();
0690: if (nameRange != null)
0691: nameStart = nameRange.getOffset();
0692: } catch (JavaModelException e) {
0693: // ignore and use default
0694: }
0695:
0696: return nameStart - offset;
0697: }
0698:
0699: }
0700:
0701: /**
0702: * Internal projection listener.
0703: */
0704: private final class ProjectionListener implements
0705: IProjectionListener {
0706: private ProjectionViewer fViewer;
0707:
0708: /**
0709: * Registers the listener with the viewer.
0710: *
0711: * @param viewer the viewer to register a listener with
0712: */
0713: public ProjectionListener(ProjectionViewer viewer) {
0714: Assert.isLegal(viewer != null);
0715: fViewer = viewer;
0716: fViewer.addProjectionListener(this );
0717: }
0718:
0719: /**
0720: * Disposes of this listener and removes the projection listener from the viewer.
0721: */
0722: public void dispose() {
0723: if (fViewer != null) {
0724: fViewer.removeProjectionListener(this );
0725: fViewer = null;
0726: }
0727: }
0728:
0729: /*
0730: * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
0731: */
0732: public void projectionEnabled() {
0733: handleProjectionEnabled();
0734: }
0735:
0736: /*
0737: * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
0738: */
0739: public void projectionDisabled() {
0740: handleProjectionDisabled();
0741: }
0742: }
0743:
0744: /* context and listeners */
0745: private JavaEditor fEditor;
0746: private ProjectionListener fProjectionListener;
0747: private IJavaElement fInput;
0748: private IElementChangedListener fElementListener;
0749:
0750: /* preferences */
0751: private boolean fCollapseJavadoc = false;
0752: private boolean fCollapseImportContainer = true;
0753: private boolean fCollapseInnerTypes = true;
0754: private boolean fCollapseMembers = false;
0755: private boolean fCollapseHeaderComments = true;
0756:
0757: /* filters */
0758: /** Member filter, matches nested members (but not top-level types). */
0759: private final Filter fMemberFilter = new MemberFilter();
0760: /** Comment filter, matches comments. */
0761: private final Filter fCommentFilter = new CommentFilter();
0762:
0763: /**
0764: * Reusable scanner.
0765: * @since 3.3
0766: */
0767: private IScanner fSharedScanner = ToolFactory.createScanner(true,
0768: false, false, false);
0769:
0770: private volatile int fUpdatingCount = 0;
0771:
0772: /**
0773: * Creates a new folding provider. It must be
0774: * {@link #install(ITextEditor, ProjectionViewer) installed} on an editor/viewer pair before it
0775: * can be used, and {@link #uninstall() uninstalled} when not used any longer.
0776: * <p>
0777: * The projection state may be reset by calling {@link #initialize()}.
0778: * </p>
0779: */
0780: public DefaultJavaFoldingStructureProvider() {
0781: }
0782:
0783: /**
0784: * {@inheritDoc}
0785: * <p>
0786: * Subclasses may extend.
0787: * </p>
0788: *
0789: * @param editor {@inheritDoc}
0790: * @param viewer {@inheritDoc}
0791: */
0792: public void install(ITextEditor editor, ProjectionViewer viewer) {
0793: Assert.isLegal(editor != null);
0794: Assert.isLegal(viewer != null);
0795:
0796: internalUninstall();
0797:
0798: if (editor instanceof JavaEditor) {
0799: fProjectionListener = new ProjectionListener(viewer);
0800: fEditor = (JavaEditor) editor;
0801: }
0802: }
0803:
0804: /**
0805: * {@inheritDoc}
0806: * <p>
0807: * Subclasses may extend.
0808: * </p>
0809: */
0810: public void uninstall() {
0811: internalUninstall();
0812: }
0813:
0814: /**
0815: * Internal implementation of {@link #uninstall()}.
0816: */
0817: private void internalUninstall() {
0818: if (isInstalled()) {
0819: handleProjectionDisabled();
0820: fProjectionListener.dispose();
0821: fProjectionListener = null;
0822: fEditor = null;
0823: }
0824: }
0825:
0826: /**
0827: * Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
0828: *
0829: * @return <code>true</code> if the provider is installed, <code>false</code> otherwise
0830: */
0831: protected final boolean isInstalled() {
0832: return fEditor != null;
0833: }
0834:
0835: /**
0836: * Called whenever projection is enabled, for example when the viewer issues a
0837: * {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider
0838: * is already enabled when this method is called, it is first
0839: * {@link #handleProjectionDisabled() disabled}.
0840: * <p>
0841: * Subclasses may extend.
0842: * </p>
0843: */
0844: protected void handleProjectionEnabled() {
0845: // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
0846: // projectionEnabled messages are not always paired with projectionDisabled
0847: // i.e. multiple enabled messages may be sent out.
0848: // we have to make sure that we disable first when getting an enable
0849: // message.
0850: handleProjectionDisabled();
0851:
0852: if (isInstalled()) {
0853: initialize();
0854: fElementListener = new ElementChangedListener();
0855: JavaCore.addElementChangedListener(fElementListener);
0856: }
0857: }
0858:
0859: /**
0860: * Called whenever projection is disabled, for example when the provider is
0861: * {@link #uninstall() uninstalled}, when the viewer issues a
0862: * {@link IProjectionListener#projectionDisabled() projectionDisabled} message and before
0863: * {@link #handleProjectionEnabled() enabling} the provider. Implementations must be prepared to
0864: * handle multiple calls to this method even if the provider is already disabled.
0865: * <p>
0866: * Subclasses may extend.
0867: * </p>
0868: */
0869: protected void handleProjectionDisabled() {
0870: if (fElementListener != null) {
0871: JavaCore.removeElementChangedListener(fElementListener);
0872: fElementListener = null;
0873: }
0874: }
0875:
0876: /*
0877: * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider#initialize()
0878: */
0879: public final void initialize() {
0880: fUpdatingCount++;
0881: try {
0882: update(createInitialContext());
0883: } finally {
0884: fUpdatingCount--;
0885: }
0886: }
0887:
0888: private FoldingStructureComputationContext createInitialContext() {
0889: initializePreferences();
0890: fInput = getInputElement();
0891: if (fInput == null)
0892: return null;
0893:
0894: return createContext(true);
0895: }
0896:
0897: private FoldingStructureComputationContext createContext(
0898: boolean allowCollapse) {
0899: if (!isInstalled())
0900: return null;
0901: ProjectionAnnotationModel model = getModel();
0902: if (model == null)
0903: return null;
0904: IDocument doc = getDocument();
0905: if (doc == null)
0906: return null;
0907:
0908: IScanner scanner = null;
0909: if (fUpdatingCount == 1)
0910: scanner = fSharedScanner; // reuse scanner
0911:
0912: return new FoldingStructureComputationContext(doc, model,
0913: allowCollapse, scanner);
0914: }
0915:
0916: private IJavaElement getInputElement() {
0917: if (fEditor == null)
0918: return null;
0919: return EditorUtility.getEditorInputJavaElement(fEditor, false);
0920: }
0921:
0922: private void initializePreferences() {
0923: IPreferenceStore store = JavaPlugin.getDefault()
0924: .getPreferenceStore();
0925: fCollapseInnerTypes = store
0926: .getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
0927: fCollapseImportContainer = store
0928: .getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
0929: fCollapseJavadoc = store
0930: .getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
0931: fCollapseMembers = store
0932: .getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
0933: fCollapseHeaderComments = store
0934: .getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
0935: }
0936:
0937: private void update(FoldingStructureComputationContext ctx) {
0938: if (ctx == null)
0939: return;
0940:
0941: Map additions = new HashMap();
0942: List deletions = new ArrayList();
0943: List updates = new ArrayList();
0944:
0945: computeFoldingStructure(ctx);
0946: Map newStructure = ctx.fMap;
0947: Map oldStructure = computeCurrentStructure(ctx);
0948:
0949: Iterator e = newStructure.keySet().iterator();
0950: while (e.hasNext()) {
0951: JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e
0952: .next();
0953: Position newPosition = (Position) newStructure
0954: .get(newAnnotation);
0955:
0956: IJavaElement element = newAnnotation.getElement();
0957: /*
0958: * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=130472 and
0959: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=127445 In the presence of syntax
0960: * errors, anonymous types may have a source range offset of 0. When such a situation is
0961: * encountered, we ignore the proposed folding range: if no corresponding folding range
0962: * exists, it is silently ignored; if there *is* a matching folding range, we ignore the
0963: * position update and keep the old range, in order to keep the folding structure
0964: * stable.
0965: */
0966: boolean isMalformedAnonymousType = newPosition.getOffset() == 0
0967: && element.getElementType() == IJavaElement.TYPE
0968: && isInnerType((IType) element);
0969: List annotations = (List) oldStructure.get(element);
0970: if (annotations == null) {
0971: if (!isMalformedAnonymousType)
0972: additions.put(newAnnotation, newPosition);
0973: } else {
0974: Iterator x = annotations.iterator();
0975: boolean matched = false;
0976: while (x.hasNext()) {
0977: Tuple tuple = (Tuple) x.next();
0978: JavaProjectionAnnotation existingAnnotation = tuple.annotation;
0979: Position existingPosition = tuple.position;
0980: if (newAnnotation.isComment() == existingAnnotation
0981: .isComment()) {
0982: boolean updateCollapsedState = ctx
0983: .allowCollapsing()
0984: && existingAnnotation.isCollapsed() != newAnnotation
0985: .isCollapsed();
0986: if (!isMalformedAnonymousType
0987: && existingPosition != null
0988: && (!newPosition
0989: .equals(existingPosition) || updateCollapsedState)) {
0990: existingPosition.setOffset(newPosition
0991: .getOffset());
0992: existingPosition.setLength(newPosition
0993: .getLength());
0994: if (updateCollapsedState)
0995: if (newAnnotation.isCollapsed())
0996: existingAnnotation.markCollapsed();
0997: else
0998: existingAnnotation.markExpanded();
0999: updates.add(existingAnnotation);
1000: }
1001: matched = true;
1002: x.remove();
1003: break;
1004: }
1005: }
1006: if (!matched)
1007: additions.put(newAnnotation, newPosition);
1008:
1009: if (annotations.isEmpty())
1010: oldStructure.remove(element);
1011: }
1012: }
1013:
1014: e = oldStructure.values().iterator();
1015: while (e.hasNext()) {
1016: List list = (List) e.next();
1017: int size = list.size();
1018: for (int i = 0; i < size; i++)
1019: deletions.add(((Tuple) list.get(i)).annotation);
1020: }
1021:
1022: match(deletions, additions, updates, ctx);
1023:
1024: Annotation[] deletedArray = (Annotation[]) deletions
1025: .toArray(new Annotation[deletions.size()]);
1026: Annotation[] changedArray = (Annotation[]) updates
1027: .toArray(new Annotation[updates.size()]);
1028: ctx.getModel().modifyAnnotations(deletedArray, additions,
1029: changedArray);
1030:
1031: ctx.fScanner.setSource(null);
1032: }
1033:
1034: private void computeFoldingStructure(
1035: FoldingStructureComputationContext ctx) {
1036: IParent parent = (IParent) fInput;
1037: try {
1038: if (!(fInput instanceof ISourceReference))
1039: return;
1040: String source = ((ISourceReference) fInput).getSource();
1041: if (source == null)
1042: return;
1043:
1044: ctx.getScanner().setSource(source.toCharArray());
1045: computeFoldingStructure(parent.getChildren(), ctx);
1046: } catch (JavaModelException x) {
1047: }
1048: }
1049:
1050: private void computeFoldingStructure(IJavaElement[] elements,
1051: FoldingStructureComputationContext ctx)
1052: throws JavaModelException {
1053: for (int i = 0; i < elements.length; i++) {
1054: IJavaElement element = elements[i];
1055:
1056: computeFoldingStructure(element, ctx);
1057:
1058: if (element instanceof IParent) {
1059: IParent parent = (IParent) element;
1060: computeFoldingStructure(parent.getChildren(), ctx);
1061: }
1062: }
1063: }
1064:
1065: /**
1066: * Computes the folding structure for a given {@link IJavaElement java element}. Computed
1067: * projection annotations are
1068: * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) added}
1069: * to the computation context.
1070: * <p>
1071: * Subclasses may extend or replace. The default implementation creates projection annotations
1072: * for the following elements:
1073: * <ul>
1074: * <li>true members (not for top-level types)</li>
1075: * <li>the javadoc comments of any member</li>
1076: * <li>header comments (javadoc or multi-line comments appearing before the first type's
1077: * javadoc or before the package or import declarations).</li>
1078: * </ul>
1079: * </p>
1080: *
1081: * @param element the java element to compute the folding structure for
1082: * @param ctx the computation context
1083: */
1084: protected void computeFoldingStructure(IJavaElement element,
1085: FoldingStructureComputationContext ctx) {
1086:
1087: boolean collapse = false;
1088: boolean collapseCode = true;
1089: switch (element.getElementType()) {
1090:
1091: case IJavaElement.IMPORT_CONTAINER:
1092: collapse = ctx.collapseImportContainer();
1093: break;
1094: case IJavaElement.TYPE:
1095: collapseCode = isInnerType((IType) element)
1096: && !isAnonymousEnum((IType) element);
1097: collapse = ctx.collapseInnerTypes() && collapseCode;
1098: break;
1099: case IJavaElement.METHOD:
1100: case IJavaElement.FIELD:
1101: case IJavaElement.INITIALIZER:
1102: collapse = ctx.collapseMembers();
1103: break;
1104: default:
1105: return;
1106: }
1107:
1108: IRegion[] regions = computeProjectionRanges(
1109: (ISourceReference) element, ctx);
1110: if (regions.length > 0) {
1111: // comments
1112: for (int i = 0; i < regions.length - 1; i++) {
1113: IRegion normalized = alignRegion(regions[i], ctx);
1114: if (normalized != null) {
1115: Position position = createCommentPosition(normalized);
1116: if (position != null) {
1117: boolean commentCollapse;
1118: if (i == 0
1119: && (regions.length > 2 || ctx
1120: .hasHeaderComment())
1121: && element == ctx.getFirstType()) {
1122: commentCollapse = ctx
1123: .collapseHeaderComments();
1124: } else {
1125: commentCollapse = ctx.collapseJavadoc();
1126: }
1127: ctx
1128: .addProjectionRange(
1129: new JavaProjectionAnnotation(
1130: commentCollapse,
1131: element, true),
1132: position);
1133: }
1134: }
1135: }
1136: // code
1137: if (collapseCode) {
1138: IRegion normalized = alignRegion(
1139: regions[regions.length - 1], ctx);
1140: if (normalized != null) {
1141: Position position = element instanceof IMember ? createMemberPosition(
1142: normalized, (IMember) element)
1143: : createCommentPosition(normalized);
1144: if (position != null)
1145: ctx.addProjectionRange(
1146: new JavaProjectionAnnotation(collapse,
1147: element, false), position);
1148: }
1149: }
1150: }
1151: }
1152:
1153: /**
1154: * Returns <code>true</code> if <code>type</code> is an anonymous enum declaration,
1155: * <code>false</code> otherwise. See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=143276
1156: *
1157: * @param type the type to test
1158: * @return <code>true</code> if <code>type</code> is an anonymous enum declaration
1159: * @since 3.3
1160: */
1161: private boolean isAnonymousEnum(IType type) {
1162: try {
1163: return type.isEnum() && type.isAnonymous();
1164: } catch (JavaModelException x) {
1165: return false; // optimistically
1166: }
1167: }
1168:
1169: /**
1170: * Returns <code>true</code> if <code>type</code> is not a top-level type, <code>false</code> if it is.
1171: *
1172: * @param type the type to test
1173: * @return <code>true</code> if <code>type</code> is an inner type
1174: */
1175: private boolean isInnerType(IType type) {
1176: return type.getDeclaringType() != null;
1177: }
1178:
1179: /**
1180: * Computes the projection ranges for a given <code>ISourceReference</code>. More than one
1181: * range or none at all may be returned. If there are no foldable regions, an empty array is
1182: * returned.
1183: * <p>
1184: * The last region in the returned array (if not empty) describes the region for the java
1185: * element that implements the source reference. Any preceding regions describe javadoc comments
1186: * of that java element.
1187: * </p>
1188: *
1189: * @param reference a java element that is a source reference
1190: * @param ctx the folding context
1191: * @return the regions to be folded
1192: */
1193: protected final IRegion[] computeProjectionRanges(
1194: ISourceReference reference,
1195: FoldingStructureComputationContext ctx) {
1196: try {
1197: ISourceRange range = reference.getSourceRange();
1198: if (!SourceRange.isAvailable(range))
1199: return new IRegion[0];
1200:
1201: String contents = reference.getSource();
1202: if (contents == null)
1203: return new IRegion[0];
1204:
1205: List regions = new ArrayList();
1206: if (!ctx.hasFirstType() && reference instanceof IType) {
1207: ctx.setFirstType((IType) reference);
1208: IRegion headerComment = computeHeaderComment(ctx);
1209: if (headerComment != null) {
1210: regions.add(headerComment);
1211: ctx.setHasHeaderComment();
1212: }
1213: }
1214:
1215: final int shift = range.getOffset();
1216: IScanner scanner = ctx.getScanner();
1217: scanner.resetTo(shift, shift + range.getLength());
1218:
1219: int start = shift;
1220: while (true) {
1221:
1222: int token = scanner.getNextToken();
1223: start = scanner.getCurrentTokenStartPosition();
1224:
1225: switch (token) {
1226: case ITerminalSymbols.TokenNameCOMMENT_JAVADOC:
1227: case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
1228: int end = scanner.getCurrentTokenEndPosition() + 1;
1229: regions.add(new Region(start, end - start));
1230: continue;
1231: }
1232: case ITerminalSymbols.TokenNameCOMMENT_LINE:
1233: continue;
1234: }
1235:
1236: break;
1237: }
1238:
1239: regions.add(new Region(start, shift + range.getLength()
1240: - start));
1241:
1242: IRegion[] result = new IRegion[regions.size()];
1243: regions.toArray(result);
1244: return result;
1245: } catch (JavaModelException e) {
1246: } catch (InvalidInputException e) {
1247: }
1248:
1249: return new IRegion[0];
1250: }
1251:
1252: private IRegion computeHeaderComment(
1253: FoldingStructureComputationContext ctx)
1254: throws JavaModelException {
1255: // search at most up to the first type
1256: ISourceRange range = ctx.getFirstType().getSourceRange();
1257: if (range == null)
1258: return null;
1259: int start = 0;
1260: int end = range.getOffset();
1261:
1262: /* code adapted from CommentFormattingStrategy:
1263: * scan the header content up to the first type. Once a comment is
1264: * found, accumulate any additional comments up to the stop condition.
1265: * The stop condition is reaching a package declaration, import container,
1266: * or the end of the input.
1267: */
1268: IScanner scanner = ctx.getScanner();
1269: scanner.resetTo(start, end);
1270:
1271: int headerStart = -1;
1272: int headerEnd = -1;
1273: try {
1274: boolean foundComment = false;
1275: int terminal = scanner.getNextToken();
1276: while (terminal != ITerminalSymbols.TokenNameEOF
1277: && !(terminal == ITerminalSymbols.TokenNameclass
1278: || terminal == ITerminalSymbols.TokenNameinterface
1279: || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) {
1280:
1281: if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC
1282: || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK
1283: || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
1284: if (!foundComment)
1285: headerStart = scanner
1286: .getCurrentTokenStartPosition();
1287: headerEnd = scanner.getCurrentTokenEndPosition();
1288: foundComment = true;
1289: }
1290: terminal = scanner.getNextToken();
1291: }
1292:
1293: } catch (InvalidInputException ex) {
1294: return null;
1295: }
1296:
1297: if (headerEnd != -1) {
1298: return new Region(headerStart, headerEnd - headerStart);
1299: }
1300: return null;
1301: }
1302:
1303: /**
1304: * Creates a comment folding position from an
1305: * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned}
1306: * region.
1307: *
1308: * @param aligned an aligned region
1309: * @return a folding position corresponding to <code>aligned</code>
1310: */
1311: protected final Position createCommentPosition(IRegion aligned) {
1312: return new CommentPosition(aligned.getOffset(), aligned
1313: .getLength());
1314: }
1315:
1316: /**
1317: * Creates a folding position that remembers its member from an
1318: * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned}
1319: * region.
1320: *
1321: * @param aligned an aligned region
1322: * @param member the member to remember
1323: * @return a folding position corresponding to <code>aligned</code>
1324: */
1325: protected final Position createMemberPosition(IRegion aligned,
1326: IMember member) {
1327: return new JavaElementPosition(aligned.getOffset(), aligned
1328: .getLength(), member);
1329: }
1330:
1331: /**
1332: * Aligns <code>region</code> to start and end at a line offset. The region's start is
1333: * decreased to the next line offset, and the end offset increased to the next line start or the
1334: * end of the document. <code>null</code> is returned if <code>region</code> is
1335: * <code>null</code> itself or does not comprise at least one line delimiter, as a single line
1336: * cannot be folded.
1337: *
1338: * @param region the region to align, may be <code>null</code>
1339: * @param ctx the folding context
1340: * @return a region equal or greater than <code>region</code> that is aligned with line
1341: * offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
1342: * only one line)
1343: */
1344: protected final IRegion alignRegion(IRegion region,
1345: FoldingStructureComputationContext ctx) {
1346: if (region == null)
1347: return null;
1348:
1349: IDocument document = ctx.getDocument();
1350:
1351: try {
1352:
1353: int start = document.getLineOfOffset(region.getOffset());
1354: int end = document.getLineOfOffset(region.getOffset()
1355: + region.getLength());
1356: if (start >= end)
1357: return null;
1358:
1359: int offset = document.getLineOffset(start);
1360: int endOffset;
1361: if (document.getNumberOfLines() > end + 1)
1362: endOffset = document.getLineOffset(end + 1);
1363: else
1364: endOffset = document.getLineOffset(end)
1365: + document.getLineLength(end);
1366:
1367: return new Region(offset, endOffset - offset);
1368:
1369: } catch (BadLocationException x) {
1370: // concurrent modification
1371: return null;
1372: }
1373: }
1374:
1375: private ProjectionAnnotationModel getModel() {
1376: return (ProjectionAnnotationModel) fEditor
1377: .getAdapter(ProjectionAnnotationModel.class);
1378: }
1379:
1380: private IDocument getDocument() {
1381: JavaEditor editor = fEditor;
1382: if (editor == null)
1383: return null;
1384:
1385: IDocumentProvider provider = editor.getDocumentProvider();
1386: if (provider == null)
1387: return null;
1388:
1389: return provider.getDocument(editor.getEditorInput());
1390: }
1391:
1392: /**
1393: * Matches deleted annotations to changed or added ones. A deleted
1394: * annotation/position tuple that has a matching addition / change
1395: * is updated and marked as changed. The matching tuple is not added
1396: * (for additions) or marked as deletion instead (for changes). The
1397: * result is that more annotations are changed and fewer get
1398: * deleted/re-added.
1399: *
1400: * @param deletions list with deleted annotations
1401: * @param additions map with position to annotation mappings
1402: * @param changes list with changed annotations
1403: * @param ctx the context
1404: */
1405: private void match(List deletions, Map additions, List changes,
1406: FoldingStructureComputationContext ctx) {
1407: if (deletions.isEmpty()
1408: || (additions.isEmpty() && changes.isEmpty()))
1409: return;
1410:
1411: List newDeletions = new ArrayList();
1412: List newChanges = new ArrayList();
1413:
1414: Iterator deletionIterator = deletions.iterator();
1415: while (deletionIterator.hasNext()) {
1416: JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator
1417: .next();
1418: Position deletedPosition = ctx.getModel().getPosition(
1419: deleted);
1420: if (deletedPosition == null)
1421: continue;
1422:
1423: Tuple deletedTuple = new Tuple(deleted, deletedPosition);
1424:
1425: Tuple match = findMatch(deletedTuple, changes, null, ctx);
1426: boolean addToDeletions = true;
1427: if (match == null) {
1428: match = findMatch(deletedTuple, additions.keySet(),
1429: additions, ctx);
1430: addToDeletions = false;
1431: }
1432:
1433: if (match != null) {
1434: IJavaElement element = match.annotation.getElement();
1435: deleted.setElement(element);
1436: deletedPosition.setLength(match.position.getLength());
1437: if (deletedPosition instanceof JavaElementPosition
1438: && element instanceof IMember) {
1439: JavaElementPosition jep = (JavaElementPosition) deletedPosition;
1440: jep.setMember((IMember) element);
1441: }
1442:
1443: deletionIterator.remove();
1444: newChanges.add(deleted);
1445:
1446: if (addToDeletions)
1447: newDeletions.add(match.annotation);
1448: }
1449: }
1450:
1451: deletions.addAll(newDeletions);
1452: changes.addAll(newChanges);
1453: }
1454:
1455: /**
1456: * Finds a match for <code>tuple</code> in a collection of
1457: * annotations. The positions for the
1458: * <code>JavaProjectionAnnotation</code> instances in
1459: * <code>annotations</code> can be found in the passed
1460: * <code>positionMap</code> or <code>fCachedModel</code> if
1461: * <code>positionMap</code> is <code>null</code>.
1462: * <p>
1463: * A tuple is said to match another if their annotations have the
1464: * same comment flag and their position offsets are equal.
1465: * </p>
1466: * <p>
1467: * If a match is found, the annotation gets removed from
1468: * <code>annotations</code>.
1469: * </p>
1470: *
1471: * @param tuple the tuple for which we want to find a match
1472: * @param annotations collection of
1473: * <code>JavaProjectionAnnotation</code>
1474: * @param positionMap a <code>Map<Annotation, Position></code>
1475: * or <code>null</code>
1476: * @param ctx the context
1477: * @return a matching tuple or <code>null</code> for no match
1478: */
1479: private Tuple findMatch(Tuple tuple, Collection annotations,
1480: Map positionMap, FoldingStructureComputationContext ctx) {
1481: Iterator it = annotations.iterator();
1482: while (it.hasNext()) {
1483: JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it
1484: .next();
1485: if (tuple.annotation.isComment() == annotation.isComment()) {
1486: Position position = positionMap == null ? ctx
1487: .getModel().getPosition(annotation)
1488: : (Position) positionMap.get(annotation);
1489: if (position == null)
1490: continue;
1491:
1492: if (tuple.position.getOffset() == position.getOffset()) {
1493: it.remove();
1494: return new Tuple(annotation, position);
1495: }
1496: }
1497: }
1498:
1499: return null;
1500: }
1501:
1502: private Map computeCurrentStructure(
1503: FoldingStructureComputationContext ctx) {
1504: Map map = new HashMap();
1505: ProjectionAnnotationModel model = ctx.getModel();
1506: Iterator e = model.getAnnotationIterator();
1507: while (e.hasNext()) {
1508: Object annotation = e.next();
1509: if (annotation instanceof JavaProjectionAnnotation) {
1510: JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
1511: Position position = model.getPosition(java);
1512: Assert.isNotNull(position);
1513: List list = (List) map.get(java.getElement());
1514: if (list == null) {
1515: list = new ArrayList(2);
1516: map.put(java.getElement(), list);
1517: }
1518: list.add(new Tuple(java, position));
1519: }
1520: }
1521:
1522: Comparator comparator = new Comparator() {
1523: public int compare(Object o1, Object o2) {
1524: return ((Tuple) o1).position.getOffset()
1525: - ((Tuple) o2).position.getOffset();
1526: }
1527: };
1528: for (Iterator it = map.values().iterator(); it.hasNext();) {
1529: List list = (List) it.next();
1530: Collections.sort(list, comparator);
1531: }
1532: return map;
1533: }
1534:
1535: /*
1536: * @see IJavaFoldingStructureProviderExtension#collapseMembers()
1537: * @since 3.2
1538: */
1539: public final void collapseMembers() {
1540: modifyFiltered(fMemberFilter, false);
1541: }
1542:
1543: /*
1544: * @see IJavaFoldingStructureProviderExtension#collapseComments()
1545: * @since 3.2
1546: */
1547: public final void collapseComments() {
1548: modifyFiltered(fCommentFilter, false);
1549: }
1550:
1551: /*
1552: * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#collapseElements(org.eclipse.jdt.core.IJavaElement[])
1553: */
1554: public final void collapseElements(IJavaElement[] elements) {
1555: Set set = new HashSet(Arrays.asList(elements));
1556: modifyFiltered(new JavaElementSetFilter(set, false), false);
1557: }
1558:
1559: /*
1560: * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#expandElements(org.eclipse.jdt.core.IJavaElement[])
1561: */
1562: public final void expandElements(IJavaElement[] elements) {
1563: Set set = new HashSet(Arrays.asList(elements));
1564: modifyFiltered(new JavaElementSetFilter(set, true), true);
1565: }
1566:
1567: /**
1568: * Collapses or expands all annotations matched by the passed filter.
1569: *
1570: * @param filter the filter to use to select which annotations to collapse
1571: * @param expand <code>true</code> to expand the matched annotations, <code>false</code> to
1572: * collapse them
1573: */
1574: private void modifyFiltered(Filter filter, boolean expand) {
1575: if (!isInstalled())
1576: return;
1577:
1578: ProjectionAnnotationModel model = getModel();
1579: if (model == null)
1580: return;
1581:
1582: List modified = new ArrayList();
1583: Iterator iter = model.getAnnotationIterator();
1584: while (iter.hasNext()) {
1585: Object annotation = iter.next();
1586: if (annotation instanceof JavaProjectionAnnotation) {
1587: JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
1588:
1589: if (expand == java.isCollapsed() && filter.match(java)) {
1590: if (expand)
1591: java.markExpanded();
1592: else
1593: java.markCollapsed();
1594: modified.add(java);
1595: }
1596:
1597: }
1598: }
1599:
1600: model.modifyAnnotations(null, null, (Annotation[]) modified
1601: .toArray(new Annotation[modified.size()]));
1602: }
1603: }
|