/*
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.astviews;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.eclipse.core.GroovyCore;
import org.codehaus.groovy.eclipse.editor.GroovyEditor;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.groovy.core.util.ContentTypeUtils;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* A view into the Groovy AST. Anyone who needs to manipulate the AST will find
* this useful for exploring various nodes.
*/
public class ASTView extends ViewPart {
private TreeViewer viewer;
private Action doubleClickAction;
private IEditorPart editor;
private IPartListener partListener;
private IElementChangedListener listener;
class ViewContentProvider implements IStructuredContentProvider, ITreeContentProvider {
ITreeNode root;
public void inputChanged(Viewer v, Object oldInput, Object newInput) {
}
public void dispose() {
}
public Object[] getElements(Object inputElement) {
if (! (inputElement instanceof ModuleNode)) {
return new Object[0];
}
root = TreeNodeFactory.createTreeNode(null, inputElement, "Module Nodes"); //$NON-NLS-1$
Object[] children = root.getChildren();
return children;
}
public Object getParent(Object child) {
Object parent = ((ITreeNode) child).getParent();
return parent;
}
public Object[] getChildren(Object parent) {
ITreeNode[] children = ((ITreeNode) parent).getChildren();
return children;
}
public boolean hasChildren(Object parent) {
boolean has = !((ITreeNode) parent).isLeaf();
return has;
}
}
class ViewLabelProvider extends LabelProvider {
@Override
public String getText(Object obj) {
return ((ITreeNode) obj).getDisplayName();
}
@Override
public Image getImage(Object obj) {
return null;
}
}
public ASTView() {
}
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
@Override
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
DrillDownAdapter drillDownAdapter = new DrillDownAdapter(viewer);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setComparator(null);
viewer.setInput(null);
makeActions();
//hookContextMenu();
hookDoubleClickAction();
hookGroovy();
//contributeToActionBars();
}
@Override
public void dispose() {
unhookGroovy();
super.dispose();
}
private void hookGroovy() {
partListener = new IPartListener() {
public void partActivated(IWorkbenchPart part) {
}
public void partBroughtToTop(IWorkbenchPart part) {
try {
if (part instanceof IEditorPart) {
@SuppressWarnings("cast")
IFile file = (IFile) ((IEditorPart) part).getEditorInput().getAdapter(IFile.class);
if (file != null && ContentTypeUtils.isGroovyLikeFileName(file.getName())) {
ICompilationUnit unit = JavaCore.createCompilationUnitFrom(file);
if (unit instanceof GroovyCompilationUnit) {
if (editor != part) {
editor = (IEditorPart) part;
Object[] treePaths = viewer.getExpandedElements();
viewer.setInput(((GroovyCompilationUnit) unit).getModuleNode());
viewer.setExpandedElements(treePaths);
} else {
// nothing to do!
}
return;
}
}
}
} catch (Exception e) {
GroovyCore.logException("Error updating AST Viewer", e); //$NON-NLS-1$
}
editor = null;
// This is a guard - the content provider should not be null,
// but sometimes this happens when the
// part is disposed of for various reasons (unhandled exceptions
// AFAIK). Without this guard,
// error message popups continue until Eclipse if forcefully
// killed.
if (viewer.getContentProvider() != null) {
viewer.setInput(null);
}
}
public void partClosed(IWorkbenchPart part) {
}
public void partDeactivated(IWorkbenchPart part) {
}
public void partOpened(IWorkbenchPart part) {
}
};
getSite().getPage().addPartListener(partListener);
// Warm the listener up.
if (getSite().getPage().getActiveEditor() instanceof GroovyEditor) {
partListener.partBroughtToTop(getSite().getPage().getActiveEditor());
}
listener = new IElementChangedListener() {
public void elementChanged(ElementChangedEvent event) {
// The editor is currently not a GroovyEditor, so
// there is not
// ASTView to refresh.
if (editor == null) {
return;
}
IJavaElementDelta delta = event.getDelta();
@SuppressWarnings("cast")
IFile file = (IFile) editor.getEditorInput().getAdapter(IFile.class);
final GroovyCompilationUnit unit = (GroovyCompilationUnit) JavaCore.createCompilationUnitFrom(file);
// determine if the delta contains the ICompUnit under question
if (isUnitInDelta(delta, unit)) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
Object[] treePaths = viewer.getExpandedElements();
viewer.setInput(unit.getModuleNode());
viewer.setExpandedElements(treePaths);
}
});
}
}
private boolean isUnitInDelta(IJavaElementDelta delta, GroovyCompilationUnit unit) {
IJavaElement elt = delta.getElement();
if (elt.getElementType() == IJavaElement.COMPILATION_UNIT) {
// comparing with a compilation unit
// if test fails, no need to go further
if (elt.getElementName().equals(unit.getElementName())) {
return true;
} else {
return false;
}
}
ICompilationUnit candidateUnit = (ICompilationUnit) elt.getAncestor(IJavaElement.COMPILATION_UNIT);
if (candidateUnit != null) {
// now if test fails, no need to go further
if (candidateUnit.getElementName().equals(unit.getElementName())) {
return true;
} else {
return false;
}
}
// delta is a potential ancestor of this compilationUnit
IJavaElementDelta[] deltas = delta.getAffectedChildren();
if (deltas != null) {
for (IJavaElementDelta delta2 : deltas) {
if (isUnitInDelta(delta2, unit)) {
return true;
}
}
}
return false;
}
};
JavaCore.addElementChangedListener(listener, ElementChangedEvent.POST_RECONCILE);
}
private void unhookGroovy() {
JavaCore.removeElementChangedListener(listener);
getSite().getPage().removePartListener(partListener);
}
private void makeActions() {
doubleClickAction = new Action() {
@Override
public void run() {
ISelection selection = viewer.getSelection();
Object obj = ((IStructuredSelection) selection).getFirstElement();
if (obj == null) {
return;
}
if (((ITreeNode) obj).getValue() instanceof ASTNode) {
Object value = ((ITreeNode) obj).getValue();
if (!(value instanceof ASTNode)) {
return;
}
ASTNode node = (ASTNode) value;
if (node.getLineNumber() != -1) {
int offset0 = node.getStart();
int offset1 = node.getEnd();
if (editor instanceof ITextEditor) {
((ITextEditor) editor).getSelectionProvider().setSelection(new TextSelection(offset0, offset1-offset0));
}
}
}
}
};
}
private void hookDoubleClickAction() {
viewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
doubleClickAction.run();
}
});
}
/**
* Passing the focus request to the viewer's control.
*/
@Override
public void setFocus() {
viewer.getControl().setFocus();
}
}