KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*******************************************************************************
2  * Copyright (c) 2000, 2005 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.internal.ui.text.folding;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Collection JavaDoc;
15 import java.util.Collections JavaDoc;
16 import java.util.Comparator JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.Iterator JavaDoc;
19 import java.util.LinkedHashMap JavaDoc;
20 import java.util.LinkedList JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import org.eclipse.jface.preference.IPreferenceStore;
25
26 import org.eclipse.jface.text.Assert;
27 import org.eclipse.jface.text.BadLocationException;
28 import org.eclipse.jface.text.IDocument;
29 import org.eclipse.jface.text.IRegion;
30 import org.eclipse.jface.text.Position;
31 import org.eclipse.jface.text.Region;
32 import org.eclipse.jface.text.source.Annotation;
33 import org.eclipse.jface.text.source.IAnnotationModel;
34 import org.eclipse.jface.text.source.projection.IProjectionListener;
35 import org.eclipse.jface.text.source.projection.IProjectionPosition;
36 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
37 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
38 import org.eclipse.jface.text.source.projection.ProjectionViewer;
39
40 import org.eclipse.ui.texteditor.IDocumentProvider;
41 import org.eclipse.ui.texteditor.ITextEditor;
42
43 import org.eclipse.jdt.core.ElementChangedEvent;
44 import org.eclipse.jdt.core.ICompilationUnit;
45 import org.eclipse.jdt.core.IElementChangedListener;
46 import org.eclipse.jdt.core.IJavaElement;
47 import org.eclipse.jdt.core.IJavaElementDelta;
48 import org.eclipse.jdt.core.IMember;
49 import org.eclipse.jdt.core.IParent;
50 import org.eclipse.jdt.core.ISourceRange;
51 import org.eclipse.jdt.core.ISourceReference;
52 import org.eclipse.jdt.core.IType;
53 import org.eclipse.jdt.core.JavaCore;
54 import org.eclipse.jdt.core.JavaModelException;
55 import org.eclipse.jdt.core.ToolFactory;
56 import org.eclipse.jdt.core.compiler.IScanner;
57 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
58 import org.eclipse.jdt.core.compiler.InvalidInputException;
59
60 import org.eclipse.jdt.ui.IWorkingCopyManager;
61 import org.eclipse.jdt.ui.PreferenceConstants;
62 import org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider;
63
64 import org.eclipse.jdt.internal.ui.JavaPlugin;
65 import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
66 import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
67 import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
68 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
69 import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator;
70
71
72 /**
73  * Updates the projection model of a class file or compilation unit.
74  *
75  * @since 3.0
76  */

77 public class DefaultJavaFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider {
78
79     private static final class JavaProjectionAnnotation extends ProjectionAnnotation {
80
81         private IJavaElement fJavaElement;
82         private boolean fIsComment;
83
84         public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) {
85             super(isCollapsed);
86             fJavaElement= element;
87             fIsComment= isComment;
88         }
89
90         public IJavaElement getElement() {
91             return fJavaElement;
92         }
93
94         public void setElement(IJavaElement element) {
95             fJavaElement= element;
96         }
97
98         public boolean isComment() {
99             return fIsComment;
100         }
101
102         public void setIsComment(boolean isComment) {
103             fIsComment= isComment;
104         }
105
106         /*
107          * @see java.lang.Object#toString()
108          */

109         public String JavaDoc toString() {
110             return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
111
"\telement: \t"+fJavaElement.toString()+"\n" + //$NON-NLS-1$ //$NON-NLS-2$
112
"\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
113
"\tcomment: \t" + fIsComment + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
114
}
115     }
116
117     private static final class Tuple {
118         JavaProjectionAnnotation annotation;
119         Position position;
120         Tuple(JavaProjectionAnnotation annotation, Position position) {
121             this.annotation= annotation;
122             this.position= position;
123         }
124     }
125
126     private class ElementChangedListener implements IElementChangedListener {
127
128         /*
129          * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
130          */

131         public void elementChanged(ElementChangedEvent e) {
132             IJavaElementDelta delta= findElement(fInput, e.getDelta());
133             if (delta != null)
134                 processDelta(delta);
135         }
136
137         private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {
138
139             if (delta == null || target == null)
140                 return null;
141
142             IJavaElement element= delta.getElement();
143
144             if (element.getElementType() > IJavaElement.CLASS_FILE)
145                 return null;
146
147             if (target.equals(element))
148                 return delta;
149
150             IJavaElementDelta[] children= delta.getAffectedChildren();
151
152             for (int i= 0; i < children.length; i++) {
153                 IJavaElementDelta d= findElement(target, children[i]);
154                 if (d != null)
155                     return d;
156             }
157
158             return null;
159         }
160     }
161
162     /**
163      * Projection position that will return two foldable regions: one folding away
164      * the region from after the '/**' to the beginning of the content, the other
165      * from after the first content line until after the comment.
166      *
167      * @since 3.1
168      */

169     private static final class CommentPosition extends Position implements IProjectionPosition {
170         CommentPosition(int offset, int length) {
171             super(offset, length);
172         }
173
174         /*
175          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
176          */

177         public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
178             DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length);
179             int prefixEnd= 0;
180             int contentStart= findFirstContent(sequence, prefixEnd);
181
182             int firstLine= document.getLineOfOffset(offset + prefixEnd);
183             int captionLine= document.getLineOfOffset(offset + contentStart);
184             int lastLine= document.getLineOfOffset(offset + length);
185
186             Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$
187
Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
188

189             IRegion preRegion;
190             if (firstLine < captionLine) {
191 // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
192
int preOffset= document.getLineOffset(firstLine);
193                 IRegion preEndLineInfo= document.getLineInformation(captionLine);
194                 int preEnd= preEndLineInfo.getOffset();
195                 preRegion= new Region(preOffset, preEnd - preOffset);
196             } else {
197                 preRegion= null;
198             }
199
200             if (captionLine < lastLine) {
201                 int postOffset= document.getLineOffset(captionLine + 1);
202                 IRegion postRegion= new Region(postOffset, offset + length - postOffset);
203
204                 if (preRegion == null)
205                     return new IRegion[] { postRegion };
206
207                 return new IRegion[] { preRegion, postRegion };
208             }
209
210             if (preRegion != null)
211                 return new IRegion[] { preRegion };
212
213             return null;
214         }
215
216         /**
217          * Finds the offset of the first identifier part within <code>content</code>.
218          * Returns 0 if none is found.
219          *
220          * @param content the content to search
221          * @return the first index of a unicode identifier part, or zero if none can
222          * be found
223          */

224         private int findFirstContent(final CharSequence JavaDoc content, int prefixEnd) {
225             int lenght= content.length();
226             for (int i= prefixEnd; i < lenght; i++) {
227                 if (Character.isUnicodeIdentifierPart(content.charAt(i)))
228                     return i;
229             }
230             return 0;
231         }
232
233 // /**
234
// * Finds the offset of the first identifier part within <code>content</code>.
235
// * Returns 0 if none is found.
236
// *
237
// * @param content the content to search
238
// * @return the first index of a unicode identifier part, or zero if none can
239
// * be found
240
// */
241
// private int findPrefixEnd(final CharSequence content) {
242
// // return the index after the leading '/*' or '/**'
243
// int len= content.length();
244
// int i= 0;
245
// while (i < len && isWhiteSpace(content.charAt(i)))
246
// i++;
247
// if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*')
248
// if (len >= i + 3 && content.charAt(i + 2) == '*')
249
// return i + 3;
250
// else
251
// return i + 2;
252
// else
253
// return i;
254
// }
255
//
256
// private boolean isWhiteSpace(char c) {
257
// return c == ' ' || c == '\t';
258
// }
259

260         /*
261          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
262          */

263         public int computeCaptionOffset(IDocument document) {
264 // return 0;
265
DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length);
266             return findFirstContent(sequence, 0);
267         }
268     }
269
270     /**
271      * Projection position that will return two foldable regions: one folding away
272      * the lines before the one containing the simple name of the java element, one
273      * folding away any lines after the caption.
274      *
275      * @since 3.1
276      */

277     private static final class JavaElementPosition extends Position implements IProjectionPosition {
278
279         private IMember fMember;
280
281         public JavaElementPosition(int offset, int length, IMember member) {
282             super(offset, length);
283             Assert.isNotNull(member);
284             fMember= member;
285         }
286         
287         public void setMember(IMember member) {
288             Assert.isNotNull(member);
289             fMember= member;
290         }
291         
292         /*
293          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
294          */

295         public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
296             int nameStart= offset;
297             try {
298                 /* The member's name range may not be correct. However,
299                  * reconciling would trigger another element delta which would
300                  * lead to reentrant situations. Therefore, we optimistically
301                  * assume that the name range is correct, but double check the
302                  * received lines below. */

303                 ISourceRange nameRange= fMember.getNameRange();
304                 if (nameRange != null)
305                     nameStart= nameRange.getOffset();
306
307             } catch (JavaModelException e) {
308                 // ignore and use default
309
}
310
311             int firstLine= document.getLineOfOffset(offset);
312             int captionLine= document.getLineOfOffset(nameStart);
313             int lastLine= document.getLineOfOffset(offset + length);
314
315             /* see comment above - adjust the caption line to be inside the
316              * entire folded region, and rely on later element deltas to correct
317              * the name range. */

318             if (captionLine < firstLine)
319                 captionLine= firstLine;
320             if (captionLine > lastLine)
321                 captionLine= lastLine;
322
323             IRegion preRegion;
324             if (firstLine < captionLine) {
325                 int preOffset= document.getLineOffset(firstLine);
326                 IRegion preEndLineInfo= document.getLineInformation(captionLine);
327                 int preEnd= preEndLineInfo.getOffset();
328                 preRegion= new Region(preOffset, preEnd - preOffset);
329             } else {
330                 preRegion= null;
331             }
332
333             if (captionLine < lastLine) {
334                 int postOffset= document.getLineOffset(captionLine + 1);
335                 IRegion postRegion= new Region(postOffset, offset + length - postOffset);
336
337                 if (preRegion == null)
338                     return new IRegion[] { postRegion };
339
340                 return new IRegion[] { preRegion, postRegion };
341             }
342
343             if (preRegion != null)
344                 return new IRegion[] { preRegion };
345
346             return null;
347         }
348
349         /*
350          * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
351          */

352         public int computeCaptionOffset(IDocument document) throws BadLocationException {
353             int nameStart= offset;
354             try {
355                 // need a reconcile here?
356
ISourceRange nameRange= fMember.getNameRange();
357                 if (nameRange != null)
358                     nameStart= nameRange.getOffset();
359             } catch (JavaModelException e) {
360                 // ignore and use default
361
}
362
363             return nameStart - offset;
364         }
365
366     }
367
368     private IDocument fCachedDocument;
369     private ProjectionAnnotationModel fCachedModel;
370
371     private ITextEditor fEditor;
372     private ProjectionViewer fViewer;
373     private IJavaElement fInput;
374     private IElementChangedListener fElementListener;
375
376     private boolean fAllowCollapsing= false;
377     private boolean fCollapseJavadoc= false;
378     private boolean fCollapseImportContainer= true;
379     private boolean fCollapseInnerTypes= true;
380     private boolean fCollapseMethods= false;
381     private boolean fCollapseHeaderComments= true;
382
383     /* caches for header comment extraction. */
384     private IType fFirstType;
385     private boolean fHasHeaderComment;
386
387
388     public DefaultJavaFoldingStructureProvider() {
389     }
390
391     public void install(ITextEditor editor, ProjectionViewer viewer) {
392         if (editor instanceof JavaEditor) {
393             fEditor= editor;
394             fViewer= viewer;
395             fViewer.addProjectionListener(this);
396         }
397     }
398
399     public void uninstall() {
400         if (isInstalled()) {
401             projectionDisabled();
402             fViewer.removeProjectionListener(this);
403             fViewer= null;
404             fEditor= null;
405         }
406     }
407
408     protected boolean isInstalled() {
409         return fEditor != null;
410     }
411
412     /*
413      * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
414      */

415     public void projectionEnabled() {
416         // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
417
// projectionEnabled messages are not always paired with projectionDisabled
418
// i.e. multiple enabled messages may be sent out.
419
// we have to make sure that we disable first when getting an enable
420
// message.
421
projectionDisabled();
422
423         if (fEditor instanceof JavaEditor) {
424             initialize();
425             fElementListener= new ElementChangedListener();
426             JavaCore.addElementChangedListener(fElementListener);
427         }
428     }
429
430     /*
431      * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
432      */

433     public void projectionDisabled() {
434         fCachedDocument= null;
435         if (fElementListener != null) {
436             JavaCore.removeElementChangedListener(fElementListener);
437             fElementListener= null;
438         }
439     }
440
441     public void initialize() {
442
443         if (!isInstalled())
444             return;
445
446         initializePreferences();
447
448         try {
449
450             IDocumentProvider provider= fEditor.getDocumentProvider();
451             fCachedDocument= provider.getDocument(fEditor.getEditorInput());
452             fAllowCollapsing= true;
453
454             fFirstType= null;
455             fHasHeaderComment= false;
456
457             if (fEditor instanceof CompilationUnitEditor) {
458                 IWorkingCopyManager manager= JavaPlugin.getDefault().getWorkingCopyManager();
459                 fInput= manager.getWorkingCopy(fEditor.getEditorInput());
460             } else if (fEditor instanceof ClassFileEditor) {
461                 IClassFileEditorInput editorInput= (IClassFileEditorInput) fEditor.getEditorInput();
462                 fInput= editorInput.getClassFile();
463             }
464
465             if (fInput != null) {
466                 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
467                 if (model != null) {
468                     fCachedModel= model;
469                     if (fInput instanceof ICompilationUnit) {
470                         ICompilationUnit unit= (ICompilationUnit) fInput;
471                         synchronized (unit) {
472                             try {
473                                 unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
474                             } catch (JavaModelException x) {
475                             }
476                         }
477                     }
478
479                     Map JavaDoc additions= computeAdditions((IParent) fInput);
480                     /*
481                      * Minimize the events being sent out - as this happens in the
482                      * UI thread merge everything into one call.
483                      */

484                     List JavaDoc removals= new LinkedList JavaDoc();
485                     Iterator JavaDoc existing= model.getAnnotationIterator();
486                     while (existing.hasNext())
487                         removals.add(existing.next());
488                     model.replaceAnnotations((Annotation[]) removals.toArray(new Annotation[removals.size()]), additions);
489                 }
490             }
491
492         } finally {
493             fCachedDocument= null;
494             fCachedModel= null;
495             fAllowCollapsing= false;
496
497             fFirstType= null;
498             fHasHeaderComment= false;
499         }
500     }
501
502     private void initializePreferences() {
503         IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
504         fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
505         fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
506         fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
507         fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
508         fCollapseHeaderComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
509     }
510
511     private Map JavaDoc computeAdditions(IParent parent) {
512         Map JavaDoc map= new LinkedHashMap JavaDoc(); // use a linked map to maintain ordering of comments
513
try {
514             computeAdditions(parent.getChildren(), map);
515         } catch (JavaModelException x) {
516         }
517         return map;
518     }
519
520     private void computeAdditions(IJavaElement[] elements, Map JavaDoc map) throws JavaModelException {
521         for (int i= 0; i < elements.length; i++) {
522             IJavaElement element= elements[i];
523
524             computeAdditions(element, map);
525
526             if (element instanceof IParent) {
527                 IParent parent= (IParent) element;
528                 computeAdditions(parent.getChildren(), map);
529             }
530         }
531     }
532
533     private void computeAdditions(IJavaElement element, Map JavaDoc map) {
534
535         boolean createProjection= false;
536
537         boolean collapse= false;
538         switch (element.getElementType()) {
539
540             case IJavaElement.IMPORT_CONTAINER:
541                 collapse= fAllowCollapsing && fCollapseImportContainer;
542                 createProjection= true;
543                 break;
544             case IJavaElement.TYPE:
545                 collapse= fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element);
546                 createProjection= true;
547                 break;
548             case IJavaElement.METHOD:
549                 collapse= fAllowCollapsing && fCollapseMethods;
550                 createProjection= true;
551                 break;
552         }
553
554         if (createProjection) {
555             IRegion[] regions= computeProjectionRanges(element);
556             if (regions != null) {
557                 // comments
558
for (int i= 0; i < regions.length - 1; i++) {
559                     Position position= createProjectionPosition(regions[i], null);
560                     boolean commentCollapse;
561                     if (position != null) {
562                         if (i == 0 && (regions.length > 2 || fHasHeaderComment) && element == fFirstType) {
563                             commentCollapse= fAllowCollapsing && fCollapseHeaderComments;
564                         } else {
565                             commentCollapse= fAllowCollapsing && fCollapseJavadoc;
566                         }
567                         map.put(new JavaProjectionAnnotation(element, commentCollapse, true), position);
568                     }
569                 }
570                 // code
571
Position position= createProjectionPosition(regions[regions.length - 1], element);
572                 if (position != null)
573                     map.put(new JavaProjectionAnnotation(element, collapse, false), position);
574             }
575         }
576     }
577
578     private boolean isInnerType(IType type) {
579
580         try {
581             return type.isMember();
582         } catch (JavaModelException x) {
583             IJavaElement parent= type.getParent();
584             if (parent != null) {
585                 int parentType= parent.getElementType();
586                 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
587             }
588         }
589
590         return false;
591     }
592
593     /**
594      * Computes the projection ranges for a given <code>IJavaElement</code>.
595      * More than one range may be returned if the element has a leading comment
596      * which gets folded separately. If there are no foldable regions,
597      * <code>null</code> is returned.
598      *
599      * @param element the java element that can be folded
600      * @return the regions to be folded, or <code>null</code> if there are
601      * none
602      */

603     private IRegion[] computeProjectionRanges(IJavaElement element) {
604
605         try {
606             if (element instanceof ISourceReference) {
607                 ISourceReference reference= (ISourceReference) element;
608                 ISourceRange range= reference.getSourceRange();
609
610                 String JavaDoc contents= reference.getSource();
611                 if (contents == null)
612                     return null;
613
614                 List JavaDoc regions= new ArrayList JavaDoc();
615                 if (fFirstType == null && element instanceof IType) {
616                     fFirstType= (IType) element;
617                     IRegion headerComment= computeHeaderComment(fFirstType);
618                     if (headerComment != null) {
619                         regions.add(headerComment);
620                         fHasHeaderComment= true;
621                     }
622                 }
623
624                 IScanner scanner= ToolFactory.createScanner(true, false, false, false);
625                 scanner.setSource(contents.toCharArray());
626                 final int shift= range.getOffset();
627                 int start= shift;
628                 while (true) {
629
630                     int token= scanner.getNextToken();
631                     start= shift + scanner.getCurrentTokenStartPosition();
632
633                     switch (token) {
634                         case ITerminalSymbols.TokenNameCOMMENT_JAVADOC:
635                         case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
636                             int end= shift + scanner.getCurrentTokenEndPosition() + 1;
637                             regions.add(new Region(start, end - start));
638                         }
639                         case ITerminalSymbols.TokenNameCOMMENT_LINE:
640                             continue;
641                     }
642
643                     break;
644                 }
645
646                 regions.add(new Region(start, shift + range.getLength() - start));
647
648                 if (regions.size() > 0) {
649                     IRegion[] result= new IRegion[regions.size()];
650                     regions.toArray(result);
651                     return result;
652                 }
653             }
654         } catch (JavaModelException e) {
655         } catch (InvalidInputException e) {
656         }
657
658         return null;
659     }
660
661     private IRegion computeHeaderComment(IType type) throws JavaModelException {
662         if (fCachedDocument == null)
663             return null;
664
665         // search at most up to the first type
666
ISourceRange range= type.getSourceRange();
667         if (range == null)
668             return null;
669         int start= 0;
670         int end= range.getOffset();
671
672         if (fInput instanceof ISourceReference) {
673             String JavaDoc content;
674             try {
675                 content= fCachedDocument.get(start, end - start);
676             } catch (BadLocationException e) {
677                 return null; // ignore header comment in that case
678
}
679
680             /* code adapted from CommentFormattingStrategy:
681              * scan the header content up to the first type. Once a comment is
682              * found, accumulate any additional comments up to the stop condition.
683              * The stop condition is reaching a package declaration, import container,
684              * or the end of the input.
685              */

686             IScanner scanner= ToolFactory.createScanner(true, false, false, false);
687             scanner.setSource(content.toCharArray());
688
689             int headerStart= -1;
690             int headerEnd= -1;
691             try {
692                 boolean foundComment= false;
693                 int terminal= scanner.getNextToken();
694                 while (terminal != ITerminalSymbols.TokenNameEOF && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) {
695
696                     if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
697                         if (!foundComment)
698                             headerStart= scanner.getCurrentTokenStartPosition();
699                         headerEnd= scanner.getCurrentTokenEndPosition();
700                         foundComment= true;
701                     }
702                     terminal= scanner.getNextToken();
703                 }
704
705
706             } catch (InvalidInputException ex) {
707                 return null;
708             }
709
710             if (headerEnd != -1) {
711                 return new Region(headerStart, headerEnd - headerStart);
712             }
713         }
714         return null;
715     }
716
717     private Position createProjectionPosition(IRegion region, IJavaElement element) {
718
719         if (fCachedDocument == null)
720             return null;
721
722         try {
723
724             int start= fCachedDocument.getLineOfOffset(region.getOffset());
725             int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
726             if (start != end) {
727                 int offset= fCachedDocument.getLineOffset(start);
728                 int endOffset;
729                 if (fCachedDocument.getNumberOfLines() > end + 1)
730                     endOffset= fCachedDocument.getLineOffset(end + 1);
731                 else if (end > start)
732                     endOffset= fCachedDocument.getLineOffset(end) + fCachedDocument.getLineLength(end);
733                 else
734                     return null;
735                 if (element instanceof IMember)
736                     return new JavaElementPosition(offset, endOffset - offset, (IMember) element);
737                 else
738                     return new CommentPosition(offset, endOffset - offset);
739             }
740
741         } catch (BadLocationException x) {
742         }
743
744         return null;
745     }
746
747     protected void processDelta(IJavaElementDelta delta) {
748
749         if (!isInstalled())
750             return;
751
752         if ((delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == 0)
753             return;
754
755         ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
756         if (model == null)
757             return;
758
759         try {
760
761             IDocumentProvider provider= fEditor.getDocumentProvider();
762             fCachedDocument= provider.getDocument(fEditor.getEditorInput());
763             fCachedModel= model;
764             fAllowCollapsing= false;
765
766             fFirstType= null;
767             fHasHeaderComment= false;
768
769             Map JavaDoc additions= new HashMap JavaDoc();
770             List JavaDoc deletions= new ArrayList JavaDoc();
771             List JavaDoc updates= new ArrayList JavaDoc();
772
773             Map JavaDoc updated= computeAdditions((IParent) fInput);
774             Map JavaDoc previous= createAnnotationMap(model);
775
776
777             Iterator JavaDoc e= updated.keySet().iterator();
778             while (e.hasNext()) {
779                 JavaProjectionAnnotation newAnnotation= (JavaProjectionAnnotation) e.next();
780                 IJavaElement element= newAnnotation.getElement();
781                 Position newPosition= (Position) updated.get(newAnnotation);
782
783                 List JavaDoc annotations= (List JavaDoc) previous.get(element);
784                 if (annotations == null) {
785
786                     additions.put(newAnnotation, newPosition);
787
788                 } else {
789                     Iterator JavaDoc x= annotations.iterator();
790                     boolean matched= false;
791                     while (x.hasNext()) {
792                         Tuple tuple= (Tuple) x.next();
793                         JavaProjectionAnnotation existingAnnotation= tuple.annotation;
794                         Position existingPosition= tuple.position;
795                         if (newAnnotation.isComment() == existingAnnotation.isComment()) {
796                             if (existingPosition != null && (!newPosition.equals(existingPosition))) {
797                                 existingPosition.setOffset(newPosition.getOffset());
798                                 existingPosition.setLength(newPosition.getLength());
799                                 updates.add(existingAnnotation);
800                             }
801                             matched= true;
802                             x.remove();
803                             break;
804                         }
805                     }
806                     if (!matched)
807                         additions.put(newAnnotation, newPosition);
808
809                     if (annotations.isEmpty())
810                         previous.remove(element);
811                 }
812             }
813
814             e= previous.values().iterator();
815             while (e.hasNext()) {
816                 List JavaDoc list= (List JavaDoc) e.next();
817                 int size= list.size();
818                 for (int i= 0; i < size; i++)
819                     deletions.add(((Tuple) list.get(i)).annotation);
820             }
821
822             match(deletions, additions, updates);
823
824             Annotation[] removals= new Annotation[deletions.size()];
825             deletions.toArray(removals);
826             Annotation[] changes= new Annotation[updates.size()];
827             updates.toArray(changes);
828             model.modifyAnnotations(removals, additions, changes);
829
830         } finally {
831             fCachedDocument= null;
832             fAllowCollapsing= true;
833             fCachedModel= null;
834
835             fFirstType= null;
836             fHasHeaderComment= false;
837         }
838     }
839
840     /**
841      * Matches deleted annotations to changed or added ones. A deleted
842      * annotation/position tuple that has a matching addition / change
843      * is updated and marked as changed. The matching tuple is not added
844      * (for additions) or marked as deletion instead (for changes). The
845      * result is that more annotations are changed and fewer get
846      * deleted/re-added.
847      */

848     private void match(List JavaDoc deletions, Map JavaDoc additions, List JavaDoc changes) {
849         if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
850             return;
851
852         List JavaDoc newDeletions= new ArrayList JavaDoc();
853         List JavaDoc newChanges= new ArrayList JavaDoc();
854
855         Iterator JavaDoc deletionIterator= deletions.iterator();
856         while (deletionIterator.hasNext()) {
857             JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next();
858             Position deletedPosition= fCachedModel.getPosition(deleted);
859             if (deletedPosition == null)
860                 continue;
861             
862             Tuple deletedTuple= new Tuple(deleted, deletedPosition);
863
864             Tuple match= findMatch(deletedTuple, changes, null);
865             boolean addToDeletions= true;
866             if (match == null) {
867                 match= findMatch(deletedTuple, additions.keySet(), additions);
868                 addToDeletions= false;
869             }
870             
871             if (match != null) {
872                 IJavaElement element= match.annotation.getElement();
873                 deleted.setElement(element);
874                 deletedPosition.setLength(match.position.getLength());
875                 if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) {
876                     JavaElementPosition jep= (JavaElementPosition) deletedPosition;
877                     jep.setMember((IMember) element);
878                 }
879
880                 deletionIterator.remove();
881                 newChanges.add(deleted);
882
883                 if (addToDeletions)
884                     newDeletions.add(match.annotation);
885             }
886         }
887
888         deletions.addAll(newDeletions);
889         changes.addAll(newChanges);
890     }
891
892     /**
893      * Finds a match for <code>tuple</code> in a collection of
894      * annotations. The positions for the
895      * <code>JavaProjectionAnnotation</code> instances in
896      * <code>annotations</code> can be found in the passed
897      * <code>positionMap</code> or <code>fCachedModel</code> if
898      * <code>positionMap</code> is <code>null</code>.
899      * <p>
900      * A tuple is said to match another if their annotations have the
901      * same comment flag and their position offsets are equal.
902      * </p>
903      * <p>
904      * If a match is found, the annotation gets removed from
905      * <code>annotations</code>.
906      * </p>
907      *
908      * @param tuple the tuple for which we want to find a match
909      * @param annotations collection of
910      * <code>JavaProjectionAnnotation</code>
911      * @param positionMap a <code>Map&lt;Annotation, Position&gt;</code>
912      * or <code>null</code>
913      * @return a matching tuple or <code>null</code> for no match
914      */

915     private Tuple findMatch(Tuple tuple, Collection JavaDoc annotations, Map JavaDoc positionMap) {
916         Iterator JavaDoc it= annotations.iterator();
917         while (it.hasNext()) {
918             JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) it.next();
919             if (tuple.annotation.isComment() == annotation.isComment()) {
920                 Position position= positionMap == null ? fCachedModel.getPosition(annotation) : (Position) positionMap.get(annotation);
921                 if (position == null)
922                     continue;
923
924                 if (tuple.position.getOffset() == position.getOffset()) {
925                     it.remove();
926                     return new Tuple(annotation, position);
927                 }
928             }
929         }
930         
931         return null;
932     }
933
934     private Map JavaDoc createAnnotationMap(IAnnotationModel model) {
935         Map JavaDoc map= new HashMap JavaDoc();
936         Iterator JavaDoc e= model.getAnnotationIterator();
937         while (e.hasNext()) {
938             Object JavaDoc annotation= e.next();
939             if (annotation instanceof JavaProjectionAnnotation) {
940                 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation;
941                 Position position= model.getPosition(java);
942                 Assert.isNotNull(position);
943                 List JavaDoc list= (List JavaDoc) map.get(java.getElement());
944                 if (list == null) {
945                     list= new ArrayList JavaDoc(2);
946                     map.put(java.getElement(), list);
947                 }
948                 list.add(new Tuple(java, position));
949             }
950         }
951
952         Comparator JavaDoc comparator= new Comparator JavaDoc() {
953             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
954                 return ((Tuple) o1).position.getOffset() - ((Tuple) o2).position.getOffset();
955             }
956         };
957         for (Iterator JavaDoc it= map.values().iterator(); it.hasNext();) {
958             List JavaDoc list= (List JavaDoc) it.next();
959             Collections.sort(list, comparator);
960         }
961         return map;
962     }
963 }
964
Popular Tags