blob: f4e40c3292f04df28edf648e582d9038a40d43e7 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.jetbrains.plugins.groovy.lang;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider;
import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
/**
* @author Sergey Evdokimov
*/
public class GroovyConstructorNamedArgumentProvider extends GroovyNamedArgumentProvider {
private static final String METACLASS = "metaClass";
@Override
public void getNamedArguments(@NotNull GrCall call,
@Nullable PsiElement resolve,
@Nullable String argumentName,
boolean forCompletion,
Map<String, NamedArgumentDescriptor> result) {
if (!(call instanceof GrNewExpression)) return;
if (resolve != null) {
if (!(resolve instanceof PsiMethod)) return;
PsiMethod method = (PsiMethod)resolve;
if (!method.isConstructor()) return;
}
GrNewExpression newCall = (GrNewExpression)call;
GrArgumentList argumentList = newCall.getArgumentList();
if (argumentList == null) return;
GrExpression[] expressionArguments = argumentList.getExpressionArguments();
if (expressionArguments.length > 1 || (expressionArguments.length == 1 && !(expressionArguments[0] instanceof GrReferenceExpression))) {
return;
}
for (GroovyResolveResult resolveResult : newCall.multiResolveClass()) {
PsiElement element = resolveResult.getElement();
if (!(element instanceof PsiClass)) continue;
PsiClass aClass = (PsiClass)element;
if (!isClassHasConstructorWithMap(aClass)) continue;
PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass);
processClass(call, classType, argumentName, result);
}
}
public static void processClass(@NotNull GrCall call,
PsiClassType type,
@Nullable String argumentName,
final Map<String, NamedArgumentDescriptor> result) {
if (argumentName == null) {
final HashMap<String, Trinity<PsiType, PsiElement, PsiSubstitutor>> map = ContainerUtil.newHashMap();
MyPsiScopeProcessor processor = new MyPsiScopeProcessor() {
@Override
protected void addNamedArgument(String propertyName, PsiType type, PsiElement element, PsiSubstitutor substitutor) {
if (result.containsKey(propertyName)) return;
Trinity<PsiType, PsiElement, PsiSubstitutor> pair = map.get(propertyName);
if (pair != null) {
if (element instanceof PsiMethod && pair.second instanceof PsiField) {
// methods should override fields
}
else {
return;
}
}
map.put(propertyName, Trinity.create(type, element, substitutor));
}
};
processor.setResolveTargetKinds(ClassHint.RESOLVE_KINDS_METHOD_PROPERTY);
ResolveUtil.processAllDeclarations(type, processor, ResolveState.initial(), call);
for (Map.Entry<String, Trinity<PsiType, PsiElement, PsiSubstitutor>> entry : map.entrySet()) {
result.put(entry.getKey(), new NamedArgumentDescriptor.TypeCondition(entry.getValue().first, entry.getValue().getSecond(), entry.getValue().getThird()).setPriority(
NamedArgumentDescriptor.Priority.AS_LOCAL_VARIABLE));
}
}
else {
MyPsiScopeProcessor processor = new MyPsiScopeProcessor() {
@Override
protected void addNamedArgument(String propertyName, PsiType type, PsiElement element, PsiSubstitutor substitutor) {
if (result.containsKey(propertyName)) return;
result.put(propertyName, new NamedArgumentDescriptor.TypeCondition(type, element, substitutor).setPriority(
NamedArgumentDescriptor.Priority.AS_LOCAL_VARIABLE));
}
};
processor.setResolveTargetKinds(ClassHint.RESOLVE_KINDS_METHOD);
processor.setNameHint(GroovyPropertyUtils.getSetterName(argumentName));
ResolveUtil.processAllDeclarations(type, processor, ResolveState.initial(), call);
processor.setResolveTargetKinds(ClassHint.RESOLVE_KINDS_PROPERTY);
processor.setNameHint(argumentName);
ResolveUtil.processAllDeclarations(type, processor, ResolveState.initial(), call);
}
}
private static boolean isClassHasConstructorWithMap(PsiClass aClass) {
PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) return true;
for (PsiMethod constructor : constructors) {
PsiParameterList parameterList = constructor.getParameterList();
PsiParameter[] parameters = parameterList.getParameters();
if (parameters.length == 0) return true;
final PsiParameter first = parameters[0];
if (InheritanceUtil.isInheritor(first.getType(), CommonClassNames.JAVA_UTIL_MAP)) return true;
if (first instanceof GrParameter && ((GrParameter)first).getTypeGroovy() == null) return true;
//if constructor has only optional parameters it can be used as default constructor with map args
if (!PsiUtil.isConstructorHasRequiredParameters(constructor)) return true;
}
return false;
}
private abstract static class MyPsiScopeProcessor implements PsiScopeProcessor, NameHint, ClassHint, ElementClassHint {
private String myNameHint;
private EnumSet<ResolveKind> myResolveTargetKinds;
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMethod || element instanceof PsiField) {
String propertyName;
PsiType type;
if (element instanceof PsiMethod) {
if (!myResolveTargetKinds.contains(ResolveKind.METHOD)) return true;
PsiMethod method = (PsiMethod)element;
if (!GroovyPropertyUtils.isSimplePropertySetter(method)) return true;
propertyName = GroovyPropertyUtils.getPropertyNameBySetter(method);
if (propertyName == null) return true;
type = method.getParameterList().getParameters()[0].getType();
}
else {
if (!myResolveTargetKinds.contains(ResolveKind.PROPERTY)) return true;
type = ((PsiField)element).getType();
propertyName = ((PsiField)element).getName();
}
if (propertyName.equals(METACLASS)) return true;
if (((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) return true;
PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
if (substitutor != null) {
type = substitutor.substitute(type);
}
addNamedArgument(propertyName, type, element, substitutor);
}
return true;
}
protected abstract void addNamedArgument(String propertyName, PsiType type, PsiElement element, PsiSubstitutor substitutor);
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
if ((NameHint.KEY == hintKey && myNameHint != null) || ClassHint.KEY == hintKey || ElementClassHint.KEY == hintKey) {
//noinspection unchecked
return (T) this;
}
return null;
}
@Override
public void handleEvent(@NotNull Event event, Object associated) {
}
@Override
public boolean shouldProcess(ResolveKind resolveKind) {
return myResolveTargetKinds.contains(resolveKind);
}
@Override
public boolean shouldProcess(DeclarationKind kind) {
switch (kind) {
case CLASS:
return shouldProcess(ResolveKind.CLASS);
case ENUM_CONST:
case VARIABLE:
case FIELD:
return shouldProcess(ResolveKind.PROPERTY);
case METHOD:
return shouldProcess(ResolveKind.METHOD);
case PACKAGE:
return shouldProcess(ResolveKind.PACKAGE);
default:
return false;
}
}
@Override
public String getName(@NotNull ResolveState state) {
return myNameHint;
}
public void setNameHint(String nameHint) {
myNameHint = nameHint;
}
public void setResolveTargetKinds(EnumSet<ResolveKind> resolveTargetKinds) {
myResolveTargetKinds = resolveTargetKinds;
}
}
}