1 11 package org.eclipse.jdt.ui.text.folding; 12 13 import java.util.ArrayList ; 14 import java.util.Arrays ; 15 import java.util.Collection ; 16 import java.util.Collections ; 17 import java.util.Comparator ; 18 import java.util.HashMap ; 19 import java.util.HashSet ; 20 import java.util.Iterator ; 21 import java.util.LinkedHashMap ; 22 import java.util.List ; 23 import java.util.Map ; 24 import java.util.Set ; 25 26 import org.eclipse.core.runtime.Assert; 27 28 import org.eclipse.jface.preference.IPreferenceStore; 29 30 import org.eclipse.jface.text.BadLocationException; 31 import org.eclipse.jface.text.IDocument; 32 import org.eclipse.jface.text.IRegion; 33 import org.eclipse.jface.text.Position; 34 import org.eclipse.jface.text.Region; 35 import org.eclipse.jface.text.TextSelection; 36 import org.eclipse.jface.text.source.Annotation; 37 import org.eclipse.jface.text.source.projection.IProjectionListener; 38 import org.eclipse.jface.text.source.projection.IProjectionPosition; 39 import org.eclipse.jface.text.source.projection.ProjectionAnnotation; 40 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; 41 import org.eclipse.jface.text.source.projection.ProjectionViewer; 42 43 import org.eclipse.ui.texteditor.IDocumentProvider; 44 import org.eclipse.ui.texteditor.ITextEditor; 45 46 import org.eclipse.jdt.core.ElementChangedEvent; 47 import org.eclipse.jdt.core.IClassFile; 48 import org.eclipse.jdt.core.ICompilationUnit; 49 import org.eclipse.jdt.core.IElementChangedListener; 50 import org.eclipse.jdt.core.IImportContainer; 51 import org.eclipse.jdt.core.IImportDeclaration; 52 import org.eclipse.jdt.core.IJavaElement; 53 import org.eclipse.jdt.core.IJavaElementDelta; 54 import org.eclipse.jdt.core.IMember; 55 import org.eclipse.jdt.core.IParent; 56 import org.eclipse.jdt.core.ISourceRange; 57 import org.eclipse.jdt.core.ISourceReference; 58 import org.eclipse.jdt.core.IType; 59 import org.eclipse.jdt.core.JavaCore; 60 import org.eclipse.jdt.core.JavaModelException; 61 import org.eclipse.jdt.core.ToolFactory; 62 import org.eclipse.jdt.core.compiler.IProblem; 63 import org.eclipse.jdt.core.compiler.IScanner; 64 import org.eclipse.jdt.core.compiler.ITerminalSymbols; 65 import org.eclipse.jdt.core.compiler.InvalidInputException; 66 import org.eclipse.jdt.core.dom.CompilationUnit; 67 68 import org.eclipse.jdt.internal.corext.SourceRange; 69 70 import org.eclipse.jdt.ui.PreferenceConstants; 71 72 import org.eclipse.jdt.internal.ui.JavaPlugin; 73 import org.eclipse.jdt.internal.ui.actions.SelectionConverter; 74 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; 75 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; 76 import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator; 77 78 88 public class DefaultJavaFoldingStructureProvider implements IJavaFoldingStructureProvider, IJavaFoldingStructureProviderExtension { 89 95 protected final class FoldingStructureComputationContext { 96 private final ProjectionAnnotationModel fModel; 97 private final IDocument fDocument; 98 99 private final boolean fAllowCollapsing; 100 101 private IType fFirstType; 102 private boolean fHasHeaderComment; 103 private LinkedHashMap fMap= new LinkedHashMap (); 104 private IScanner fScanner; 105 106 private FoldingStructureComputationContext(IDocument document, ProjectionAnnotationModel model, boolean allowCollapsing, IScanner scanner) { 107 Assert.isNotNull(document); 108 Assert.isNotNull(model); 109 fDocument= document; 110 fModel= model; 111 fAllowCollapsing= allowCollapsing; 112 fScanner= scanner; 113 } 114 115 private void setFirstType(IType type) { 116 if (hasFirstType()) 117 throw new IllegalStateException (); 118 fFirstType= type; 119 } 120 121 boolean hasFirstType() { 122 return fFirstType != null; 123 } 124 125 private IType getFirstType() { 126 return fFirstType; 127 } 128 129 private boolean hasHeaderComment() { 130 return fHasHeaderComment; 131 } 132 133 private void setHasHeaderComment() { 134 fHasHeaderComment= true; 135 } 136 137 146 public boolean allowCollapsing() { 147 return fAllowCollapsing; 148 } 149 150 155 private IDocument getDocument() { 156 return fDocument; 157 } 158 159 private ProjectionAnnotationModel getModel() { 160 return fModel; 161 } 162 163 private IScanner getScanner() { 164 if (fScanner == null) 165 fScanner= ToolFactory.createScanner(true, false, false, false); 166 return fScanner; 167 } 168 169 177 public void addProjectionRange(JavaProjectionAnnotation annotation, Position position) { 178 fMap.put(annotation, position); 179 } 180 181 186 public boolean collapseHeaderComments() { 187 return fAllowCollapsing && fCollapseHeaderComments; 188 } 189 190 195 public boolean collapseImportContainer() { 196 return fAllowCollapsing && fCollapseImportContainer; 197 } 198 199 204 public boolean collapseInnerTypes() { 205 return fAllowCollapsing && fCollapseInnerTypes; 206 } 207 208 213 public boolean collapseJavadoc() { 214 return fAllowCollapsing && fCollapseJavadoc; 215 } 216 217 222 public boolean collapseMembers() { 223 return fAllowCollapsing && fCollapseMembers; 224 } 225 } 226 227 230 protected static final class JavaProjectionAnnotation extends ProjectionAnnotation { 231 232 private IJavaElement fJavaElement; 233 private boolean fIsComment; 234 235 244 public JavaProjectionAnnotation(boolean isCollapsed, IJavaElement element, boolean isComment) { 245 super(isCollapsed); 246 fJavaElement= element; 247 fIsComment= isComment; 248 } 249 250 IJavaElement getElement() { 251 return fJavaElement; 252 } 253 254 void setElement(IJavaElement element) { 255 fJavaElement= element; 256 } 257 258 boolean isComment() { 259 return fIsComment; 260 } 261 262 void setIsComment(boolean isComment) { 263 fIsComment= isComment; 264 } 265 266 269 public String toString() { 270 return "JavaProjectionAnnotation:\n" + "\telement: \t"+ fJavaElement.toString() + "\n" + "\tcollapsed: \t" + isCollapsed() + "\n" + "\tcomment: \t" + isComment() + "\n"; } 275 } 276 277 278 private static final class Tuple { 279 JavaProjectionAnnotation annotation; 280 Position position; 281 Tuple(JavaProjectionAnnotation annotation, Position position) { 282 this.annotation= annotation; 283 this.position= position; 284 } 285 } 286 287 290 private static interface Filter { 291 boolean match(JavaProjectionAnnotation annotation); 292 } 293 294 297 private static final class CommentFilter implements Filter { 298 public boolean match(JavaProjectionAnnotation annotation) { 299 if (annotation.isComment() && !annotation.isMarkedDeleted()) { 300 return true; 301 } 302 return false; 303 } 304 } 305 306 309 private static final class MemberFilter implements Filter { 310 public boolean match(JavaProjectionAnnotation annotation) { 311 if (!annotation.isComment() && !annotation.isMarkedDeleted()) { 312 IJavaElement element= annotation.getElement(); 313 if (element instanceof IMember) { 314 if (element.getElementType() != IJavaElement.TYPE || ((IMember) element).getDeclaringType() != null) { 315 return true; 316 } 317 } 318 } 319 return false; 320 } 321 } 322 323 326 private static final class JavaElementSetFilter implements Filter { 327 private final Set fSet; 328 private final boolean fMatchCollapsed; 329 330 private JavaElementSetFilter(Set set, boolean matchCollapsed) { 331 fSet= set; 332 fMatchCollapsed= matchCollapsed; 333 } 334 335 public boolean match(JavaProjectionAnnotation annotation) { 336 boolean stateMatch= fMatchCollapsed == annotation.isCollapsed(); 337 if (stateMatch && !annotation.isComment() && !annotation.isMarkedDeleted()) { 338 IJavaElement element= annotation.getElement(); 339 if (fSet.contains(element)) { 340 return true; 341 } 342 } 343 return false; 344 } 345 } 346 347 private class ElementChangedListener implements IElementChangedListener { 348 349 352 public void elementChanged(ElementChangedEvent e) { 353 IJavaElementDelta delta= findElement(fInput, e.getDelta()); 354 if (delta != null && (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) { 355 356 if (shouldIgnoreDelta(e.getDelta().getCompilationUnitAST(), delta)) 357 return; 358 359 fUpdatingCount++; 360 try { 361 update(createContext(false)); 362 } finally { 363 fUpdatingCount--; 364 } 365 } 366 } 367 368 380 private boolean shouldIgnoreDelta(CompilationUnit ast, IJavaElementDelta delta) { 381 if (ast == null) 382 return false; 384 IDocument document= getDocument(); 385 if (document == null) 386 return false; 388 JavaEditor editor= fEditor; 389 if (editor == null || editor.getCachedSelectedRange() == null) 390 return false; 392 try { 393 if (delta.getAffectedChildren().length == 1 && delta.getAffectedChildren()[0].getElement() instanceof IImportContainer) { 394 IJavaElement elem= SelectionConverter.getElementAtOffset(ast.getJavaElement(), new TextSelection(editor.getCachedSelectedRange().x, editor.getCachedSelectedRange().y)); 395 if (!(elem instanceof IImportDeclaration)) 396 return false; 397 398 } 399 } catch (JavaModelException e) { 400 return false; } 402 403 int caretLine= 0; 404 try { 405 caretLine= document.getLineOfOffset(editor.getCachedSelectedRange().x) + 1; 406 } catch (BadLocationException x) { 407 return false; } 409 410 if (caretLine > 0 && ast != null) { 411 IProblem[] problems= ast.getProblems(); 412 for (int i= 0; i < problems.length; i++) { 413 if (problems[i].isError() && caretLine == problems[i].getSourceLineNumber()) 414 return true; 415 } 416 } 417 418 return false; 419 } 420 421 private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) { 422 423 if (delta == null || target == null) 424 return null; 425 426 IJavaElement element= delta.getElement(); 427 428 if (element.getElementType() > IJavaElement.CLASS_FILE) 429 return null; 430 431 if (target.equals(element)) 432 return delta; 433 434 IJavaElementDelta[] children= delta.getAffectedChildren(); 435 436 for (int i= 0; i < children.length; i++) { 437 IJavaElementDelta d= findElement(target, children[i]); 438 if (d != null) 439 return d; 440 } 441 442 return null; 443 } 444 } 445 446 451 private static final class CommentPosition extends Position implements IProjectionPosition { 452 CommentPosition(int offset, int length) { 453 super(offset, length); 454 } 455 456 459 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { 460 DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); 461 int prefixEnd= 0; 462 int contentStart= findFirstContent(sequence, prefixEnd); 463 464 int firstLine= document.getLineOfOffset(offset + prefixEnd); 465 int captionLine= document.getLineOfOffset(offset + contentStart); 466 int lastLine= document.getLineOfOffset(offset + length); 467 468 Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); 471 IRegion preRegion; 472 if (firstLine < captionLine) { 473 int preOffset= document.getLineOffset(firstLine); 475 IRegion preEndLineInfo= document.getLineInformation(captionLine); 476 int preEnd= preEndLineInfo.getOffset(); 477 preRegion= new Region(preOffset, preEnd - preOffset); 478 } else { 479 preRegion= null; 480 } 481 482 if (captionLine < lastLine) { 483 int postOffset= document.getLineOffset(captionLine + 1); 484 IRegion postRegion= new Region(postOffset, offset + length - postOffset); 485 486 if (preRegion == null) 487 return new IRegion[] { postRegion }; 488 489 return new IRegion[] { preRegion, postRegion }; 490 } 491 492 if (preRegion != null) 493 return new IRegion[] { preRegion }; 494 495 return null; 496 } 497 498 507 private int findFirstContent(final CharSequence content, int prefixEnd) { 508 int lenght= content.length(); 509 for (int i= prefixEnd; i < lenght; i++) { 510 if (Character.isUnicodeIdentifierPart(content.charAt(i))) 511 return i; 512 } 513 return 0; 514 } 515 516 543 546 public int computeCaptionOffset(IDocument document) { 547 DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); 549 return findFirstContent(sequence, 0); 550 } 551 } 552 553 558 private static final class JavaElementPosition extends Position implements IProjectionPosition { 559 560 private IMember fMember; 561 562 public JavaElementPosition(int offset, int length, IMember member) { 563 super(offset, length); 564 Assert.isNotNull(member); 565 fMember= member; 566 } 567 568 public void setMember(IMember member) { 569 Assert.isNotNull(member); 570 fMember= member; 571 } 572 573 576 public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { 577 int nameStart= offset; 578 try { 579 584 ISourceRange nameRange= fMember.getNameRange(); 585 if (nameRange != null) 586 nameStart= nameRange.getOffset(); 587 588 } catch (JavaModelException e) { 589 } 591 592 int firstLine= document.getLineOfOffset(offset); 593 int captionLine= document.getLineOfOffset(nameStart); 594 int lastLine= document.getLineOfOffset(offset + length); 595 596 599 if (captionLine < firstLine) 600 captionLine= firstLine; 601 if (captionLine > lastLine) 602 captionLine= lastLine; 603 604 IRegion preRegion; 605 if (firstLine < captionLine) { 606 int preOffset= document.getLineOffset(firstLine); 607 IRegion preEndLineInfo= document.getLineInformation(captionLine); 608 int preEnd= preEndLineInfo.getOffset(); 609 preRegion= new Region(preOffset, preEnd - preOffset); 610 } else { 611 preRegion= null; 612 } 613 614 if (captionLine < lastLine) { 615 int postOffset= document.getLineOffset(captionLine + 1); 616 IRegion postRegion= new Region(postOffset, offset + length - postOffset); 617 618 if (preRegion == null) 619 return new IRegion[] { postRegion }; 620 621 return new IRegion[] { preRegion, postRegion }; 622 } 623 624 if (preRegion != null) 625 return new IRegion[] { preRegion }; 626 627 return null; 628 } 629 630 633 public int computeCaptionOffset(IDocument document) throws BadLocationException { 634 int nameStart= offset; 635 try { 636 ISourceRange nameRange= fMember.getNameRange(); 638 if (nameRange != null) 639 nameStart= nameRange.getOffset(); 640 } catch (JavaModelException e) { 641 } 643 644 return nameStart - offset; 645 } 646 647 } 648 649 652 private final class ProjectionListener implements IProjectionListener { 653 private ProjectionViewer fViewer; 654 655 660 public ProjectionListener(ProjectionViewer viewer) { 661 Assert.isLegal(viewer != null); 662 fViewer= viewer; 663 fViewer.addProjectionListener(this); 664 } 665 666 669 public void dispose() { 670 if (fViewer != null) { 671 fViewer.removeProjectionListener(this); 672 fViewer= null; 673 } 674 } 675 676 679 public void projectionEnabled() { 680 handleProjectionEnabled(); 681 } 682 683 686 public void projectionDisabled() { 687 handleProjectionDisabled(); 688 } 689 } 690 691 692 private JavaEditor fEditor; 693 private ProjectionListener fProjectionListener; 694 private IJavaElement fInput; 695 private IElementChangedListener fElementListener; 696 697 698 private boolean fCollapseJavadoc= false; 699 private boolean fCollapseImportContainer= true; 700 private boolean fCollapseInnerTypes= true; 701 private boolean fCollapseMembers= false; 702 private boolean fCollapseHeaderComments= true; 703 704 705 706 private final Filter fMemberFilter = new MemberFilter(); 707 708 private final Filter fCommentFilter = new CommentFilter(); 709 710 714 private IScanner fSharedScanner= ToolFactory.createScanner(true, false, false, false); 715 716 private volatile int fUpdatingCount= 0; 717 718 726 public DefaultJavaFoldingStructureProvider() { 727 } 728 729 738 public void install(ITextEditor editor, ProjectionViewer viewer) { 739 Assert.isLegal(editor != null); 740 Assert.isLegal(viewer != null); 741 742 internalUninstall(); 743 744 if (editor instanceof JavaEditor) { 745 fProjectionListener= new ProjectionListener(viewer); 746 fEditor= (JavaEditor)editor; 747 } 748 } 749 750 756 public void uninstall() { 757 internalUninstall(); 758 } 759 760 763 private void internalUninstall() { 764 if (isInstalled()) { 765 handleProjectionDisabled(); 766 fProjectionListener.dispose(); 767 fProjectionListener= null; 768 fEditor= null; 769 } 770 } 771 772 777 protected final boolean isInstalled() { 778 return fEditor != null; 779 } 780 781 790 protected void handleProjectionEnabled() { 791 handleProjectionDisabled(); 797 798 if (isInstalled()) { 799 initialize(); 800 fElementListener= new ElementChangedListener(); 801 JavaCore.addElementChangedListener(fElementListener); 802 } 803 } 804 805 815 protected void handleProjectionDisabled() { 816 if (fElementListener != null) { 817 JavaCore.removeElementChangedListener(fElementListener); 818 fElementListener= null; 819 } 820 } 821 822 825 public final void initialize() { 826 fUpdatingCount++; 827 try { 828 update(createInitialContext()); 829 } finally { 830 fUpdatingCount--; 831 } 832 } 833 834 private FoldingStructureComputationContext createInitialContext() { 835 initializePreferences(); 836 fInput= getInputElement(); 837 if (fInput == null) 838 return null; 839 840 return createContext(true); 841 } 842 843 private FoldingStructureComputationContext createContext(boolean allowCollapse) { 844 if (!isInstalled()) 845 return null; 846 ProjectionAnnotationModel model= getModel(); 847 if (model == null) 848 return null; 849 IDocument doc= getDocument(); 850 if (doc == null) 851 return null; 852 853 IScanner scanner= null; 854 if (fUpdatingCount == 1) 855 scanner= fSharedScanner; 857 return new FoldingStructureComputationContext(doc, model, allowCollapse, scanner); 858 } 859 860 private IJavaElement getInputElement() { 861 if (fEditor == null) 862 return null; 863 return EditorUtility.getEditorInputJavaElement(fEditor, false); 864 } 865 866 private void initializePreferences() { 867 IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); 868 fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); 869 fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); 870 fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); 871 fCollapseMembers= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); 872 fCollapseHeaderComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS); 873 } 874 875 private void update(FoldingStructureComputationContext ctx) { 876 if (ctx == null) 877 return; 878 879 Map additions= new HashMap (); 880 List deletions= new ArrayList (); 881 List updates= new ArrayList (); 882 883 computeFoldingStructure(ctx); 884 Map newStructure= ctx.fMap; 885 Map oldStructure= computeCurrentStructure(ctx); 886 887 Iterator e= newStructure.keySet().iterator(); 888 while (e.hasNext()) { 889 JavaProjectionAnnotation newAnnotation= (JavaProjectionAnnotation) e.next(); 890 Position newPosition= (Position) newStructure.get(newAnnotation); 891 892 IJavaElement element= newAnnotation.getElement(); 893 902 boolean isMalformedAnonymousType= newPosition.getOffset() == 0 && element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element); 903 List annotations= (List ) oldStructure.get(element); 904 if (annotations == null) { 905 if (!isMalformedAnonymousType) 906 additions.put(newAnnotation, newPosition); 907 } else { 908 Iterator x= annotations.iterator(); 909 boolean matched= false; 910 while (x.hasNext()) { 911 Tuple tuple= (Tuple) x.next(); 912 JavaProjectionAnnotation existingAnnotation= tuple.annotation; 913 Position existingPosition= tuple.position; 914 if (newAnnotation.isComment() == existingAnnotation.isComment()) { 915 boolean updateCollapsedState= ctx.allowCollapsing() && existingAnnotation.isCollapsed() != newAnnotation.isCollapsed(); 916 if (!isMalformedAnonymousType && existingPosition != null && (!newPosition.equals(existingPosition) || updateCollapsedState)) { 917 existingPosition.setOffset(newPosition.getOffset()); 918 existingPosition.setLength(newPosition.getLength()); 919 if (updateCollapsedState) 920 if (newAnnotation.isCollapsed()) 921 existingAnnotation.markCollapsed(); 922 else 923 existingAnnotation.markExpanded(); 924 updates.add(existingAnnotation); 925 } 926 matched= true; 927 x.remove(); 928 break; 929 } 930 } 931 if (!matched) 932 additions.put(newAnnotation, newPosition); 933 934 if (annotations.isEmpty()) 935 oldStructure.remove(element); 936 } 937 } 938 939 e= oldStructure.values().iterator(); 940 while (e.hasNext()) { 941 List list= (List ) e.next(); 942 int size= list.size(); 943 for (int i= 0; i < size; i++) 944 deletions.add(((Tuple) list.get(i)).annotation); 945 } 946 947 match(deletions, additions, updates, ctx); 948 949 Annotation[] deletedArray= (Annotation[]) deletions.toArray(new Annotation[deletions.size()]); 950 Annotation[] changedArray= (Annotation[]) updates.toArray(new Annotation[updates.size()]); 951 ctx.getModel().modifyAnnotations(deletedArray, additions, changedArray); 952 953 ctx.fScanner.setSource(null); 954 } 955 956 private void computeFoldingStructure(FoldingStructureComputationContext ctx) { 957 IParent parent= (IParent) fInput; 958 try { 959 if (!(fInput instanceof ISourceReference)) 960 return; 961 String source= ((ISourceReference)fInput).getSource(); 962 if (source == null) 963 return; 964 965 ctx.getScanner().setSource(source.toCharArray()); 966 computeFoldingStructure(parent.getChildren(), ctx); 967 } catch (JavaModelException x) { 968 } 969 } 970 971 private void computeFoldingStructure(IJavaElement[] elements, FoldingStructureComputationContext ctx) throws JavaModelException { 972 for (int i= 0; i < elements.length; i++) { 973 IJavaElement element= elements[i]; 974 975 computeFoldingStructure(element, ctx); 976 977 if (element instanceof IParent) { 978 IParent parent= (IParent) element; 979 computeFoldingStructure(parent.getChildren(), ctx); 980 } 981 } 982 } 983 984 1003 protected void computeFoldingStructure(IJavaElement element, FoldingStructureComputationContext ctx) { 1004 1005 boolean collapse= false; 1006 boolean collapseCode= true; 1007 switch (element.getElementType()) { 1008 1009 case IJavaElement.IMPORT_CONTAINER: 1010 collapse= ctx.collapseImportContainer(); 1011 break; 1012 case IJavaElement.TYPE: 1013 collapseCode= isInnerType((IType) element) && !isAnonymousEnum((IType) element); 1014 collapse= ctx.collapseInnerTypes() && collapseCode; 1015 break; 1016 case IJavaElement.METHOD: 1017 case IJavaElement.FIELD: 1018 case IJavaElement.INITIALIZER: 1019 collapse= ctx.collapseMembers(); 1020 break; 1021 default: 1022 return; 1023 } 1024 1025 IRegion[] regions= computeProjectionRanges((ISourceReference) element, ctx); 1026 if (regions.length > 0) { 1027 for (int i= 0; i < regions.length - 1; i++) { 1029 IRegion normalized= alignRegion(regions[i], ctx); 1030 if (normalized != null) { 1031 Position position= createCommentPosition(normalized); 1032 if (position != null) { 1033 boolean commentCollapse; 1034 if (i == 0 && (regions.length > 2 || ctx.hasHeaderComment()) && element == ctx.getFirstType()) { 1035 commentCollapse= ctx.collapseHeaderComments(); 1036 } else { 1037 commentCollapse= ctx.collapseJavadoc(); 1038 } 1039 ctx.addProjectionRange(new JavaProjectionAnnotation(commentCollapse, element, true), position); 1040 } 1041 } 1042 } 1043 if (collapseCode) { 1045 IRegion normalized= alignRegion(regions[regions.length - 1], ctx); 1046 if (normalized != null) { 1047 Position position= element instanceof IMember ? createMemberPosition(normalized, (IMember) element) : createCommentPosition(normalized); 1048 if (position != null) 1049 ctx.addProjectionRange(new JavaProjectionAnnotation(collapse, element, false), position); 1050 } 1051 } 1052 } 1053 } 1054 1055 1063 private boolean isAnonymousEnum(IType type) { 1064 try { 1065 return type.isEnum() && type.isAnonymous(); 1066 } catch (JavaModelException x) { 1067 return false; } 1069 } 1070 1071 1077 private boolean isInnerType(IType type) { 1078 return type.getDeclaringType() != null; 1079 } 1080 1081 1095 protected final IRegion[] computeProjectionRanges(ISourceReference reference, FoldingStructureComputationContext ctx) { 1096 try { 1097 ISourceRange range= reference.getSourceRange(); 1098 if (!SourceRange.isAvailable(range)) 1099 return new IRegion[0]; 1100 1101 String contents= reference.getSource(); 1102 if (contents == null) 1103 return new IRegion[0]; 1104 1105 List regions= new ArrayList (); 1106 if (!ctx.hasFirstType() && reference instanceof IType) { 1107 ctx.setFirstType((IType) reference); 1108 IRegion headerComment= computeHeaderComment(ctx); 1109 if (headerComment != null) { 1110 regions.add(headerComment); 1111 ctx.setHasHeaderComment(); 1112 } 1113 } 1114 1115 final int shift= range.getOffset(); 1116 IScanner scanner= ctx.getScanner(); 1117 scanner.resetTo(shift, shift + range.getLength()); 1118 1119 int start= shift; 1120 while (true) { 1121 1122 int token= scanner.getNextToken(); 1123 start= scanner.getCurrentTokenStartPosition(); 1124 1125 switch (token) { 1126 case ITerminalSymbols.TokenNameCOMMENT_JAVADOC: 1127 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: { 1128 int end= scanner.getCurrentTokenEndPosition() + 1; 1129 regions.add(new Region(start, end - start)); 1130 continue; 1131 } 1132 case ITerminalSymbols.TokenNameCOMMENT_LINE: 1133 continue; 1134 } 1135 1136 break; 1137 } 1138 1139 regions.add(new Region(start, shift + range.getLength() - start)); 1140 1141 IRegion[] result= new IRegion[regions.size()]; 1142 regions.toArray(result); 1143 return result; 1144 } catch (JavaModelException e) { 1145 } catch (InvalidInputException e) { 1146 } 1147 1148 return new IRegion[0]; 1149 } 1150 1151 private IRegion computeHeaderComment(FoldingStructureComputationContext ctx) throws JavaModelException { 1152 ISourceRange range= ctx.getFirstType().getSourceRange(); 1154 if (range == null) 1155 return null; 1156 int start= 0; 1157 int end= range.getOffset(); 1158 1159 1160 1166 IScanner scanner= ctx.getScanner(); 1167 scanner.resetTo(start, end); 1168 1169 int headerStart= -1; 1170 int headerEnd= -1; 1171 try { 1172 boolean foundComment= false; 1173 int terminal= scanner.getNextToken(); 1174 while (terminal != ITerminalSymbols.TokenNameEOF && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) { 1175 1176 if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) { 1177 if (!foundComment) 1178 headerStart= scanner.getCurrentTokenStartPosition(); 1179 headerEnd= scanner.getCurrentTokenEndPosition(); 1180 foundComment= true; 1181 } 1182 terminal= scanner.getNextToken(); 1183 } 1184 1185 1186 } catch (InvalidInputException ex) { 1187 return null; 1188 } 1189 1190 if (headerEnd != -1) { 1191 return new Region(headerStart, headerEnd - headerStart); 1192 } 1193 return null; 1194 } 1195 1196 1204 protected final Position createCommentPosition(IRegion aligned) { 1205 return new CommentPosition(aligned.getOffset(), aligned.getLength()); 1206 } 1207 1208 1217 protected final Position createMemberPosition(IRegion aligned, IMember member) { 1218 return new JavaElementPosition(aligned.getOffset(), aligned.getLength(), member); 1219 } 1220 1221 1234 protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) { 1235 if (region == null) 1236 return null; 1237 1238 IDocument document= ctx.getDocument(); 1239 1240 try { 1241 1242 int start= document.getLineOfOffset(region.getOffset()); 1243 int end= document.getLineOfOffset(region.getOffset() + region.getLength()); 1244 if (start >= end) 1245 return null; 1246 1247 int offset= document.getLineOffset(start); 1248 int endOffset; 1249 if (document.getNumberOfLines() > end + 1) 1250 endOffset= document.getLineOffset(end + 1); 1251 else 1252 endOffset= document.getLineOffset(end) + document.getLineLength(end); 1253 1254 return new Region(offset, endOffset - offset); 1255 1256 } catch (BadLocationException x) { 1257 return null; 1259 } 1260 } 1261 1262 private ProjectionAnnotationModel getModel() { 1263 return (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); 1264 } 1265 1266 private IDocument getDocument() { 1267 JavaEditor editor= fEditor; 1268 if (editor == null) 1269 return null; 1270 1271 IDocumentProvider provider= editor.getDocumentProvider(); 1272 if (provider == null) 1273 return null; 1274 1275 return provider.getDocument(editor.getEditorInput()); 1276 } 1277 1278 1291 private void match(List deletions, Map additions, List changes, FoldingStructureComputationContext ctx) { 1292 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty())) 1293 return; 1294 1295 List newDeletions= new ArrayList (); 1296 List newChanges= new ArrayList (); 1297 1298 Iterator deletionIterator= deletions.iterator(); 1299 while (deletionIterator.hasNext()) { 1300 JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next(); 1301 Position deletedPosition= ctx.getModel().getPosition(deleted); 1302 if (deletedPosition == null) 1303 continue; 1304 1305 Tuple deletedTuple= new Tuple(deleted, deletedPosition); 1306 1307 Tuple match= findMatch(deletedTuple, changes, null, ctx); 1308 boolean addToDeletions= true; 1309 if (match == null) { 1310 match= findMatch(deletedTuple, additions.keySet(), additions, ctx); 1311 addToDeletions= false; 1312 } 1313 1314 if (match != null) { 1315 IJavaElement element= match.annotation.getElement(); 1316 deleted.setElement(element); 1317 deletedPosition.setLength(match.position.getLength()); 1318 if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) { 1319 JavaElementPosition jep= (JavaElementPosition) deletedPosition; 1320 jep.setMember((IMember) element); 1321 } 1322 1323 deletionIterator.remove(); 1324 newChanges.add(deleted); 1325 1326 if (addToDeletions) 1327 newDeletions.add(match.annotation); 1328 } 1329 } 1330 1331 deletions.addAll(newDeletions); 1332 changes.addAll(newChanges); 1333 } 1334 1335 1359 private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap, FoldingStructureComputationContext ctx) { 1360 Iterator it= annotations.iterator(); 1361 while (it.hasNext()) { 1362 JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) it.next(); 1363 if (tuple.annotation.isComment() == annotation.isComment()) { 1364 Position position= positionMap == null ? ctx.getModel().getPosition(annotation) : (Position) positionMap.get(annotation); 1365 if (position == null) 1366 continue; 1367 1368 if (tuple.position.getOffset() == position.getOffset()) { 1369 it.remove(); 1370 return new Tuple(annotation, position); 1371 } 1372 } 1373 } 1374 1375 return null; 1376 } 1377 1378 private Map computeCurrentStructure(FoldingStructureComputationContext ctx) { 1379 Map map= new HashMap (); 1380 ProjectionAnnotationModel model= ctx.getModel(); 1381 Iterator e= model.getAnnotationIterator(); 1382 while (e.hasNext()) { 1383 Object annotation= e.next(); 1384 if (annotation instanceof JavaProjectionAnnotation) { 1385 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; 1386 Position position= model.getPosition(java); 1387 Assert.isNotNull(position); 1388 List list= (List ) map.get(java.getElement()); 1389 if (list == null) { 1390 list= new ArrayList (2); 1391 map.put(java.getElement(), list); 1392 } 1393 list.add(new Tuple(java, position)); 1394 } 1395 } 1396 1397 Comparator comparator= new Comparator () { 1398 public int compare(Object o1, Object o2) { 1399 return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset(); 1400 } 1401 }; 1402 for (Iterator it= map.values().iterator(); it.hasNext();) { 1403 List list= (List ) it.next(); 1404 Collections.sort(list, comparator); 1405 } 1406 return map; 1407 } 1408 1409 1413 public final void collapseMembers() { 1414 modifyFiltered(fMemberFilter, false); 1415 } 1416 1417 1421 public final void collapseComments() { 1422 modifyFiltered(fCommentFilter, false); 1423 } 1424 1425 1428 public final void collapseElements(IJavaElement[] elements) { 1429 Set set= new HashSet (Arrays.asList(elements)); 1430 modifyFiltered(new JavaElementSetFilter(set, false), false); 1431 } 1432 1433 1436 public final void expandElements(IJavaElement[] elements) { 1437 Set set= new HashSet (Arrays.asList(elements)); 1438 modifyFiltered(new JavaElementSetFilter(set, true), true); 1439 } 1440 1441 1448 private void modifyFiltered(Filter filter, boolean expand) { 1449 if (!isInstalled()) 1450 return; 1451 1452 ProjectionAnnotationModel model= getModel(); 1453 if (model == null) 1454 return; 1455 1456 List modified= new ArrayList (); 1457 Iterator iter= model.getAnnotationIterator(); 1458 while (iter.hasNext()) { 1459 Object annotation= iter.next(); 1460 if (annotation instanceof JavaProjectionAnnotation) { 1461 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; 1462 1463 if (expand == java.isCollapsed() && filter.match(java)) { 1464 if (expand) 1465 java.markExpanded(); 1466 else 1467 java.markCollapsed(); 1468 modified.add(java); 1469 } 1470 1471 } 1472 } 1473 1474 model.modifyAnnotations(null, null, (Annotation[]) modified.toArray(new Annotation[modified.size()])); 1475 } 1476} 1477 | Popular Tags |