blob: 0eda91bdc384c942d03d00fe3e5dcfe9901756c3 [file] [log] [blame]
/*******************************************************************************
* 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 org.eclipse.jdt.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 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;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.ui.IWorkingCopyManager;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
/**
* 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 org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.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 JavaEditor) {
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 JavaEditor) {
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 CompilationUnitEditor) {
IWorkingCopyManager manager= JavaPlugin.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= JavaPlugin.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_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;
}
}