| /******************************************************************************* |
| * Copyright (c) 2004 - 2005 University Of British Columbia and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * University Of British Columbia - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.java.ui.editor; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jdt.core.ElementChangedEvent; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IElementChangedListener; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaElementDelta; |
| import org.eclipse.jdt.core.IMember; |
| import org.eclipse.jdt.core.IParent; |
| import org.eclipse.jdt.core.ISourceRange; |
| import org.eclipse.jdt.core.ISourceReference; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.core.compiler.IScanner; |
| import org.eclipse.jdt.core.compiler.ITerminalSymbols; |
| import org.eclipse.jdt.core.compiler.InvalidInputException; |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor; |
| import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor; |
| import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput; |
| import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; |
| import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator; |
| import org.eclipse.jdt.ui.IWorkingCopyManager; |
| import org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider; |
| import org.eclipse.jface.text.Assert; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.IAnnotationModel; |
| import org.eclipse.jface.text.source.projection.IProjectionListener; |
| import org.eclipse.jface.text.source.projection.IProjectionPosition; |
| import org.eclipse.jface.text.source.projection.ProjectionAnnotation; |
| import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; |
| import org.eclipse.jface.text.source.projection.ProjectionViewer; |
| import org.eclipse.mylyn.core.IMylarElement; |
| import org.eclipse.mylyn.core.MylarPlugin; |
| import org.eclipse.mylyn.core.util.MylarStatusHandler; |
| import org.eclipse.mylyn.ui.MylarUiPlugin; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| |
| /** |
| * Copied from: DefaultJavaFoldingStructureProvider |
| * |
| * @author Mik Kersten |
| */ |
| @SuppressWarnings("unchecked") |
| public class AutoFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider { |
| |
| public static final String ID = "org.eclipse.mylyn.java.ui.editor.foldingprovider"; |
| |
| private void computeAdditions(IJavaElement element, Map map) { |
| boolean createProjection= false; |
| boolean collapse= false; |
| switch (element.getElementType()) { |
| case IJavaElement.IMPORT_CONTAINER: |
| collapse = true; |
| // collapse= fAllowCollapsing && fCollapseImportContainer; |
| createProjection= true; |
| break; |
| case IJavaElement.TYPE: |
| if (isInnerType((IType)element)) { |
| IMylarElement node = ContextCorePlugin.getContextManager().getElement(element.getHandleIdentifier()); |
| if (!MylarUiPlugin.getDefault().isGlobalFoldingEnabled()) { |
| collapse = false; |
| } else if (node == null || node.getInterest().isInteresting()) { |
| collapse = false; |
| } else { |
| collapse = true; |
| } |
| createProjection= true; |
| break; |
| } |
| // collapse= fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element); |
| createProjection= true; |
| break; |
| case IJavaElement.METHOD: |
| IMylarElement node = ContextCorePlugin.getContextManager().getElement(element.getHandleIdentifier()); |
| if (!MylarUiPlugin.getDefault().isGlobalFoldingEnabled()) { |
| collapse = false; |
| } else if (node == null || node.getInterest().isInteresting()) { |
| collapse = false; |
| } else { |
| collapse = true; |
| } |
| createProjection= true; |
| break; |
| default: |
| collapse = true; |
| // collapse= fAllowCollapsing && fCollapseMethods; |
| // createProjection= true; |
| // break; |
| } |
| |
| if (createProjection) { |
| IRegion[] regions= computeProjectionRanges(element); |
| if (regions != null) { |
| // comments |
| for (int i= 0; i < regions.length - 1; i++) { |
| Position position= createProjectionPosition(regions[i], null); |
| boolean commentCollapse; |
| if (position != null) { |
| if (i == 0 && (regions.length > 2 || fHasHeaderComment) && element == fFirstType) { |
| commentCollapse= fAllowCollapsing && fCollapseHeaderComments; |
| } else { |
| commentCollapse= fAllowCollapsing && fCollapseJavadoc; |
| } |
| map.put(new JavaProjectionAnnotation(element, commentCollapse, true), position); |
| } |
| } |
| // code |
| Position position= createProjectionPosition(regions[regions.length - 1], element); |
| if (position != null) |
| map.put(new JavaProjectionAnnotation(element, collapse, false), position); |
| } |
| } |
| } |
| |
| private void initializePreferences() { |
| // IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); |
| // fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); |
| // fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); |
| fCollapseJavadoc= true ;//store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); |
| // fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); XXX never used |
| fCollapseHeaderComments= true; //store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS); |
| } |
| |
| /* |
| ************ COPIED FROM: DefaultJavaFoldingStructureProvider **************** |
| */ |
| |
| private static final class JavaProjectionAnnotation extends ProjectionAnnotation { |
| |
| private IJavaElement fJavaElement; |
| private boolean fIsComment; |
| |
| public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) { |
| super(isCollapsed); |
| fJavaElement= element; |
| fIsComment= isComment; |
| } |
| |
| public IJavaElement getElement() { |
| return fJavaElement; |
| } |
| |
| public void setElement(IJavaElement element) { |
| fJavaElement= element; |
| } |
| |
| public boolean isComment() { |
| return fIsComment; |
| } |
| |
| public void setIsComment(boolean isComment) { |
| fIsComment= isComment; |
| } |
| |
| @Override |
| public String toString() { |
| return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$ |
| "\telement: \t"+fJavaElement.toString()+"\n" + //$NON-NLS-1$ //$NON-NLS-2$ |
| "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$ |
| "\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private static final class Tuple { |
| JavaProjectionAnnotation annotation; |
| Position position; |
| Tuple(JavaProjectionAnnotation annotation, Position position) { |
| this.annotation= annotation; |
| this.position= position; |
| } |
| } |
| |
| private class ElementChangedListener implements IElementChangedListener { |
| |
| /* |
| * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent) |
| */ |
| public void elementChanged(ElementChangedEvent e) { |
| IJavaElementDelta delta= findElement(fInput, e.getDelta()); |
| if (delta != null) |
| processDelta(delta); |
| } |
| |
| private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) { |
| |
| if (delta == null || target == null) |
| return null; |
| |
| IJavaElement element= delta.getElement(); |
| |
| if (element.getElementType() > IJavaElement.CLASS_FILE) |
| return null; |
| |
| if (target.equals(element)) |
| return delta; |
| |
| IJavaElementDelta[] children= delta.getAffectedChildren(); |
| |
| for (int i= 0; i < children.length; i++) { |
| IJavaElementDelta d= findElement(target, children[i]); |
| if (d != null) |
| return d; |
| } |
| |
| return null; |
| } |
| } |
| |
| /** |
| * Projection position that will return two foldable regions: one folding away |
| * the region from after the '/**' to the beginning of the content, the other |
| * from after the first content line until after the comment. |
| * |
| * @since 3.1 |
| */ |
| private static final class CommentPosition extends Position implements IProjectionPosition { |
| CommentPosition(int offset, int length) { |
| super(offset, length); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument) |
| */ |
| public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { |
| DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); |
| int prefixEnd= 0; |
| int contentStart= findFirstContent(sequence, prefixEnd); |
| |
| int firstLine= document.getLineOfOffset(offset + prefixEnd); |
| int captionLine= document.getLineOfOffset(offset + contentStart); |
| int lastLine= document.getLineOfOffset(offset + length); |
| |
| Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$ |
| Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$ |
| |
| IRegion preRegion; |
| if (firstLine < captionLine) { |
| // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd); |
| int preOffset= document.getLineOffset(firstLine); |
| IRegion preEndLineInfo= document.getLineInformation(captionLine); |
| int preEnd= preEndLineInfo.getOffset(); |
| preRegion= new Region(preOffset, preEnd - preOffset); |
| } else { |
| preRegion= null; |
| } |
| |
| if (captionLine < lastLine) { |
| int postOffset= document.getLineOffset(captionLine + 1); |
| IRegion postRegion= new Region(postOffset, offset + length - postOffset); |
| |
| if (preRegion == null) |
| return new IRegion[] { postRegion }; |
| |
| return new IRegion[] { preRegion, postRegion }; |
| } |
| |
| if (preRegion != null) |
| return new IRegion[] { preRegion }; |
| |
| return null; |
| } |
| |
| /** |
| * Finds the offset of the first identifier part within <code>content</code>. |
| * Returns 0 if none is found. |
| * |
| * @param content the content to search |
| * @return the first index of a unicode identifier part, or zero if none can |
| * be found |
| */ |
| private int findFirstContent(final CharSequence content, int prefixEnd) { |
| int lenght= content.length(); |
| for (int i= prefixEnd; i < lenght; i++) { |
| if (Character.isUnicodeIdentifierPart(content.charAt(i))) |
| return i; |
| } |
| return 0; |
| } |
| |
| // /** |
| // * Finds the offset of the first identifier part within <code>content</code>. |
| // * Returns 0 if none is found. |
| // * |
| // * @param content the content to search |
| // * @return the first index of a unicode identifier part, or zero if none can |
| // * be found |
| // */ |
| // private int findPrefixEnd(final CharSequence content) { |
| // // return the index after the leading '/*' or '/**' |
| // int len= content.length(); |
| // int i= 0; |
| // while (i < len && isWhiteSpace(content.charAt(i))) |
| // i++; |
| // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*') |
| // if (len >= i + 3 && content.charAt(i + 2) == '*') |
| // return i + 3; |
| // else |
| // return i + 2; |
| // else |
| // return i; |
| // } |
| // |
| // private boolean isWhiteSpace(char c) { |
| // return c == ' ' || c == '\t'; |
| // } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) |
| */ |
| public int computeCaptionOffset(IDocument document) { |
| // return 0; |
| DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); |
| return findFirstContent(sequence, 0); |
| } |
| } |
| |
| /** |
| * Projection position that will return two foldable regions: one folding away |
| * the lines before the one containing the simple name of the java element, one |
| * folding away any lines after the caption. |
| * |
| * @since 3.1 |
| */ |
| private static final class JavaElementPosition extends Position implements IProjectionPosition { |
| |
| private final IMember fMember; |
| |
| public JavaElementPosition(int offset, int length, IMember member) { |
| super(offset, length); |
| Assert.isNotNull(member); |
| fMember= member; |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument) |
| */ |
| public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { |
| int nameStart= offset; |
| try { |
| /* The member's name range may not be correct. However, |
| * reconciling would trigger another element delta which would |
| * lead to reentrant situations. Therefore, we optimistically |
| * assume that the name range is correct, but double check the |
| * received lines below. */ |
| ISourceRange nameRange= fMember.getNameRange(); |
| if (nameRange != null) |
| nameStart= nameRange.getOffset(); |
| |
| } catch (JavaModelException e) { |
| // ignore and use default |
| } |
| |
| int firstLine= document.getLineOfOffset(offset); |
| int captionLine= document.getLineOfOffset(nameStart); |
| int lastLine= document.getLineOfOffset(offset + length); |
| |
| /* see comment above - adjust the caption line to be inside the |
| * entire folded region, and rely on later element deltas to correct |
| * the name range. */ |
| if (captionLine < firstLine) |
| captionLine= firstLine; |
| if (captionLine > lastLine) |
| captionLine= lastLine; |
| |
| IRegion preRegion; |
| if (firstLine < captionLine) { |
| int preOffset= document.getLineOffset(firstLine); |
| IRegion preEndLineInfo= document.getLineInformation(captionLine); |
| int preEnd= preEndLineInfo.getOffset(); |
| preRegion= new Region(preOffset, preEnd - preOffset); |
| } else { |
| preRegion= null; |
| } |
| |
| if (captionLine < lastLine) { |
| int postOffset= document.getLineOffset(captionLine + 1); |
| IRegion postRegion= new Region(postOffset, offset + length - postOffset); |
| |
| if (preRegion == null) |
| return new IRegion[] { postRegion }; |
| |
| return new IRegion[] { preRegion, postRegion }; |
| } |
| |
| if (preRegion != null) |
| return new IRegion[] { preRegion }; |
| |
| return null; |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) |
| */ |
| public int computeCaptionOffset(IDocument document) throws BadLocationException { |
| int nameStart= offset; |
| try { |
| // need a reconcile here? |
| ISourceRange nameRange= fMember.getNameRange(); |
| if (nameRange != null) |
| nameStart= nameRange.getOffset(); |
| } catch (JavaModelException e) { |
| // ignore and use default |
| } |
| |
| return nameStart - offset; |
| } |
| |
| } |
| |
| private IDocument fCachedDocument; |
| |
| private ITextEditor fEditor; |
| private ProjectionViewer fViewer; |
| private IJavaElement fInput; |
| private IElementChangedListener fElementListener; |
| |
| private boolean fAllowCollapsing= false; |
| private boolean fCollapseJavadoc= false; |
| // private boolean fCollapseImportContainer= true; |
| // private boolean fCollapseInnerTypes= true; |
| // private boolean fCollapseMethods= false; |
| |
| private boolean fCollapseHeaderComments= true; |
| |
| /* caches for header comment extraction. */ |
| private IType fFirstType; |
| private boolean fHasHeaderComment; |
| |
| |
| public AutoFoldingStructureProvider() { |
| // no initialization needed |
| } |
| |
| public void install(ITextEditor editor, ProjectionViewer viewer) { |
| if (editor instanceof JavaEditor) { |
| fEditor= editor; |
| fViewer= viewer; |
| fViewer.addProjectionListener(this); |
| } |
| } |
| |
| public void uninstall() { |
| if (isInstalled()) { |
| projectionDisabled(); |
| fViewer.removeProjectionListener(this); |
| fViewer= null; |
| fEditor= null; |
| } |
| } |
| |
| protected boolean isInstalled() { |
| return fEditor != null; |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled() |
| */ |
| public void projectionEnabled() { |
| // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html |
| // projectionEnabled messages are not always paired with projectionDisabled |
| // i.e. multiple enabled messages may be sent out. |
| // we have to make sure that we disable first when getting an enable |
| // message. |
| projectionDisabled(); |
| |
| if (fEditor instanceof JavaEditor) { |
| initialize(); |
| fElementListener= new ElementChangedListener(); |
| JavaCore.addElementChangedListener(fElementListener); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled() |
| */ |
| public void projectionDisabled() { |
| fCachedDocument= null; |
| if (fElementListener != null) { |
| JavaCore.removeElementChangedListener(fElementListener); |
| fElementListener= null; |
| } |
| } |
| |
| public void initialize() { |
| |
| if (!isInstalled()) |
| return; |
| |
| initializePreferences(); |
| |
| try { |
| |
| IDocumentProvider provider= fEditor.getDocumentProvider(); |
| fCachedDocument= provider.getDocument(fEditor.getEditorInput()); |
| fAllowCollapsing= true; |
| |
| fFirstType= null; |
| fHasHeaderComment= false; |
| |
| if (fEditor instanceof CompilationUnitEditor) { |
| IWorkingCopyManager manager= JavaPlugin.getDefault().getWorkingCopyManager(); |
| fInput= manager.getWorkingCopy(fEditor.getEditorInput()); |
| } else if (fEditor instanceof ClassFileEditor) { |
| IClassFileEditorInput editorInput= (IClassFileEditorInput) fEditor.getEditorInput(); |
| fInput= editorInput.getClassFile(); |
| } |
| |
| if (fInput != null) { |
| ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); |
| if (model != null) { |
| |
| if (fInput instanceof ICompilationUnit) { |
| ICompilationUnit unit= (ICompilationUnit) fInput; |
| synchronized (unit) { |
| try { |
| unit.reconcile(ICompilationUnit.NO_AST, false, null, null); |
| } catch (JavaModelException e) { |
| MylarStatusHandler.log(e, "could not initialize"); |
| } |
| } |
| } |
| |
| Map additions= computeAdditions((IParent) fInput); |
| model.removeAllAnnotations(); |
| model.replaceAnnotations(null, additions); |
| } |
| } |
| |
| } finally { |
| fCachedDocument= null; |
| fAllowCollapsing= false; |
| |
| fFirstType= null; |
| fHasHeaderComment= false; |
| } |
| } |
| |
| |
| private Map computeAdditions(IParent parent) { |
| Map map= new LinkedHashMap(); // use a linked map to maintain ordering of comments |
| try { |
| computeAdditions(parent.getChildren(), map); |
| } catch (JavaModelException e) { |
| } |
| return map; |
| } |
| |
| private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException { |
| for (int i= 0; i < elements.length; i++) { |
| IJavaElement element= elements[i]; |
| |
| computeAdditions(element, map); |
| |
| if (element instanceof IParent) { |
| IParent parent= (IParent) element; |
| computeAdditions(parent.getChildren(), map); |
| } |
| } |
| } |
| |
| private boolean isInnerType(IType type) { |
| |
| try { |
| return type.isMember(); |
| } catch (JavaModelException x) { |
| IJavaElement parent= type.getParent(); |
| if (parent != null) { |
| int parentType= parent.getElementType(); |
| return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE); |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Computes the projection ranges for a given <code>IJavaElement</code>. |
| * More than one range may be returned if the element has a leading comment |
| * which gets folded separately. If there are no foldable regions, |
| * <code>null</code> is returned. |
| * |
| * @param element the java element that can be folded |
| * @return the regions to be folded, or <code>null</code> if there are |
| * none |
| */ |
| private IRegion[] computeProjectionRanges(IJavaElement element) { |
| |
| try { |
| if (element instanceof ISourceReference) { |
| ISourceReference reference= (ISourceReference) element; |
| ISourceRange range= reference.getSourceRange(); |
| |
| String contents= reference.getSource(); |
| if (contents == null) |
| return null; |
| |
| List regions= new ArrayList(); |
| if (fFirstType == null && element instanceof IType) { |
| fFirstType= (IType) element; |
| IRegion headerComment= computeHeaderComment(fFirstType); |
| if (headerComment != null) { |
| regions.add(headerComment); |
| fHasHeaderComment= true; |
| } |
| } |
| |
| IScanner scanner= ToolFactory.createScanner(true, false, false, false); |
| scanner.setSource(contents.toCharArray()); |
| final int shift= range.getOffset(); |
| int start= shift; |
| while (true) { |
| |
| int token= scanner.getNextToken(); |
| start= shift + scanner.getCurrentTokenStartPosition(); |
| |
| switch (token) { |
| case ITerminalSymbols.TokenNameCOMMENT_JAVADOC: |
| case ITerminalSymbols.TokenNameCOMMENT_BLOCK: { |
| int end= shift + scanner.getCurrentTokenEndPosition() + 1; |
| regions.add(new Region(start, end - start)); |
| } |
| case ITerminalSymbols.TokenNameCOMMENT_LINE: |
| continue; |
| } |
| |
| break; |
| } |
| |
| regions.add(new Region(start, shift + range.getLength() - start)); |
| |
| if (regions.size() > 0) { |
| IRegion[] result= new IRegion[regions.size()]; |
| regions.toArray(result); |
| return result; |
| } |
| } |
| } catch (JavaModelException e) { |
| MylarStatusHandler.log(e, ""); |
| } catch (InvalidInputException e) { |
| MylarStatusHandler.log(e, ""); |
| } |
| |
| return null; |
| } |
| |
| private IRegion computeHeaderComment(IType type) throws JavaModelException { |
| if (fCachedDocument == null) |
| return null; |
| |
| // search at most up to the first type |
| ISourceRange range= type.getSourceRange(); |
| if (range == null) |
| return null; |
| int start= 0; |
| int end= range.getOffset(); |
| |
| if (fInput instanceof ISourceReference) { |
| String content; |
| try { |
| content= fCachedDocument.get(start, end - start); |
| } catch (BadLocationException e) { |
| return null; // ignore header comment in that case |
| } |
| |
| /* code adapted from CommentFormattingStrategy: |
| * scan the header content up to the first type. Once a comment is |
| * found, accumulate any additional comments up to the stop condition. |
| * The stop condition is reaching a package declaration, import container, |
| * or the end of the input. |
| */ |
| IScanner scanner= ToolFactory.createScanner(true, false, false, false); |
| scanner.setSource(content.toCharArray()); |
| |
| int headerStart= -1; |
| int headerEnd= -1; |
| try { |
| boolean foundComment= false; |
| int terminal= scanner.getNextToken(); |
| while (terminal != ITerminalSymbols.TokenNameEOF && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) { |
| |
| if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) { |
| if (!foundComment) |
| headerStart= scanner.getCurrentTokenStartPosition(); |
| headerEnd= scanner.getCurrentTokenEndPosition(); |
| foundComment= true; |
| } |
| terminal= scanner.getNextToken(); |
| } |
| |
| |
| } catch (InvalidInputException ex) { |
| return null; |
| } |
| |
| if (headerEnd != -1) { |
| return new Region(headerStart, headerEnd - headerStart); |
| } |
| } |
| return null; |
| } |
| |
| private Position createProjectionPosition(IRegion region, IJavaElement element) { |
| |
| if (fCachedDocument == null) |
| return null; |
| |
| try { |
| |
| int start= fCachedDocument.getLineOfOffset(region.getOffset()); |
| int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength()); |
| if (start != end) { |
| int offset= fCachedDocument.getLineOffset(start); |
| int endOffset; |
| if (fCachedDocument.getNumberOfLines() > end + 1) |
| endOffset= fCachedDocument.getLineOffset(end + 1); |
| else if (end > start) |
| endOffset= fCachedDocument.getLineOffset(end) + fCachedDocument.getLineLength(end); |
| else |
| return null; |
| if (element instanceof IMember) |
| return new JavaElementPosition(offset, endOffset - offset, (IMember) element); |
| else |
| return new CommentPosition(offset, endOffset - offset); |
| } |
| |
| } catch (BadLocationException e) { |
| MylarStatusHandler.log(e, ""); |
| } |
| |
| return null; |
| } |
| |
| protected void processDelta(IJavaElementDelta delta) { |
| |
| if (!isInstalled()) |
| return; |
| |
| ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); |
| if (model == null) |
| return; |
| |
| try { |
| |
| IDocumentProvider provider= fEditor.getDocumentProvider(); |
| fCachedDocument= provider.getDocument(fEditor.getEditorInput()); |
| fAllowCollapsing= false; |
| |
| fFirstType= null; |
| fHasHeaderComment= false; |
| |
| Map additions= new HashMap(); |
| List deletions= new ArrayList(); |
| List updates= new ArrayList(); |
| |
| Map updated= computeAdditions((IParent) fInput); |
| Map previous= createAnnotationMap(model); |
| |
| |
| Iterator e= updated.keySet().iterator(); |
| while (e.hasNext()) { |
| JavaProjectionAnnotation newAnnotation= (JavaProjectionAnnotation) e.next(); |
| IJavaElement element= newAnnotation.getElement(); |
| Position newPosition= (Position) updated.get(newAnnotation); |
| |
| List annotations= (List) previous.get(element); |
| if (annotations == null) { |
| |
| additions.put(newAnnotation, newPosition); |
| |
| } else { |
| Iterator x= annotations.iterator(); |
| boolean matched= false; |
| while (x.hasNext()) { |
| Tuple tuple= (Tuple) x.next(); |
| JavaProjectionAnnotation existingAnnotation= tuple.annotation; |
| Position existingPosition= tuple.position; |
| if (newAnnotation.isComment() == existingAnnotation.isComment()) { |
| if (existingPosition != null && (!newPosition.equals(existingPosition))) { |
| existingPosition.setOffset(newPosition.getOffset()); |
| existingPosition.setLength(newPosition.getLength()); |
| updates.add(existingAnnotation); |
| } |
| matched= true; |
| x.remove(); |
| break; |
| } |
| } |
| if (!matched) |
| additions.put(newAnnotation, newPosition); |
| |
| if (annotations.isEmpty()) |
| previous.remove(element); |
| } |
| } |
| |
| e= previous.values().iterator(); |
| while (e.hasNext()) { |
| List list= (List) e.next(); |
| int size= list.size(); |
| for (int i= 0; i < size; i++) |
| deletions.add(((Tuple) list.get(i)).annotation); |
| } |
| |
| match(model, deletions, additions, updates); |
| |
| Annotation[] removals= new Annotation[deletions.size()]; |
| deletions.toArray(removals); |
| Annotation[] changes= new Annotation[updates.size()]; |
| updates.toArray(changes); |
| model.modifyAnnotations(removals, additions, changes); |
| |
| } finally { |
| fCachedDocument= null; |
| fAllowCollapsing= true; |
| |
| fFirstType= null; |
| fHasHeaderComment= false; |
| } |
| } |
| |
| private void match(ProjectionAnnotationModel model, List deletions, Map additions, List changes) { |
| if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty())) |
| return; |
| |
| List newDeletions= new ArrayList(); |
| List newChanges= new ArrayList(); |
| |
| Iterator deletionIterator= deletions.iterator(); |
| outer: while (deletionIterator.hasNext()) { |
| JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next(); |
| Position deletedPosition= model.getPosition(deleted); |
| if (deletedPosition == null) |
| continue; |
| |
| Iterator changesIterator= changes.iterator(); |
| while (changesIterator.hasNext()) { |
| JavaProjectionAnnotation changed= (JavaProjectionAnnotation) changesIterator.next(); |
| if (deleted.isComment() == changed.isComment()) { |
| Position changedPosition= model.getPosition(changed); |
| if (changedPosition == null) |
| continue; |
| |
| if (deletedPosition.getOffset() == changedPosition.getOffset()) { |
| |
| deletedPosition.setLength(changedPosition.getLength()); |
| deleted.setElement(changed.getElement()); |
| |
| deletionIterator.remove(); |
| newChanges.add(deleted); |
| |
| changesIterator.remove(); |
| newDeletions.add(changed); |
| |
| continue outer; |
| } |
| } |
| } |
| |
| Iterator additionsIterator= additions.keySet().iterator(); |
| while (additionsIterator.hasNext()) { |
| JavaProjectionAnnotation added= (JavaProjectionAnnotation) additionsIterator.next(); |
| if (deleted.isComment() == added.isComment()) { |
| Position addedPosition= (Position) additions.get(added); |
| |
| if (deletedPosition.getOffset() == addedPosition.getOffset()) { |
| |
| deletedPosition.setLength(addedPosition.getLength()); |
| deleted.setElement(added.getElement()); |
| |
| deletionIterator.remove(); |
| newChanges.add(deleted); |
| |
| additionsIterator.remove(); |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| deletions.addAll(newDeletions); |
| changes.addAll(newChanges); |
| } |
| |
| private Map createAnnotationMap(IAnnotationModel model) { |
| Map map= new HashMap(); |
| Iterator e= model.getAnnotationIterator(); |
| while (e.hasNext()) { |
| Object annotation= e.next(); |
| if (annotation instanceof JavaProjectionAnnotation) { |
| JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; |
| Position position= model.getPosition(java); |
| Assert.isNotNull(position); |
| List list= (List) map.get(java.getElement()); |
| if (list == null) { |
| list= new ArrayList(2); |
| map.put(java.getElement(), list); |
| } |
| list.add(new Tuple(java, position)); |
| } |
| } |
| |
| Comparator comparator= new Comparator() { |
| public int compare(Object o1, Object o2) { |
| return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset(); |
| } |
| }; |
| for (Iterator it= map.values().iterator(); it.hasNext();) { |
| List list= (List) it.next(); |
| Collections.sort(list, comparator); |
| } |
| return map; |
| } |
| } |