]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2006, 2011 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; | |
14 | import java.util.Arrays; | |
15 | import java.util.Collection; | |
16 | import java.util.Collections; | |
17 | import java.util.Comparator; | |
18 | import java.util.HashMap; | |
19 | import java.util.HashSet; | |
20 | import java.util.Iterator; | |
21 | import java.util.LinkedHashMap; | |
22 | import java.util.List; | |
23 | import java.util.Map; | |
24 | import java.util.Set; | |
25 | ||
26 | import org.eclipse.core.runtime.Assert; | |
27 | ||
28 | import org.eclipse.jface.preference.IPreferenceStore; | |
29 | ||
30 | import org.eclipse.jface.text.BadLocationException; | |
31 | import org.eclipse.jface.text.IDocument; | |
32 | import org.eclipse.jface.text.IRegion; | |
33 | import org.eclipse.jface.text.Position; | |
34 | import org.eclipse.jface.text.Region; | |
35 | import org.eclipse.jface.text.source.Annotation; | |
36 | import org.eclipse.jface.text.source.projection.IProjectionListener; | |
37 | import org.eclipse.jface.text.source.projection.IProjectionPosition; | |
38 | import org.eclipse.jface.text.source.projection.ProjectionAnnotation; | |
39 | import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; | |
40 | import org.eclipse.jface.text.source.projection.ProjectionViewer; | |
41 | ||
42 | import org.eclipse.ui.texteditor.ITextEditor; | |
43 | ||
44 | import org.eclipse.jdt.core.ElementChangedEvent; | |
45 | import org.eclipse.jdt.core.IClassFile; | |
46 | import org.eclipse.jdt.core.ICompilationUnit; | |
47 | import org.eclipse.jdt.core.IElementChangedListener; | |
48 | import org.eclipse.jdt.core.IJavaElement; | |
49 | import org.eclipse.jdt.core.IJavaElementDelta; | |
50 | import org.eclipse.jdt.core.IMember; | |
51 | import org.eclipse.jdt.core.IParent; | |
52 | import org.eclipse.jdt.core.ISourceRange; | |
53 | import org.eclipse.jdt.core.ISourceReference; | |
54 | import org.eclipse.jdt.core.IType; | |
55 | import org.eclipse.jdt.core.JavaCore; | |
56 | import org.eclipse.jdt.core.JavaModelException; | |
57 | import org.eclipse.jdt.core.SourceRange; | |
58 | import org.eclipse.jdt.core.ToolFactory; | |
59 | import org.eclipse.jdt.core.compiler.IScanner; | |
60 | import org.eclipse.jdt.core.compiler.ITerminalSymbols; | |
61 | import org.eclipse.jdt.core.compiler.InvalidInputException; | |
62 | import org.eclipse.jdt.core.dom.CompilationUnit; | |
63 | ||
64 | import org.eclipse.jdt.ui.PreferenceConstants; | |
65 | ||
66 | import org.eclipse.jdt.internal.ui.JavaPlugin; | |
67 | import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; | |
68 | import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; | |
69 | import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator; | |
70 | ||
71 | /** | |
72 | * Updates the projection model of a class file or compilation unit. | |
73 | * <p> | |
74 | * Clients may instantiate or subclass. Subclasses must make sure to always call the superclass' | |
75 | * code when overriding methods that are marked with "subclasses may extend". | |
76 | * </p> | |
77 | * | |
78 | * @since 3.0 (internal) | |
79 | * @since 3.2 (API) | |
80 | */ | |
81 | public class DefaultJavaFoldingStructureProvider implements IJavaFoldingStructureProvider, IJavaFoldingStructureProviderExtension { | |
82 | /** | |
83 | * A context that contains the information needed to compute the folding structure of an | |
84 | * {@link ICompilationUnit} or an {@link IClassFile}. Computed folding regions are collected | |
85 | * via | |
86 | * {@linkplain #addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) addProjectionRange}. | |
87 | */ | |
88 | protected final class FoldingStructureComputationContext { | |
89 | private final ProjectionAnnotationModel fModel; | |
90 | private final IDocument fDocument; | |
91 | ||
92 | private final boolean fAllowCollapsing; | |
93 | ||
94 | private IType fFirstType; | |
95 | private boolean fHasHeaderComment; | |
96 | private LinkedHashMap<JavaProjectionAnnotation, Position> fMap= new LinkedHashMap<JavaProjectionAnnotation, Position>(); | |
97 | private IScanner fScanner; | |
98 | ||
99 | private FoldingStructureComputationContext(IDocument document, ProjectionAnnotationModel model, boolean allowCollapsing, IScanner scanner) { | |
100 | Assert.isNotNull(document); | |
101 | Assert.isNotNull(model); | |
102 | fDocument= document; | |
103 | fModel= model; | |
104 | fAllowCollapsing= allowCollapsing; | |
105 | fScanner= scanner; | |
106 | } | |
107 | ||
108 | void setFirstType(IType type) { | |
109 | if (hasFirstType()) | |
110 | throw new IllegalStateException(); | |
111 | fFirstType= type; | |
112 | } | |
113 | ||
114 | boolean hasFirstType() { | |
115 | return fFirstType != null; | |
116 | } | |
117 | ||
118 | IType getFirstType() { | |
119 | return fFirstType; | |
120 | } | |
121 | ||
122 | boolean hasHeaderComment() { | |
123 | return fHasHeaderComment; | |
124 | } | |
125 | ||
126 | void setHasHeaderComment() { | |
127 | fHasHeaderComment= true; | |
128 | } | |
129 | ||
130 | /** | |
131 | * Returns <code>true</code> if newly created folding regions may be collapsed, | |
132 | * <code>false</code> if not. This is usually <code>false</code> when updating the | |
133 | * folding structure while typing; it may be <code>true</code> when computing or restoring | |
134 | * the initial folding structure. | |
135 | * | |
136 | * @return <code>true</code> if newly created folding regions may be collapsed, | |
137 | * <code>false</code> if not | |
138 | */ | |
139 | public boolean allowCollapsing() { | |
140 | return fAllowCollapsing; | |
141 | } | |
142 | ||
143 | /** | |
144 | * Returns the document which contains the code being folded. | |
145 | * | |
146 | * @return the document which contains the code being folded | |
147 | */ | |
148 | private IDocument getDocument() { | |
149 | return fDocument; | |
150 | } | |
151 | ||
152 | ProjectionAnnotationModel getModel() { | |
153 | return fModel; | |
154 | } | |
155 | ||
156 | IScanner getScanner() { | |
157 | if (fScanner == null) | |
158 | fScanner= ToolFactory.createScanner(true, false, false, false); | |
159 | return fScanner; | |
160 | } | |
161 | ||
162 | /** | |
163 | * Adds a projection (folding) region to this context. The created annotation / position | |
164 | * pair will be added to the {@link ProjectionAnnotationModel} of the | |
165 | * {@link ProjectionViewer} of the editor. | |
166 | * | |
167 | * @param annotation the annotation to add | |
168 | * @param position the corresponding position | |
169 | */ | |
170 | public void addProjectionRange(JavaProjectionAnnotation annotation, Position position) { | |
171 | fMap.put(annotation, position); | |
172 | } | |
173 | ||
174 | /** | |
175 | * Returns <code>true</code> if header comments should be collapsed. | |
176 | * | |
177 | * @return <code>true</code> if header comments should be collapsed | |
178 | */ | |
179 | public boolean collapseHeaderComments() { | |
180 | return fAllowCollapsing && fCollapseHeaderComments; | |
181 | } | |
182 | ||
183 | /** | |
184 | * Returns <code>true</code> if import containers should be collapsed. | |
185 | * | |
186 | * @return <code>true</code> if import containers should be collapsed | |
187 | */ | |
188 | public boolean collapseImportContainer() { | |
189 | return fAllowCollapsing && fCollapseImportContainer; | |
190 | } | |
191 | ||
192 | /** | |
193 | * Returns <code>true</code> if inner types should be collapsed. | |
194 | * | |
195 | * @return <code>true</code> if inner types should be collapsed | |
196 | */ | |
197 | public boolean collapseInnerTypes() { | |
198 | return fAllowCollapsing && fCollapseInnerTypes; | |
199 | } | |
200 | ||
201 | /** | |
202 | * Returns <code>true</code> if javadoc comments should be collapsed. | |
203 | * | |
204 | * @return <code>true</code> if javadoc comments should be collapsed | |
205 | */ | |
206 | public boolean collapseJavadoc() { | |
207 | return fAllowCollapsing && fCollapseJavadoc; | |
208 | } | |
209 | ||
210 | /** | |
211 | * Returns <code>true</code> if methods should be collapsed. | |
212 | * | |
213 | * @return <code>true</code> if methods should be collapsed | |
214 | */ | |
215 | public boolean collapseMembers() { | |
216 | return fAllowCollapsing && fCollapseMembers; | |
217 | } | |
218 | ||
219 | public void generated_100928497251475465(DefaultJavaFoldingStructureProvider defaultjavafoldingstructureprovider, IParent parent, String source) throws JavaModelException { | |
220 | getScanner().setSource(source.toCharArray()); | |
221 | defaultjavafoldingstructureprovider.computeFoldingStructure(parent.getChildren(), this); | |
222 | } | |
223 | ||
224 | public void generated_323515713912890140(IJavaElement element, DefaultJavaFoldingStructureProvider defaultjavafoldingstructureprovider, boolean collapse, boolean collapseCode) { | |
225 | switch (element.getElementType()) { | |
226 | ||
227 | case IJavaElement.IMPORT_CONTAINER: | |
228 | collapse= collapseImportContainer(); | |
229 | break; | |
230 | case IJavaElement.TYPE: | |
231 | collapseCode= defaultjavafoldingstructureprovider.isInnerType((IType) element) && !defaultjavafoldingstructureprovider.isAnonymousEnum((IType) element); | |
232 | collapse= collapseInnerTypes() && collapseCode; | |
233 | break; | |
234 | case IJavaElement.METHOD: | |
235 | case IJavaElement.FIELD: | |
236 | case IJavaElement.INITIALIZER: | |
237 | collapse= collapseMembers(); | |
238 | break; | |
239 | default: | |
240 | return; | |
241 | } | |
242 | ||
243 | IRegion[] regions= defaultjavafoldingstructureprovider.computeProjectionRanges((ISourceReference) element, this); | |
244 | if (regions.length > 0) { | |
245 | // comments | |
246 | for (int i= 0; i < regions.length - 1; i++) { | |
247 | IRegion normalized= defaultjavafoldingstructureprovider.alignRegion(regions[i], this); | |
248 | if (normalized != null) { | |
249 | Position position= defaultjavafoldingstructureprovider.createCommentPosition(normalized); | |
250 | if (position != null) { | |
251 | boolean commentCollapse; | |
252 | if (i == 0 && (regions.length > 2 || hasHeaderComment()) && element == getFirstType()) { | |
253 | commentCollapse= collapseHeaderComments(); | |
254 | } else { | |
255 | commentCollapse= collapseJavadoc(); | |
256 | } | |
257 | addProjectionRange(new JavaProjectionAnnotation(commentCollapse, element, true), position); | |
258 | } | |
259 | } | |
260 | } | |
261 | // code | |
262 | if (collapseCode) { | |
263 | IRegion normalized= defaultjavafoldingstructureprovider.alignRegion(regions[regions.length - 1], this); | |
264 | if (normalized != null) { | |
265 | Position position= element instanceof IMember ? defaultjavafoldingstructureprovider.createMemberPosition(normalized, (IMember) element) : defaultjavafoldingstructureprovider.createCommentPosition(normalized); | |
266 | if (position != null) | |
267 | addProjectionRange(new JavaProjectionAnnotation(collapse, element, false), position); | |
268 | } | |
269 | } | |
270 | } | |
271 | } | |
272 | ||
273 | public void generated_8988951760493717037(ISourceReference reference, DefaultJavaFoldingStructureProvider defaultjavafoldingstructureprovider, List<IRegion> regions) throws JavaModelException { | |
274 | setFirstType((IType) reference); | |
275 | IRegion headerComment= defaultjavafoldingstructureprovider.computeHeaderComment(this); | |
276 | if (headerComment != null) { | |
277 | regions.add(headerComment); | |
278 | setHasHeaderComment(); | |
279 | } | |
280 | } | |
281 | ||
282 | public IRegion generated_3843785453625915794() throws JavaModelException { | |
283 | ISourceRange range= getFirstType().getSourceRange(); | |
284 | if (range == null) | |
285 | return null; | |
286 | int start= 0; | |
287 | int end= range.getOffset(); | |
288 | ||
289 | ||
290 | /* code adapted from CommentFormattingStrategy: | |
291 | * scan the header content up to the first type. Once a comment is | |
292 | * found, accumulate any additional comments up to the stop condition. | |
293 | * The stop condition is reaching a package declaration, import container, | |
294 | * or the end of the input. | |
295 | */ | |
296 | IScanner scanner= getScanner(); | |
297 | scanner.resetTo(start, end); | |
298 | ||
299 | int headerStart= -1; | |
300 | int headerEnd= -1; | |
301 | try { | |
302 | boolean foundComment= false; | |
303 | int terminal= scanner.getNextToken(); | |
304 | while (terminal != ITerminalSymbols.TokenNameEOF && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) { | |
305 | ||
306 | if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK || terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) { | |
307 | if (!foundComment) | |
308 | headerStart= scanner.getCurrentTokenStartPosition(); | |
309 | headerEnd= scanner.getCurrentTokenEndPosition(); | |
310 | foundComment= true; | |
311 | } | |
312 | terminal= scanner.getNextToken(); | |
313 | } | |
314 | ||
315 | ||
316 | } catch (InvalidInputException ex) { | |
317 | return null; | |
318 | } | |
319 | ||
320 | if (headerEnd != -1) { | |
321 | return new Region(headerStart, headerEnd - headerStart); | |
322 | } | |
323 | return null; | |
324 | } | |
325 | } | |
326 | ||
327 | /** | |
328 | * A {@link ProjectionAnnotation} for java code. | |
329 | */ | |
330 | protected static final class JavaProjectionAnnotation extends ProjectionAnnotation { | |
331 | ||
332 | private IJavaElement fJavaElement; | |
333 | private boolean fIsComment; | |
334 | ||
335 | /** | |
336 | * Creates a new projection annotation. | |
337 | * | |
338 | * @param isCollapsed <code>true</code> to set the initial state to collapsed, | |
339 | * <code>false</code> to set it to expanded | |
340 | * @param element the java element this annotation refers to | |
341 | * @param isComment <code>true</code> for a foldable comment, <code>false</code> for a | |
342 | * foldable code element | |
343 | */ | |
344 | public JavaProjectionAnnotation(boolean isCollapsed, IJavaElement element, boolean isComment) { | |
345 | super(isCollapsed); | |
346 | fJavaElement= element; | |
347 | fIsComment= isComment; | |
348 | } | |
349 | ||
350 | IJavaElement getElement() { | |
351 | return fJavaElement; | |
352 | } | |
353 | ||
354 | void setElement(IJavaElement element) { | |
355 | fJavaElement= element; | |
356 | } | |
357 | ||
358 | boolean isComment() { | |
359 | return fIsComment; | |
360 | } | |
361 | ||
362 | void setIsComment(boolean isComment) { | |
363 | fIsComment= isComment; | |
364 | } | |
365 | ||
366 | /* | |
367 | * @see java.lang.Object#toString() | |
368 | */ | |
369 | @Override | |
370 | public String toString() { | |
371 | return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$ | |
372 | "\telement: \t"+ fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$ | |
373 | "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$ | |
374 | "\tcomment: \t" + isComment() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ | |
375 | } | |
376 | ||
377 | public void generated_7723998622636648457(JavaProjectionAnnotation newAnnotation) { | |
378 | if (newAnnotation.isCollapsed()) | |
379 | markCollapsed(); | |
380 | else | |
381 | markExpanded(); | |
382 | } | |
383 | ||
384 | public Position generated_7272779290687362405(Map<JavaProjectionAnnotation, Position> positionMap, FoldingStructureComputationContext ctx) { | |
385 | Position position= positionMap == null ? ctx.getModel().getPosition(this) : positionMap.get(this); | |
386 | return position; | |
387 | } | |
388 | ||
389 | public void generated_6809230897511527110(Filter filter, boolean expand, List<JavaProjectionAnnotation> modified) { | |
390 | if (expand == isCollapsed() && filter.match(this)) { | |
391 | if (expand) | |
392 | markExpanded(); | |
393 | else | |
394 | markCollapsed(); | |
395 | modified.add(this); | |
396 | } | |
397 | } | |
398 | } | |
399 | ||
400 | ||
401 | static final class Tuple { | |
402 | JavaProjectionAnnotation annotation; | |
403 | Position position; | |
404 | Tuple(JavaProjectionAnnotation annotation, Position position) { | |
405 | this.annotation= annotation; | |
406 | this.position= position; | |
407 | } | |
408 | public void generated_5894515189644979979(List<JavaProjectionAnnotation> newDeletions, List<JavaProjectionAnnotation> newChanges, Iterator<JavaProjectionAnnotation> deletionIterator, JavaProjectionAnnotation deleted, Position deletedPosition, DefaultJavaFoldingStructureProvider defaultjavafoldingstructureprovider, boolean addToDeletions, IJavaElement element) { | |
409 | deletedPosition.setLength(position.getLength()); | |
410 | if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) { | |
411 | JavaElementPosition jep= (JavaElementPosition) deletedPosition; | |
412 | jep.setMember((IMember) element); | |
413 | } | |
414 | ||
415 | deletionIterator.remove(); | |
416 | newChanges.add(deleted); | |
417 | ||
418 | if (addToDeletions) | |
419 | newDeletions.add(annotation); | |
420 | } | |
421 | public int generated_4432417452402132400(Tuple o1, Comparator<Tuple> arg) { | |
422 | return o1.position.getOffset() - position.getOffset(); | |
423 | } | |
424 | } | |
425 | ||
426 | /** | |
427 | * Filter for annotations. | |
428 | */ | |
429 | static interface Filter { | |
430 | boolean match(JavaProjectionAnnotation annotation); | |
431 | } | |
432 | ||
433 | /** | |
434 | * Matches comments. | |
435 | */ | |
436 | private static final class CommentFilter implements Filter { | |
437 | public boolean match(JavaProjectionAnnotation annotation) { | |
438 | if (annotation.isComment() && !annotation.isMarkedDeleted()) { | |
439 | return true; | |
440 | } | |
441 | return false; | |
442 | } | |
443 | } | |
444 | ||
445 | /** | |
446 | * Matches members. | |
447 | */ | |
448 | private static final class MemberFilter implements Filter { | |
449 | public boolean match(JavaProjectionAnnotation annotation) { | |
450 | if (!annotation.isComment() && !annotation.isMarkedDeleted()) { | |
451 | IJavaElement element= annotation.getElement(); | |
452 | if (element instanceof IMember) { | |
453 | if (element.getElementType() != IJavaElement.TYPE || ((IMember) element).getDeclaringType() != null) { | |
454 | return true; | |
455 | } | |
456 | } | |
457 | } | |
458 | return false; | |
459 | } | |
460 | } | |
461 | ||
462 | /** | |
463 | * Matches java elements contained in a certain set. | |
464 | */ | |
465 | private static final class JavaElementSetFilter implements Filter { | |
466 | private final Set<? extends IJavaElement> fSet; | |
467 | private final boolean fMatchCollapsed; | |
468 | ||
469 | private JavaElementSetFilter(Set<? extends IJavaElement> set, boolean matchCollapsed) { | |
470 | fSet= set; | |
471 | fMatchCollapsed= matchCollapsed; | |
472 | } | |
473 | ||
474 | public boolean match(JavaProjectionAnnotation annotation) { | |
475 | boolean stateMatch= fMatchCollapsed == annotation.isCollapsed(); | |
476 | if (stateMatch && !annotation.isComment() && !annotation.isMarkedDeleted()) { | |
477 | IJavaElement element= annotation.getElement(); | |
478 | if (fSet.contains(element)) { | |
479 | return true; | |
480 | } | |
481 | } | |
482 | return false; | |
483 | } | |
484 | } | |
485 | ||
486 | public class ElementChangedListener implements IElementChangedListener { | |
487 | ||
488 | /* | |
489 | * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent) | |
490 | */ | |
491 | public void elementChanged(ElementChangedEvent e) { | |
492 | IJavaElementDelta delta= findElement(fInput, e.getDelta()); | |
493 | if (delta != null && (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) { | |
494 | ||
495 | if (shouldIgnoreDelta(e.getDelta().getCompilationUnitAST(), delta)) | |
496 | return; | |
497 | ||
498 | fUpdatingCount++; | |
499 | try { | |
500 | update(createContext(false)); | |
501 | } finally { | |
502 | fUpdatingCount--; | |
503 | } | |
504 | } | |
505 | } | |
506 | ||
507 | /** | |
508 | * Ignore the delta if there are errors on the caret line. | |
509 | * <p> | |
510 | * We don't ignore the delta if an import is added and the | |
511 | * caret isn't inside the import container. | |
512 | * </p> | |
513 | * | |
514 | * @param ast the compilation unit AST | |
515 | * @param delta the Java element delta for the given AST element | |
516 | * @return <code>true</code> if the delta should be ignored | |
517 | * @since 3.3 | |
518 | */ | |
519 | private boolean shouldIgnoreDelta(CompilationUnit ast, IJavaElementDelta delta) { | |
520 | if (ast == null) | |
521 | return false; // can't compute | |
522 | ||
523 | IDocument document= getDocument(); | |
524 | if (document == null) | |
525 | return false; // can't compute | |
526 | ||
527 | JavaEditor editor= fEditor; | |
528 | if (editor == null || editor.getCachedSelectedRange() == null) | |
529 | return false; // can't compute | |
530 | ||
531 | return editor.generated_3091946843584142038(ast, delta, document, ElementChangedListener.this); | |
532 | } | |
533 | ||
534 | private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) { | |
535 | ||
536 | if (delta == null || target == null) | |
537 | return null; | |
538 | ||
539 | IJavaElement element= delta.getElement(); | |
540 | ||
541 | if (element.getElementType() > IJavaElement.CLASS_FILE) | |
542 | return null; | |
543 | ||
544 | if (target.equals(element)) | |
545 | return delta; | |
546 | ||
547 | IJavaElementDelta[] children= delta.getAffectedChildren(); | |
548 | ||
549 | for (int i= 0; i < children.length; i++) { | |
550 | IJavaElementDelta d= findElement(target, children[i]); | |
551 | if (d != null) | |
552 | return d; | |
553 | } | |
554 | ||
555 | return null; | |
556 | } | |
557 | } | |
558 | ||
559 | /** | |
560 | * Projection position that will return two foldable regions: one folding away | |
561 | * the region from after the '/**' to the beginning of the content, the other | |
562 | * from after the first content line until after the comment. | |
563 | */ | |
564 | private static final class CommentPosition extends Position implements IProjectionPosition { | |
565 | CommentPosition(int offset, int length) { | |
566 | super(offset, length); | |
567 | } | |
568 | ||
569 | /* | |
570 | * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument) | |
571 | */ | |
572 | public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { | |
573 | DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); | |
574 | int prefixEnd= 0; | |
575 | int contentStart= findFirstContent(sequence, prefixEnd); | |
576 | ||
577 | int firstLine= document.getLineOfOffset(offset + prefixEnd); | |
578 | int captionLine= document.getLineOfOffset(offset + contentStart); | |
579 | int lastLine= document.getLineOfOffset(offset + length); | |
580 | ||
581 | Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$ | |
582 | Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$ | |
583 | ||
584 | IRegion preRegion; | |
585 | if (firstLine < captionLine) { | |
586 | // preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd); | |
587 | int preOffset= document.getLineOffset(firstLine); | |
588 | IRegion preEndLineInfo= document.getLineInformation(captionLine); | |
589 | int preEnd= preEndLineInfo.getOffset(); | |
590 | preRegion= new Region(preOffset, preEnd - preOffset); | |
591 | } else { | |
592 | preRegion= null; | |
593 | } | |
594 | ||
595 | if (captionLine < lastLine) { | |
596 | int postOffset= document.getLineOffset(captionLine + 1); | |
597 | int postLength= offset + length - postOffset; | |
598 | if (postLength > 0) { | |
599 | IRegion postRegion= new Region(postOffset, postLength); | |
600 | if (preRegion == null) | |
601 | return new IRegion[] { postRegion }; | |
602 | return new IRegion[] { preRegion, postRegion }; | |
603 | } | |
604 | } | |
605 | ||
606 | if (preRegion != null) | |
607 | return new IRegion[] { preRegion }; | |
608 | ||
609 | return null; | |
610 | } | |
611 | ||
612 | /** | |
613 | * Finds the offset of the first identifier part within <code>content</code>. | |
614 | * Returns 0 if none is found. | |
615 | * | |
616 | * @param content the content to search | |
617 | * @param prefixEnd the end of the prefix | |
618 | * @return the first index of a unicode identifier part, or zero if none can | |
619 | * be found | |
620 | */ | |
621 | private int findFirstContent(final CharSequence content, int prefixEnd) { | |
622 | int lenght= content.length(); | |
623 | for (int i= prefixEnd; i < lenght; i++) { | |
624 | if (Character.isUnicodeIdentifierPart(content.charAt(i))) | |
625 | return i; | |
626 | } | |
627 | return 0; | |
628 | } | |
629 | ||
630 | // /** | |
631 | // * Finds the offset of the first identifier part within <code>content</code>. | |
632 | // * Returns 0 if none is found. | |
633 | // * | |
634 | // * @param content the content to search | |
635 | // * @return the first index of a unicode identifier part, or zero if none can | |
636 | // * be found | |
637 | // */ | |
638 | // private int findPrefixEnd(final CharSequence content) { | |
639 | // // return the index after the leading '/*' or '/**' | |
640 | // int len= content.length(); | |
641 | // int i= 0; | |
642 | // while (i < len && isWhiteSpace(content.charAt(i))) | |
643 | // i++; | |
644 | // if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*') | |
645 | // if (len >= i + 3 && content.charAt(i + 2) == '*') | |
646 | // return i + 3; | |
647 | // else | |
648 | // return i + 2; | |
649 | // else | |
650 | // return i; | |
651 | // } | |
652 | // | |
653 | // private boolean isWhiteSpace(char c) { | |
654 | // return c == ' ' || c == '\t'; | |
655 | // } | |
656 | ||
657 | /* | |
658 | * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) | |
659 | */ | |
660 | public int computeCaptionOffset(IDocument document) throws BadLocationException { | |
661 | DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length); | |
662 | return findFirstContent(sequence, 0); | |
663 | } | |
664 | } | |
665 | ||
666 | /** | |
667 | * Projection position that will return two foldable regions: one folding away | |
668 | * the lines before the one containing the simple name of the java element, one | |
669 | * folding away any lines after the caption. | |
670 | */ | |
671 | static final class JavaElementPosition extends Position implements IProjectionPosition { | |
672 | ||
673 | private IMember fMember; | |
674 | ||
675 | public JavaElementPosition(int offset, int length, IMember member) { | |
676 | super(offset, length); | |
677 | Assert.isNotNull(member); | |
678 | fMember= member; | |
679 | } | |
680 | ||
681 | public void setMember(IMember member) { | |
682 | Assert.isNotNull(member); | |
683 | fMember= member; | |
684 | } | |
685 | ||
686 | /* | |
687 | * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument) | |
688 | */ | |
689 | public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { | |
690 | int nameStart= offset; | |
691 | try { | |
692 | /* The member's name range may not be correct. However, | |
693 | * reconciling would trigger another element delta which would | |
694 | * lead to reentrant situations. Therefore, we optimistically | |
695 | * assume that the name range is correct, but double check the | |
696 | * received lines below. */ | |
697 | ISourceRange nameRange= fMember.getNameRange(); | |
698 | if (nameRange != null) | |
699 | nameStart= nameRange.getOffset(); | |
700 | ||
701 | } catch (JavaModelException e) { | |
702 | // ignore and use default | |
703 | } | |
704 | ||
705 | int firstLine= document.getLineOfOffset(offset); | |
706 | int captionLine= document.getLineOfOffset(nameStart); | |
707 | int lastLine= document.getLineOfOffset(offset + length); | |
708 | ||
709 | /* see comment above - adjust the caption line to be inside the | |
710 | * entire folded region, and rely on later element deltas to correct | |
711 | * the name range. */ | |
712 | if (captionLine < firstLine) | |
713 | captionLine= firstLine; | |
714 | if (captionLine > lastLine) | |
715 | captionLine= lastLine; | |
716 | ||
717 | IRegion preRegion; | |
718 | if (firstLine < captionLine) { | |
719 | int preOffset= document.getLineOffset(firstLine); | |
720 | IRegion preEndLineInfo= document.getLineInformation(captionLine); | |
721 | int preEnd= preEndLineInfo.getOffset(); | |
722 | preRegion= new Region(preOffset, preEnd - preOffset); | |
723 | } else { | |
724 | preRegion= null; | |
725 | } | |
726 | ||
727 | if (captionLine < lastLine) { | |
728 | int postOffset= document.getLineOffset(captionLine + 1); | |
729 | int postLength= offset + length - postOffset; | |
730 | if (postLength > 0) { | |
731 | IRegion postRegion= new Region(postOffset, postLength); | |
732 | if (preRegion == null) | |
733 | return new IRegion[] { postRegion }; | |
734 | return new IRegion[] { preRegion, postRegion }; | |
735 | } | |
736 | } | |
737 | ||
738 | if (preRegion != null) | |
739 | return new IRegion[] { preRegion }; | |
740 | ||
741 | return null; | |
742 | } | |
743 | ||
744 | /* | |
745 | * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) | |
746 | */ | |
747 | public int computeCaptionOffset(IDocument document) throws BadLocationException { | |
748 | int nameStart= offset; | |
749 | try { | |
750 | // need a reconcile here? | |
751 | ISourceRange nameRange= fMember.getNameRange(); | |
752 | if (nameRange != null) | |
753 | nameStart= nameRange.getOffset(); | |
754 | } catch (JavaModelException e) { | |
755 | // ignore and use default | |
756 | } | |
757 | ||
758 | return nameStart - offset; | |
759 | } | |
760 | ||
761 | } | |
762 | ||
763 | /** | |
764 | * Internal projection listener. | |
765 | */ | |
766 | private final class ProjectionListener implements IProjectionListener { | |
767 | private ProjectionViewer fViewer; | |
768 | ||
769 | /** | |
770 | * Registers the listener with the viewer. | |
771 | * | |
772 | * @param viewer the viewer to register a listener with | |
773 | */ | |
774 | public ProjectionListener(ProjectionViewer viewer) { | |
775 | Assert.isLegal(viewer != null); | |
776 | fViewer= viewer; | |
777 | fViewer.addProjectionListener(this); | |
778 | } | |
779 | ||
780 | /** | |
781 | * Disposes of this listener and removes the projection listener from the viewer. | |
782 | */ | |
783 | public void dispose() { | |
784 | if (fViewer != null) { | |
785 | fViewer.removeProjectionListener(this); | |
786 | fViewer= null; | |
787 | } | |
788 | } | |
789 | ||
790 | /* | |
791 | * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled() | |
792 | */ | |
793 | public void projectionEnabled() { | |
794 | handleProjectionEnabled(); | |
795 | } | |
796 | ||
797 | /* | |
798 | * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled() | |
799 | */ | |
800 | public void projectionDisabled() { | |
801 | handleProjectionDisabled(); | |
802 | } | |
803 | } | |
804 | ||
805 | /* context and listeners */ | |
806 | private JavaEditor fEditor; | |
807 | private ProjectionListener fProjectionListener; | |
808 | private IJavaElement fInput; | |
809 | private IElementChangedListener fElementListener; | |
810 | ||
811 | /* preferences */ | |
812 | private boolean fCollapseJavadoc= false; | |
813 | private boolean fCollapseImportContainer= true; | |
814 | private boolean fCollapseInnerTypes= true; | |
815 | private boolean fCollapseMembers= false; | |
816 | private boolean fCollapseHeaderComments= true; | |
817 | ||
818 | /* filters */ | |
819 | /** Member filter, matches nested members (but not top-level types). */ | |
820 | private final Filter fMemberFilter = new MemberFilter(); | |
821 | /** Comment filter, matches comments. */ | |
822 | private final Filter fCommentFilter = new CommentFilter(); | |
823 | ||
824 | /** | |
825 | * Reusable scanner. | |
826 | * @since 3.3 | |
827 | */ | |
828 | private IScanner fSharedScanner= ToolFactory.createScanner(true, false, false, false); | |
829 | ||
830 | private volatile int fUpdatingCount= 0; | |
831 | ||
832 | /** | |
833 | * Creates a new folding provider. It must be | |
834 | * {@link #install(ITextEditor, ProjectionViewer) installed} on an editor/viewer pair before it | |
835 | * can be used, and {@link #uninstall() uninstalled} when not used any longer. | |
836 | * <p> | |
837 | * The projection state may be reset by calling {@link #initialize()}. | |
838 | * </p> | |
839 | */ | |
840 | public DefaultJavaFoldingStructureProvider() { | |
841 | } | |
842 | ||
843 | /** | |
844 | * {@inheritDoc} | |
845 | * <p> | |
846 | * Subclasses may extend. | |
847 | * </p> | |
848 | * | |
849 | * @param editor {@inheritDoc} | |
850 | * @param viewer {@inheritDoc} | |
851 | */ | |
852 | public void install(ITextEditor editor, ProjectionViewer viewer) { | |
853 | Assert.isLegal(editor != null); | |
854 | Assert.isLegal(viewer != null); | |
855 | ||
856 | internalUninstall(); | |
857 | ||
858 | if (editor instanceof JavaEditor) { | |
859 | fProjectionListener= new ProjectionListener(viewer); | |
860 | fEditor= (JavaEditor)editor; | |
861 | } | |
862 | } | |
863 | ||
864 | /** | |
865 | * {@inheritDoc} | |
866 | * <p> | |
867 | * Subclasses may extend. | |
868 | * </p> | |
869 | */ | |
870 | public void uninstall() { | |
871 | internalUninstall(); | |
872 | } | |
873 | ||
874 | /** | |
875 | * Internal implementation of {@link #uninstall()}. | |
876 | */ | |
877 | private void internalUninstall() { | |
878 | if (isInstalled()) { | |
879 | handleProjectionDisabled(); | |
880 | fProjectionListener.dispose(); | |
881 | fProjectionListener= null; | |
882 | fEditor= null; | |
883 | } | |
884 | } | |
885 | ||
886 | /** | |
887 | * Returns <code>true</code> if the provider is installed, <code>false</code> otherwise. | |
888 | * | |
889 | * @return <code>true</code> if the provider is installed, <code>false</code> otherwise | |
890 | */ | |
891 | protected final boolean isInstalled() { | |
892 | return fEditor != null; | |
893 | } | |
894 | ||
895 | /** | |
896 | * Called whenever projection is enabled, for example when the viewer issues a | |
897 | * {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider | |
898 | * is already enabled when this method is called, it is first | |
899 | * {@link #handleProjectionDisabled() disabled}. | |
900 | * <p> | |
901 | * Subclasses may extend. | |
902 | * </p> | |
903 | */ | |
904 | protected void handleProjectionEnabled() { | |
905 | // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html | |
906 | // projectionEnabled messages are not always paired with projectionDisabled | |
907 | // i.e. multiple enabled messages may be sent out. | |
908 | // we have to make sure that we disable first when getting an enable | |
909 | // message. | |
910 | handleProjectionDisabled(); | |
911 | ||
912 | if (isInstalled()) { | |
913 | initialize(); | |
914 | fElementListener= new ElementChangedListener(); | |
915 | JavaCore.addElementChangedListener(fElementListener); | |
916 | } | |
917 | } | |
918 | ||
919 | /** | |
920 | * Called whenever projection is disabled, for example when the provider is | |
921 | * {@link #uninstall() uninstalled}, when the viewer issues a | |
922 | * {@link IProjectionListener#projectionDisabled() projectionDisabled} message and before | |
923 | * {@link #handleProjectionEnabled() enabling} the provider. Implementations must be prepared to | |
924 | * handle multiple calls to this method even if the provider is already disabled. | |
925 | * <p> | |
926 | * Subclasses may extend. | |
927 | * </p> | |
928 | */ | |
929 | protected void handleProjectionDisabled() { | |
930 | if (fElementListener != null) { | |
931 | JavaCore.removeElementChangedListener(fElementListener); | |
932 | fElementListener= null; | |
933 | } | |
934 | } | |
935 | ||
936 | /* | |
937 | * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider#initialize() | |
938 | */ | |
939 | public final void initialize() { | |
940 | fUpdatingCount++; | |
941 | try { | |
942 | update(createInitialContext()); | |
943 | } finally { | |
944 | fUpdatingCount--; | |
945 | } | |
946 | } | |
947 | ||
948 | private FoldingStructureComputationContext createInitialContext() { | |
949 | initializePreferences(); | |
950 | fInput= getInputElement(); | |
951 | if (fInput == null) | |
952 | return null; | |
953 | ||
954 | return createContext(true); | |
955 | } | |
956 | ||
957 | private FoldingStructureComputationContext createContext(boolean allowCollapse) { | |
958 | if (!isInstalled()) | |
959 | return null; | |
960 | ProjectionAnnotationModel model= getModel(); | |
961 | if (model == null) | |
962 | return null; | |
963 | IDocument doc= getDocument(); | |
964 | if (doc == null) | |
965 | return null; | |
966 | ||
967 | IScanner scanner= null; | |
968 | if (fUpdatingCount == 1) | |
969 | scanner= fSharedScanner; // reuse scanner | |
970 | ||
971 | return new FoldingStructureComputationContext(doc, model, allowCollapse, scanner); | |
972 | } | |
973 | ||
974 | private IJavaElement getInputElement() { | |
975 | if (fEditor == null) | |
976 | return null; | |
977 | return EditorUtility.getEditorInputJavaElement(fEditor, false); | |
978 | } | |
979 | ||
980 | private void initializePreferences() { | |
981 | IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); | |
982 | fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); | |
983 | fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); | |
984 | fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); | |
985 | fCollapseMembers= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); | |
986 | fCollapseHeaderComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS); | |
987 | } | |
988 | ||
989 | private void update(FoldingStructureComputationContext ctx) { | |
990 | if (ctx == null) | |
991 | return; | |
992 | ||
993 | Map<JavaProjectionAnnotation, Position> additions= new HashMap<JavaProjectionAnnotation, Position>(); | |
994 | List<JavaProjectionAnnotation> deletions= new ArrayList<JavaProjectionAnnotation>(); | |
995 | List<JavaProjectionAnnotation> updates= new ArrayList<JavaProjectionAnnotation>(); | |
996 | ||
997 | computeFoldingStructure(ctx); | |
998 | Map<JavaProjectionAnnotation, Position> newStructure= ctx.fMap; | |
999 | Map<IJavaElement, List<Tuple>> oldStructure= computeCurrentStructure(ctx); | |
1000 | ||
1001 | Iterator<JavaProjectionAnnotation> e= newStructure.keySet().iterator(); | |
1002 | while (e.hasNext()) { | |
1003 | JavaProjectionAnnotation newAnnotation= e.next(); | |
1004 | Position newPosition= newStructure.get(newAnnotation); | |
1005 | ||
1006 | IJavaElement element= newAnnotation.getElement(); | |
1007 | /* | |
1008 | * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=130472 and | |
1009 | * https://bugs.eclipse.org/bugs/show_bug.cgi?id=127445 In the presence of syntax | |
1010 | * errors, anonymous types may have a source range offset of 0. When such a situation is | |
1011 | * encountered, we ignore the proposed folding range: if no corresponding folding range | |
1012 | * exists, it is silently ignored; if there *is* a matching folding range, we ignore the | |
1013 | * position update and keep the old range, in order to keep the folding structure | |
1014 | * stable. | |
1015 | */ | |
1016 | boolean isMalformedAnonymousType= newPosition.getOffset() == 0 && element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element); | |
1017 | List<Tuple> annotations= oldStructure.get(element); | |
1018 | if (annotations == null) { | |
1019 | if (!isMalformedAnonymousType) | |
1020 | additions.put(newAnnotation, newPosition); | |
1021 | } else { | |
1022 | Iterator<Tuple> x= annotations.iterator(); | |
1023 | boolean matched= false; | |
1024 | while (x.hasNext()) { | |
1025 | Tuple tuple= x.next(); | |
1026 | JavaProjectionAnnotation existingAnnotation= tuple.annotation; | |
1027 | Position existingPosition= tuple.position; | |
1028 | if (newAnnotation.isComment() == existingAnnotation.isComment()) { | |
1029 | boolean updateCollapsedState= ctx.allowCollapsing() && existingAnnotation.isCollapsed() != newAnnotation.isCollapsed(); | |
1030 | if (!isMalformedAnonymousType && existingPosition != null && (!newPosition.equals(existingPosition) || updateCollapsedState)) { | |
1031 | existingPosition.setOffset(newPosition.getOffset()); | |
1032 | existingPosition.setLength(newPosition.getLength()); | |
1033 | if (updateCollapsedState) | |
1034 | existingAnnotation.generated_7723998622636648457(newAnnotation); | |
1035 | updates.add(existingAnnotation); | |
1036 | } | |
1037 | matched= true; | |
1038 | x.remove(); | |
1039 | break; | |
1040 | } | |
1041 | } | |
1042 | if (!matched) | |
1043 | additions.put(newAnnotation, newPosition); | |
1044 | ||
1045 | if (annotations.isEmpty()) | |
1046 | oldStructure.remove(element); | |
1047 | } | |
1048 | } | |
1049 | ||
1050 | Iterator<List<Tuple>> iter= oldStructure.values().iterator(); | |
1051 | while (iter.hasNext()) { | |
1052 | List<Tuple> list= iter.next(); | |
1053 | int size= list.size(); | |
1054 | for (int i= 0; i < size; i++) | |
1055 | deletions.add(list.get(i).annotation); | |
1056 | } | |
1057 | ||
1058 | match(deletions, additions, updates, ctx); | |
1059 | ||
1060 | Annotation[] deletedArray= deletions.toArray(new Annotation[deletions.size()]); | |
1061 | Annotation[] changedArray= updates.toArray(new Annotation[updates.size()]); | |
1062 | ctx.getModel().modifyAnnotations(deletedArray, additions, changedArray); | |
1063 | ||
1064 | ctx.fScanner.setSource(null); | |
1065 | } | |
1066 | ||
1067 | private void computeFoldingStructure(FoldingStructureComputationContext ctx) { | |
1068 | IParent parent= (IParent) fInput; | |
1069 | try { | |
1070 | if (!(fInput instanceof ISourceReference)) | |
1071 | return; | |
1072 | String source= ((ISourceReference)fInput).getSource(); | |
1073 | if (source == null) | |
1074 | return; | |
1075 | ||
1076 | ctx.generated_100928497251475465(this, parent, source); | |
1077 | } catch (JavaModelException x) { | |
1078 | } | |
1079 | } | |
1080 | ||
1081 | private void computeFoldingStructure(IJavaElement[] elements, FoldingStructureComputationContext ctx) throws JavaModelException { | |
1082 | for (int i= 0; i < elements.length; i++) { | |
1083 | IJavaElement element= elements[i]; | |
1084 | ||
1085 | computeFoldingStructure(element, ctx); | |
1086 | ||
1087 | if (element instanceof IParent) { | |
1088 | IParent parent= (IParent) element; | |
1089 | computeFoldingStructure(parent.getChildren(), ctx); | |
1090 | } | |
1091 | } | |
1092 | } | |
1093 | ||
1094 | /** | |
1095 | * Computes the folding structure for a given {@link IJavaElement java element}. Computed | |
1096 | * projection annotations are | |
1097 | * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) added} | |
1098 | * to the computation context. | |
1099 | * <p> | |
1100 | * Subclasses may extend or replace. The default implementation creates projection annotations | |
1101 | * for the following elements: | |
1102 | * <ul> | |
1103 | * <li>true members (not for top-level types)</li> | |
1104 | * <li>the javadoc comments of any member</li> | |
1105 | * <li>header comments (javadoc or multi-line comments appearing before the first type's | |
1106 | * javadoc or before the package or import declarations).</li> | |
1107 | * </ul> | |
1108 | * </p> | |
1109 | * | |
1110 | * @param element the java element to compute the folding structure for | |
1111 | * @param ctx the computation context | |
1112 | */ | |
1113 | protected void computeFoldingStructure(IJavaElement element, FoldingStructureComputationContext ctx) { | |
1114 | ||
1115 | boolean collapse= false; | |
1116 | boolean collapseCode= true; | |
1117 | ctx.generated_323515713912890140(element, this, collapse, collapseCode); | |
1118 | } | |
1119 | ||
1120 | /** | |
1121 | * Returns <code>true</code> if <code>type</code> is an anonymous enum declaration, | |
1122 | * <code>false</code> otherwise. See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=143276 | |
1123 | * | |
1124 | * @param type the type to test | |
1125 | * @return <code>true</code> if <code>type</code> is an anonymous enum declaration | |
1126 | * @since 3.3 | |
1127 | */ | |
1128 | private boolean isAnonymousEnum(IType type) { | |
1129 | try { | |
1130 | return type.isEnum() && type.isAnonymous(); | |
1131 | } catch (JavaModelException x) { | |
1132 | return false; // optimistically | |
1133 | } | |
1134 | } | |
1135 | ||
1136 | /** | |
1137 | * Returns <code>true</code> if <code>type</code> is not a top-level type, <code>false</code> if it is. | |
1138 | * | |
1139 | * @param type the type to test | |
1140 | * @return <code>true</code> if <code>type</code> is an inner type | |
1141 | */ | |
1142 | private boolean isInnerType(IType type) { | |
1143 | return type.getDeclaringType() != null; | |
1144 | } | |
1145 | ||
1146 | /** | |
1147 | * Computes the projection ranges for a given <code>ISourceReference</code>. More than one | |
1148 | * range or none at all may be returned. If there are no foldable regions, an empty array is | |
1149 | * returned. | |
1150 | * <p> | |
1151 | * The last region in the returned array (if not empty) describes the region for the java | |
1152 | * element that implements the source reference. Any preceding regions describe javadoc comments | |
1153 | * of that java element. | |
1154 | * </p> | |
1155 | * | |
1156 | * @param reference a java element that is a source reference | |
1157 | * @param ctx the folding context | |
1158 | * @return the regions to be folded | |
1159 | */ | |
1160 | protected final IRegion[] computeProjectionRanges(ISourceReference reference, FoldingStructureComputationContext ctx) { | |
1161 | try { | |
1162 | ISourceRange range= reference.getSourceRange(); | |
1163 | if (!SourceRange.isAvailable(range)) | |
1164 | return new IRegion[0]; | |
1165 | ||
1166 | String contents= reference.getSource(); | |
1167 | if (contents == null) | |
1168 | return new IRegion[0]; | |
1169 | ||
1170 | List<IRegion> regions= new ArrayList<IRegion>(); | |
1171 | if (!ctx.hasFirstType() && reference instanceof IType) { | |
1172 | ctx.generated_8988951760493717037(reference, this, regions); | |
1173 | } | |
1174 | ||
1175 | final int shift= range.getOffset(); | |
1176 | IScanner scanner= ctx.getScanner(); | |
1177 | scanner.resetTo(shift, shift + range.getLength()); | |
1178 | ||
1179 | int start= shift; | |
1180 | while (true) { | |
1181 | ||
1182 | int token= scanner.getNextToken(); | |
1183 | start= scanner.getCurrentTokenStartPosition(); | |
1184 | ||
1185 | switch (token) { | |
1186 | case ITerminalSymbols.TokenNameCOMMENT_JAVADOC: | |
1187 | case ITerminalSymbols.TokenNameCOMMENT_BLOCK: { | |
1188 | int end= scanner.getCurrentTokenEndPosition() + 1; | |
1189 | regions.add(new Region(start, end - start)); | |
1190 | continue; | |
1191 | } | |
1192 | case ITerminalSymbols.TokenNameCOMMENT_LINE: | |
1193 | continue; | |
1194 | } | |
1195 | ||
1196 | break; | |
1197 | } | |
1198 | ||
1199 | regions.add(new Region(start, shift + range.getLength() - start)); | |
1200 | ||
1201 | IRegion[] result= new IRegion[regions.size()]; | |
1202 | regions.toArray(result); | |
1203 | return result; | |
1204 | } catch (JavaModelException e) { | |
1205 | } catch (InvalidInputException e) { | |
1206 | } | |
1207 | ||
1208 | return new IRegion[0]; | |
1209 | } | |
1210 | ||
1211 | private IRegion computeHeaderComment(FoldingStructureComputationContext ctx) throws JavaModelException { | |
1212 | // search at most up to the first type | |
1213 | return ctx.generated_3843785453625915794(); | |
1214 | } | |
1215 | ||
1216 | /** | |
1217 | * Creates a comment folding position from an | |
1218 | * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} | |
1219 | * region. | |
1220 | * | |
1221 | * @param aligned an aligned region | |
1222 | * @return a folding position corresponding to <code>aligned</code> | |
1223 | */ | |
1224 | protected final Position createCommentPosition(IRegion aligned) { | |
1225 | return new CommentPosition(aligned.getOffset(), aligned.getLength()); | |
1226 | } | |
1227 | ||
1228 | /** | |
1229 | * Creates a folding position that remembers its member from an | |
1230 | * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} | |
1231 | * region. | |
1232 | * | |
1233 | * @param aligned an aligned region | |
1234 | * @param member the member to remember | |
1235 | * @return a folding position corresponding to <code>aligned</code> | |
1236 | */ | |
1237 | protected final Position createMemberPosition(IRegion aligned, IMember member) { | |
1238 | return new JavaElementPosition(aligned.getOffset(), aligned.getLength(), member); | |
1239 | } | |
1240 | ||
1241 | /** | |
1242 | * Aligns <code>region</code> to start and end at a line offset. The region's start is | |
1243 | * decreased to the next line offset, and the end offset increased to the next line start or the | |
1244 | * end of the document. <code>null</code> is returned if <code>region</code> is | |
1245 | * <code>null</code> itself or does not comprise at least one line delimiter, as a single line | |
1246 | * cannot be folded. | |
1247 | * | |
1248 | * @param region the region to align, may be <code>null</code> | |
1249 | * @param ctx the folding context | |
1250 | * @return a region equal or greater than <code>region</code> that is aligned with line | |
1251 | * offsets, <code>null</code> if the region is too small to be foldable (e.g. covers | |
1252 | * only one line) | |
1253 | */ | |
1254 | protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) { | |
1255 | if (region == null) | |
1256 | return null; | |
1257 | ||
1258 | IDocument document= ctx.getDocument(); | |
1259 | ||
1260 | try { | |
1261 | ||
1262 | int start= document.getLineOfOffset(region.getOffset()); | |
1263 | int end= document.getLineOfOffset(region.getOffset() + region.getLength()); | |
1264 | if (start >= end) | |
1265 | return null; | |
1266 | ||
1267 | int offset= document.getLineOffset(start); | |
1268 | int endOffset; | |
1269 | if (document.getNumberOfLines() > end + 1) | |
1270 | endOffset= document.getLineOffset(end + 1); | |
1271 | else | |
1272 | endOffset= document.getLineOffset(end) + document.getLineLength(end); | |
1273 | ||
1274 | return new Region(offset, endOffset - offset); | |
1275 | ||
1276 | } catch (BadLocationException x) { | |
1277 | // concurrent modification | |
1278 | return null; | |
1279 | } | |
1280 | } | |
1281 | ||
1282 | private ProjectionAnnotationModel getModel() { | |
1283 | return (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); | |
1284 | } | |
1285 | ||
1286 | private IDocument getDocument() { | |
1287 | JavaEditor editor= fEditor; | |
1288 | if (editor == null) | |
1289 | return null; | |
1290 | ||
1291 | return editor.generated_4083872133110632577(); | |
1292 | } | |
1293 | ||
1294 | /** | |
1295 | * Matches deleted annotations to changed or added ones. A deleted | |
1296 | * annotation/position tuple that has a matching addition / change | |
1297 | * is updated and marked as changed. The matching tuple is not added | |
1298 | * (for additions) or marked as deletion instead (for changes). The | |
1299 | * result is that more annotations are changed and fewer get | |
1300 | * deleted/re-added. | |
1301 | * | |
1302 | * @param deletions list with deleted annotations | |
1303 | * @param additions map with position to annotation mappings | |
1304 | * @param changes list with changed annotations | |
1305 | * @param ctx the context | |
1306 | */ | |
1307 | private void match(List<JavaProjectionAnnotation> deletions, Map<JavaProjectionAnnotation, Position> additions, List<JavaProjectionAnnotation> changes, FoldingStructureComputationContext ctx) { | |
1308 | if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty())) | |
1309 | return; | |
1310 | ||
1311 | List<JavaProjectionAnnotation> newDeletions= new ArrayList<JavaProjectionAnnotation>(); | |
1312 | List<JavaProjectionAnnotation> newChanges= new ArrayList<JavaProjectionAnnotation>(); | |
1313 | ||
1314 | Iterator<JavaProjectionAnnotation> deletionIterator= deletions.iterator(); | |
1315 | while (deletionIterator.hasNext()) { | |
1316 | JavaProjectionAnnotation deleted= deletionIterator.next(); | |
1317 | Position deletedPosition= ctx.getModel().getPosition(deleted); | |
1318 | if (deletedPosition == null) | |
1319 | continue; | |
1320 | ||
1321 | Tuple deletedTuple= new Tuple(deleted, deletedPosition); | |
1322 | ||
1323 | Tuple match= findMatch(deletedTuple, changes, null, ctx); | |
1324 | boolean addToDeletions= true; | |
1325 | if (match == null) { | |
1326 | match= findMatch(deletedTuple, additions.keySet(), additions, ctx); | |
1327 | addToDeletions= false; | |
1328 | } | |
1329 | ||
1330 | if (match != null) { | |
1331 | IJavaElement element= match.annotation.getElement(); | |
1332 | deleted.setElement(element); | |
1333 | match.generated_5894515189644979979(newDeletions, newChanges, deletionIterator, deleted, deletedPosition, this, addToDeletions, element); | |
1334 | } | |
1335 | } | |
1336 | ||
1337 | deletions.addAll(newDeletions); | |
1338 | changes.addAll(newChanges); | |
1339 | } | |
1340 | ||
1341 | /** | |
1342 | * Finds a match for <code>tuple</code> in a collection of | |
1343 | * annotations. The positions for the | |
1344 | * <code>JavaProjectionAnnotation</code> instances in | |
1345 | * <code>annotations</code> can be found in the passed | |
1346 | * <code>positionMap</code> or <code>fCachedModel</code> if | |
1347 | * <code>positionMap</code> is <code>null</code>. | |
1348 | * <p> | |
1349 | * A tuple is said to match another if their annotations have the | |
1350 | * same comment flag and their position offsets are equal. | |
1351 | * </p> | |
1352 | * <p> | |
1353 | * If a match is found, the annotation gets removed from | |
1354 | * <code>annotations</code>. | |
1355 | * </p> | |
1356 | * | |
1357 | * @param tuple the tuple for which we want to find a match | |
1358 | * @param annotations collection of | |
1359 | * <code>JavaProjectionAnnotation</code> | |
1360 | * @param positionMap a <code>Map<Annotation, Position></code> | |
1361 | * or <code>null</code> | |
1362 | * @param ctx the context | |
1363 | * @return a matching tuple or <code>null</code> for no match | |
1364 | */ | |
1365 | private Tuple findMatch(Tuple tuple, Collection<JavaProjectionAnnotation> annotations, Map<JavaProjectionAnnotation, Position> positionMap, FoldingStructureComputationContext ctx) { | |
1366 | Iterator<JavaProjectionAnnotation> it= annotations.iterator(); | |
1367 | while (it.hasNext()) { | |
1368 | JavaProjectionAnnotation annotation= it.next(); | |
1369 | if (tuple.annotation.isComment() == annotation.isComment()) { | |
1370 | Position position= annotation.generated_7272779290687362405(positionMap, ctx); | |
1371 | if (position == null) | |
1372 | continue; | |
1373 | ||
1374 | if (tuple.position.getOffset() == position.getOffset()) { | |
1375 | it.remove(); | |
1376 | return new Tuple(annotation, position); | |
1377 | } | |
1378 | } | |
1379 | } | |
1380 | ||
1381 | return null; | |
1382 | } | |
1383 | ||
1384 | private Map<IJavaElement, List<Tuple>> computeCurrentStructure(FoldingStructureComputationContext ctx) { | |
1385 | Map<IJavaElement, List<Tuple>> map= new HashMap<IJavaElement, List<Tuple>>(); | |
1386 | ProjectionAnnotationModel model= ctx.getModel(); | |
1387 | Iterator<Annotation> e= model.getAnnotationIterator(); | |
1388 | while (e.hasNext()) { | |
1389 | Object annotation= e.next(); | |
1390 | if (annotation instanceof JavaProjectionAnnotation) { | |
1391 | JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; | |
1392 | Position position= model.getPosition(java); | |
1393 | Assert.isNotNull(position); | |
1394 | List<Tuple> list= map.get(java.getElement()); | |
1395 | if (list == null) { | |
1396 | list= new ArrayList<Tuple>(2); | |
1397 | map.put(java.getElement(), list); | |
1398 | } | |
1399 | list.add(new Tuple(java, position)); | |
1400 | } | |
1401 | } | |
1402 | ||
1403 | Comparator<Tuple> comparator= new Comparator<Tuple>() { | |
1404 | public int compare(Tuple o1, Tuple o2) { | |
1405 | return o2.generated_4432417452402132400(o1, this); | |
1406 | } | |
1407 | }; | |
1408 | for (Iterator<List<Tuple>> it= map.values().iterator(); it.hasNext();) { | |
1409 | List<Tuple> list= it.next(); | |
1410 | Collections.sort(list, comparator); | |
1411 | } | |
1412 | return map; | |
1413 | } | |
1414 | ||
1415 | /* | |
1416 | * @see IJavaFoldingStructureProviderExtension#collapseMembers() | |
1417 | * @since 3.2 | |
1418 | */ | |
1419 | public final void collapseMembers() { | |
1420 | modifyFiltered(fMemberFilter, false); | |
1421 | } | |
1422 | ||
1423 | /* | |
1424 | * @see IJavaFoldingStructureProviderExtension#collapseComments() | |
1425 | * @since 3.2 | |
1426 | */ | |
1427 | public final void collapseComments() { | |
1428 | modifyFiltered(fCommentFilter, false); | |
1429 | } | |
1430 | ||
1431 | /* | |
1432 | * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#collapseElements(org.eclipse.jdt.core.IJavaElement[]) | |
1433 | */ | |
1434 | public final void collapseElements(IJavaElement[] elements) { | |
1435 | Set<IJavaElement> set= new HashSet<IJavaElement>(Arrays.asList(elements)); | |
1436 | modifyFiltered(new JavaElementSetFilter(set, false), false); | |
1437 | } | |
1438 | ||
1439 | /* | |
1440 | * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#expandElements(org.eclipse.jdt.core.IJavaElement[]) | |
1441 | */ | |
1442 | public final void expandElements(IJavaElement[] elements) { | |
1443 | Set<IJavaElement> set= new HashSet<IJavaElement>(Arrays.asList(elements)); | |
1444 | modifyFiltered(new JavaElementSetFilter(set, true), true); | |
1445 | } | |
1446 | ||
1447 | /** | |
1448 | * Collapses or expands all annotations matched by the passed filter. | |
1449 | * | |
1450 | * @param filter the filter to use to select which annotations to collapse | |
1451 | * @param expand <code>true</code> to expand the matched annotations, <code>false</code> to | |
1452 | * collapse them | |
1453 | */ | |
1454 | private void modifyFiltered(Filter filter, boolean expand) { | |
1455 | if (!isInstalled()) | |
1456 | return; | |
1457 | ||
1458 | ProjectionAnnotationModel model= getModel(); | |
1459 | if (model == null) | |
1460 | return; | |
1461 | ||
1462 | List<JavaProjectionAnnotation> modified= new ArrayList<JavaProjectionAnnotation>(); | |
1463 | Iterator<Annotation> iter= model.getAnnotationIterator(); | |
1464 | while (iter.hasNext()) { | |
1465 | Object annotation= iter.next(); | |
1466 | if (annotation instanceof JavaProjectionAnnotation) { | |
1467 | JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; | |
1468 | ||
1469 | java.generated_6809230897511527110(filter, expand, modified); | |
1470 | ||
1471 | } | |
1472 | } | |
1473 | ||
1474 | model.modifyAnnotations(null, null, modified.toArray(new Annotation[modified.size()])); | |
1475 | } | |
1476 | } |