/* * 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 accept(@NotNull PsiTypeVisitor 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 staticFactoryRef = new Ref(); final PsiSubstitutor inferredSubstitutor = ourDiamondGuard.doPreventingRecursion(context, false, new Computable() { @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 conflicts = new ArrayList(); 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() { @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 canonicalTypePresentationFun = new Function() { @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() { @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() { 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 params = new LinkedHashSet(); 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() { @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; } }