blob: 5f3eee5c6be30e45d6cf71d67df744c602ff9516 [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 com.intellij.psi;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.scope.PsiConflictResolver;
import com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver;
import com.intellij.psi.scope.processor.MethodCandidatesProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author anna
* @since Jul 30, 2010
*/
public class PsiDiamondTypeImpl extends PsiDiamondType {
private static final Logger LOG = Logger.getInstance("#" + PsiDiamondTypeImpl.class.getName());
private final PsiManager myManager;
private final PsiTypeElement myTypeElement;
public PsiDiamondTypeImpl(PsiManager manager, PsiTypeElement psiTypeElement) {
super(PsiAnnotation.EMPTY_ARRAY);
myManager = manager;
myTypeElement = psiTypeElement;
}
@NotNull
@Override
public String getPresentableText() {
return "";
}
@NotNull
@Override
public String getCanonicalText() {
return "";
}
@NotNull
@Override
public String getInternalCanonicalText() {
return "Diamond Type";
}
@Override
public boolean isValid() {
return false;
}
@Override
public boolean equalsToText(@NotNull @NonNls String text) {
return text.isEmpty();
}
@Override
public <A> A accept(@NotNull PsiTypeVisitor<A> visitor) {
return visitor.visitDiamondType(this);
}
@NotNull
@Override
public GlobalSearchScope getResolveScope() {
return GlobalSearchScope.allScope(myManager.getProject());
}
@NotNull
@Override
public PsiType[] getSuperTypes() {
return new PsiType[]{getJavaLangObject(myManager, getResolveScope())};
}
@Override
public DiamondInferenceResult resolveInferredTypes() {
final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(myTypeElement, PsiNewExpression.class);
if (newExpression == null) {
return PsiDiamondTypeImpl.DiamondInferenceResult.NULL_RESULT;
}
return resolveInferredTypes(newExpression);
}
public static DiamondInferenceResult resolveInferredTypes(PsiNewExpression newExpression) {
return resolveInferredTypes(newExpression, newExpression);
}
public static DiamondInferenceResult resolveInferredTypes(PsiNewExpression newExpression,
PsiElement context) {
final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
if (anonymousClass != null) {
final PsiElement resolve = anonymousClass.getBaseClassReference().resolve();
if (resolve instanceof PsiClass) {
return PsiDiamondTypeImpl.DiamondInferenceResult.ANONYMOUS_INNER_RESULT;
}
}
final PsiReferenceParameterList referenceParameterList = PsiTreeUtil.getChildOfType(newExpression, PsiReferenceParameterList.class);
if (referenceParameterList != null && referenceParameterList.getTypeParameterElements().length > 0) {
return DiamondInferenceResult.EXPLICIT_CONSTRUCTOR_TYPE_ARGS;
}
return resolveInferredTypesNoCheck(newExpression, context);
}
public static DiamondInferenceResult resolveInferredTypesNoCheck(final PsiNewExpression newExpression, final PsiElement context) {
final PsiClass psiClass = findClass(newExpression);
if (psiClass == null) return DiamondInferenceResult.NULL_RESULT;
final PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) return DiamondInferenceResult.NULL_RESULT;
final Ref<PsiMethod> staticFactoryRef = new Ref<PsiMethod>();
final PsiSubstitutor inferredSubstitutor = ourDiamondGuard.doPreventingRecursion(context, false, new Computable<PsiSubstitutor>() {
@Override
public PsiSubstitutor compute() {
final PsiMethod staticFactory = findConstructorStaticFactory(psiClass, newExpression);
if (staticFactory == null) {
return null;
}
staticFactoryRef.set(staticFactory);
return inferTypeParametersForStaticFactory(staticFactory, newExpression, context, false);
}
});
if (inferredSubstitutor == null) {
return DiamondInferenceResult.NULL_RESULT;
}
final PsiMethod staticFactory = staticFactoryRef.get();
if (staticFactory == null) {
LOG.error(inferredSubstitutor);
return DiamondInferenceResult.NULL_RESULT;
}
final PsiTypeParameter[] parameters = staticFactory.getTypeParameters();
final PsiTypeParameter[] classParameters = psiClass.getTypeParameters();
final PsiJavaCodeReferenceElement classOrAnonymousClassReference = newExpression.getClassOrAnonymousClassReference();
LOG.assertTrue(classOrAnonymousClassReference != null);
final DiamondInferenceResult result = new DiamondInferenceResult(classOrAnonymousClassReference.getReferenceName() + "<>");
if (PsiUtil.isRawSubstitutor(staticFactory, inferredSubstitutor)) {
return result;
}
for (PsiTypeParameter parameter : parameters) {
for (PsiTypeParameter classParameter : classParameters) {
if (Comparing.strEqual(classParameter.getName(), parameter.getName())) {
result.addInferredType(inferredSubstitutor.substitute(parameter));
break;
}
}
}
return result;
}
@Nullable
private static PsiMethod findConstructorStaticFactory(final PsiClass containingClass, PsiNewExpression newExpression) {
final PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) return null;
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(newExpression);
final List<CandidateInfo> conflicts = new ArrayList<CandidateInfo>();
PsiMethod[] constructors = containingClass.getConstructors();
if (constructors.length == 0) {
//default constructor
constructors = new PsiMethod[] {null};
}
final PsiConflictResolver[] conflictResolvers = {new JavaMethodsConflictResolver(argumentList, languageLevel)};
final MethodCandidatesProcessor processor = new MethodCandidatesProcessor(argumentList, argumentList.getContainingFile(), conflictResolvers, conflicts) {
@Override
protected boolean isAccepted(PsiMethod candidate) {
return true;
}
@Override
protected PsiClass getContainingClass(PsiMethod method) {
return containingClass;
}
};
processor.setArgumentList(argumentList);
for (PsiMethod constructor : constructors) {
final PsiTypeParameter[] params = getAllTypeParams(constructor, containingClass);
final PsiMethod staticFactory = generateStaticFactory(constructor, containingClass, params, newExpression.getClassReference());
if (staticFactory != null) {
processor.add(staticFactory, PsiSubstitutor.EMPTY);
}
}
final JavaResolveResult[] result = processor.getResult();
return result.length == 1 ? (PsiMethod)result[0].getElement() : null;
}
@Nullable
private static PsiClass findClass(PsiNewExpression newExpression) {
final PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference();
if (classReference != null) {
final String text = classReference.getReferenceName();
if (text != null) {
final Project project = newExpression.getProject();
final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
final PsiResolveHelper resolveHelper = facade.getResolveHelper();
final PsiExpression newExpressionQualifier = newExpression.getQualifier();
final PsiElement qualifierElement = classReference.getQualifier();
final String qualifier = qualifierElement != null ? qualifierElement.getText() : "";
final String qualifiedName = StringUtil.getQualifiedName(qualifier, text);
if (newExpressionQualifier != null) {
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(newExpressionQualifier.getType());
if (aClass != null) {
return aClass.findInnerClassByName(qualifiedName, false);
}
}
return resolveHelper.resolveReferencedClass(qualifiedName, newExpression);
} else {
return null;
}
}
return null;
}
@Nullable
private static PsiMethod generateStaticFactory(@Nullable PsiMethod constructor,
PsiClass containingClass,
PsiTypeParameter[] params,
PsiJavaCodeReferenceElement reference) {
final StringBuilder buf = new StringBuilder();
final String modifier = VisibilityUtil.getVisibilityModifier(constructor != null ? constructor.getModifierList() : containingClass.getModifierList());
if (!PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
buf.append(modifier);
buf.append(" ");
}
buf.append("static ");
buf.append("<");
buf.append(StringUtil.join(params, new Function<PsiTypeParameter, String>() {
@Override
public String fun(PsiTypeParameter psiTypeParameter) {
String extendsList = "";
if (psiTypeParameter.getLanguage().isKindOf(JavaLanguage.INSTANCE)) {
final PsiClassType[] extendsListTypes = psiTypeParameter.getExtendsListTypes();
if (extendsListTypes.length > 0) {
final Function<PsiClassType, String> canonicalTypePresentationFun = new Function<PsiClassType, String>() {
@Override
public String fun(PsiClassType type) {
return type.getCanonicalText();
}
};
extendsList = " extends " + StringUtil.join(extendsListTypes, canonicalTypePresentationFun, "&");
}
}
return psiTypeParameter.getName() + extendsList;
}
}, ", "));
buf.append(">");
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(containingClass.getProject());
String qualifiedName = containingClass.getQualifiedName();
PsiElement qualifier = reference != null ? reference.getQualifier() : null;
if (qualifier instanceof PsiJavaCodeReferenceElement) {
final JavaResolveResult resolveResult = ((PsiJavaCodeReferenceElement)qualifier).advancedResolve(false);
final PsiElement element = resolveResult.getElement();
if (element instanceof PsiClass) {
final String outerClassSubstitutedQName =
elementFactory.createType((PsiClass)element, resolveResult.getSubstitutor()).getInternalCanonicalText();
qualifiedName = outerClassSubstitutedQName + "." + containingClass.getName();
}
} else if (reference != null && qualifier == null && containingClass.getContainingClass() != null) {
qualifiedName = null;
}
buf.append(qualifiedName != null ? qualifiedName : containingClass.getName());
final PsiTypeParameter[] parameters = containingClass.getTypeParameters();
buf.append("<");
buf.append(StringUtil.join(parameters, new Function<PsiTypeParameter, String>() {
@Override
public String fun(PsiTypeParameter psiTypeParameter) {
return psiTypeParameter.getName();
}
}, ", "));
buf.append("> ");
String staticFactoryName = "staticFactory";
final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(containingClass.getProject());
staticFactoryName = styleManager.suggestUniqueVariableName(staticFactoryName, containingClass, false);
buf.append(staticFactoryName);
if (constructor == null) {
buf.append("()");
}
else {
buf.append("(").append(StringUtil.join(constructor.getParameterList().getParameters(), new Function<PsiParameter, String>() {
int myIdx = 0;
@Override
public String fun(PsiParameter psiParameter) {
return psiParameter.getType().getCanonicalText() + " p" + myIdx++;
}
}, ",")).append(")");
}
buf.append("{}");
try {
return elementFactory.createMethodFromText(buf.toString(), constructor != null ? constructor : containingClass);
}
catch (IncorrectOperationException e) {
return null;
}
}
private static PsiTypeParameter[] getAllTypeParams(PsiTypeParameterListOwner listOwner, PsiClass containingClass) {
Set<PsiTypeParameter> params = new LinkedHashSet<PsiTypeParameter>();
if (listOwner != null) {
Collections.addAll(params, listOwner.getTypeParameters());
}
Collections.addAll(params, containingClass.getTypeParameters());
return params.toArray(new PsiTypeParameter[params.size()]);
}
private static PsiSubstitutor inferTypeParametersForStaticFactory(@NotNull PsiMethod staticFactoryMethod,
PsiNewExpression expression,
final PsiElement parent,
final boolean varargs) {
final PsiExpressionList argumentList = expression.getArgumentList();
if (argumentList != null) {
final MethodCandidateInfo staticFactoryCandidateInfo =
new MethodCandidateInfo(staticFactoryMethod, PsiSubstitutor.EMPTY, false, false, argumentList, parent,
argumentList.getExpressionTypes(), null) {
@Override
public boolean isVarargs() {
return varargs;
}
@Override
protected PsiElement getParent() {
return parent;
}
@Override
protected PsiElement getMarkerList() {
return parent instanceof PsiNewExpression ? ((PsiNewExpression)parent).getArgumentList() : super.getMarkerList();
}
};
if (!varargs && staticFactoryMethod.isVarArgs() && staticFactoryCandidateInfo.getPertinentApplicabilityLevel() < MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY) {
return inferTypeParametersForStaticFactory(staticFactoryMethod, expression, parent, true);
}
return staticFactoryCandidateInfo.getSubstitutor();
}
else {
return PsiSubstitutor.EMPTY;
}
}
public static boolean hasDefaultConstructor(@NotNull final PsiClass psiClass) {
final PsiMethod[] constructors = psiClass.getConstructors();
for (PsiMethod method : constructors) {
if (method.getParameterList().getParametersCount() == 0) return true;
}
return constructors.length == 0;
}
public static boolean haveConstructorsGenericsParameters(@NotNull final PsiClass psiClass) {
for (final PsiMethod method : psiClass.getConstructors()) {
for (PsiParameter parameter : method.getParameterList().getParameters()) {
final PsiType type = parameter.getType();
final Boolean accept = type.accept(new PsiTypeVisitor<Boolean>() {
@Override
public Boolean visitArrayType(PsiArrayType arrayType) {
return arrayType.getComponentType().accept(this);
}
@Override
public Boolean visitClassType(PsiClassType classType) {
for (PsiType psiType : classType.getParameters()) {
if (psiType != null) {
final Boolean typeParamFound = psiType.accept(this);
if (typeParamFound != null && typeParamFound) return true;
}
}
final PsiClass aClass = PsiUtil.resolveClassInType(classType);
return aClass instanceof PsiTypeParameter && ((PsiTypeParameter)aClass).getOwner() == method;
}
@Override
public Boolean visitWildcardType(PsiWildcardType wildcardType) {
final PsiType bound = wildcardType.getBound();
if (bound == null) return false;
return bound.accept(this);
}
});
if (accept != null && accept.booleanValue()) return true;
}
}
return false;
}
}