/* * 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.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation; import org.codehaus.groovy.eclipse.codeassist.requestor.MethodInfoContentAssistContext; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTResolver; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.groovy.search.ITypeResolver; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; public class ConstructorCompletionProcessor extends AbstractGroovyCompletionProcessor implements ITypeResolver { private JDTResolver resolver; public ConstructorCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) { super(context, javaContext, nameEnvironment); } public List<ICompletionProposal> generateProposals(IProgressMonitor monitor) { ContentAssistContext context = getContext(); char[] constructorCompletionText = getCompletionText(context.fullCompletionExpression); if (constructorCompletionText == null) { return Collections.emptyList(); } int completionExprStart; if (context.location == ContentAssistLocation.METHOD_CONTEXT) { completionExprStart = ((MethodInfoContentAssistContext) context).methodNameEnd - ((MethodInfoContentAssistContext) context).methodName.length(); } else { completionExprStart = context.completionLocation - constructorCompletionText.length; } if (completionExprStart < 0) { // will get here for some kinds of bad syntax return Collections.emptyList(); } GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor( context, getJavaContext(), completionExprStart, context.completionEnd - completionExprStart, getNameEnvironment().nameLookup, monitor); getNameEnvironment().findConstructorDeclarations( constructorCompletionText, true, requestor, monitor); List<ICompletionProposal> constructorProposals = requestor.processAcceptedConstructors(findUsedParameters(context), resolver); return constructorProposals; } private Set<String> findUsedParameters(ContentAssistContext context) { if (context.location != ContentAssistLocation.METHOD_CONTEXT) { return Collections.emptySet(); } Set<String> usedParams = new HashSet<String>(); ASTNode completionNode = context.completionNode; if (completionNode instanceof ConstructorCallExpression) { // next find out if there are any existing named args ConstructorCallExpression call = (ConstructorCallExpression) completionNode; Expression arguments = call.getArguments(); if (arguments instanceof TupleExpression) { for (Expression maybeArg : ((TupleExpression) arguments).getExpressions()) { if (maybeArg instanceof MapExpression) { arguments = maybeArg; break; } } } // now remove the arguments that are already written if (arguments instanceof MapExpression) { // Do extra filtering to determine what parameters are still // available MapExpression enclosingCallArgs = (MapExpression) arguments; for (MapEntryExpression entry : enclosingCallArgs.getMapEntryExpressions()) { usedParams.add(entry.getKeyExpression().getText()); } } } return usedParams; } /** * Removes whitespace and the 'new ' prefix and does a fail-fast if a * non-java identifier is found. */ private char[] getCompletionText(String fullCompletionExpression) { List<Character> chars = new LinkedList<Character>(); if (fullCompletionExpression == null) { return CharOperation.NO_CHAR; } char[] fullArray = fullCompletionExpression.toCharArray(); int newIndex = CharOperation.indexOf("new ".toCharArray(), fullArray, true) + 4; if (newIndex == -1) { return null; } for (int i = newIndex; i < fullArray.length; i++) { if (Character.isWhitespace(fullArray[i])) { continue; } else if (Character.isJavaIdentifierPart(fullArray[i]) || fullArray[i] == '.') { chars.add(fullArray[i]); } else { // fail fast if something odd is found like parens or brackets return null; } } char[] res = new char[chars.size()]; int i = 0; for (Character c : chars) { res[i] = c.charValue(); i++; } return res; } public void setResolverInformation(ModuleNode module, JDTResolver resolver) { this.resolver = resolver; } }