KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > ui > text > folding > DefaultJavaFoldingStructureProvider


1 /*******************************************************************************
2  * Copyright (c) 2006, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.ui.text.folding;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.Collection JavaDoc;
16 import java.util.Collections JavaDoc;
17 import java.util.Comparator JavaDoc;
18 import java.util.HashMap JavaDoc;
19 import java.util.HashSet JavaDoc;
20 import java.util.Iterator JavaDoc;
21 import java.util.LinkedHashMap JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24 import java.util.Set JavaDoc;
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 /**
79  * Updates the projection model of a class file or compilation unit.
80  * <p>
81  * Clients may instantiate or subclass. Subclasses must make sure to always call the superclass'
82  * code when overriding methods that are marked with "subclasses may extend".
83  * </p>
84  *
85  * @since 3.0 (internal)
86  * @since 3.2 (API)
87  */

88 public class DefaultJavaFoldingStructureProvider implements IJavaFoldingStructureProvider, IJavaFoldingStructureProviderExtension {
89     /**
90      * A context that contains the information needed to compute the folding structure of an
91      * {@link ICompilationUnit} or an {@link IClassFile}. Computed folding regions are collected
92      * via
93      * {@linkplain #addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) addProjectionRange}.
94      */

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 JavaDoc fMap= new LinkedHashMap JavaDoc();
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 JavaDoc();
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         /**
138          * Returns <code>true</code> if newly created folding regions may be collapsed,
139          * <code>false</code> if not. This is usually <code>false</code> when updating the
140          * folding structure while typing; it may be <code>true</code> when computing or restoring
141          * the initial folding structure.
142          *
143          * @return <code>true</code> if newly created folding regions may be collapsed,
144          * <code>false</code> if not
145          */

146         public boolean allowCollapsing() {
147             return fAllowCollapsing;
148         }
149
150         /**
151          * Returns the document which contains the code being folded.
152          *
153          * @return the document which contains the code being folded
154          */

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         /**
170          * Adds a projection (folding) region to this context. The created annotation / position
171          * pair will be added to the {@link ProjectionAnnotationModel} of the
172          * {@link ProjectionViewer} of the editor.
173          *
174          * @param annotation the annotation to add
175          * @param position the corresponding position
176          */

177         public void addProjectionRange(JavaProjectionAnnotation annotation, Position position) {
178             fMap.put(annotation, position);
179         }
180
181         /**
182          * Returns <code>true</code> if header comments should be collapsed.
183          *
184          * @return <code>true</code> if header comments should be collapsed
185          */

186         public boolean collapseHeaderComments() {
187             return fAllowCollapsing && fCollapseHeaderComments;
188         }
189
190         /**
191          * Returns <code>true</code> if import containers should be collapsed.
192          *
193          * @return <code>true</code> if import containers should be collapsed
194          */

195         public boolean collapseImportContainer() {
196             return fAllowCollapsing && fCollapseImportContainer;
197         }
198
199         /**
200          * Returns <code>true</code> if inner types should be collapsed.
201          *
202          * @return <code>true</code> if inner types should be collapsed
203          */

204         public boolean collapseInnerTypes() {
205             return fAllowCollapsing && fCollapseInnerTypes;
206         }
207
208         /**
209          * Returns <code>true</code> if javadoc comments should be collapsed.
210          *
211          * @return <code>true</code> if javadoc comments should be collapsed
212          */

213         public boolean collapseJavadoc() {
214             return fAllowCollapsing && fCollapseJavadoc;
215         }
216
217         /**
218          * Returns <code>true</code> if methods should be collapsed.
219          *
220          * @return <code>true</code> if methods should be collapsed
221          */

222         public boolean collapseMembers() {
223             return fAllowCollapsing && fCollapseMembers;
224         }
225     }
226     
227     /**
228      * A {@link ProjectionAnnotation} for java code.
229      */

230     protected static final class JavaProjectionAnnotation extends ProjectionAnnotation {
231
232         private IJavaElement fJavaElement;
233         private boolean fIsComment;
234
235         /**
236          * Creates a new projection annotation.
237          *
238          * @param isCollapsed <code>true</code> to set the initial state to collapsed,
239          * <code>false</code> to set it to expanded
240          * @param element the java element this annotation refers to
241          * @param isComment <code>true</code> for a foldable comment, <code>false</code> for a
242          * foldable code element
243          */

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         /*
267          * @see java.lang.Object#toString()
268          */

269         public String JavaDoc toString() {
270             return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
271
"\telement: \t"+ fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
272
"\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
273
"\tcomment: \t" + isComment() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
274
}
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     /**
288      * Filter for annotations.
289      */

290     private static interface Filter {
291         boolean match(JavaProjectionAnnotation annotation);
292     }
293     
294     /**
295      * Matches comments.
296      */

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     /**
307      * Matches members.
308      */

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     /**
324      * Matches java elements contained in a certain set.
325      */

326     private static final class JavaElementSetFilter implements Filter {
327         private final Set JavaDoc/*<? extends IJavaElement>*/ fSet;
328         private final boolean fMatchCollapsed;
329
330         private JavaElementSetFilter(Set JavaDoc/*<? extends IJavaElement>*/ 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         /*
350          * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
351          */

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         /**
369          * Ignore the delta if there are errors on the caret line.
370          * <p>
371          * We don't ignore the delta if an import is added and the
372          * caret isn't inside the import container.
373          * </p>
374          *
375          * @param ast the compilation unit AST
376          * @param delta the Java element delta for the given AST element
377          * @return <code>true</code> if the delta should be ignored
378          * @since 3.3
379          */

380         private boolean shouldIgnoreDelta(CompilationUnit ast, IJavaElementDelta delta) {
381             if (ast == null)
382                 return false; // can't compute
383

384             IDocument document= getDocument();
385             if (document == null)
386                 return false; // can't compute
387

388             JavaEditor editor= fEditor;
389             if (editor == null || editor.getCachedSelectedRange() == null)
390                 return false; // can't compute
391

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; // can't compute
401
}
402             
403             int caretLine= 0;
404             try {
405                 caretLine= document.getLineOfOffset(editor.getCachedSelectedRange().x) + 1;
406             } catch (BadLocationException x) {
407                 return false; // can't compute
408
}
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     /**
447      * Projection position that will return two foldable regions: one folding away
448      * the region from after the '/**' to the beginning of the content, the other
449      * from after the first content line until after the comment.
450      */

451     private static final class CommentPosition extends Position implements IProjectionPosition {
452         CommentPosition(int offset, int length) {
453             super(offset, length);
454         }
455
456         /*
457          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
458          */

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"); //$NON-NLS-1$
469
Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
470

471             IRegion preRegion;
472             if (firstLine < captionLine) {
473 // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
474
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         /**
499          * Finds the offset of the first identifier part within <code>content</code>.
500          * Returns 0 if none is found.
501          *
502          * @param content the content to search
503          * @param prefixEnd the end of the prefix
504          * @return the first index of a unicode identifier part, or zero if none can
505          * be found
506          */

507         private int findFirstContent(final CharSequence JavaDoc 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 // /**
517
// * Finds the offset of the first identifier part within <code>content</code>.
518
// * Returns 0 if none is found.
519
// *
520
// * @param content the content to search
521
// * @return the first index of a unicode identifier part, or zero if none can
522
// * be found
523
// */
524
// private int findPrefixEnd(final CharSequence content) {
525
// // return the index after the leading '/*' or '/**'
526
// int len= content.length();
527
// int i= 0;
528
// while (i < len && isWhiteSpace(content.charAt(i)))
529
// i++;
530
// if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*')
531
// if (len >= i + 3 && content.charAt(i + 2) == '*')
532
// return i + 3;
533
// else
534
// return i + 2;
535
// else
536
// return i;
537
// }
538
//
539
// private boolean isWhiteSpace(char c) {
540
// return c == ' ' || c == '\t';
541
// }
542

543         /*
544          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
545          */

546         public int computeCaptionOffset(IDocument document) {
547 // return 0;
548
DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length);
549             return findFirstContent(sequence, 0);
550         }
551     }
552
553     /**
554      * Projection position that will return two foldable regions: one folding away
555      * the lines before the one containing the simple name of the java element, one
556      * folding away any lines after the caption.
557      */

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         /*
574          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
575          */

576         public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
577             int nameStart= offset;
578             try {
579                 /* The member's name range may not be correct. However,
580                  * reconciling would trigger another element delta which would
581                  * lead to reentrant situations. Therefore, we optimistically
582                  * assume that the name range is correct, but double check the
583                  * received lines below. */

584                 ISourceRange nameRange= fMember.getNameRange();
585                 if (nameRange != null)
586                     nameStart= nameRange.getOffset();
587
588             } catch (JavaModelException e) {
589                 // ignore and use default
590
}
591
592             int firstLine= document.getLineOfOffset(offset);
593             int captionLine= document.getLineOfOffset(nameStart);
594             int lastLine= document.getLineOfOffset(offset + length);
595
596             /* see comment above - adjust the caption line to be inside the
597              * entire folded region, and rely on later element deltas to correct
598              * the name range. */

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         /*
631          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
632          */

633         public int computeCaptionOffset(IDocument document) throws BadLocationException {
634             int nameStart= offset;
635             try {
636                 // need a reconcile here?
637
ISourceRange nameRange= fMember.getNameRange();
638                 if (nameRange != null)
639                     nameStart= nameRange.getOffset();
640             } catch (JavaModelException e) {
641                 // ignore and use default
642
}
643
644             return nameStart - offset;
645         }
646
647     }
648     
649     /**
650      * Internal projection listener.
651      */

652     private final class ProjectionListener implements IProjectionListener {
653         private ProjectionViewer fViewer;
654
655         /**
656          * Registers the listener with the viewer.
657          *
658          * @param viewer the viewer to register a listener with
659          */

660         public ProjectionListener(ProjectionViewer viewer) {
661             Assert.isLegal(viewer != null);
662             fViewer= viewer;
663             fViewer.addProjectionListener(this);
664         }
665         
666         /**
667          * Disposes of this listener and removes the projection listener from the viewer.
668          */

669         public void dispose() {
670             if (fViewer != null) {
671                 fViewer.removeProjectionListener(this);
672                 fViewer= null;
673             }
674         }
675         
676         /*
677          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
678          */

679         public void projectionEnabled() {
680             handleProjectionEnabled();
681         }
682
683         /*
684          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
685          */

686         public void projectionDisabled() {
687             handleProjectionDisabled();
688         }
689     }
690     
691     /* context and listeners */
692     private JavaEditor fEditor;
693     private ProjectionListener fProjectionListener;
694     private IJavaElement fInput;
695     private IElementChangedListener fElementListener;
696
697     /* preferences */
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     /* filters */
705     /** Member filter, matches nested members (but not top-level types). */
706     private final Filter fMemberFilter = new MemberFilter();
707     /** Comment filter, matches comments. */
708     private final Filter fCommentFilter = new CommentFilter();
709     
710     /**
711      * Reusable scanner.
712      * @since 3.3
713      */

714     private IScanner fSharedScanner= ToolFactory.createScanner(true, false, false, false);
715     
716     private volatile int fUpdatingCount= 0;
717
718     /**
719      * Creates a new folding provider. It must be
720      * {@link #install(ITextEditor, ProjectionViewer) installed} on an editor/viewer pair before it
721      * can be used, and {@link #uninstall() uninstalled} when not used any longer.
722      * <p>
723      * The projection state may be reset by calling {@link #initialize()}.
724      * </p>
725      */

726     public DefaultJavaFoldingStructureProvider() {
727     }
728
729     /**
730      * {@inheritDoc}
731      * <p>
732      * Subclasses may extend.
733      * </p>
734      *
735      * @param editor {@inheritDoc}
736      * @param viewer {@inheritDoc}
737      */

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     /**
751      * {@inheritDoc}
752      * <p>
753      * Subclasses may extend.
754      * </p>
755      */

756     public void uninstall() {
757         internalUninstall();
758     }
759     
760     /**
761      * Internal implementation of {@link #uninstall()}.
762      */

763     private void internalUninstall() {
764         if (isInstalled()) {
765             handleProjectionDisabled();
766             fProjectionListener.dispose();
767             fProjectionListener= null;
768             fEditor= null;
769         }
770     }
771
772     /**
773      * Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
774      *
775      * @return <code>true</code> if the provider is installed, <code>false</code> otherwise
776      */

777     protected final boolean isInstalled() {
778         return fEditor != null;
779     }
780
781     /**
782      * Called whenever projection is enabled, for example when the viewer issues a
783      * {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider
784      * is already enabled when this method is called, it is first
785      * {@link #handleProjectionDisabled() disabled}.
786      * <p>
787      * Subclasses may extend.
788      * </p>
789      */

790     protected void handleProjectionEnabled() {
791         // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
792
// projectionEnabled messages are not always paired with projectionDisabled
793
// i.e. multiple enabled messages may be sent out.
794
// we have to make sure that we disable first when getting an enable
795
// message.
796
handleProjectionDisabled();
797
798         if (isInstalled()) {
799             initialize();
800             fElementListener= new ElementChangedListener();
801             JavaCore.addElementChangedListener(fElementListener);
802         }
803     }
804
805     /**
806      * Called whenever projection is disabled, for example when the provider is
807      * {@link #uninstall() uninstalled}, when the viewer issues a
808      * {@link IProjectionListener#projectionDisabled() projectionDisabled} message and before
809      * {@link #handleProjectionEnabled() enabling} the provider. Implementations must be prepared to
810      * handle multiple calls to this method even if the provider is already disabled.
811      * <p>
812      * Subclasses may extend.
813      * </p>
814      */

815     protected void handleProjectionDisabled() {
816         if (fElementListener != null) {
817             JavaCore.removeElementChangedListener(fElementListener);
818             fElementListener= null;
819         }
820     }
821
822     /*
823      * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider#initialize()
824      */

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; // reuse scanner
856

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 JavaDoc additions= new HashMap JavaDoc();
880         List JavaDoc deletions= new ArrayList JavaDoc();
881         List JavaDoc updates= new ArrayList JavaDoc();
882
883         computeFoldingStructure(ctx);
884         Map JavaDoc newStructure= ctx.fMap;
885         Map JavaDoc oldStructure= computeCurrentStructure(ctx);
886
887         Iterator JavaDoc 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             /*
894              * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=130472 and
895              * https://bugs.eclipse.org/bugs/show_bug.cgi?id=127445 In the presence of syntax
896              * errors, anonymous types may have a source range offset of 0. When such a situation is
897              * encountered, we ignore the proposed folding range: if no corresponding folding range
898              * exists, it is silently ignored; if there *is* a matching folding range, we ignore the
899              * position update and keep the old range, in order to keep the folding structure
900              * stable.
901              */

902             boolean isMalformedAnonymousType= newPosition.getOffset() == 0 && element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element);
903             List JavaDoc annotations= (List JavaDoc) oldStructure.get(element);
904             if (annotations == null) {
905                 if (!isMalformedAnonymousType)
906                     additions.put(newAnnotation, newPosition);
907             } else {
908                 Iterator JavaDoc 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 JavaDoc list= (List JavaDoc) 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 JavaDoc 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     /**
985      * Computes the folding structure for a given {@link IJavaElement java element}. Computed
986      * projection annotations are
987      * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) added}
988      * to the computation context.
989      * <p>
990      * Subclasses may extend or replace. The default implementation creates projection annotations
991      * for the following elements:
992      * <ul>
993      * <li>true members (not for top-level types)</li>
994      * <li>the javadoc comments of any member</li>
995      * <li>header comments (javadoc or multi-line comments appearing before the first type's
996      * javadoc or before the package or import declarations).</li>
997      * </ul>
998      * </p>
999      *
1000     * @param element the java element to compute the folding structure for
1001     * @param ctx the computation context
1002     */

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            // comments
1028
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            // code
1044
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    /**
1056     * Returns <code>true</code> if <code>type</code> is an anonymous enum declaration,
1057     * <code>false</code> otherwise. See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=143276
1058     *
1059     * @param type the type to test
1060     * @return <code>true</code> if <code>type</code> is an anonymous enum declaration
1061     * @since 3.3
1062     */

1063    private boolean isAnonymousEnum(IType type) {
1064        try {
1065            return type.isEnum() && type.isAnonymous();
1066        } catch (JavaModelException x) {
1067            return false; // optimistically
1068
}
1069    }
1070
1071    /**
1072     * Returns <code>true</code> if <code>type</code> is not a top-level type, <code>false</code> if it is.
1073     *
1074     * @param type the type to test
1075     * @return <code>true</code> if <code>type</code> is an inner type
1076     */

1077    private boolean isInnerType(IType type) {
1078        return type.getDeclaringType() != null;
1079    }
1080
1081    /**
1082     * Computes the projection ranges for a given <code>ISourceReference</code>. More than one
1083     * range or none at all may be returned. If there are no foldable regions, an empty array is
1084     * returned.
1085     * <p>
1086     * The last region in the returned array (if not empty) describes the region for the java
1087     * element that implements the source reference. Any preceding regions describe javadoc comments
1088     * of that java element.
1089     * </p>
1090     *
1091     * @param reference a java element that is a source reference
1092     * @param ctx the folding context
1093     * @return the regions to be folded
1094     */

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 JavaDoc contents= reference.getSource();
1102                if (contents == null)
1103                    return new IRegion[0];
1104
1105                List JavaDoc regions= new ArrayList JavaDoc();
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        // search at most up to the first type
1153
ISourceRange range= ctx.getFirstType().getSourceRange();
1154        if (range == null)
1155            return null;
1156        int start= 0;
1157        int end= range.getOffset();
1158
1159
1160        /* code adapted from CommentFormattingStrategy:
1161         * scan the header content up to the first type. Once a comment is
1162         * found, accumulate any additional comments up to the stop condition.
1163         * The stop condition is reaching a package declaration, import container,
1164         * or the end of the input.
1165         */

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    /**
1197     * Creates a comment folding position from an
1198     * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned}
1199     * region.
1200     *
1201     * @param aligned an aligned region
1202     * @return a folding position corresponding to <code>aligned</code>
1203     */

1204    protected final Position createCommentPosition(IRegion aligned) {
1205        return new CommentPosition(aligned.getOffset(), aligned.getLength());
1206    }
1207    
1208    /**
1209     * Creates a folding position that remembers its member from an
1210     * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned}
1211     * region.
1212     *
1213     * @param aligned an aligned region
1214     * @param member the member to remember
1215     * @return a folding position corresponding to <code>aligned</code>
1216     */

1217    protected final Position createMemberPosition(IRegion aligned, IMember member) {
1218        return new JavaElementPosition(aligned.getOffset(), aligned.getLength(), member);
1219    }
1220
1221    /**
1222     * Aligns <code>region</code> to start and end at a line offset. The region's start is
1223     * decreased to the next line offset, and the end offset increased to the next line start or the
1224     * end of the document. <code>null</code> is returned if <code>region</code> is
1225     * <code>null</code> itself or does not comprise at least one line delimiter, as a single line
1226     * cannot be folded.
1227     *
1228     * @param region the region to align, may be <code>null</code>
1229     * @param ctx the folding context
1230     * @return a region equal or greater than <code>region</code> that is aligned with line
1231     * offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
1232     * only one line)
1233     */

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            // concurrent modification
1258
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    /**
1279     * Matches deleted annotations to changed or added ones. A deleted
1280     * annotation/position tuple that has a matching addition / change
1281     * is updated and marked as changed. The matching tuple is not added
1282     * (for additions) or marked as deletion instead (for changes). The
1283     * result is that more annotations are changed and fewer get
1284     * deleted/re-added.
1285     *
1286     * @param deletions list with deleted annotations
1287     * @param additions map with position to annotation mappings
1288     * @param changes list with changed annotations
1289     * @param ctx the context
1290     */

1291    private void match(List JavaDoc deletions, Map JavaDoc additions, List JavaDoc changes, FoldingStructureComputationContext ctx) {
1292        if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
1293            return;
1294
1295        List JavaDoc newDeletions= new ArrayList JavaDoc();
1296        List JavaDoc newChanges= new ArrayList JavaDoc();
1297
1298        Iterator JavaDoc 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    /**
1336     * Finds a match for <code>tuple</code> in a collection of
1337     * annotations. The positions for the
1338     * <code>JavaProjectionAnnotation</code> instances in
1339     * <code>annotations</code> can be found in the passed
1340     * <code>positionMap</code> or <code>fCachedModel</code> if
1341     * <code>positionMap</code> is <code>null</code>.
1342     * <p>
1343     * A tuple is said to match another if their annotations have the
1344     * same comment flag and their position offsets are equal.
1345     * </p>
1346     * <p>
1347     * If a match is found, the annotation gets removed from
1348     * <code>annotations</code>.
1349     * </p>
1350     *
1351     * @param tuple the tuple for which we want to find a match
1352     * @param annotations collection of
1353     * <code>JavaProjectionAnnotation</code>
1354     * @param positionMap a <code>Map&lt;Annotation, Position&gt;</code>
1355     * or <code>null</code>
1356     * @param ctx the context
1357     * @return a matching tuple or <code>null</code> for no match
1358     */

1359    private Tuple findMatch(Tuple tuple, Collection JavaDoc annotations, Map JavaDoc positionMap, FoldingStructureComputationContext ctx) {
1360        Iterator JavaDoc 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 JavaDoc computeCurrentStructure(FoldingStructureComputationContext ctx) {
1379        Map JavaDoc map= new HashMap JavaDoc();
1380        ProjectionAnnotationModel model= ctx.getModel();
1381        Iterator JavaDoc e= model.getAnnotationIterator();
1382        while (e.hasNext()) {
1383            Object JavaDoc 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 JavaDoc list= (List JavaDoc) map.get(java.getElement());
1389                if (list == null) {
1390                    list= new ArrayList JavaDoc(2);
1391                    map.put(java.getElement(), list);
1392                }
1393                list.add(new Tuple(java, position));
1394            }
1395        }
1396
1397        Comparator JavaDoc comparator= new Comparator JavaDoc() {
1398            public int compare(Object JavaDoc o1, Object JavaDoc o2) {
1399                return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset();
1400            }
1401        };
1402        for (Iterator JavaDoc it= map.values().iterator(); it.hasNext();) {
1403            List JavaDoc list= (List JavaDoc) it.next();
1404            Collections.sort(list, comparator);
1405        }
1406        return map;
1407    }
1408    
1409    /*
1410     * @see IJavaFoldingStructureProviderExtension#collapseMembers()
1411     * @since 3.2
1412     */

1413    public final void collapseMembers() {
1414        modifyFiltered(fMemberFilter, false);
1415    }
1416    
1417    /*
1418     * @see IJavaFoldingStructureProviderExtension#collapseComments()
1419     * @since 3.2
1420     */

1421    public final void collapseComments() {
1422        modifyFiltered(fCommentFilter, false);
1423    }
1424
1425    /*
1426     * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#collapseElements(org.eclipse.jdt.core.IJavaElement[])
1427     */

1428    public final void collapseElements(IJavaElement[] elements) {
1429        Set JavaDoc set= new HashSet JavaDoc(Arrays.asList(elements));
1430        modifyFiltered(new JavaElementSetFilter(set, false), false);
1431    }
1432
1433    /*
1434     * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#expandElements(org.eclipse.jdt.core.IJavaElement[])
1435     */

1436    public final void expandElements(IJavaElement[] elements) {
1437        Set JavaDoc set= new HashSet JavaDoc(Arrays.asList(elements));
1438        modifyFiltered(new JavaElementSetFilter(set, true), true);
1439    }
1440
1441    /**
1442     * Collapses or expands all annotations matched by the passed filter.
1443     *
1444     * @param filter the filter to use to select which annotations to collapse
1445     * @param expand <code>true</code> to expand the matched annotations, <code>false</code> to
1446     * collapse them
1447     */

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 JavaDoc modified= new ArrayList JavaDoc();
1457        Iterator JavaDoc iter= model.getAnnotationIterator();
1458        while (iter.hasNext()) {
1459            Object JavaDoc 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