cideplus.ui.astview.ASTView.java Source code

Java tutorial

Introduction

Here is the source code for cideplus.ui.astview.ASTView.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package cideplus.ui.astview;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferListener;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IProblemRequestor;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jdt.ui.actions.ShowInPackageViewAction;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditor;

import cideplus.model.Feature;
import cideplus.ui.astview.action.SetFeatureAction;
import cideplus.ui.configuration.FeaturesConfigurationUtil;
import cideplus.ui.configuration.ICompilationUnitFeaturesManager;
import cideplus.ui.configuration.IFeaturesManager;

public class ASTView extends ViewPart implements IShowInSource {

    private static final int JLS3 = AST.JLS3;
    /** (Used to get rid of deprecation warnings in code)
     * @deprecated
     */
    private static final int JLS2 = AST.JLS2;

    private class ASTViewSelectionProvider implements ISelectionProvider {
        ListenerList fListeners = new ListenerList(ListenerList.IDENTITY);

        public void addSelectionChangedListener(ISelectionChangedListener listener) {
            fListeners.add(listener);
        }

        public ISelection getSelection() {
            IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
            ArrayList externalSelection = new ArrayList();
            for (Iterator iter = selection.iterator(); iter.hasNext();) {
                Object unwrapped = ASTView.unwrapAttribute(iter.next());
                if (unwrapped != null)
                    externalSelection.add(unwrapped);
            }
            return new StructuredSelection(externalSelection);
        }

        public void removeSelectionChangedListener(ISelectionChangedListener listener) {
            fListeners.remove(listener);
        }

        public void setSelection(ISelection selection) {
            //not supported
        }
    }

    private class ASTLevelToggle extends Action {
        private int fLevel;

        public ASTLevelToggle(String label, int level) {
            super(label, AS_RADIO_BUTTON);
            fLevel = level;
            if (level == getCurrentASTLevel()) {
                setChecked(true);
            }
        }

        public int getLevel() {
            return fLevel;
        }

        @Override
        public void run() {
            setASTLevel(fLevel, true);
        }
    }

    private class ASTInputKindAction extends Action {
        public static final int USE_PARSER = 1;
        public static final int USE_RECONCILE = 2;
        public static final int USE_CACHE = 3;
        public static final int USE_FOCAL = 4;

        private int fInputKind;

        public ASTInputKindAction(String label, int inputKind) {
            super(label, AS_RADIO_BUTTON);
            fInputKind = inputKind;
            if (inputKind == getCurrentInputKind()) {
                setChecked(true);
            }
        }

        public int getInputKind() {
            return fInputKind;
        }

        @Override
        public void run() {
            setASTInputType(fInputKind);
        }
    }

    private static class ListenerMix implements ISelectionListener, IFileBufferListener, IDocumentListener,
            ISelectionChangedListener, IDoubleClickListener, IPartListener2 {

        private boolean fASTViewVisible = true;
        private ASTView fView;

        public ListenerMix(ASTView view) {
            fView = view;
        }

        public void dispose() {
            fView = null;
        }

        public void selectionChanged(IWorkbenchPart part, ISelection selection) {
            if (fASTViewVisible) {
                fView.handleEditorPostSelectionChanged(part, selection);
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferCreated(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void bufferCreated(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferDisposed(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void bufferDisposed(IFileBuffer buffer) {
            if (buffer instanceof ITextFileBuffer) {
                fView.handleDocumentDisposed(((ITextFileBuffer) buffer).getDocument());
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferContentAboutToBeReplaced(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void bufferContentAboutToBeReplaced(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#bufferContentReplaced(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void bufferContentReplaced(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#stateChanging(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void stateChanging(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#dirtyStateChanged(org.eclipse.core.filebuffers.IFileBuffer, boolean)
         */
        public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#stateValidationChanged(org.eclipse.core.filebuffers.IFileBuffer, boolean)
         */
        public void stateValidationChanged(IFileBuffer buffer, boolean isStateValidated) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#underlyingFileMoved(org.eclipse.core.filebuffers.IFileBuffer, org.eclipse.core.runtime.IPath)
         */
        public void underlyingFileMoved(IFileBuffer buffer, IPath path) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#underlyingFileDeleted(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void underlyingFileDeleted(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.filebuffers.IFileBufferListener#stateChangeFailed(org.eclipse.core.filebuffers.IFileBuffer)
         */
        public void stateChangeFailed(IFileBuffer buffer) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
         */
        public void documentAboutToBeChanged(DocumentEvent event) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
         */
        public void documentChanged(DocumentEvent event) {
            fView.handleDocumentChanged(event.getDocument());
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
         */
        public void selectionChanged(SelectionChangedEvent event) {
            fView.handleSelectionChanged(event.getSelection());
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
         */
        public void doubleClick(DoubleClickEvent event) {
            fView.handleDoubleClick(event);
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partHidden(IWorkbenchPartReference partRef) {
            IWorkbenchPart part = partRef.getPart(false);
            if (part == fView) {
                fASTViewVisible = false;
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partVisible(IWorkbenchPartReference partRef) {
            IWorkbenchPart part = partRef.getPart(false);
            if (part == fView) {
                fASTViewVisible = true;
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partActivated(IWorkbenchPartReference partRef) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partBroughtToTop(IWorkbenchPartReference partRef) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partClosed(IWorkbenchPartReference partRef) {
            fView.notifyWorkbenchPartClosed(partRef);
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partDeactivated(IWorkbenchPartReference partRef) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partOpened(IWorkbenchPartReference partRef) {
            // not interesting
        }

        /* (non-Javadoc)
         * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partInputChanged(IWorkbenchPartReference partRef) {
            // not interesting
        }
    }

    private final static String SETTINGS_LINK_WITH_EDITOR = "link_with_editor"; //$NON-NLS-1$
    private final static String SETTINGS_INPUT_KIND = "input_kind"; //$NON-NLS-1$
    private final static String SETTINGS_NO_BINDINGS = "create_bindings"; //$NON-NLS-1$
    private final static String SETTINGS_NO_STATEMENTS_RECOVERY = "no_statements_recovery"; //$NON-NLS-1$
    private final static String SETTINGS_NO_BINDINGS_RECOVERY = "no_bindings_recovery"; //$NON-NLS-1$
    private final static String SETTINGS_IGNORE_METHOD_BODIES = "ignore_method_bodies"; //$NON-NLS-1$
    private final static String SETTINGS_SHOW_NON_RELEVANT = "show_non_relevant";//$NON-NLS-1$
    private final static String SETTINGS_JLS = "jls"; //$NON-NLS-1$

    private SashForm fSash;
    private TreeViewer fViewer;

    private ASTViewLabelProvider fASTLabelProvider;
    private TreeViewer fTray;

    private DrillDownAdapter fDrillDownAdapter;
    private Action fFocusAction;
    private Action fRefreshAction;
    private Action fCreateBindingsAction;
    private Action fStatementsRecoveryAction;
    private Action fBindingsRecoveryAction;
    private Action fIgnoreMethodBodiesAction;
    private Action fFilterNonRelevantAction;
    private Action fFindDeclaringNodeAction;
    private Action fParseBindingFromKeyAction;
    private Action fParseBindingFromElementAction;
    private Action fCollapseAction;
    private Action fExpandAction;
    private Action fClearAction;
    private TreeCopyAction fCopyAction;
    private Action fDoubleClickAction;
    private Action fLinkWithEditor;
    private Action fAddToTrayAction;
    private Action fDeleteAction;

    private ASTLevelToggle[] fASTVersionToggleActions;
    private int fCurrentASTLevel;

    private ASTInputKindAction[] fASTInputKindActions;
    private int fCurrentInputKind;

    private IFeaturesManager featuresManager;
    private ICompilationUnitFeaturesManager compilationUnitFeaturesManager;

    private ITextEditor fEditor;
    private ITypeRoot fTypeRoot;
    private CompilationUnit fRoot;
    private IDocument fCurrentDocument;
    private ArrayList fTrayRoots;

    private boolean fDoLinkWithEditor;
    private boolean fCreateBindings;
    private NonRelevantFilter fNonRelevantFilter;
    private boolean fStatementsRecovery;
    private boolean fBindingsRecovery;
    private boolean fIgnoreMethodBodies;

    private Object fPreviousDouble;

    private ListenerMix fSuperListener;
    private ISelectionChangedListener fTrayUpdater;

    private IDialogSettings fDialogSettings;

    public ASTView() {
        fSuperListener = null;
        fDialogSettings = ASTViewPlugin.getDefault().getDialogSettings();
        fDoLinkWithEditor = fDialogSettings.getBoolean(SETTINGS_LINK_WITH_EDITOR);
        try {
            fCurrentInputKind = fDialogSettings.getInt(SETTINGS_INPUT_KIND);
        } catch (NumberFormatException e) {
            fCurrentInputKind = ASTInputKindAction.USE_PARSER;
        }
        fCreateBindings = !fDialogSettings.getBoolean(SETTINGS_NO_BINDINGS); // inverse so that default is to create bindings
        fStatementsRecovery = !fDialogSettings.getBoolean(SETTINGS_NO_STATEMENTS_RECOVERY); // inverse so that default is use recovery
        fBindingsRecovery = !fDialogSettings.getBoolean(SETTINGS_NO_BINDINGS_RECOVERY); // inverse so that default is use recovery
        fIgnoreMethodBodies = fDialogSettings.getBoolean(SETTINGS_IGNORE_METHOD_BODIES);
        fCurrentASTLevel = JLS3;
        try {
            int level = fDialogSettings.getInt(SETTINGS_JLS);
            if (level == JLS2 || level == JLS3) {
                fCurrentASTLevel = level;
            }
        } catch (NumberFormatException e) {
            // ignore
        }
        fNonRelevantFilter = new NonRelevantFilter();
        fNonRelevantFilter.setShowNonRelevant(fDialogSettings.getBoolean(SETTINGS_SHOW_NON_RELEVANT));
    }

    public ICompilationUnitFeaturesManager getCompilationUnitFeaturesManager() {
        return compilationUnitFeaturesManager;
    }

    public TreeViewer getViewer() {
        return fViewer;
    }

    final void notifyWorkbenchPartClosed(IWorkbenchPartReference partRef) {
        if (fEditor != null && fEditor.equals(partRef.getPart(false))) {
            try {
                setInput(null);
            } catch (CoreException e) {
                // ignore
            }
        }
    }

    /*(non-Javadoc)
     * @see org.eclipse.ui.IViewPart#init(org.eclipse.ui.IViewSite)
     */
    @Override
    public void init(IViewSite site) throws PartInitException {
        super.setSite(site);
        if (fSuperListener == null) {
            fSuperListener = new ListenerMix(this);

            ISelectionService service = site.getWorkbenchWindow().getSelectionService();
            service.addPostSelectionListener(fSuperListener);
            site.getPage().addPartListener(fSuperListener);
            FileBuffers.getTextFileBufferManager().addFileBufferListener(fSuperListener);
        }
    }

    public int getCurrentASTLevel() {
        return fCurrentASTLevel;
    }

    public int getCurrentInputKind() {
        return fCurrentInputKind;
    }

    public void setInput(ITextEditor editor) throws CoreException {
        if (fEditor != null) {
            uninstallModificationListener();
        }

        fEditor = null;
        fRoot = null;

        if (editor != null) {
            ITypeRoot typeRoot = EditorUtility.getJavaInput(editor);
            /*
            try {
               System.out.println(editor.getEditorInput().getName());
               Field declaredField = AbstractTextEditor.class.getDeclaredField("fSourceViewer");
               declaredField.setAccessible(true);
               ISourceViewer sourceViewer = (ISourceViewer) declaredField.get(editor);
               ((ITextViewerExtension4)sourceViewer).addTextPresentationListener(new ColorPresentation(sourceViewer, editor));
               sourceViewer.changeTextPresentation(new TextPresentation(200), false);
            } catch (Exception e) {
               System.err.println("ERRROOO   "+e+" "+e.getMessage());
               e.printStackTrace();
            }
             */
            if (typeRoot == null) {
                throw new CoreException(getErrorStatus("Editor not showing a CU or class file", null)); //$NON-NLS-1$
            }
            fTypeRoot = typeRoot;
            int astLevel = getInitialASTLevel(typeRoot);

            ISelection selection = editor.getSelectionProvider().getSelection();
            if (selection instanceof ITextSelection) {
                ITextSelection textSelection = (ITextSelection) selection;
                fRoot = internalSetInput(typeRoot, textSelection.getOffset(), textSelection.getLength(), astLevel);
                fEditor = editor;
                setASTLevel(astLevel, false);
            }
            /* Inicia o featuresManager para o projeto selecionado */
            featuresManager = FeaturesConfigurationUtil.getFeaturesManager(typeRoot.getJavaProject().getProject());

            if (fEditor.getEditorInput() instanceof IFileEditorInput) {
                //IFile file = ((IFileEditorInput) fEditor.getEditorInput()).getFile();
                //javaFileFullPath = file.getFullPath().toString();
                final IProblemRequestor problemRequestor = new IProblemRequestor() { //strange: don't get bindings when supplying null as problemRequestor
                    public void acceptProblem(IProblem problem) {
                        /*not interested*/}

                    public void beginReporting() {
                        /*not interested*/}

                    public void endReporting() {
                        /*not interested*/}

                    public boolean isActive() {
                        return true;
                    }
                };
                WorkingCopyOwner copyOwner = new WorkingCopyOwner() {
                    @Override
                    public IProblemRequestor getProblemRequestor(ICompilationUnit workingCopy) {
                        return problemRequestor;
                    }
                };
                ICompilationUnit compilation = typeRoot.getWorkingCopy(copyOwner, null);
                try {
                    compilationUnitFeaturesManager = featuresManager.getManagerForFile(compilation);
                    fASTLabelProvider.setCompilationUnitFeaturesManager(compilationUnitFeaturesManager);
                    //refreshAST();
                    fViewer.refresh();
                } catch (Exception e) {
                    showAndLogError("Could not read featurer file for compilation unit. " + e.getMessage(), e);
                }
            }

            installModificationListener();
        }

    }

    private int getInitialASTLevel(ITypeRoot typeRoot) {
        String option = typeRoot.getJavaProject().getOption(JavaCore.COMPILER_SOURCE, true);
        if (option.compareTo(JavaCore.VERSION_1_5) >= 0) {
            return JLS3;
        }
        return fCurrentASTLevel; // use previous level
    }

    private CompilationUnit internalSetInput(ITypeRoot input, int offset, int length, int astLevel)
            throws CoreException {
        if (input.getBuffer() == null) {
            throw new CoreException(getErrorStatus("Input has no buffer", null)); //$NON-NLS-1$
        }

        try {
            CompilationUnit root = createAST(input, astLevel, offset);
            resetView(root);
            if (root == null) {
                setContentDescription("AST could not be created."); //$NON-NLS-1$
                return null;
            }
            ASTNode node = NodeFinder.perform(root, offset, length);
            if (node != null) {
                fViewer.getTree().setRedraw(false);
                try {
                    fASTLabelProvider.setSelectedRange(node.getStartPosition(), node.getLength());
                    fViewer.setSelection(new StructuredSelection(node), true);
                } finally {
                    fViewer.getTree().setRedraw(true);
                }
            }
            return root;

        } catch (RuntimeException e) {
            throw new CoreException(getErrorStatus("Could not create AST:\n" + e.getMessage(), e)); //$NON-NLS-1$
        }
    }

    private void clearView() {
        resetView(null);
        setContentDescription("Open a Java editor and press the 'Show AST of active editor' toolbar button"); //$NON-NLS-1$
    }

    private void resetView(CompilationUnit root) {
        fViewer.setInput(root);
        fViewer.getTree().setEnabled(root != null);
        fSash.setMaximizedControl(fViewer.getTree());
        fTrayRoots = new ArrayList();
        if (fTray != null)
            fTray.setInput(fTrayRoots);
        setASTUptoDate(root != null);
        fClearAction.setEnabled(root != null);
        fFindDeclaringNodeAction.setEnabled(root != null);
        fPreviousDouble = null; // avoid leaking AST
    }

    private CompilationUnit createAST(ITypeRoot input, int astLevel, int offset)
            throws JavaModelException, CoreException {
        long startTime;
        long endTime;
        CompilationUnit root;

        if ((getCurrentInputKind() == ASTInputKindAction.USE_RECONCILE)) {
            final IProblemRequestor problemRequestor = new IProblemRequestor() { //strange: don't get bindings when supplying null as problemRequestor
                public void acceptProblem(IProblem problem) {
                    /*not interested*/}

                public void beginReporting() {
                    /*not interested*/}

                public void endReporting() {
                    /*not interested*/}

                public boolean isActive() {
                    return true;
                }
            };
            WorkingCopyOwner workingCopyOwner = new WorkingCopyOwner() {
                @Override
                public IProblemRequestor getProblemRequestor(ICompilationUnit workingCopy) {
                    return problemRequestor;
                }
            };
            ICompilationUnit wc = input.getWorkingCopy(workingCopyOwner, null);
            try {
                int reconcileFlags = ICompilationUnit.FORCE_PROBLEM_DETECTION;
                if (fStatementsRecovery)
                    reconcileFlags |= ICompilationUnit.ENABLE_STATEMENTS_RECOVERY;
                if (fBindingsRecovery)
                    reconcileFlags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY;
                if (fIgnoreMethodBodies)
                    reconcileFlags |= ICompilationUnit.IGNORE_METHOD_BODIES;
                startTime = System.currentTimeMillis();
                root = wc.reconcile(getCurrentASTLevel(), reconcileFlags, null, null);
                endTime = System.currentTimeMillis();
            } finally {
                wc.discardWorkingCopy();
            }

        } else if (input instanceof ICompilationUnit && (getCurrentInputKind() == ASTInputKindAction.USE_CACHE)) {
            ICompilationUnit cu = (ICompilationUnit) input;
            startTime = System.currentTimeMillis();
            root = SharedASTProvider.getAST(cu, SharedASTProvider.WAIT_NO, null);
            endTime = System.currentTimeMillis();

        } else {
            ASTParser parser = ASTParser.newParser(astLevel);
            parser.setResolveBindings(fCreateBindings);
            if (input instanceof ICompilationUnit) {
                parser.setSource((ICompilationUnit) input);
            } else {
                parser.setSource((IClassFile) input);
            }
            parser.setStatementsRecovery(fStatementsRecovery);
            parser.setBindingsRecovery(fBindingsRecovery);
            parser.setIgnoreMethodBodies(fIgnoreMethodBodies);
            if (getCurrentInputKind() == ASTInputKindAction.USE_FOCAL) {
                parser.setFocalPosition(offset);
            }
            startTime = System.currentTimeMillis();
            root = (CompilationUnit) parser.createAST(null);
            endTime = System.currentTimeMillis();
        }
        if (root != null) {
            updateContentDescription(input, root, endTime - startTime);
        }
        return root;
    }

    protected void refreshASTSettingsActions() {
        boolean enabled;
        switch (getCurrentInputKind()) {
        case ASTInputKindAction.USE_CACHE:
            enabled = false;
            break;
        default:
            enabled = true;
            break;
        }
        fCreateBindingsAction.setEnabled(enabled && getCurrentInputKind() != ASTInputKindAction.USE_RECONCILE);
        fStatementsRecoveryAction.setEnabled(enabled);
        fBindingsRecoveryAction.setEnabled(enabled);
        fIgnoreMethodBodiesAction.setEnabled(enabled);
        fASTVersionToggleActions[0].setEnabled(enabled);
        fASTVersionToggleActions[1].setEnabled(enabled);
    }

    private void updateContentDescription(IJavaElement element, CompilationUnit root, long time) {
        String version = root.getAST().apiLevel() == JLS2 ? "AST Level 2" : "AST Level 3"; //$NON-NLS-1$//$NON-NLS-2$
        if (getCurrentInputKind() == ASTInputKindAction.USE_RECONCILE) {
            version += ", from reconciler"; //$NON-NLS-1$
        } else if (getCurrentInputKind() == ASTInputKindAction.USE_CACHE) {
            version += ", from ASTProvider"; //$NON-NLS-1$
        } else if (getCurrentInputKind() == ASTInputKindAction.USE_FOCAL) {
            version += ", using focal position"; //$NON-NLS-1$
        }
        TreeInfoCollector collector = new TreeInfoCollector(root);

        String msg = "{0} ({1}).  Creation time: {2,number} ms.  Size: {3,number} nodes, {4,number} bytes (AST nodes only)."; //$NON-NLS-1$
        Object[] args = { element.getElementName(), version, new Long(time),
                new Integer(collector.getNumberOfNodes()), new Integer(collector.getSize()) };
        setContentDescription(MessageFormat.format(msg, args));

    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkbenchPart#dispose()
     */
    @Override
    public void dispose() {
        if (fSuperListener != null) {
            if (fEditor != null) {
                uninstallModificationListener();
            }
            ISelectionService service = getSite().getWorkbenchWindow().getSelectionService();
            service.removePostSelectionListener(fSuperListener);
            getSite().getPage().removePartListener(fSuperListener);
            FileBuffers.getTextFileBufferManager().removeFileBufferListener(fSuperListener);
            fSuperListener.dispose(); // removes reference to view
            fSuperListener = null;
        }
        if (fTrayUpdater != null) {
            fViewer.removePostSelectionChangedListener(fTrayUpdater);
            fTray.removePostSelectionChangedListener(fTrayUpdater);
            fTrayUpdater = null;
        }
        super.dispose();
    }

    private IStatus getErrorStatus(String message, Throwable th) {
        return new Status(IStatus.ERROR, ASTViewPlugin.getPluginId(), IStatus.ERROR, message, th);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        fSash = new SashForm(parent, SWT.VERTICAL | SWT.SMOOTH);
        fViewer = new TreeViewer(fSash, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
        fDrillDownAdapter = new DrillDownAdapter(fViewer);
        fViewer.setContentProvider(new ASTViewContentProvider());
        fASTLabelProvider = new ASTViewLabelProvider();
        fViewer.setLabelProvider(fASTLabelProvider);
        fViewer.addSelectionChangedListener(fSuperListener);
        fViewer.addDoubleClickListener(fSuperListener);
        fViewer.addFilter(new ViewerFilter() {
            @Override
            public boolean select(Viewer viewer, Object parentElement, Object element) {
                if (!fCreateBindings && element instanceof Binding)
                    return false;
                return true;
            }
        });
        fViewer.addFilter(fNonRelevantFilter);

        ViewForm trayForm = new ViewForm(fSash, SWT.NONE);
        Label label = new Label(trayForm, SWT.NONE);
        label.setText(" Comparison Tray (* = selection in the upper tree):"); //$NON-NLS-1$
        trayForm.setTopLeft(label);

        fTray = new TreeViewer(trayForm, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
        trayForm.setContent(fTray.getTree());

        fTrayRoots = new ArrayList();
        fTray.setContentProvider(new TrayContentProvider());
        final TrayLabelProvider trayLabelProvider = new TrayLabelProvider();
        fTray.setLabelProvider(trayLabelProvider);
        fTray.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
        fTrayUpdater = new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                IStructuredSelection viewerSelection = (IStructuredSelection) fViewer.getSelection();
                if (viewerSelection.size() == 1) {
                    Object first = viewerSelection.getFirstElement();
                    if (unwrapAttribute(first) != null) {
                        trayLabelProvider.setViewerElement(first);
                        return;
                    }
                }
                trayLabelProvider.setViewerElement(null);
            }
        };
        fTray.addPostSelectionChangedListener(fTrayUpdater);
        fViewer.addPostSelectionChangedListener(fTrayUpdater);
        fTray.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                performTrayDoubleClick();
            }
        });
        fTray.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                fDeleteAction.setEnabled(selection.size() >= 1 && fTray.getTree().isFocusControl());
            }
        });
        fTray.getTree().addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                IStructuredSelection selection = (IStructuredSelection) fTray.getSelection();
                fDeleteAction.setEnabled(selection.size() >= 1);
            }

            @Override
            public void focusLost(FocusEvent e) {
                fDeleteAction.setEnabled(false);
            }
        });

        makeActions();
        hookContextMenu();
        hookTrayContextMenu();
        contributeToActionBars();
        getSite().setSelectionProvider(new ASTViewSelectionProvider());

        try {
            IEditorPart part = EditorUtility.getActiveEditor();
            if (part instanceof ITextEditor) {
                setInput((ITextEditor) part);
            }
        } catch (CoreException e) {
            // ignore
        }
        if (fTypeRoot == null) {
            clearView();
        } else {
            setASTUptoDate(fTypeRoot != null);
        }
    }

    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                ASTView.this.fillContextMenu(manager);
            }
        });
        Menu menu = menuMgr.createContextMenu(fViewer.getControl());
        fViewer.getControl().setMenu(menu);
        getSite().registerContextMenu(menuMgr, fViewer);
    }

    private void hookTrayContextMenu() {
        MenuManager menuMgr = new MenuManager("#TrayPopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                manager.add(fCopyAction);
                manager.add(fDeleteAction);
                manager.add(new Separator());
                manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
            }
        });
        Menu menu = menuMgr.createContextMenu(fTray.getControl());
        fTray.getControl().setMenu(menu);
        getSite().registerContextMenu("#TrayPopupMenu", menuMgr, fTray); //$NON-NLS-1$
    }

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        fillLocalPullDown(bars.getMenuManager());
        fillLocalToolBar(bars.getToolBarManager());
        bars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction);
        bars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), fFocusAction);
        bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fDeleteAction);

        IHandlerService handlerService = (IHandlerService) getViewSite().getService(IHandlerService.class);
        handlerService.activateHandler(IWorkbenchCommandConstants.NAVIGATE_TOGGLE_LINK_WITH_EDITOR,
                new ActionHandler(fLinkWithEditor));
    }

    private void fillLocalPullDown(IMenuManager manager) {
        for (int i = 0; i < fASTVersionToggleActions.length; i++) {
            manager.add(fASTVersionToggleActions[i]);
        }
        manager.add(new Separator());
        manager.add(fCreateBindingsAction);
        manager.add(fStatementsRecoveryAction);
        manager.add(fBindingsRecoveryAction);
        manager.add(fIgnoreMethodBodiesAction);
        manager.add(new Separator());
        for (int i = 0; i < fASTInputKindActions.length; i++) {
            manager.add(fASTInputKindActions[i]);
        }
        manager.add(new Separator());
        manager.add(fFindDeclaringNodeAction);
        manager.add(fParseBindingFromKeyAction);
        manager.add(fParseBindingFromElementAction);
        manager.add(new Separator());
        manager.add(fFilterNonRelevantAction);
        manager.add(fLinkWithEditor);
    }

    protected void fillContextMenu(IMenuManager manager) {
        Set<Feature> features;
        ISelection selection = fViewer.getSelection();
        Object obj = ((IStructuredSelection) selection).getFirstElement();
        final ASTNode node;
        if (obj instanceof ASTNode) {
            node = (ASTNode) obj;
        } else {
            node = null;
        }
        try {
            features = featuresManager.getFeatures();
            for (final Feature feature : features) {
                manager.add(new SetFeatureAction(this, feature, node /*featuresManager.getManagerForFile()*/));
            }
            manager.add(new Separator());
        } catch (Exception e) {
            showAndLogError("Could not read features. " + e.getMessage(), e);
        }

        manager.add(fFocusAction);
        manager.add(fRefreshAction);
        manager.add(fClearAction);
        manager.add(fCollapseAction);
        manager.add(fExpandAction);
        manager.add(new Separator());
        manager.add(fCopyAction);
        //if (fAddToTrayAction.isEnabled())
        //   manager.add(fAddToTrayAction);
        manager.add(new Separator());

        fDrillDownAdapter.addNavigationActions(manager);
        // Other plug-ins can contribute there actions here
        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    private void fillLocalToolBar(IToolBarManager manager) {
        manager.add(fFocusAction);
        manager.add(fRefreshAction);
        manager.add(fClearAction);
        manager.add(new Separator());
        fDrillDownAdapter.addNavigationActions(manager);
        manager.add(new Separator());
        manager.add(fExpandAction);
        manager.add(fCollapseAction);
        manager.add(fLinkWithEditor);
    }

    private void setASTUptoDate(boolean isuptoDate) {
        fRefreshAction.setEnabled(!isuptoDate && fTypeRoot != null);
    }

    private void makeActions() {
        fRefreshAction = new Action() {
            @Override
            public void run() {
                performRefresh();
            }
        };
        fRefreshAction.setText("&Refresh AST"); //$NON-NLS-1$
        fRefreshAction.setToolTipText("Refresh AST"); //$NON-NLS-1$
        fRefreshAction.setEnabled(false);
        ASTViewImages.setImageDescriptors(fRefreshAction, ASTViewImages.REFRESH);

        fClearAction = new Action() {
            @Override
            public void run() {
                performClear();
            }
        };
        fClearAction.setText("&Clear AST"); //$NON-NLS-1$
        fClearAction.setToolTipText("Clear AST and release memory"); //$NON-NLS-1$
        fClearAction.setEnabled(false);
        ASTViewImages.setImageDescriptors(fClearAction, ASTViewImages.CLEAR);

        fASTInputKindActions = new ASTInputKindAction[] {
                new ASTInputKindAction("Use ASTParser.&createAST", ASTInputKindAction.USE_PARSER), //$NON-NLS-1$
                new ASTInputKindAction("Use ASTParser with &focal position", ASTInputKindAction.USE_FOCAL), //$NON-NLS-1$
                new ASTInputKindAction("Use ICompilationUnit.&reconcile", ASTInputKindAction.USE_RECONCILE), //$NON-NLS-1$
                new ASTInputKindAction("Use SharedASTProvider.&getAST", ASTInputKindAction.USE_CACHE) //$NON-NLS-1$
        };

        fCreateBindingsAction = new Action("&Create Bindings", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
            @Override
            public void run() {
                performCreateBindings();
            }
        };
        fCreateBindingsAction.setChecked(fCreateBindings);
        fCreateBindingsAction.setToolTipText("Create Bindings"); //$NON-NLS-1$
        fCreateBindingsAction.setEnabled(true);

        fStatementsRecoveryAction = new Action("&Statements Recovery", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
            @Override
            public void run() {
                performStatementsRecovery();
            }
        };
        fStatementsRecoveryAction.setChecked(fStatementsRecovery);
        fStatementsRecoveryAction.setEnabled(true);

        fBindingsRecoveryAction = new Action("&Bindings Recovery", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
            @Override
            public void run() {
                performBindingsRecovery();
            }
        };
        fBindingsRecoveryAction.setChecked(fBindingsRecovery);
        fBindingsRecoveryAction.setEnabled(true);

        fIgnoreMethodBodiesAction = new Action("&Ignore Method Bodies", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
            @Override
            public void run() {
                performIgnoreMethodBodies();
            }
        };
        fIgnoreMethodBodiesAction.setChecked(fIgnoreMethodBodies);
        fIgnoreMethodBodiesAction.setEnabled(true);

        fFilterNonRelevantAction = new Action("&Hide Non-Relevant Attributes", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
            @Override
            public void run() {
                performFilterNonRelevant();
            }
        };
        fFilterNonRelevantAction.setChecked(!fNonRelevantFilter.isShowNonRelevant());
        fFilterNonRelevantAction.setToolTipText("Hide non-relevant binding attributes"); //$NON-NLS-1$
        fFilterNonRelevantAction.setEnabled(true);

        fFindDeclaringNodeAction = new Action("Find &Declaring Node...", IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
            @Override
            public void run() {
                performFindDeclaringNode();
            }
        };
        fFindDeclaringNodeAction.setToolTipText("Find Declaring Node..."); //$NON-NLS-1$
        fFindDeclaringNodeAction.setEnabled(false);

        fParseBindingFromElementAction = new Action("&Parse Binding from &Element Handle...", //$NON-NLS-1$
                IAction.AS_PUSH_BUTTON) {
            @Override
            public void run() {
                performParseBindingFromElement();
            }
        };
        fParseBindingFromElementAction.setToolTipText("Parse Binding from Element Handle..."); //$NON-NLS-1$
        fParseBindingFromElementAction.setEnabled(true);

        fParseBindingFromKeyAction = new Action("Parse Binding from &Key...", IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
            @Override
            public void run() {
                performParseBindingFromKey();
            }
        };
        fParseBindingFromKeyAction.setToolTipText("Parse Binding from Key..."); //$NON-NLS-1$
        fParseBindingFromKeyAction.setEnabled(true);

        fFocusAction = new Action() {
            @Override
            public void run() {
                performSetFocus();
            }
        };
        fFocusAction.setText("&Show AST of active editor"); //$NON-NLS-1$
        fFocusAction.setToolTipText("Show AST of active editor"); //$NON-NLS-1$
        fFocusAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_REFRESH);
        ASTViewImages.setImageDescriptors(fFocusAction, ASTViewImages.SETFOCUS);

        fCollapseAction = new Action() {
            @Override
            public void run() {
                performCollapse();
            }
        };
        fCollapseAction.setText("C&ollapse"); //$NON-NLS-1$
        fCollapseAction.setToolTipText("Collapse Selected Node"); //$NON-NLS-1$
        fCollapseAction.setEnabled(false);
        ASTViewImages.setImageDescriptors(fCollapseAction, ASTViewImages.COLLAPSE);

        fExpandAction = new Action() {
            @Override
            public void run() {
                performExpand();
            }
        };
        fExpandAction.setText("E&xpand"); //$NON-NLS-1$
        fExpandAction.setToolTipText("Expand Selected Node"); //$NON-NLS-1$
        fExpandAction.setEnabled(false);
        ASTViewImages.setImageDescriptors(fExpandAction, ASTViewImages.EXPAND);

        fCopyAction = new TreeCopyAction(new Tree[] { fViewer.getTree(), fTray.getTree() });

        fDoubleClickAction = new Action() {
            @Override
            public void run() {
                performDoubleClick();
            }
        };

        fLinkWithEditor = new Action() {
            @Override
            public void run() {
                performLinkWithEditor();
            }
        };
        fLinkWithEditor.setChecked(fDoLinkWithEditor);
        fLinkWithEditor.setText("&Link with Editor"); //$NON-NLS-1$
        fLinkWithEditor.setToolTipText("Link With Editor"); //$NON-NLS-1$
        fLinkWithEditor.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_TOGGLE_LINK_WITH_EDITOR);
        ASTViewImages.setImageDescriptors(fLinkWithEditor, ASTViewImages.LINK_WITH_EDITOR);

        fASTVersionToggleActions = new ASTLevelToggle[] { new ASTLevelToggle("AST Level &2.0", JLS2), //$NON-NLS-1$
                new ASTLevelToggle("AST Level &3.0", JLS3) //$NON-NLS-1$
        };

        fAddToTrayAction = new Action() {
            @Override
            public void run() {
                performAddToTray();
            }
        };
        fAddToTrayAction.setText("&Add to Comparison Tray"); //$NON-NLS-1$
        fAddToTrayAction.setToolTipText("Add Selected Node to Comparison Tray"); //$NON-NLS-1$
        fAddToTrayAction.setEnabled(false);
        ASTViewImages.setImageDescriptors(fAddToTrayAction, ASTViewImages.ADD_TO_TRAY);

        fDeleteAction = new Action() {
            @Override
            public void run() {
                performDelete();
            }
        };
        fDeleteAction.setText("&Delete"); //$NON-NLS-1$
        fDeleteAction.setToolTipText("Delete Binding from Tray"); //$NON-NLS-1$
        fDeleteAction.setEnabled(false);
        fDeleteAction.setImageDescriptor(ASTViewPlugin.getDefault().getWorkbench().getSharedImages()
                .getImageDescriptor(ISharedImages.IMG_TOOL_DELETE));
        fDeleteAction.setId(ActionFactory.DELETE.getId());
        fDeleteAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_DELETE);

        refreshASTSettingsActions();
    }

    private void refreshAST() throws CoreException {
        ASTNode node = getASTNodeNearSelection((IStructuredSelection) fViewer.getSelection());
        int offset = 0;
        int length = 0;
        if (node != null) {
            offset = node.getStartPosition();
            length = node.getLength();
        }

        internalSetInput(fTypeRoot, offset, length, getCurrentASTLevel());
    }

    protected void setASTLevel(int level, boolean doRefresh) {
        int oldLevel = fCurrentASTLevel;
        fCurrentASTLevel = level;

        fDialogSettings.put(SETTINGS_JLS, fCurrentASTLevel);

        if (doRefresh && fTypeRoot != null && oldLevel != fCurrentASTLevel) {
            try {
                refreshAST();
            } catch (CoreException e) {
                showAndLogError("Could not set AST to new level.", e); //$NON-NLS-1$
                // set back to old level
                fCurrentASTLevel = oldLevel;
            }
        }
        // update action state
        for (int i = 0; i < fASTVersionToggleActions.length; i++) {
            ASTLevelToggle curr = fASTVersionToggleActions[i];
            curr.setChecked(curr.getLevel() == fCurrentASTLevel);
        }
    }

    protected void setASTInputType(int inputKind) {
        if (inputKind != fCurrentInputKind) {
            fCurrentInputKind = inputKind;
            fDialogSettings.put(SETTINGS_INPUT_KIND, inputKind);
            for (int i = 0; i < fASTInputKindActions.length; i++) {
                ASTInputKindAction curr = fASTInputKindActions[i];
                curr.setChecked(curr.getInputKind() == inputKind);
            }
            refreshASTSettingsActions();
            performRefresh();
        }
    }

    private ASTNode getASTNodeNearSelection(IStructuredSelection selection) {
        Object elem = selection.getFirstElement();
        if (elem instanceof ASTAttribute) {
            return ((ASTAttribute) elem).getParentASTNode();
        } else if (elem instanceof ASTNode) {
            return (ASTNode) elem;
        }
        return null;
    }

    private void installModificationListener() {
        fCurrentDocument = fEditor.getDocumentProvider().getDocument(fEditor.getEditorInput());
        fCurrentDocument.addDocumentListener(fSuperListener);
    }

    private void uninstallModificationListener() {
        if (fCurrentDocument != null) {
            fCurrentDocument.removeDocumentListener(fSuperListener);
            fCurrentDocument = null;
        }
    }

    protected void handleDocumentDisposed(IDocument document) {
        uninstallModificationListener();
    }

    protected void handleDocumentChanged(IDocument document) {
        setASTUptoDate(false);
    }

    protected void handleSelectionChanged(ISelection selection) {
        fExpandAction.setEnabled(!selection.isEmpty());
        fCollapseAction.setEnabled(!selection.isEmpty());
        fCopyAction.setEnabled(!selection.isEmpty());

        boolean addEnabled = false;
        IStructuredSelection structuredSelection = (IStructuredSelection) selection;
        if (structuredSelection.size() == 1 && fViewer.getTree().isFocusControl()) {
            Object first = structuredSelection.getFirstElement();
            Object unwrapped = ASTView.unwrapAttribute(first);
            addEnabled = unwrapped != null;
        }
        fAddToTrayAction.setEnabled(addEnabled);
    }

    protected void handleEditorPostSelectionChanged(IWorkbenchPart part, ISelection selection) {
        if (!(selection instanceof ITextSelection)) {
            return;
        }
        ITextSelection textSelection = (ITextSelection) selection;
        if (part == fEditor) {
            fViewer.getTree().setRedraw(false);
            try {
                fASTLabelProvider.setSelectedRange(textSelection.getOffset(), textSelection.getLength());
            } finally {
                fViewer.getTree().setRedraw(true);
            }
        }
        if (!fDoLinkWithEditor) {
            return;
        }
        if (fRoot == null || part != fEditor) {
            if (part instanceof ITextEditor && (EditorUtility.getJavaInput((ITextEditor) part) != null)) {
                try {
                    setInput((ITextEditor) part);
                } catch (CoreException e) {
                    setContentDescription(e.getStatus().getMessage());
                }
            }

        } else { // fRoot != null && part == fEditor
            doLinkWithEditor(selection);
        }
    }

    private void doLinkWithEditor(ISelection selection) {
        ITextSelection textSelection = (ITextSelection) selection;
        int offset = textSelection.getOffset();
        int length = textSelection.getLength();

        NodeFinder finder = new NodeFinder(offset, length);
        fRoot.accept(finder);
        ASTNode covering = finder.getCoveringNode();
        if (covering != null) {
            fViewer.reveal(covering);
            fViewer.setSelection(new StructuredSelection(covering));
        }
    }

    protected void handleDoubleClick(DoubleClickEvent event) {
        fDoubleClickAction.run();
    }

    protected void performLinkWithEditor() {
        fDoLinkWithEditor = fLinkWithEditor.isChecked();
        fDialogSettings.put(SETTINGS_LINK_WITH_EDITOR, fDoLinkWithEditor);

        if (fDoLinkWithEditor && fEditor != null) {
            ISelectionProvider selectionProvider = fEditor.getSelectionProvider();
            if (selectionProvider != null) { // can be null when editor is closed
                doLinkWithEditor(selectionProvider.getSelection());
            }
        }
    }

    protected void performCollapse() {
        IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
        if (selection.isEmpty()) {
            fViewer.collapseAll();
        } else {
            Object[] selected = selection.toArray();
            fViewer.getTree().setRedraw(false);
            for (int i = 0; i < selected.length; i++) {
                fViewer.collapseToLevel(selected[i], AbstractTreeViewer.ALL_LEVELS);
            }
            fViewer.getTree().setRedraw(true);
        }
    }

    protected void performExpand() {
        IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
        if (selection.isEmpty()) {
            fViewer.expandToLevel(3);
        } else {
            Object[] selected = selection.toArray();
            fViewer.getTree().setRedraw(false);
            for (int i = 0; i < selected.length; i++) {
                fViewer.expandToLevel(selected[i], 3);
            }
            fViewer.getTree().setRedraw(true);
        }
    }

    protected void performSetFocus() {
        IEditorPart part = EditorUtility.getActiveEditor();
        if (part instanceof ITextEditor) {
            try {
                setInput((ITextEditor) part);
            } catch (CoreException e) {
                showAndLogError("Could not set AST view input ", e); //$NON-NLS-1$
            }
        }
    }

    protected void performRefresh() {
        if (fTypeRoot != null) {
            try {
                refreshAST();
            } catch (CoreException e) {
                showAndLogError("Could not set AST view input ", e); //$NON-NLS-1$
            }
        }
    }

    protected void performClear() {
        fTypeRoot = null;
        featuresManager = null;
        try {
            setInput(null);
        } catch (CoreException e) {
            showAndLogError("Could not reset AST view ", e); //$NON-NLS-1$
        }
        clearView();
    }

    private void showAndLogError(String message, CoreException e) {
        ASTViewPlugin.log(message, e);
        ErrorDialog.openError(getSite().getShell(), "AST View", message, e.getStatus()); //$NON-NLS-1$
    }

    private void showAndLogError(String message, Throwable e) {
        IStatus status = new Status(IStatus.ERROR, ASTViewPlugin.getPluginId(), 0, message, e);
        ASTViewPlugin.log(status);
        ErrorDialog.openError(getSite().getShell(), "Featurer", null, status); //$NON-NLS-1$
    }

    protected void performCreateBindings() {
        fCreateBindings = fCreateBindingsAction.isChecked();
        fDialogSettings.put(SETTINGS_NO_BINDINGS, !fCreateBindings);
        performRefresh();
    }

    protected void performStatementsRecovery() {
        fStatementsRecovery = fStatementsRecoveryAction.isChecked();
        fDialogSettings.put(SETTINGS_NO_STATEMENTS_RECOVERY, !fStatementsRecovery);
        performRefresh();
    }

    protected void performBindingsRecovery() {
        fBindingsRecovery = fBindingsRecoveryAction.isChecked();
        fDialogSettings.put(SETTINGS_NO_BINDINGS_RECOVERY, !fBindingsRecovery);
        performRefresh();
    }

    protected void performIgnoreMethodBodies() {
        fIgnoreMethodBodies = fIgnoreMethodBodiesAction.isChecked();
        fDialogSettings.put(SETTINGS_IGNORE_METHOD_BODIES, fIgnoreMethodBodies);
        performRefresh();
    }

    protected void performFilterNonRelevant() {
        boolean showNonRelevant = !fFilterNonRelevantAction.isChecked();
        fNonRelevantFilter.setShowNonRelevant(showNonRelevant);
        fDialogSettings.put(SETTINGS_SHOW_NON_RELEVANT, showNonRelevant);
        fViewer.refresh();
    }

    protected void performFindDeclaringNode() {
        String msg = "Find Declaring Node from Key";
        String key = askForKey(msg);
        if (key == null)
            return;
        ASTNode node = fRoot.findDeclaringNode(key);
        if (node != null) {
            fViewer.setSelection(new StructuredSelection(node), true);
        } else {
            MessageDialog.openError(getSite().getShell(), "Find Declaring Node from Key",
                    "The declaring node for key '" + key + "' could not be found");
        }
    }

    private String askForKey(String dialogTitle) {
        InputDialog dialog = new InputDialog(getSite().getShell(), dialogTitle,
                "Key: (optionally surrounded by <KEY: '> and <'>)", "", null);
        if (dialog.open() != Window.OK)
            return null;

        String key = dialog.getValue();
        if (key.startsWith("KEY: '") && key.endsWith("'"))
            key = key.substring(6, key.length() - 1);
        return key;
    }

    protected void performParseBindingFromKey() {
        String msg = "Parse Binding from Key";
        String key = askForKey(msg);
        if (key == null)
            return;
        ASTParser parser = ASTParser.newParser(fCurrentASTLevel);
        parser.setResolveBindings(true);
        parser.setProject(fTypeRoot.getJavaProject());
        class MyASTRequestor extends ASTRequestor {
            String fBindingKey;
            IBinding fBinding;

            @Override
            public void acceptBinding(String bindingKey, IBinding binding) {
                fBindingKey = bindingKey;
                fBinding = binding;
            }
        }
        MyASTRequestor requestor = new MyASTRequestor();
        ASTAttribute item;
        Object viewerInput = fViewer.getInput();
        try {
            parser.createASTs(new ICompilationUnit[0], new String[] { key }, requestor, null);
            if (requestor.fBindingKey != null) {
                String name = requestor.fBindingKey + ": " + Binding.getBindingLabel(requestor.fBinding);
                item = new Binding(viewerInput, name, requestor.fBinding, true);
            } else {
                item = new Error(viewerInput, "Key not resolved: " + key, null);
            }
        } catch (RuntimeException e) {
            item = new Error(viewerInput, "Error resolving key: " + key, e);
        }
        fViewer.add(viewerInput, item);
        fViewer.setSelection(new StructuredSelection(item), true);
    }

    protected void performParseBindingFromElement() {
        InputDialog dialog = new InputDialog(getSite().getShell(), "Parse Binding from Java Element",
                "IJavaElement#getHandleIdentifier():", "", null);
        if (dialog.open() != Window.OK)
            return;

        String handleIdentifier = dialog.getValue();
        IJavaElement handle = JavaCore.create(handleIdentifier);

        Object viewerInput = fViewer.getInput();
        ASTAttribute item;
        if (handle == null) {
            item = new Error(viewerInput, "handleIdentifier not resolved: " + handleIdentifier, null);
        } else if (!handle.exists()) {
            item = new Error(viewerInput, "element does not exist: " + handleIdentifier, null);
        } else if (handle.getJavaProject() == null) {
            item = new Error(viewerInput, "getJavaProject() is null: " + handleIdentifier, null);
        } else {
            IJavaProject project = handle.getJavaProject();
            ASTParser parser = ASTParser.newParser(fCurrentASTLevel);
            parser.setProject(project);
            IBinding[] bindings = parser.createBindings(new IJavaElement[] { handle }, null);
            String name = handleIdentifier + ": " + Binding.getBindingLabel(bindings[0]);
            item = new Binding(viewerInput, name, bindings[0], true);
        }
        fViewer.add(viewerInput, item);
        fViewer.setSelection(new StructuredSelection(item), true);
    }

    protected void performDoubleClick() {
        if (fEditor == null) {
            return;
        }

        ISelection selection = fViewer.getSelection();
        Object obj = ((IStructuredSelection) selection).getFirstElement();

        boolean isTripleClick = (obj == fPreviousDouble);
        fPreviousDouble = isTripleClick ? null : obj;

        if (obj instanceof ExceptionAttribute) {
            Throwable exception = ((ExceptionAttribute) obj).getException();
            if (exception != null) {
                String label = ((ExceptionAttribute) obj).getLabel();
                showAndLogError("An error occurred while calculating an AST View Label:\n" + label, exception); //$NON-NLS-1$
                return;
            }
        }

        ASTNode node = null;
        if (obj instanceof ASTNode) {
            node = (ASTNode) obj;

        } else if (obj instanceof NodeProperty) {
            Object val = ((NodeProperty) obj).getNode();
            if (val instanceof ASTNode) {
                node = (ASTNode) val;
            }

        } else if (obj instanceof Binding) {
            IBinding binding = ((Binding) obj).getBinding();
            ASTNode declaring = fRoot.findDeclaringNode(binding);
            if (declaring != null) {
                fViewer.reveal(declaring);
                fViewer.setSelection(new StructuredSelection(declaring));
            } else {
                fViewer.getTree().getDisplay().beep();
            }
            return;

        } else if (obj instanceof ProblemNode) {
            ProblemNode problemNode = (ProblemNode) obj;
            EditorUtility.selectInEditor(fEditor, problemNode.getOffset(), problemNode.getLength());
            return;

        } else if (obj instanceof JavaElement) {
            IJavaElement javaElement = ((JavaElement) obj).getJavaElement();
            if (javaElement instanceof IPackageFragment) {
                ShowInPackageViewAction showInPackageViewAction = new ShowInPackageViewAction(getViewSite());
                showInPackageViewAction.run(javaElement);
            } else {
                try {
                    IEditorPart editorPart = JavaUI.openInEditor(javaElement);
                    if (editorPart != null)
                        JavaUI.revealInEditor(editorPart, javaElement);
                } catch (PartInitException e) {
                    showAndLogError("Could not open editor.", e); //$NON-NLS-1$
                } catch (JavaModelException e) {
                    showAndLogError("Could not open editor.", e); //$NON-NLS-1$
                }
            }
            return;
        }

        if (node != null) {
            int offset = isTripleClick ? fRoot.getExtendedStartPosition(node) : node.getStartPosition();
            int length = isTripleClick ? fRoot.getExtendedLength(node) : node.getLength();

            EditorUtility.selectInEditor(fEditor, offset, length);
        }
    }

    protected void performAddToTray() {
        IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
        Object firstElement = selection.getFirstElement();
        if (!fTrayRoots.contains(firstElement)) {
            fTrayRoots.add(firstElement);
            fTray.setInput(fTrayRoots);
        }
        if (fSash.getMaximizedControl() != null) {
            int trayHeight = fTray.getTree().getItemHeight() * (2 + TrayContentProvider.DEFAULT_CHILDREN_COUNT);
            int sashHeight = fSash.getClientArea().height;
            fSash.setWeights(new int[] { sashHeight - trayHeight, trayHeight });
            fSash.setMaximizedControl(null);
        }
        setTraySelection(selection);
    }

    private void setTraySelection(IStructuredSelection selection) {
        fTray.setSelection(selection, true);
        TreeItem[] itemSelection = fTray.getTree().getSelection();
        if (itemSelection.length > 0)
            fTray.getTree().setTopItem(itemSelection[0]);
    }

    protected void performTrayDoubleClick() {
        IStructuredSelection selection = (IStructuredSelection) fTray.getSelection();
        if (selection.size() != 1)
            return;
        Object obj = selection.getFirstElement();
        if (obj instanceof ExceptionAttribute) {
            Throwable exception = ((ExceptionAttribute) obj).getException();
            if (exception != null) {
                String label = ((ExceptionAttribute) obj).getLabel();
                showAndLogError("An error occurred while calculating an AST View Label:\n" + label, exception); //$NON-NLS-1$
                return;
            }
        }
        if (obj instanceof Binding) {
            Binding binding = (Binding) obj;
            fViewer.setSelection(new StructuredSelection(binding), true);
        }
    }

    protected void performDelete() {
        boolean removed = false;
        IStructuredSelection selection = (IStructuredSelection) fTray.getSelection();
        for (Iterator iter = selection.iterator(); iter.hasNext();) {
            Object obj = iter.next();
            if (obj instanceof DynamicAttributeProperty)
                obj = ((DynamicAttributeProperty) obj).getParent();
            if (obj instanceof DynamicBindingProperty)
                obj = ((DynamicBindingProperty) obj).getParent();

            removed |= fTrayRoots.remove(obj);
        }
        if (removed)
            fTray.setInput(fTrayRoots);
    }

    @Override
    public void setFocus() {
        fViewer.getControl().setFocus();
    }

    public ShowInContext getShowInContext() {
        return new ShowInContext(null, getSite().getSelectionProvider().getSelection());
    }

    /**
     * @param attribute an attribute
     * @return the object inside the attribute, or <code>null</code> iff none
     */
    static Object unwrapAttribute(Object attribute) {
        if (attribute instanceof Binding) {
            return ((Binding) attribute).getBinding();
        } else if (attribute instanceof JavaElement) {
            return ((JavaElement) attribute).getJavaElement();
        } else if (attribute instanceof ASTNode) {
            return attribute;
        } else {
            return null;
        }
    }
}