/* * Copyright 2009-2017 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.codebrowsing.requestor; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.eclipse.GroovyLogManager; import org.codehaus.groovy.eclipse.TraceCategory; import org.codehaus.jdt.groovy.model.GroovyClassFileWorkingCopy; import org.codehaus.jdt.groovy.model.GroovyCompilationUnit; import org.codehaus.jdt.groovy.model.ICodeSelectHelper; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.groovy.search.TypeInferencingVisitorFactory; import org.eclipse.jdt.groovy.search.TypeInferencingVisitorWithRequestor; public class CodeSelectHelper implements ICodeSelectHelper { public IJavaElement[] select(GroovyCompilationUnit unit, int start, int length) { // GRECLIPSE-1330: check for possible reference in GString char[] contents = unit.getContents(); if (length > 1 && start + length < contents.length && contents[start] == '$' && contents[start + 1] != '{') { start += 1; length -= 1; } ModuleNode module = unit.getModuleNode(); if (module != null) { String event = null; if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.CODE_SELECT, unit.getElementName() + " at [" + start + "," + length + "]"); event = "Code select: " + unit.getElementName(); GroovyLogManager.manager.logStart(event); } try { Region select = new Region(start, length); Object[] result = findNodeForRegion(module, select); ASTNode node = (ASTNode) result[0]; Region region = (Region) result[1]; if (node != null && !isKeywordSelection(node, contents, start, length)) { // shortcut: check to see if we are looking for this type itself if (isTypeDeclaration(node, module)) { return returnThisNode(node, unit); } CodeSelectRequestor requestor = createRequestor(node, region, select, unit); TypeInferencingVisitorWithRequestor visitor = new TypeInferencingVisitorFactory().createVisitor(unit); visitor.visitCompilationUnit(requestor); IJavaElement element = requestor.getRequestedElement(); if (element != null) { return new IJavaElement[] {element}; } } } catch (RuntimeException e) { if (event != null) { GroovyLogManager.manager.logException(TraceCategory.CODE_SELECT, e); } throw e; } finally { if (event != null) { GroovyLogManager.manager.logEnd(event, TraceCategory.CODE_SELECT); } } } return new IJavaElement[0]; } public ASTNode selectASTNode(GroovyCompilationUnit unit, int start, int length) { ModuleNode module = unit.getModuleNode(); if (module != null) { String event = null; if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.CODE_SELECT, "Code select starting on " + unit.getElementName() + " at [ " + start + "," + length + " ]"); event = "Code select: " + unit.getElementName(); GroovyLogManager.manager.logStart(event); } try { Region select = new Region(start, length); Object[] result = findNodeForRegion(module, select); ASTNode node = (ASTNode) result[0]; Region region = (Region) result[1]; if (node != null) { // shortcut: check to see if we are looking for this type itself if (isTypeDeclaration(node, module)) { return ((ClassNode) node).redirect(); } CodeSelectRequestor requestor = createRequestor(node, region, select, unit); TypeInferencingVisitorWithRequestor visitor = new TypeInferencingVisitorFactory().createVisitor(unit); visitor.visitCompilationUnit(requestor); return requestor.getRequestedNode(); } } finally { if (event != null) { GroovyLogManager.manager.logEnd(event, TraceCategory.CODE_SELECT); } } } return null; } /** * Allows sub-classes to provide their own requestor. * * @param node the selected AST node * @param nodeRegion the source range for {@code node} * @param selectRegion the source range for the selection */ protected CodeSelectRequestor createRequestor(ASTNode node, Region nodeRegion, Region selectRegion, GroovyCompilationUnit unit) { return new CodeSelectRequestor(node, nodeRegion, selectRegion, unit); } /** * @return array of {@link ASTNode} and {@link Region} */ protected Object[] findNodeForRegion(ModuleNode module, Region r) { ASTNodeFinder finder = new ASTNodeFinder(r); finder.doVisit(module); return new Object[] {finder.result, finder.sloc}; } protected static boolean isKeywordSelection(ASTNode node, char[] contents, int start, int length) { boolean keyword = false; // "this." something if (node instanceof VariableExpression && ((VariableExpression) node).isThisExpression()) { keyword = true; } // "def " something else if (node == ClassHelper.DYNAMIC_TYPE && length == 3) { keyword = String.valueOf(contents, start, length).equals("def"); } // "import " or "import static " something else if (node instanceof ImportNode && length == 6 && (start == node.getStart() || ((ImportNode) node).isStatic() && start < node.getStart() + 14/*"import static ".length()*/)) { keyword = true; } else if (node instanceof ImportNode && ((ImportNode) node).getAliasExpr() != null && length == 2) { keyword = String.valueOf(contents, start, length).equals("as"); } else if (node instanceof ImportNode && ((ImportNode) node).isStar() && length == 1 && start == node.getEnd() - 1) { keyword = true; } return keyword; } protected static boolean isTypeDeclaration(ASTNode node, ModuleNode module) { // don't use inner class nodes since they really should resolve to the super type if (node instanceof ClassNode && !(node instanceof InnerClassNode)) { for (ClassNode clazz : (Iterable<ClassNode>) module.getClasses()) { if (clazz == node) { return true; } } } return false; } protected static IJavaElement[] returnThisNode(ASTNode node, GroovyCompilationUnit unit) { // GRECLIPSE-803: ensure inner classes are handled correctly String rawName = ((ClassNode) node).getNameWithoutPackage(); String[] enclosingTypes = rawName.split("\\$"); IType candidate = null; for (int i = 0, n = enclosingTypes.length; i < n; i += 1) { if (i == 0) { candidate = unit.getType(enclosingTypes[i]); } else { candidate = candidate.getType(enclosingTypes[i]); } } IJavaElement result; if (unit instanceof GroovyClassFileWorkingCopy) { result = ((GroovyClassFileWorkingCopy) unit).convertToBinary(candidate); } else { result = candidate; } return new IJavaElement[] {result}; } }