/******************************************************************************* * Copyright (c) 2000, 2003 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.ui.text.folding; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sourceforge.phpdt.core.ElementChangedEvent; import net.sourceforge.phpdt.core.IElementChangedListener; import net.sourceforge.phpdt.core.IJavaElement; import net.sourceforge.phpdt.core.IJavaElementDelta; import net.sourceforge.phpdt.core.IParent; import net.sourceforge.phpdt.core.ISourceRange; import net.sourceforge.phpdt.core.ISourceReference; import net.sourceforge.phpdt.core.IType; import net.sourceforge.phpdt.core.JavaCore; import net.sourceforge.phpdt.core.JavaModelException; import net.sourceforge.phpdt.core.ToolFactory; import net.sourceforge.phpdt.core.compiler.IScanner; import net.sourceforge.phpdt.core.compiler.ITerminalSymbols; import net.sourceforge.phpdt.core.compiler.InvalidInputException; import net.sourceforge.phpdt.ui.IWorkingCopyManager; import net.sourceforge.phpdt.ui.PreferenceConstants; import net.sourceforge.phpdt.ui.text.folding.IJavaFoldingStructureProvider; import net.sourceforge.phpeclipse.PHPeclipsePlugin; import net.sourceforge.phpeclipse.phpeditor.PHPEditor; import net.sourceforge.phpeclipse.phpeditor.PHPUnitEditor; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.projection.IProjectionListener; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; /** * Updates the projection model of a class file or compilation unit. * * @since 3.0 */ public class DefaultJavaFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider { private static class JavaProjectionAnnotation extends ProjectionAnnotation { private IJavaElement fJavaElement; private boolean fIsComment; public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) { super(isCollapsed); fJavaElement= element; fIsComment= isComment; } public IJavaElement getElement() { return fJavaElement; } public void setElement(IJavaElement element) { fJavaElement= element; } public boolean isComment() { return fIsComment; } public void setIsComment(boolean isComment) { fIsComment= isComment; } } private class ElementChangedListener implements IElementChangedListener { /* * @see net.sourceforge.phpdt.core.IElementChangedListener#elementChanged(net.sourceforge.phpdt.core.ElementChangedEvent) */ public void elementChanged(ElementChangedEvent e) { IJavaElementDelta delta= findElement(fInput, e.getDelta()); if (delta != null) processDelta(delta); } private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) { if (delta == null || target == null) return null; IJavaElement element= delta.getElement(); if (element.getElementType() > IJavaElement.CLASS_FILE) return null; if (target.equals(element)) return delta; IJavaElementDelta[] children= delta.getAffectedChildren(); if (children == null || children.length == 0) return null; for (int i= 0; i < children.length; i++) { IJavaElementDelta d= findElement(target, children[i]); if (d != null) return d; } return null; } } private IDocument fCachedDocument; private ITextEditor fEditor; private ProjectionViewer fViewer; private IJavaElement fInput; private IElementChangedListener fElementListener; private boolean fAllowCollapsing= false; private boolean fCollapseJavadoc= false; private boolean fCollapseImportContainer= true; private boolean fCollapseInnerTypes= true; private boolean fCollapseMethods= false; public DefaultJavaFoldingStructureProvider() { } public void install(ITextEditor editor, ProjectionViewer viewer) { if (editor instanceof PHPEditor) { fEditor= editor; fViewer= viewer; fViewer.addProjectionListener(this); } } public void uninstall() { if (isInstalled()) { projectionDisabled(); fViewer.removeProjectionListener(this); fViewer= null; fEditor= null; } } protected boolean isInstalled() { return fEditor != null; } /* * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled() */ public void projectionEnabled() { if (fEditor instanceof PHPEditor) { initialize(); fElementListener= new ElementChangedListener(); JavaCore.addElementChangedListener(fElementListener); } } /* * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled() */ public void projectionDisabled() { fCachedDocument= null; if (fElementListener != null) { JavaCore.removeElementChangedListener(fElementListener); fElementListener= null; } } public void initialize() { if (!isInstalled()) return; initializePreferences(); try { IDocumentProvider provider= fEditor.getDocumentProvider(); fCachedDocument= provider.getDocument(fEditor.getEditorInput()); fAllowCollapsing= true; if (fEditor instanceof PHPUnitEditor) { IWorkingCopyManager manager= PHPeclipsePlugin.getDefault().getWorkingCopyManager(); fInput= manager.getWorkingCopy(fEditor.getEditorInput()); } // else if (fEditor instanceof ClassFileEditor) { // IClassFileEditorInput editorInput= (IClassFileEditorInput) fEditor.getEditorInput(); // fInput= editorInput.getClassFile(); // } if (fInput != null) { ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); if (model != null) { Map additions= computeAdditions((IParent) fInput); model.removeAllAnnotations(); model.replaceAnnotations(null, additions); } } } finally { fCachedDocument= null; fAllowCollapsing= false; } } private void initializePreferences() { IPreferenceStore store= PHPeclipsePlugin.getDefault().getPreferenceStore(); fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); } private Map computeAdditions(IParent parent) { Map map= new HashMap(); try { computeAdditions(parent.getChildren(), map); } catch (JavaModelException x) { } return map; } private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException { for (int i= 0; i < elements.length; i++) { IJavaElement element= elements[i]; computeAdditions(element, map); if (element instanceof IParent) { IParent parent= (IParent) element; computeAdditions(parent.getChildren(), map); } } } private void computeAdditions(IJavaElement element, Map map) { boolean createProjection= false; boolean collapse= false; switch (element.getElementType()) { case IJavaElement.IMPORT_CONTAINER: collapse= fAllowCollapsing && fCollapseImportContainer; createProjection= true; break; case IJavaElement.TYPE: collapse= fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element); createProjection= true; break; case IJavaElement.METHOD: collapse= fAllowCollapsing && fCollapseMethods; createProjection= true; break; } if (createProjection) { IRegion[] regions= computeProjectionRanges(element); if (regions != null) { // comments for (int i= 0; i < regions.length - 1; i++) { Position position= createProjectionPosition(regions[i]); if (position != null) map.put(new JavaProjectionAnnotation(element, fAllowCollapsing && fCollapseJavadoc, true), position); } // code Position position= createProjectionPosition(regions[regions.length - 1]); if (position != null) map.put(new JavaProjectionAnnotation(element, collapse, false), position); } } } private boolean isInnerType(IType type) { try { return type.isMember(); } catch (JavaModelException x) { IJavaElement parent= type.getParent(); if (parent != null) { int parentType= parent.getElementType(); return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE); } } return false; } private IRegion[] computeProjectionRanges(IJavaElement element) { try { if (element instanceof ISourceReference) { ISourceReference reference= (ISourceReference) element; ISourceRange range= reference.getSourceRange(); String contents= reference.getSource(); if (contents == null) return null; IScanner scanner= ToolFactory.createScanner(true/*tokenizeComments*/, false, false, true /* phpMode */ ); scanner.setSource(contents.toCharArray()); List regions= new ArrayList(); int shift= range.getOffset(); int start= shift; while (true) { int token= scanner.getNextToken(); start= shift + scanner.getCurrentTokenStartPosition(); switch (token) { case ITerminalSymbols.TokenNameCOMMENT_PHPDOC: // COMMENT_JAVADOC: case ITerminalSymbols.TokenNameCOMMENT_BLOCK: { int end= shift + scanner.getCurrentTokenEndPosition() + 1; regions.add(new Region(start, end - start)); } case ITerminalSymbols.TokenNameCOMMENT_LINE: continue; } break; } regions.add(new Region(start, range.getOffset() + range.getLength() - start)); if (regions.size() > 0) { IRegion[] result= new IRegion[regions.size()]; regions.toArray(result); return result; } } } catch (JavaModelException e) { } catch (InvalidInputException e) { } return null; } private Position createProjectionPosition(IRegion region) { if (fCachedDocument == null) return null; try { int start= fCachedDocument.getLineOfOffset(region.getOffset()); int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength()); if (start != end) { int offset= fCachedDocument.getLineOffset(start); int endOffset= fCachedDocument.getLineOffset(end + 1); return new Position(offset, endOffset - offset); } } catch (BadLocationException x) { } return null; } protected void processDelta(IJavaElementDelta delta) { if (!isInstalled()) return; ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class); if (model == null) return; try { IDocumentProvider provider= fEditor.getDocumentProvider(); fCachedDocument= provider.getDocument(fEditor.getEditorInput()); fAllowCollapsing= false; Map additions= new HashMap(); List deletions= new ArrayList(); List updates= new ArrayList(); Map updated= computeAdditions((IParent) fInput); Map previous= createAnnotationMap(model); Iterator e= updated.keySet().iterator(); while (e.hasNext()) { JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) e.next(); IJavaElement element= annotation.getElement(); Position position= (Position) updated.get(annotation); List annotations= (List) previous.get(element); if (annotations == null) { additions.put(annotation, position); } else { Iterator x= annotations.iterator(); while (x.hasNext()) { JavaProjectionAnnotation a= (JavaProjectionAnnotation) x.next(); if (annotation.isComment() == a.isComment()) { Position p= model.getPosition(a); if (p != null && !position.equals(p)) { p.setOffset(position.getOffset()); p.setLength(position.getLength()); updates.add(a); } x.remove(); break; } } if (annotations.isEmpty()) previous.remove(element); } } e= previous.values().iterator(); while (e.hasNext()) { List list= (List) e.next(); int size= list.size(); for (int i= 0; i < size; i++) deletions.add(list.get(i)); } match(model, deletions, additions, updates); Annotation[] removals= new Annotation[deletions.size()]; deletions.toArray(removals); Annotation[] changes= new Annotation[updates.size()]; updates.toArray(changes); model.modifyAnnotations(removals, additions, changes); } finally { fCachedDocument= null; fAllowCollapsing= true; } } private void match(ProjectionAnnotationModel model, List deletions, Map additions, List changes) { if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty())) return; List newDeletions= new ArrayList(); List newChanges= new ArrayList(); Iterator deletionIterator= deletions.iterator(); outer: while (deletionIterator.hasNext()) { JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next(); Position deletedPosition= model.getPosition(deleted); if (deletedPosition == null) continue; Iterator changesIterator= changes.iterator(); while (changesIterator.hasNext()) { JavaProjectionAnnotation changed= (JavaProjectionAnnotation) changesIterator.next(); if (deleted.isComment() == changed.isComment()) { Position changedPosition= model.getPosition(changed); if (changedPosition == null) continue; if (deletedPosition.getOffset() == changedPosition.getOffset()) { deletedPosition.setLength(changedPosition.getLength()); deleted.setElement(changed.getElement()); deletionIterator.remove(); newChanges.add(deleted); changesIterator.remove(); newDeletions.add(changed); continue outer; } } } Iterator additionsIterator= additions.keySet().iterator(); while (additionsIterator.hasNext()) { JavaProjectionAnnotation added= (JavaProjectionAnnotation) additionsIterator.next(); if (deleted.isComment() == added.isComment()) { Position addedPosition= (Position) additions.get(added); if (deletedPosition.getOffset() == addedPosition.getOffset()) { deletedPosition.setLength(addedPosition.getLength()); deleted.setElement(added.getElement()); deletionIterator.remove(); newChanges.add(deleted); additionsIterator.remove(); break; } } } } deletions.addAll(newDeletions); changes.addAll(newChanges); } private Map createAnnotationMap(IAnnotationModel model) { Map map= new HashMap(); Iterator e= model.getAnnotationIterator(); while (e.hasNext()) { Object annotation= e.next(); if (annotation instanceof JavaProjectionAnnotation) { JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation; List list= (List) map.get(java.getElement()); if (list == null) { list= new ArrayList(2); map.put(java.getElement(), list); } list.add(java); } } return map; } }