/* * 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.editor.outline; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; 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.runtime.Assert; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.internal.core.JavaElement; /** * @author andrew * @created Apr 13, 2011 */ public class GroovyScriptOutlineExtender implements IOutlineExtender { public static final String NO_STRUCTURE_FOUND = " -- No structure found"; public GroovyOutlinePage getGroovyOutlinePageForEditor(String contextMenuID, GroovyEditor editor) { GroovyOutlinePage outlinePage = new GroovyOutlinePage(contextMenuID, editor, new GroovyScriptOCompilationUnit( editor.getGroovyCompilationUnit())); return outlinePage; } /** * Applicable whenever there is a script */ public boolean appliesTo(GroovyCompilationUnit unit) { ModuleNode moduleNode = unit.getModuleNode(); return moduleNode != null && moduleNode.getClasses().size() > 0 && moduleNode.getClasses().get(0) != null && moduleNode.getClasses().get(0).isScript(); } } class GroovyScriptOCompilationUnit extends OCompilationUnit { public GroovyScriptOCompilationUnit(GroovyCompilationUnit unit) { super(unit); } @Override public IJavaElement[] refreshChildren() { ModuleNode node = (ModuleNode) getNode(); ClassNode scriptClassDummy; String scriptName; if (node != null) { scriptClassDummy = node.getScriptClassDummy(); if (scriptClassDummy == null) { if (node.getClasses().size() > 0) { scriptClassDummy = node.getClasses().get(0); } } if (scriptClassDummy == null) { scriptName = "Problem"; } else { scriptName = scriptClassDummy.getNameWithoutPackage(); } } else { scriptName = null; scriptClassDummy = null; } if (node == null || node.encounteredUnrecoverableError() || scriptClassDummy == null) { // we have no idea what the structure is. // Let the user know return new IJavaElement[] { new OType(getUnit(), node, scriptName + GroovyScriptOutlineExtender.NO_STRUCTURE_FOUND) }; } // otherwise, add all children directly except for the script class try { IJavaElement[] children = getUnit().getChildren(); final IType scriptType; final List<IJavaElement> fakeChildren = new ArrayList<IJavaElement>(); IType candidate = null; for (IJavaElement elt : children) { if (elt.getElementName().equals(scriptName)) { candidate = (IType) elt; } else { fakeChildren.add(elt); } } scriptType = candidate; if (scriptType != null) { // do not add the script type directly. Rather, add all of the // children // Additionally, do not add the run or main methods IJavaElement[] scriptChildren = scriptType.getChildren(); for (IJavaElement scriptElt : scriptChildren) { if (scriptElt instanceof IMember) { if (isRunMethod(scriptElt) || isMainMethod(scriptElt) || isConstructor(scriptElt)) { continue; } fakeChildren.add(scriptElt); } } // next add all of the variable declarations BlockStatement block = node.getStatementBlock(); ClassCodeVisitorSupport visitor = new ClassCodeVisitorSupport() { @Override public void visitClosureExpression(ClosureExpression expression) { } @Override public void visitDeclarationExpression(DeclarationExpression expression) { fakeChildren.add(new GroovyScriptVariable((JavaElement) scriptType, expression)); super.visitDeclarationExpression(expression); } }; visitor.visitBlockStatement(block); } // finally, sort all the elements by source location IJavaElement[] fakeChildrenArr = fakeChildren.toArray(new IJavaElement[fakeChildren.size()]); sort(fakeChildrenArr); return fakeChildrenArr; } catch (JavaModelException e) { GroovyCore.logException("Encountered exception when calculating children", e); return new IJavaElement[] { new OType(getUnit(), node, scriptName + " -- Encountered exception. See log.") }; } } /** * Utility method that does an in place sort of child IJavaElements by their * start position * * @param elts * @return elements sorted by starting position */ public static IJavaElement[] sort(IJavaElement[] elts) { Arrays.sort(elts, new Comparator<IJavaElement>() { public int compare(IJavaElement e1, IJavaElement e2) { try { // really we should only be getting source refs elements // here Assert.isTrue(e1 instanceof ISourceReference, "Expecting a ISourceReference, but found " + e1); Assert.isTrue(e2 instanceof ISourceReference, "Expecting a ISourceReference, but found " + e2); return ((ISourceReference) e1).getSourceRange().getOffset() - ((ISourceReference) e2).getSourceRange().getOffset(); } catch (JavaModelException e) { GroovyCore.logException("Exception when comparing " + e1 + " and " + e2, e); return 0; } } }); return elts; } /** * @param scriptElt * @return */ private boolean isConstructor(IJavaElement scriptElt) throws JavaModelException { if (scriptElt.getElementType() != IJavaElement.METHOD) { return false; } return ((IMethod) scriptElt).isConstructor(); } private boolean isMainMethod(IJavaElement scriptElt) throws JavaModelException { if (scriptElt.getElementType() != IJavaElement.METHOD) { return false; } return ((IMethod) scriptElt).isMainMethod(); } private boolean isRunMethod(IJavaElement scriptElt) { if (scriptElt.getElementType() != IJavaElement.METHOD) { return false; } if (!scriptElt.getElementName().equals("run")) { return false; } String[] parammeterTypes = ((IMethod) scriptElt).getParameterTypes(); return parammeterTypes == null || parammeterTypes.length == 0; } } /** * A variable declaration in a script * * @author andrew * @created Apr 14, 2011 */ class GroovyScriptVariable extends OField { /** * */ private static final String NO_NAME = "no name"; private static final String DEF_SIGNATURE = "Qdef;"; private String typeSignature; public GroovyScriptVariable(JavaElement parent, DeclarationExpression node) { super(parent, node, extractName(node)); ClassNode fieldType = node.getLeftExpression().getType(); if (ClassHelper.DYNAMIC_TYPE == fieldType) { typeSignature = DEF_SIGNATURE; } else { typeSignature = fieldType.getNameWithoutPackage(); // don't need to convert array signatures if (!typeSignature.startsWith("[")) { typeSignature = Signature.createTypeSignature(typeSignature, false); } } } private static String extractName(DeclarationExpression node) { Expression leftExpression = node.getLeftExpression(); if (leftExpression instanceof VariableExpression) { return ((VariableExpression) leftExpression).getName(); } else { // multi-variable expression if (leftExpression instanceof TupleExpression) { List<Expression> exprs = ((TupleExpression) leftExpression).getExpressions(); StringBuilder sb = new StringBuilder(); for (Iterator<Expression> exprIter = exprs.iterator(); exprIter.hasNext();) { Expression expr = exprIter.next(); sb.append(expr.getText()); if (exprIter.hasNext()) { sb.append(", "); } } return sb.toString(); } } return NO_NAME; } @Override public ASTNode getElementNameNode() { DeclarationExpression decl = (DeclarationExpression) getNode(); return decl.getLeftExpression(); } @Override public String getTypeSignature() { return typeSignature; } }