--- /dev/null
+/*******************************************************************************
+ * 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, false, false);//, false);
+ 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;
+ }
+}