1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text.folding;
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.List;
19 import net.sourceforge.phpdt.core.ElementChangedEvent;
20 import net.sourceforge.phpdt.core.IElementChangedListener;
21 import net.sourceforge.phpdt.core.IJavaElement;
22 import net.sourceforge.phpdt.core.IJavaElementDelta;
23 import net.sourceforge.phpdt.core.IParent;
24 import net.sourceforge.phpdt.core.ISourceRange;
25 import net.sourceforge.phpdt.core.ISourceReference;
26 import net.sourceforge.phpdt.core.IType;
27 import net.sourceforge.phpdt.core.JavaCore;
28 import net.sourceforge.phpdt.core.JavaModelException;
29 import net.sourceforge.phpdt.core.ToolFactory;
30 import net.sourceforge.phpdt.core.compiler.IScanner;
31 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
32 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
33 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
34 import net.sourceforge.phpdt.ui.PreferenceConstants;
35 import net.sourceforge.phpdt.ui.text.folding.IJavaFoldingStructureProvider;
36 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
37 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
38 import net.sourceforge.phpeclipse.phpeditor.PHPUnitEditor;
40 import org.eclipse.jface.preference.IPreferenceStore;
41 import org.eclipse.jface.text.BadLocationException;
42 import org.eclipse.jface.text.IDocument;
43 import org.eclipse.jface.text.IRegion;
44 import org.eclipse.jface.text.Position;
45 import org.eclipse.jface.text.Region;
46 import org.eclipse.jface.text.source.Annotation;
47 import org.eclipse.jface.text.source.IAnnotationModel;
48 import org.eclipse.jface.text.source.projection.IProjectionListener;
49 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
50 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
51 import org.eclipse.jface.text.source.projection.ProjectionViewer;
52 import org.eclipse.ui.texteditor.IDocumentProvider;
53 import org.eclipse.ui.texteditor.ITextEditor;
57 * Updates the projection model of a class file or compilation unit.
61 public class DefaultJavaFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider {
63 private static class JavaProjectionAnnotation extends ProjectionAnnotation {
65 private IJavaElement fJavaElement;
66 private boolean fIsComment;
68 public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) {
70 fJavaElement= element;
71 fIsComment= isComment;
74 public IJavaElement getElement() {
78 public void setElement(IJavaElement element) {
79 fJavaElement= element;
82 public boolean isComment() {
86 public void setIsComment(boolean isComment) {
87 fIsComment= isComment;
91 private class ElementChangedListener implements IElementChangedListener {
94 * @see net.sourceforge.phpdt.core.IElementChangedListener#elementChanged(net.sourceforge.phpdt.core.ElementChangedEvent)
96 public void elementChanged(ElementChangedEvent e) {
97 IJavaElementDelta delta= findElement(fInput, e.getDelta());
102 private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {
104 if (delta == null || target == null)
107 IJavaElement element= delta.getElement();
109 if (element.getElementType() > IJavaElement.CLASS_FILE)
112 if (target.equals(element))
115 IJavaElementDelta[] children= delta.getAffectedChildren();
116 if (children == null || children.length == 0)
119 for (int i= 0; i < children.length; i++) {
120 IJavaElementDelta d= findElement(target, children[i]);
130 private IDocument fCachedDocument;
132 private ITextEditor fEditor;
133 private ProjectionViewer fViewer;
134 private IJavaElement fInput;
135 private IElementChangedListener fElementListener;
137 private boolean fAllowCollapsing= false;
138 private boolean fCollapseJavadoc= false;
139 private boolean fCollapseImportContainer= true;
140 private boolean fCollapseInnerTypes= true;
141 private boolean fCollapseMethods= false;
143 public DefaultJavaFoldingStructureProvider() {
146 public void install(ITextEditor editor, ProjectionViewer viewer) {
147 if (editor instanceof PHPEditor) {
150 fViewer.addProjectionListener(this);
154 public void uninstall() {
156 projectionDisabled();
157 fViewer.removeProjectionListener(this);
163 protected boolean isInstalled() {
164 return fEditor != null;
168 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
170 public void projectionEnabled() {
171 if (fEditor instanceof PHPEditor) {
173 fElementListener= new ElementChangedListener();
174 JavaCore.addElementChangedListener(fElementListener);
179 * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
181 public void projectionDisabled() {
182 fCachedDocument= null;
183 if (fElementListener != null) {
184 JavaCore.removeElementChangedListener(fElementListener);
185 fElementListener= null;
189 public void initialize() {
194 initializePreferences();
198 IDocumentProvider provider= fEditor.getDocumentProvider();
199 fCachedDocument= provider.getDocument(fEditor.getEditorInput());
200 fAllowCollapsing= true;
202 if (fEditor instanceof PHPUnitEditor) {
203 IWorkingCopyManager manager= PHPeclipsePlugin.getDefault().getWorkingCopyManager();
204 fInput= manager.getWorkingCopy(fEditor.getEditorInput());
206 // else if (fEditor instanceof ClassFileEditor) {
207 // IClassFileEditorInput editorInput= (IClassFileEditorInput) fEditor.getEditorInput();
208 // fInput= editorInput.getClassFile();
211 if (fInput != null) {
212 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
214 Map additions= computeAdditions((IParent) fInput);
215 model.removeAllAnnotations();
216 model.replaceAnnotations(null, additions);
221 fCachedDocument= null;
222 fAllowCollapsing= false;
226 private void initializePreferences() {
227 IPreferenceStore store= PHPeclipsePlugin.getDefault().getPreferenceStore();
228 fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
229 fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
230 fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
231 fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
234 private Map computeAdditions(IParent parent) {
235 Map map= new HashMap();
237 computeAdditions(parent.getChildren(), map);
238 } catch (JavaModelException x) {
243 private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException {
244 for (int i= 0; i < elements.length; i++) {
245 IJavaElement element= elements[i];
247 computeAdditions(element, map);
249 if (element instanceof IParent) {
250 IParent parent= (IParent) element;
251 computeAdditions(parent.getChildren(), map);
256 private void computeAdditions(IJavaElement element, Map map) {
258 boolean createProjection= false;
260 boolean collapse= false;
261 switch (element.getElementType()) {
263 case IJavaElement.IMPORT_CONTAINER:
264 collapse= fAllowCollapsing && fCollapseImportContainer;
265 createProjection= true;
267 case IJavaElement.TYPE:
268 collapse= fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element);
269 createProjection= true;
271 case IJavaElement.METHOD:
272 collapse= fAllowCollapsing && fCollapseMethods;
273 createProjection= true;
277 if (createProjection) {
278 IRegion[] regions= computeProjectionRanges(element);
279 if (regions != null) {
281 for (int i= 0; i < regions.length - 1; i++) {
282 Position position= createProjectionPosition(regions[i]);
283 if (position != null)
284 map.put(new JavaProjectionAnnotation(element, fAllowCollapsing && fCollapseJavadoc, true), position);
287 Position position= createProjectionPosition(regions[regions.length - 1]);
288 if (position != null)
289 map.put(new JavaProjectionAnnotation(element, collapse, false), position);
294 private boolean isInnerType(IType type) {
297 return type.isMember();
298 } catch (JavaModelException x) {
299 IJavaElement parent= type.getParent();
300 if (parent != null) {
301 int parentType= parent.getElementType();
302 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
309 private IRegion[] computeProjectionRanges(IJavaElement element) {
312 if (element instanceof ISourceReference) {
313 ISourceReference reference= (ISourceReference) element;
314 ISourceRange range= reference.getSourceRange();
315 String contents= reference.getSource();
316 if (contents == null)
319 IScanner scanner= ToolFactory.createScanner(true, false, false);//, false);
320 scanner.setSource(contents.toCharArray());
321 List regions= new ArrayList();
322 int shift= range.getOffset();
326 int token= scanner.getNextToken();
327 start= shift + scanner.getCurrentTokenStartPosition();
330 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC: // COMMENT_JAVADOC:
331 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
332 int end= shift + scanner.getCurrentTokenEndPosition() + 1;
333 regions.add(new Region(start, end - start));
335 case ITerminalSymbols.TokenNameCOMMENT_LINE:
342 regions.add(new Region(start, range.getOffset() + range.getLength() - start));
344 if (regions.size() > 0) {
345 IRegion[] result= new IRegion[regions.size()];
346 regions.toArray(result);
350 } catch (JavaModelException e) {
351 } catch (InvalidInputException e) {
357 private Position createProjectionPosition(IRegion region) {
359 if (fCachedDocument == null)
364 int start= fCachedDocument.getLineOfOffset(region.getOffset());
365 int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
367 int offset= fCachedDocument.getLineOffset(start);
368 int endOffset= fCachedDocument.getLineOffset(end + 1);
369 return new Position(offset, endOffset - offset);
372 } catch (BadLocationException x) {
378 protected void processDelta(IJavaElementDelta delta) {
383 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
389 IDocumentProvider provider= fEditor.getDocumentProvider();
390 fCachedDocument= provider.getDocument(fEditor.getEditorInput());
391 fAllowCollapsing= false;
393 Map additions= new HashMap();
394 List deletions= new ArrayList();
395 List updates= new ArrayList();
397 Map updated= computeAdditions((IParent) fInput);
398 Map previous= createAnnotationMap(model);
401 Iterator e= updated.keySet().iterator();
402 while (e.hasNext()) {
403 JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) e.next();
404 IJavaElement element= annotation.getElement();
405 Position position= (Position) updated.get(annotation);
407 List annotations= (List) previous.get(element);
408 if (annotations == null) {
410 additions.put(annotation, position);
414 Iterator x= annotations.iterator();
415 while (x.hasNext()) {
416 JavaProjectionAnnotation a= (JavaProjectionAnnotation) x.next();
417 if (annotation.isComment() == a.isComment()) {
418 Position p= model.getPosition(a);
419 if (p != null && !position.equals(p)) {
420 p.setOffset(position.getOffset());
421 p.setLength(position.getLength());
429 if (annotations.isEmpty())
430 previous.remove(element);
434 e= previous.values().iterator();
435 while (e.hasNext()) {
436 List list= (List) e.next();
437 int size= list.size();
438 for (int i= 0; i < size; i++)
439 deletions.add(list.get(i));
442 match(model, deletions, additions, updates);
444 Annotation[] removals= new Annotation[deletions.size()];
445 deletions.toArray(removals);
446 Annotation[] changes= new Annotation[updates.size()];
447 updates.toArray(changes);
448 model.modifyAnnotations(removals, additions, changes);
451 fCachedDocument= null;
452 fAllowCollapsing= true;
456 private void match(ProjectionAnnotationModel model, List deletions, Map additions, List changes) {
457 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
460 List newDeletions= new ArrayList();
461 List newChanges= new ArrayList();
463 Iterator deletionIterator= deletions.iterator();
464 outer: while (deletionIterator.hasNext()) {
465 JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next();
466 Position deletedPosition= model.getPosition(deleted);
467 if (deletedPosition == null)
470 Iterator changesIterator= changes.iterator();
471 while (changesIterator.hasNext()) {
472 JavaProjectionAnnotation changed= (JavaProjectionAnnotation) changesIterator.next();
473 if (deleted.isComment() == changed.isComment()) {
474 Position changedPosition= model.getPosition(changed);
475 if (changedPosition == null)
478 if (deletedPosition.getOffset() == changedPosition.getOffset()) {
480 deletedPosition.setLength(changedPosition.getLength());
481 deleted.setElement(changed.getElement());
483 deletionIterator.remove();
484 newChanges.add(deleted);
486 changesIterator.remove();
487 newDeletions.add(changed);
494 Iterator additionsIterator= additions.keySet().iterator();
495 while (additionsIterator.hasNext()) {
496 JavaProjectionAnnotation added= (JavaProjectionAnnotation) additionsIterator.next();
497 if (deleted.isComment() == added.isComment()) {
498 Position addedPosition= (Position) additions.get(added);
500 if (deletedPosition.getOffset() == addedPosition.getOffset()) {
502 deletedPosition.setLength(addedPosition.getLength());
503 deleted.setElement(added.getElement());
505 deletionIterator.remove();
506 newChanges.add(deleted);
508 additionsIterator.remove();
516 deletions.addAll(newDeletions);
517 changes.addAll(newChanges);
520 private Map createAnnotationMap(IAnnotationModel model) {
521 Map map= new HashMap();
522 Iterator e= model.getAnnotationIterator();
523 while (e.hasNext()) {
524 Object annotation= e.next();
525 if (annotation instanceof JavaProjectionAnnotation) {
526 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation;
527 List list= (List) map.get(java.getElement());
529 list= new ArrayList(2);
530 map.put(java.getElement(), list);