/*
* Copyright 2000-2010 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.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Function;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* User: anna
* Date: Jul 30, 2010
*/
public class PsiDiamondTypeImpl extends PsiDiamondType {
private final PsiManager myManager;
private final PsiTypeElement myTypeElement;
private static final Logger LOG = Logger.getInstance("#" + PsiDiamondTypeImpl.class.getName());
public PsiDiamondTypeImpl(PsiManager manager, PsiTypeElement psiTypeElement) {
super(PsiAnnotation.EMPTY_ARRAY);
myManager = manager;
myTypeElement = psiTypeElement;
}
@Override
public String getPresentableText() {
return "";
}
@Override
public String getCanonicalText() {
return "";
}
@Override
public String getInternalCanonicalText() {
return "Diamond Type";
}
@Override
public boolean isValid() {
return false;
}
@Override
public boolean equalsToText(@NonNls String text) {
return text != null && text.isEmpty();
}
@Override
public <A> A accept(@NotNull PsiTypeVisitor<A> visitor) {
return visitor.visitDiamondType(this);
}
@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 constructor = findConstructor(psiClass, newExpression);
PsiTypeParameter[] params = getAllTypeParams(constructor, psiClass);
final PsiMethod staticFactory = generateStaticFactory(constructor, psiClass, params);
if (staticFactory == null) {
return null;
}
staticFactoryRef.set(staticFactory);
return inferTypeParametersForStaticFactory(staticFactory, newExpression, context);
}
});
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() + "<>", newExpression.getProject());
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 findConstructor(PsiClass containingClass, PsiNewExpression newExpression) {
final PsiExpressionList argumentList = newExpression.getArgumentList();
final Project project = newExpression.getProject();
final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
final PsiResolveHelper resolveHelper = facade.getResolveHelper();
final JavaResolveResult result =
resolveHelper.resolveConstructor(facade.getElementFactory().createType(containingClass), argumentList, argumentList);
return (PsiMethod)result.getElement();
}
@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) {
final StringBuilder buf = new StringBuilder();
buf.append("public static ");
buf.append("<");
buf.append(StringUtil.join(params, new Function<PsiTypeParameter, String>() {
@Override
public String fun(PsiTypeParameter psiTypeParameter) {
final String extendsList = psiTypeParameter.getLanguage().isKindOf(JavaLanguage.INSTANCE) ? psiTypeParameter.getExtendsList().getText() : null;
return psiTypeParameter.getName() + (StringUtil.isEmpty(extendsList) ? "" : " " + extendsList);
}
}, ", "));
buf.append(">");
final String qualifiedName = containingClass.getQualifiedName();
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("{}");
return JavaPsiFacade.getElementFactory(containingClass.getProject()).createMethodFromText(buf.toString(), constructor != null ? constructor : containingClass);
}
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 PsiExpressionList argumentList = expression.getArgumentList();
if (argumentList != null) {
final MethodCandidateInfo staticFactoryCandidateInfo =
new MethodCandidateInfo(staticFactoryMethod, PsiSubstitutor.EMPTY, false, false, argumentList, parent,
argumentList.getExpressionTypes(), null) {
@Override
protected PsiElement getParent() {
return parent;
}
@Override
protected PsiElement getMarkerList() {
return parent instanceof PsiNewExpression ? ((PsiNewExpression)parent).getArgumentList() : super.getMarkerList();
}
};
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 typaParamFound = psiType.accept(this);
if (typaParamFound != null && typaParamFound) 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;
}
}