/* * 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.codeassist.processors; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import groovyjarjarasm.asm.Opcodes; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyFieldProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.GroovyMethodProposal; import org.codehaus.groovy.eclipse.codeassist.proposals.IGroovyProposal; import org.codehaus.groovy.eclipse.codeassist.relevance.Relevance; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.groovy.search.VariableScope.VariableInfo; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; /** * @author Andrew Eisenberg * @created Nov 10, 2009 * */ public class LocalVariableCompletionProcessor extends AbstractGroovyCompletionProcessor { private final int offset; private final int replaceLength; private final JavaContentAssistInvocationContext javaContext; public LocalVariableCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) { super(context, javaContext, nameEnvironment); this.javaContext = javaContext; this.replaceLength = context.completionExpression.length(); this.offset = context.completionLocation; } public List<ICompletionProposal> generateProposals(IProgressMonitor monitor) { Map<String,ClassNode> localNames = findLocalNames(extractVariableNameStart()); List<ICompletionProposal> proposals = createProposals(localNames); // now add closure proposals if necessary proposals.addAll(createClosureProposals()); return proposals; } private List<ICompletionProposal> createClosureProposals() { if (getContext().currentScope.getEnclosingClosure() != null) { List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(1); VariableInfo ownerInfo = getContext().currentScope.lookupName("owner"); VariableInfo delegateInfo = getContext().currentScope.lookupName("delegate"); maybeAddClosureProperty(proposals, "owner", ownerInfo.declaringType, ownerInfo.type, false); maybeAddClosureProperty(proposals, "getOwner", ownerInfo.declaringType, ownerInfo.type, true); maybeAddClosureProperty(proposals, "delegate", delegateInfo.declaringType, delegateInfo.type, false); maybeAddClosureProperty(proposals, "getDelegate", delegateInfo.declaringType, delegateInfo.type, true); maybeAddClosureProperty(proposals, "thisObject", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.OBJECT_CLASS_NODE, false); maybeAddClosureProperty(proposals, "getThisObject", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.OBJECT_CLASS_NODE, true); maybeAddClosureProperty(proposals, "resolveStrategy", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.INTEGER_CLASS_NODE, false); maybeAddClosureProperty(proposals, "getResolveStrategy", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.OBJECT_CLASS_NODE, true); maybeAddClosureProperty(proposals, "directive", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.INTEGER_CLASS_NODE, false); maybeAddClosureProperty(proposals, "getDirective", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.OBJECT_CLASS_NODE, true); maybeAddClosureProperty(proposals, "maximumNumberOfParameters", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.INTEGER_CLASS_NODE, false); maybeAddClosureProperty(proposals, "getMaximumNumberOfParameters", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.OBJECT_CLASS_NODE, true); maybeAddClosureProperty(proposals, "parameterTypes", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.CLASS_ARRAY_CLASS_NODE, false); maybeAddClosureProperty(proposals, "getParameterTypes", org.eclipse.jdt.groovy.search.VariableScope.CLOSURE_CLASS_NODE, org.eclipse.jdt.groovy.search.VariableScope.CLASS_ARRAY_CLASS_NODE, true); return proposals; } else { return Collections.emptyList(); } } private void maybeAddClosureProperty(List<ICompletionProposal> proposals, String name, ClassNode type, ClassNode declaringType, boolean isMethod) { if (ProposalUtils.looselyMatches(getContext().completionExpression, name)) { IGroovyProposal proposal; if (isMethod) { proposal = createMethodProposal(name, declaringType, type); } else { proposal = createFieldProposal(name, declaringType, type); } proposals.add(proposal.createJavaProposal(getContext(), getJavaContext())); } } private GroovyFieldProposal createFieldProposal(String name, ClassNode declaring, ClassNode type) { FieldNode field = new FieldNode(name, Opcodes.ACC_PUBLIC, type, declaring, null); field.setDeclaringClass(declaring); return new GroovyFieldProposal(field); } private GroovyMethodProposal createMethodProposal(String name, ClassNode declaring, ClassNode returnType) { MethodNode method = new MethodNode(name, Opcodes.ACC_PUBLIC, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); method.setDeclaringClass(declaring); return new GroovyMethodProposal(method); } // removes any leading whitespace or non-java identifier chars private String extractVariableNameStart() { String fullExpression = getContext().completionExpression; if (fullExpression.length() == 0) { return ""; } int end = fullExpression.length() - 1; while (end >= 0 && Character.isJavaIdentifierPart(fullExpression.charAt(end))) { end--; } if (end >= 0) { return fullExpression.substring(++end); } else { return fullExpression; } } private Map<String,ClassNode> findLocalNames(String prefix) { Map<String,ClassNode> nameTypeMap = new HashMap<String,ClassNode>(); VariableScope scope = getVariableScope(getContext().containingCodeBlock); while (scope != null) { for (Iterator<Variable> varIter = scope.getDeclaredVariablesIterator(); varIter.hasNext();) { Variable var = varIter.next(); boolean inBounds; if (var instanceof Parameter) { inBounds = ((Parameter) var).getEnd() < offset; } else if (var instanceof VariableExpression) { inBounds = ((VariableExpression) var).getEnd() < offset; } else { inBounds = true; } if (inBounds && ProposalUtils.looselyMatches(prefix, var.getName())) { nameTypeMap.put(var.getName(), var.getOriginType() != null ? var.getOriginType() : var.getType()); } } scope = scope.getParent(); } return nameTypeMap; } private VariableScope getVariableScope(ASTNode astNode) { if (astNode instanceof BlockStatement) { return ((BlockStatement) astNode).getVariableScope(); } else if (astNode instanceof ClassNode && ((ClassNode) astNode).isScript()) { // use scope of the run method ClassNode clazz = (ClassNode) astNode; MethodNode method = clazz.getMethod("run", Parameter.EMPTY_ARRAY); if (method != null && method.getCode() instanceof BlockStatement) { return ((BlockStatement) method.getCode()).getVariableScope(); } } return null; } private List<ICompletionProposal> createProposals(Map<String,ClassNode> nameTypes) { List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); for (Entry<String,ClassNode> nameType : nameTypes.entrySet()) { proposals.add(createProposal(nameType.getKey(), nameType.getValue())); } return proposals; } /** * @param offset * @param replaceLength * @param context * @param proposals * @param nameType */ private ICompletionProposal createProposal(String replaceName, ClassNode type) { CompletionProposal proposal = CompletionProposal.create(CompletionProposal.LOCAL_VARIABLE_REF, offset); proposal.setCompletion(replaceName.toCharArray()); proposal.setReplaceRange(offset - replaceLength, getContext().completionEnd); proposal.setSignature(ProposalUtils.createTypeSignature(type)); proposal.setRelevance(Relevance.HIGH.getRelavance()); LazyJavaCompletionProposal completion = new LazyJavaCompletionProposal(proposal, javaContext); completion.setRelevance(proposal.getRelevance()); return completion; } }