blob: e9cee8c84f5944504c59f49b0078e8a2af24dd88 [file] [log] [blame]
/*
* Copyright 2000-2013 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 com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.intention.impl.TypeExpression;
import com.intellij.codeInsight.template.TemplateBuilder;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author ven
*/
public class GuessTypeParameters {
private final JVMElementFactory myFactory;
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters");
public GuessTypeParameters(JVMElementFactory factory) {
myFactory = factory;
}
private List<PsiType> matchingTypeParameters (PsiType[] paramVals, PsiTypeParameter[] params, ExpectedTypeInfo info) {
PsiType type = info.getType();
int kind = info.getKind();
List<PsiType> result = new ArrayList<PsiType>();
for (int i = 0; i < paramVals.length; i++) {
PsiType val = paramVals[i];
if (val != null) {
switch (kind) {
case ExpectedTypeInfo.TYPE_STRICTLY:
if (val.equals(type)) result.add(myFactory.createType(params[i]));
break;
case ExpectedTypeInfo.TYPE_OR_SUBTYPE:
if (type.isAssignableFrom(val)) result.add(myFactory.createType(params[i]));
break;
case ExpectedTypeInfo.TYPE_OR_SUPERTYPE:
if (val.isAssignableFrom(type)) result.add(myFactory.createType(params[i]));
break;
}
}
}
return result;
}
public void setupTypeElement (PsiTypeElement typeElement, ExpectedTypeInfo[] infos, PsiSubstitutor substitutor,
TemplateBuilder builder, @Nullable PsiElement context, PsiClass targetClass) {
LOG.assertTrue(typeElement.isValid());
ApplicationManager.getApplication().assertWriteAccessAllowed();
PsiManager manager = typeElement.getManager();
GlobalSearchScope scope = typeElement.getResolveScope();
Project project = manager.getProject();
if (infos.length == 1 && substitutor != null && substitutor != PsiSubstitutor.EMPTY) {
ExpectedTypeInfo info = infos[0];
Map<PsiTypeParameter, PsiType> map = substitutor.getSubstitutionMap();
PsiType[] vals = map.values().toArray(PsiType.createArray(map.size()));
PsiTypeParameter[] params = map.keySet().toArray(new PsiTypeParameter[map.size()]);
List<PsiType> types = matchingTypeParameters(vals, params, info);
if (!types.isEmpty()) {
ContainerUtil.addAll(types, ExpectedTypesProvider.processExpectedTypes(infos, new MyTypeVisitor(manager, scope), project));
builder.replaceElement(typeElement, new TypeExpression(project, types.toArray(PsiType.createArray(types.size()))));
return;
}
else {
PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
PsiType type = info.getType();
PsiType defaultType = info.getDefaultType();
try {
PsiTypeElement inplaceTypeElement = ((PsiVariable)factory.createVariableDeclarationStatement("foo", type, null).getDeclaredElements()[0]).getTypeElement();
PsiSubstitutor rawingSubstitutor = getRawingSubstitutor (context, targetClass);
int substitionResult = substituteToTypeParameters(typeElement, inplaceTypeElement, vals, params, builder, rawingSubstitutor, true);
if (substitionResult != SUBSTITUTED_NONE) {
if (substitionResult == SUBSTITUTED_IN_PARAMETERS) {
PsiJavaCodeReferenceElement refElement = typeElement.getInnermostComponentReferenceElement();
LOG.assertTrue(refElement != null && refElement.getReferenceNameElement() != null);
type = getComponentType(type);
LOG.assertTrue(type != null);
defaultType = getComponentType(defaultType);
LOG.assertTrue(defaultType != null);
ExpectedTypeInfo info1 = ExpectedTypesProvider.createInfo(((PsiClassType)defaultType).rawType(),
ExpectedTypeInfo.TYPE_STRICTLY,
((PsiClassType)defaultType).rawType(),
info.getTailType());
MyTypeVisitor visitor = new MyTypeVisitor(manager, scope);
builder.replaceElement(refElement.getReferenceNameElement(),
new TypeExpression(project, ExpectedTypesProvider.processExpectedTypes(new ExpectedTypeInfo[]{info1}, visitor, project)));
}
return;
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
PsiType[] types = infos.length == 0 ? new PsiType[] {typeElement.getType()} : ExpectedTypesProvider.processExpectedTypes(infos, new MyTypeVisitor(manager, scope), project);
builder.replaceElement(typeElement,
new TypeExpression(project, types));
}
private static PsiSubstitutor getRawingSubstitutor(PsiElement context, PsiClass targetClass) {
if (context == null || targetClass == null) return PsiSubstitutor.EMPTY;
PsiTypeParameterListOwner currContext = PsiTreeUtil.getParentOfType(context, PsiTypeParameterListOwner.class);
PsiManager manager = context.getManager();
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
while (currContext != null && !manager.areElementsEquivalent(currContext, targetClass)) {
PsiTypeParameter[] typeParameters = currContext.getTypeParameters();
substitutor = JavaPsiFacade.getInstance(context.getProject()).getElementFactory().createRawSubstitutor(substitutor, typeParameters);
currContext = currContext.getContainingClass();
}
return substitutor;
}
@Nullable
private static PsiClassType getComponentType (PsiType type) {
type = type.getDeepComponentType();
if (type instanceof PsiClassType) return (PsiClassType)type;
return null;
}
private static final int SUBSTITUTED_NONE = 0;
private static final int SUBSTITUTED_IN_REF = 1;
private static final int SUBSTITUTED_IN_PARAMETERS = 2;
private int substituteToTypeParameters (PsiTypeElement typeElement,
PsiTypeElement inplaceTypeElement,
PsiType[] paramVals,
PsiTypeParameter[] params,
TemplateBuilder builder,
PsiSubstitutor rawingSubstitutor,
boolean toplevel) {
PsiType type = inplaceTypeElement.getType();
List<PsiType> types = new ArrayList<PsiType>();
for (int i = 0; i < paramVals.length; i++) {
PsiType val = paramVals[i];
if (val == null) return SUBSTITUTED_NONE;
if (type.equals(val)) {
types.add(myFactory.createType(params[i]));
}
}
if (!types.isEmpty()) {
Project project = typeElement.getProject();
PsiType substituted = rawingSubstitutor.substitute(type);
if (!CommonClassNames.JAVA_LANG_OBJECT.equals(substituted.getCanonicalText()) && (toplevel || substituted.equals(type))) {
types.add(substituted);
}
builder.replaceElement(typeElement, new TypeExpression(project, types.toArray(PsiType.createArray(types.size()))));
return toplevel ? SUBSTITUTED_IN_REF : SUBSTITUTED_IN_PARAMETERS;
}
boolean substituted = false;
PsiJavaCodeReferenceElement ref = typeElement.getInnermostComponentReferenceElement();
PsiJavaCodeReferenceElement inplaceRef = inplaceTypeElement.getInnermostComponentReferenceElement();
if (ref != null) {
LOG.assertTrue(inplaceRef != null);
PsiTypeElement[] innerTypeElements = ref.getParameterList().getTypeParameterElements();
PsiTypeElement[] inplaceInnerTypeElements = inplaceRef.getParameterList().getTypeParameterElements();
for (int i = 0; i < innerTypeElements.length; i++) {
substituted |= substituteToTypeParameters(innerTypeElements[i], inplaceInnerTypeElements[i], paramVals, params, builder,
rawingSubstitutor, false) != SUBSTITUTED_NONE;
}
}
return substituted ? SUBSTITUTED_IN_PARAMETERS : SUBSTITUTED_NONE;
}
public static class MyTypeVisitor extends PsiTypeVisitor<PsiType> {
private final GlobalSearchScope myResolveScope;
private final PsiManager myManager;
public MyTypeVisitor(PsiManager manager, GlobalSearchScope resolveScope) {
myManager = manager;
myResolveScope = resolveScope;
}
@Override
public PsiType visitType(PsiType type) {
if (type.equals(PsiType.NULL)) return PsiType.getJavaLangObject(myManager, myResolveScope);
return type;
}
@Override
public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
return capturedWildcardType.getUpperBound().accept(this);
}
}
}