diff --git a/checkstyleConfig.xml b/checkstyleConfig.xml index 21058b3ae4..c46c3a475f 100644 --- a/checkstyleConfig.xml +++ b/checkstyleConfig.xml @@ -744,7 +744,7 @@ - + diff --git a/ide/org.codehaus.groovy.eclipse.astviews/plugin.xml b/ide/org.codehaus.groovy.eclipse.astviews/plugin.xml index 19953701a4..43848c7a35 100644 --- a/ide/org.codehaus.groovy.eclipse.astviews/plugin.xml +++ b/ide/org.codehaus.groovy.eclipse.astviews/plugin.xml @@ -11,7 +11,11 @@ class="org.codehaus.groovy.eclipse.astviews.ASTView" icon="platform:/plugin/org.codehaus.groovy.eclipse/$nl$/groovy16.png" id="org.codehaus.groovy.eclipse.astviews.ASTView" - name="Groovy AST Viewer"> + name="Groovy AST Viewer" + restorable="false"> + + Inspect the Abstract Syntax Tree (AST) of a Groovy source + diff --git a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ASTView.groovy b/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ASTView.groovy index e6cc7ac636..15df024738 100644 --- a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ASTView.groovy +++ b/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ASTView.groovy @@ -15,14 +15,16 @@ */ package org.codehaus.groovy.eclipse.astviews +import static java.beans.Introspector.decapitalize + import static org.eclipse.jdt.core.JavaCore.addElementChangedListener import static org.eclipse.jdt.core.JavaCore.removeElementChangedListener import static org.eclipse.swt.widgets.Display.getDefault as getDisplay import groovy.transform.* -import org.codehaus.groovy.ast.ASTNode -import org.codehaus.groovy.ast.ModuleNode +import org.codehaus.groovy.ast.* +import org.codehaus.groovy.control.* import org.codehaus.groovy.eclipse.editor.GroovyEditor import org.codehaus.jdt.groovy.model.GroovyCompilationUnit import org.eclipse.core.runtime.Adapters @@ -31,12 +33,15 @@ 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.groovy.core.util.GroovyUtils +import org.eclipse.jface.action.MenuManager import org.eclipse.jface.text.TextSelection -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.jface.viewers.ViewerFilter import org.eclipse.swt.SWT import org.eclipse.swt.widgets.Composite import org.eclipse.ui.IEditorPart @@ -47,7 +52,8 @@ 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. + * A view into the Groovy AST. Anyone who needs to manipulate the AST will find + * this useful for exploring various nodes. */ @AutoFinal @CompileStatic class ASTView extends ViewPart { @@ -58,43 +64,60 @@ class ASTView extends ViewPart { private IPartListener partListener - private IElementChangedListener listener + private IElementChangedListener reconcileListener @Override void createPartControl(Composite parent) { - viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL) - DrillDownAdapter drillDownAdapter = new DrillDownAdapter(viewer) - viewer.contentProvider = new ViewContentProvider() - viewer.labelProvider = new ViewLabelProvider() - viewer.comparator = null - viewer.input = null + viewer = new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL) + viewer.contentProvider = new TreeContentProvider() + viewer.labelProvider = new TreeLabelProvider() + viewer.addFilter(new TreeNodeFilter()) viewer.addDoubleClickListener { event -> - def obj = ((IStructuredSelection) viewer.selection).firstElement - def val = ((ITreeNode) obj)?.value - if (val instanceof ASTNode) { - ASTNode node = (ASTNode) val - if (node.lineNumber > 0 && editor instanceof ITextEditor) { - ((ITextEditor) editor).selectionProvider.selection = new TextSelection(node.start, node.length) + def element = ((IStructuredSelection) viewer.selection).firstElement + if (((TreeNode) element).value instanceof ASTNode) { + def astNode = (ASTNode) ((TreeNode) element).value + if (astNode.lineNumber > 0 && editor instanceof ITextEditor) { + ((ITextEditor) editor).selectionProvider.selection = new TextSelection(astNode.start, astNode.length) } } } - listener = { ElementChangedEvent event -> - if (editor != null) { - def unit = Adapters.adapt(editor.editorInput, GroovyCompilationUnit) - if (isUnitInDelta(event.delta, unit)) { - display.asyncExec { -> - def treePaths = - viewer.expandedElements - viewer.input = unit.moduleNode - viewer.expandedElements = treePaths - } + // add tree navigation actions to toolbar + def downer = new DrillDownAdapter(viewer) + viewSite.actionBars.toolBarManager.with { + downer.addNavigationActions(it) + update(true) + } + + // add tree navigation actions to context menu + viewer.tree.menu = new MenuManager('#PopupMenu').with { + viewSite.registerContextMenu(it, viewer) + downer.addNavigationActions(it) + createContextMenu(viewer.tree) + } + + def resetTreeView = { root -> + viewer.input = root + downer.reset() + } + + // + + reconcileListener = { ElementChangedEvent event -> + def unit = Adapters.adapt(editor, GroovyCompilationUnit) + if (unit != null && isUnitInDelta(event.delta, unit)) { + display.asyncExec { -> + def ee = viewer.expandedElements + resetTreeView(unit.moduleNode) + viewer.setExpandedElements(ee) } } } - addElementChangedListener(listener, ElementChangedEvent.POST_RECONCILE) + addElementChangedListener(reconcileListener, ElementChangedEvent.POST_RECONCILE) + + // partListener = new IPartListener() { @Override @@ -103,35 +126,28 @@ class ASTView extends ViewPart { @Override void partBroughtToTop(IWorkbenchPart part) { - try { - if (part instanceof IEditorPart) { - def unit = Adapters.adapt(part.editorInput, GroovyCompilationUnit) - if (unit != null) { - if (editor != part) { - editor = part - def treePaths = - viewer.expandedElements - viewer.input = unit.moduleNode - viewer.expandedElements = treePaths - } + if (part != editor && part instanceof IEditorPart) { + try { + def node = Adapters.adapt(part, ModuleNode) + if (node != null) { + resetTreeView(node) + editor = part return } + } catch (Throwable t) { + Activator.warn('Error updating AST Viewer', t) } - } catch (err) { - Activator.warn('Error updating AST Viewer', err) + resetTreeView(null) + editor = null } - partClosed(part) } @Override void partClosed(IWorkbenchPart part) { - // 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.contentProvider != null) { - viewer.input = null + if (part != null && part == editor) { + resetTreeView(null) + editor = null } - editor = null } @Override @@ -156,11 +172,11 @@ class ASTView extends ViewPart { @Override void dispose() { try { + removeElementChangedListener(reconcileListener) site.page.removePartListener(partListener) - removeElementChangedListener(listener) } finally { + reconcileListener = null partListener = null - listener = null super.dispose() } } @@ -184,47 +200,191 @@ class ASTView extends ViewPart { } } - @AutoImplement - private static class ViewContentProvider implements IStructuredContentProvider, ITreeContentProvider { - private ITreeNode root + private static class TreeContentProvider implements ITreeContentProvider { + @Override + Object[] getElements(Object input) { + getChildren(input instanceof ModuleNode ? new TreeNode(value: input) : input) + } @Override - Object getParent(Object child) { - if (child instanceof ITreeNode) { - child.parent + Object[] getChildren(Object node) { + def treeNode = (TreeNode) node + def nodeValue = treeNode.value + + if (nodeValue instanceof ASTNode || nodeValue instanceof DynamicVariable || nodeValue instanceof VariableScope || + nodeValue instanceof SourceUnit || nodeValue instanceof CompileUnit || nodeValue instanceof CompilerConfiguration) { + def methods = nodeValue.class.methods.findAll { method -> + method.parameterCount == 0 && method.name =~ /^(is|has(?!hCode$)|get(?!(Type)?Class$|(Static)?(Star)?Imports$)|redirect$)/ + } + def results = methods.findResults { method -> + String name = method.name + if (name.startsWith('get')) { + name = decapitalize(name.substring(3)) + } + try { + def value = method.invoke(nodeValue) + if ((name != 'text' && !nodeValue.is(value)) || + (name == 'text' && !(value =~ /^ + list << new TreeNode(label: "[$i]", value: e, parent: treeNode) + } + return list.toArray() + } + + if (nodeValue instanceof Map) { + def list = ((Map) nodeValue).collect { k, v -> + new TreeNode(label: k instanceof String ? /"$k"/ : "[$k]", value: v, parent: treeNode) + } + return list.toArray() } } @Override - Object[] getElements(Object inputElement) { - if (inputElement instanceof ModuleNode) { - root = TreeNodeFactory.createTreeNode(null, inputElement, 'Module Nodes') - return root.children + boolean hasChildren(Object node) { + def value = ((TreeNode) node).value + + if (value instanceof Map || value instanceof Iterable || value instanceof Object[]) { + return value // false if empty } - return new Object[0] + + (value instanceof ASTNode || value instanceof DynamicVariable || value instanceof VariableScope || + value instanceof SourceUnit || value instanceof CompileUnit || value instanceof CompilerConfiguration) } @Override - Object[] getChildren(Object parent) { - if (parent instanceof ITreeNode) { - parent.children - } + Object getParent(Object node) { + ((TreeNode) node).parent } + } + private static class TreeLabelProvider extends LabelProvider { @Override - boolean hasChildren(Object parent) { - if (parent instanceof ITreeNode) { - !parent.isLeaf() + String getText(Object node) { + def label = ((TreeNode) node).label + def value = ((TreeNode) node).value + + switch (value) { + case Map: + case Iterable: + case Object[]: + return label + case Character: + return "$label : '$value'" + case CharSequence: + return "$label : \"$value\"" + case stmt.Statement: + case expr.Expression: + def valueType = value.class.simpleName + return "$label : ${valueType - ~/(Expression|Statement)$/}" + case ClassNode: + def clazz = (ClassNode) value + return "$label : ${clazz.toString(false).replace(' ', '')}" + case MethodNode: + def descriptor = ((MethodNode) value).typeDescriptor.replace( + '', ((MethodNode) value).declaringClass.nameWithoutPackage) + return "$label : ${descriptor.substring(descriptor.indexOf(' ') + 1)}" + case Variable: + return "$label : ${value['name']}" + case ImportNode: + return "$label : ${value['text']}" + case ASTNode: + case SourceUnit: + case CompileUnit: + case VariableScope: + case CompilerConfiguration: + if (label.charAt(0) != '[') return label + default: + return "$label : $value" } } } - private static class ViewLabelProvider extends LabelProvider { + private static class TreeNodeFilter extends ViewerFilter { @Override - String getText(Object obj) { - if (obj instanceof ITreeNode) { - obj.displayName + boolean select(Viewer viewer, Object parent, Object node) { + def label = ((TreeNode) node).label + def value = ((TreeNode) node).value + def outer = parent instanceof TreeNode ? parent.value : parent + + // filter redundant properties + if (outer instanceof ClassNode) { + if (label ==~ /abstractMethods|allDeclaredMethods|allInterfaces|declaredMethodsMap|fieldIndex|(hasP|p)ackageName|/ + + /isDerivedFromGroovyObject|isRedirectNode|module|name(WithoutPackage)?|outer(Most)?Class|plainNodeReference|text/) { + return false + } + } else if (outer instanceof Variable) { // FieldNode, PropertyNode, etc. + if (label ==~ /name|hasInitialExpression|initialValueExpression|is(Enum|Final|Private|Protected|Public|Static|Volatile)/) { + return false + } + } else if (outer instanceof MethodNode) { + if (label ==~ /firstStatement|is(Abstract|Default|Final|PackageScope|Private|Protected|Public|Static|VoidMethod)|name|typeDescriptor/) { + return false + } + } else if (outer instanceof ModuleNode) { + if (label ==~ /isEmpty|packageName|hasPackage(Name)?|scriptClassDummy/) { + return false + } + } else if (outer instanceof CompileUnit) { + if (label ==~ /(c|generatedInnerC|sortedC)lasses|hasClassNodeToCompile/) { + return false + } + } else if (outer instanceof ProcessingUnit) { + if (label ==~ /AST|CST|source/) { + return false + } + } else if (outer instanceof stmt.Statement) { + if (label ==~ /isEmpty|statementLabel/) { + return false + } + } else if (outer instanceof ImportNode || outer instanceof PackageNode) { + if (label == 'text') { + return false + } + } + if (outer instanceof ASTNode) { + if (label ==~ /instance|metaDataMap/) { + return false + } + if (label == 'groovydoc') { + return value['present'] + } } + + if (value instanceof ClassNode && value['outerClass'] != null && + parent['label'] == 'classes' && parent['parent']['value'] instanceof ModuleNode) { + return false + } + + if (value instanceof Map || value instanceof Iterable || value instanceof Object[]) { + return value // false if empty + } + + return true + } + } + + @EqualsAndHashCode(includes='label,parent') + private static class TreeNode { + String label + Object value + TreeNode parent + + void setValue(value) { + this.value = value instanceof Iterator ? value.collect() : value } } } diff --git a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ITreeNode.java b/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ITreeNode.java deleted file mode 100644 index b4bf7e6ae0..0000000000 --- a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/ITreeNode.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2009-2018 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; - -interface ITreeNode { - /** - * Gets the value this tree node represents. This is the wrapped value. - */ - Object getValue(); - - ITreeNode getParent(); - - String getDisplayName(); - - ITreeNode[] getChildren(); - - boolean isLeaf(); -} diff --git a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/MapTo.groovy b/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/MapTo.groovy deleted file mode 100644 index ac3145124b..0000000000 --- a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/MapTo.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2009-2018 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 groovy.transform.PackageScope - -import org.codehaus.groovy.ast.* -import org.codehaus.groovy.ast.expr.* -import org.codehaus.groovy.ast.stmt.* - -@PackageScope class MapTo { - - private static final Map mapToNames = [ - (ModuleNode) : { it.description }, - (ClassNode) : { it.name }, - (FieldNode) : { it.name }, - (PropertyNode) : { it.name }, - (MethodNode) : { it.name }, - (Parameter) : { it.name }, - (Expression) : { it.class.canonicalName }, - (ExpressionStatement) : { "(${it.expression.class.canonicalName}) ${it.expression.text}" } - ].asImmutable() - - @PackageScope static String names(value) { - def cls = value.class - def getter = mapToNames[cls] - while (getter == null && cls != null) { - cls = cls.superclass - getter = mapToNames[cls] - } - return getter?.call(value) - } -} diff --git a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/TreeNodeFactory.groovy b/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/TreeNodeFactory.groovy deleted file mode 100644 index 0f0dae2925..0000000000 --- a/ide/org.codehaus.groovy.eclipse.astviews/src/org/codehaus/groovy/eclipse/astviews/TreeNodeFactory.groovy +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2009-2018 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 java.beans.Introspector -import java.lang.reflect.Method - -import groovy.transform.PackageScope - -import org.codehaus.groovy.GroovyBugError -import org.codehaus.groovy.ast.ASTNode -import org.codehaus.groovy.ast.ClassNode -import org.codehaus.groovy.ast.ModuleNode -import org.codehaus.groovy.control.ProcessingUnit - -@PackageScope class TreeNodeFactory { - - private static final List forceDefaultNode = [ - org.codehaus.groovy.ast.CompileUnit, - org.codehaus.groovy.ast.DynamicVariable, - org.codehaus.groovy.ast.VariableScope, - org.codehaus.groovy.control.CompilerConfiguration, - org.codehaus.groovy.control.SourceUnit, - org.codehaus.jdt.groovy.control.EclipseSourceUnit, - ].asImmutable() - - private static final Object NULL_VALUE = new Object() - - @PackageScope static ITreeNode createTreeNode(ITreeNode parent, Object value, String displayName) { - if (value == null) { - value = NULL_VALUE - } - // The displayName attributes are set after the constructor in the code below because - // displayName relies on the value object being set before it. Since groovy does not - // honor the order of attributes in the constructor declaration, value is null when - // setDisplayName() is called and the ASTViewer becomes frustrating. - if (value instanceof ASTNode || value.class in forceDefaultNode) { - def node = new DefaultTreeNode(parent: parent, value: value) - node.displayName = displayName - return node - } else if (value instanceof Iterable || value instanceof Iterator || value instanceof Object[]) { - def node = new CollectionTreeNode(parent: parent, value: value) - node.displayName = displayName - return node - } else if (value instanceof Map) { - def node = new MapTreeNode(parent: parent, value: value) - node.displayName = displayName - return node - } else { - def node = new AtomTreeNode(parent: parent, value: value) - node.displayName = displayName - return node - } - } -} - -@PackageScope class StringUtil { - @PackageScope static String toString(Object obj) { - return TreeNodeFactory.NULL_VALUE.is(obj) ? 'null' : Objects.toString(obj) - } - - @PackageScope static String toString(Class type) { - // If o is Integer, then Integer.toString() tries to call either of the static toString() methods of Integer. - // There may be other classes with static toString(args) methods. This is the same code as in Class.toString(). - return (type.isInterface() ? 'interface ' : (type.isPrimitive() ? '' : 'class ')) + type.name - } -} - -@PackageScope abstract class TreeNode implements ITreeNode { - protected static final ITreeNode[] NO_CHILDREN = new ITreeNode[0] - - ITreeNode parent - Object value - String displayName - Boolean leaf - ITreeNode[] children - - void setDisplayName(String name) { - def mappedName = MapTo.names(value) - if (mappedName) { - name = "$name - $mappedName" - } - displayName = name - } - - @Override - ITreeNode[] getChildren() { - if (children == null) { - children = loadChildren() - if (children == null) { - children = NO_CHILDREN - } - } - return children - } - - @Override - boolean isLeaf() { - if (leaf == null) { - leaf = (getChildren().length == 0) - } - return leaf - } - - abstract ITreeNode[] loadChildren() -} - -@PackageScope class DefaultTreeNode extends TreeNode { - @Override - ITreeNode[] loadChildren() { - List methods = value.class.methods.findAll { method -> - method.parameterCount == 0 && method.name =~ /^(is|get|redirect)/ && method.declaringClass.name != 'java.lang.Object' - } - // remove some redundant methods - if (value instanceof ClassNode) { - methods = methods.findAll { method -> - !(method.name ==~ /get(AbstractMethods|AllDeclaredMethods|AllInterfaces|DeclaredMethodsMap|FieldIndex|Module|NameWithoutPackage|PackageName|PlainNodeReference|Text)|isRedirectNode/) - } - } else if (value instanceof ModuleNode) { - methods = methods.findAll { method -> - !(method.name ==~ /get(PackageName|ScriptClassDummy)/) - } - } else if (value instanceof ProcessingUnit) { - methods = methods.findAll { method -> - !(method.name ==~ /get(AST|CST)/) - } - } - if (value instanceof ASTNode) { - methods = methods.findAll { method -> - !(method.name ==~ /get(Instance|MetaDataMap)/) - } - } - - Collection children = methods.findResults { method -> - String name = method.name - if (name.startsWith('is')) { - name = Introspector.decapitalize(name.substring(2)) - } else if (name.startsWith('get')) { - name = Introspector.decapitalize(name.substring(3)) - } - try { - Object value = this.value."${method.name}"() - if ((name != 'text' && !this.value.is(value)) || - (name == 'text' && !(value =~ /^ - TreeNodeFactory.createTreeNode(this, v, k as String) - } as ITreeNode[] - } -} - -@PackageScope class AtomTreeNode implements ITreeNode { - ITreeNode parent - Object value - String displayName - final boolean leaf = true - final ITreeNode[] children = TreeNode.NO_CHILDREN - - void setDisplayName(String name) { - def mappedName = MapTo.names(value) - if (mappedName) { - name = "$name - $mappedName" - } - if (value instanceof String) { - displayName = "$name : '$value'" - } else { - def valueString = StringUtil.toString(value) - if (valueString.indexOf('@') != -1) { - valueString = value.class.canonicalName - } - displayName = "$name : $valueString" - } - } -}