diamond type visitor
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / PsiDiamondTypeImpl.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.psi;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Comparing;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
23 import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
24 import com.intellij.psi.search.GlobalSearchScope;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.util.PsiUtil;
27 import com.intellij.util.Function;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.util.Collections;
33 import java.util.LinkedHashSet;
34 import java.util.Set;
35
36 /**
37  * User: anna
38  * Date: Jul 30, 2010
39  */
40 public class PsiDiamondTypeImpl extends PsiDiamondType {
41   private final PsiManager myManager;
42   private final PsiTypeElement myTypeElement;
43   private static final Logger LOG = Logger.getInstance("#" + PsiDiamondTypeImpl.class.getName());
44
45   public PsiDiamondTypeImpl(PsiManager manager, PsiTypeElement psiTypeElement) {
46     super(PsiAnnotation.EMPTY_ARRAY);
47     myManager = manager;
48     myTypeElement = psiTypeElement;
49   }
50
51   @Override
52   public String getPresentableText() {
53     return "";
54   }
55
56   @Override
57   public String getCanonicalText() {
58     return "";
59   }
60
61   @Override
62   public String getInternalCanonicalText() {
63     return "Diamond Type";
64   }
65
66   @Override
67   public boolean isValid() {
68     return false;
69   }
70
71   @Override
72   public boolean equalsToText(@NonNls String text) {
73     return text != null && text.isEmpty();
74   }
75
76   @Override
77   public <A> A accept(PsiTypeVisitor<A> visitor) {
78     return visitor.visitDiamondType(this);
79   }
80
81   @Override
82   public GlobalSearchScope getResolveScope() {
83     return GlobalSearchScope.allScope(myManager.getProject());
84   }
85
86   @NotNull
87   @Override
88   public PsiType[] getSuperTypes() {
89     return new PsiType[]{getJavaLangObject(myManager, getResolveScope())};
90   }
91
92   @Override
93   public DiamondInferenceResult resolveInferredTypes() {
94     final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(myTypeElement, PsiNewExpression.class);
95     if (newExpression == null) {
96       return PsiDiamondTypeImpl.DiamondInferenceResult.NULL_RESULT;
97     }
98
99     return resolveInferredTypes(newExpression);
100   }
101
102   public static DiamondInferenceResult resolveInferredTypes(PsiNewExpression newExpression) {
103     return resolveInferredTypes(newExpression, newExpression);
104   }
105
106   public static DiamondInferenceResult resolveInferredTypes(PsiNewExpression newExpression,
107                                                             PsiElement context) {
108     final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
109     if (anonymousClass != null) {
110       final PsiElement resolve = anonymousClass.getBaseClassReference().resolve();
111       if (resolve instanceof PsiClass) {
112         return PsiDiamondTypeImpl.DiamondInferenceResult.ANONYMOUS_INNER_RESULT;
113       }
114     }
115
116     final PsiReferenceParameterList referenceParameterList = PsiTreeUtil.getChildOfType(newExpression, PsiReferenceParameterList.class);
117     if (referenceParameterList != null && referenceParameterList.getTypeParameterElements().length > 0) {
118       return DiamondInferenceResult.EXPLICIT_CONSTRUCTOR_TYPE_ARGS;
119     }
120
121     return resolveInferredTypesNoCheck(newExpression, context);
122   }
123
124   public static DiamondInferenceResult resolveInferredTypesNoCheck(PsiNewExpression newExpression, PsiElement context) {
125     final PsiClass psiClass = findClass(newExpression);
126     if (psiClass == null) return DiamondInferenceResult.NULL_RESULT;
127     final PsiExpressionList argumentList = newExpression.getArgumentList();
128     if (argumentList == null) return DiamondInferenceResult.NULL_RESULT;
129     final PsiMethod constructor = findConstructor(psiClass, newExpression);
130     PsiTypeParameter[] params = getAllTypeParams(constructor, psiClass);
131     PsiMethod staticFactory = generateStaticFactory(constructor, psiClass, params);
132     if (staticFactory == null) {
133       return DiamondInferenceResult.NULL_RESULT;
134     }
135     final PsiSubstitutor inferredSubstitutor = inferTypeParametersForStaticFactory(staticFactory, newExpression, context);
136     final PsiTypeParameter[] parameters = staticFactory.getTypeParameters();
137     final PsiTypeParameter[] classParameters = psiClass.getTypeParameters();
138     final PsiJavaCodeReferenceElement classOrAnonymousClassReference = newExpression.getClassOrAnonymousClassReference();
139     LOG.assertTrue(classOrAnonymousClassReference != null);
140     final DiamondInferenceResult
141       result = new DiamondInferenceResult(classOrAnonymousClassReference.getReferenceName() + "<>", newExpression.getProject());
142     for (PsiTypeParameter parameter : parameters) {
143       for (PsiTypeParameter classParameter : classParameters) {
144         if (Comparing.strEqual(classParameter.getName(), parameter.getName())) {
145           result.addInferredType(inferredSubstitutor.substitute(parameter));
146           break;
147         }
148       }
149     }
150     return result;
151   }
152
153
154   @Nullable
155   private static PsiMethod findConstructor(PsiClass containingClass, PsiNewExpression newExpression) {
156     final PsiExpressionList argumentList = newExpression.getArgumentList();
157     final Project project = newExpression.getProject();
158     final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
159     final PsiResolveHelper resolveHelper = facade.getResolveHelper();
160     final JavaResolveResult result =
161       resolveHelper.resolveConstructor(facade.getElementFactory().createType(containingClass), argumentList, argumentList);
162     return (PsiMethod)result.getElement();
163   }
164
165   @Nullable
166   private static PsiClass findClass(PsiNewExpression newExpression) {
167     final PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference();
168     if (classReference != null) {
169       final String text = classReference.getReferenceName();
170       if (text != null) {
171         final Project project = newExpression.getProject();
172         final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
173         final PsiResolveHelper resolveHelper = facade.getResolveHelper();
174         final PsiExpression newExpressionQualifier = newExpression.getQualifier();
175         final PsiElement qualifierElement = classReference.getQualifier();
176         final String qualifier = qualifierElement != null ? qualifierElement.getText() : "";
177         final String qualifiedName = StringUtil.getQualifiedName(qualifier, text);
178         if (newExpressionQualifier != null) {
179           final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(newExpressionQualifier.getType());
180           if (aClass != null) {
181             return aClass.findInnerClassByName(qualifiedName, false);
182           }
183         }
184         return resolveHelper.resolveReferencedClass(qualifiedName, newExpression);
185       } else {
186         return null;
187       }
188     }
189     return null;
190   }
191
192   @Nullable
193   private static PsiMethod generateStaticFactory(@Nullable PsiMethod constructor, PsiClass containingClass, PsiTypeParameter[] params) {
194     final StringBuilder buf = new StringBuilder();
195     buf.append("public static ");
196     buf.append("<");
197     buf.append(StringUtil.join(params, new Function<PsiTypeParameter, String>() {
198       @Override
199       public String fun(PsiTypeParameter psiTypeParameter) {
200         return psiTypeParameter.getName();
201       }
202     }, ", "));
203     buf.append(">");
204
205     final String qualifiedName = containingClass.getQualifiedName();
206     buf.append(qualifiedName != null ? qualifiedName : containingClass.getName());
207     final PsiTypeParameter[] parameters = containingClass.getTypeParameters();
208     buf.append("<");
209     buf.append(StringUtil.join(parameters, new Function<PsiTypeParameter, String>() {
210       @Override
211       public String fun(PsiTypeParameter psiTypeParameter) {
212         return psiTypeParameter.getName();
213       }
214     }, ", "));
215     buf.append("> ");
216
217     String staticFactoryName = "staticFactory";
218     final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(containingClass.getProject());
219     staticFactoryName = styleManager.suggestUniqueVariableName(staticFactoryName, containingClass, false);
220     buf.append(staticFactoryName);
221     if (constructor == null) {
222       buf.append("()");
223     }
224     else {
225       buf.append("(").append(StringUtil.join(constructor.getParameterList().getParameters(), new Function<PsiParameter, String>() {
226         int myIdx = 0;
227         @Override
228         public String fun(PsiParameter psiParameter) {
229           return psiParameter.getType().getCanonicalText() + " p" + myIdx++;
230         }
231       }, ",")).append(")");
232     }
233     buf.append("{}");
234
235     return JavaPsiFacade.getElementFactory(containingClass.getProject()).createMethodFromText(buf.toString(), constructor != null ? constructor : containingClass);
236   }
237
238   private static PsiTypeParameter[] getAllTypeParams(PsiTypeParameterListOwner listOwner, PsiClass containingClass) {
239     Set<PsiTypeParameter> params = new LinkedHashSet<PsiTypeParameter>();
240     if (listOwner != null) {
241       Collections.addAll(params, listOwner.getTypeParameters());
242     }
243     Collections.addAll(params, containingClass.getTypeParameters());
244     return params.toArray(new PsiTypeParameter[params.size()]);
245   }
246
247
248   private static PsiSubstitutor inferTypeParametersForStaticFactory(@NotNull PsiMethod staticFactoryMethod,
249                                                                     PsiNewExpression expression,
250                                                                     PsiElement parent) {
251     final JavaPsiFacade facade = JavaPsiFacade.getInstance(staticFactoryMethod.getProject());
252     final PsiResolveHelper resolveHelper = facade.getResolveHelper();
253     final PsiParameter[] parameters = staticFactoryMethod.getParameterList().getParameters();
254     final PsiExpressionList argumentList = expression.getArgumentList();
255     final PsiExpression[] expressions = argumentList.getExpressions();
256     return resolveHelper
257       .inferTypeArguments(staticFactoryMethod.getTypeParameters(), parameters, expressions, PsiSubstitutor.EMPTY, parent, DefaultParameterTypeInferencePolicy.INSTANCE);
258   }
259
260   public static boolean hasDefaultConstructor(@NotNull final PsiClass psiClass) {
261     final PsiMethod[] constructors = psiClass.getConstructors();
262     for (PsiMethod method : constructors) {
263       if (method.getParameterList().getParametersCount() == 0) return true;
264     }
265     return constructors.length == 0;
266   }
267
268   public static boolean haveConstructorsGenericsParameters(@NotNull final PsiClass psiClass) {
269     for (PsiMethod method : psiClass.getConstructors()) {
270       for (PsiParameter parameter : method.getParameterList().getParameters()) {
271         final PsiType type = parameter.getType();
272         final Boolean accept = type.accept(new PsiTypeVisitor<Boolean>() {
273           @Override
274           public Boolean visitArrayType(PsiArrayType arrayType) {
275             return arrayType.getComponentType().accept(this);
276           }
277
278           @Override
279           public Boolean visitClassType(PsiClassType classType) {
280             for (PsiType psiType : classType.getParameters()) {
281               if (psiType != null) {
282                 final Boolean typaParamFound = psiType.accept(this);
283                 if (typaParamFound != null && typaParamFound) return true;
284               }
285             }
286             return PsiUtil.resolveClassInType(classType) instanceof PsiTypeParameter;
287           }
288
289           @Override
290           public Boolean visitWildcardType(PsiWildcardType wildcardType) {
291             final PsiType bound = wildcardType.getBound();
292             if (bound == null) return false;
293             return bound.accept(this);
294           }
295         });
296         if (accept != null && accept.booleanValue()) return true;
297       }
298     }
299     return false;
300   }
301 }