IDEADEV-40882 add static imports when needed
[idea/community.git] / plugins / IntentionPowerPak / src / com / siyeh / ipp / psiutils / ImportUtils.java
1 /*
2  * Copyright 2003-2009 Dave Griffith, Bas Leijdekkers
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.siyeh.ipp.psiutils;
17
18 import com.intellij.psi.*;
19 import com.intellij.psi.codeStyle.CodeStyleSettings;
20 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
21 import com.intellij.psi.search.GlobalSearchScope;
22 import com.intellij.psi.util.ClassUtil;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.util.IncorrectOperationException;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28
29 import java.util.ArrayList;
30 import java.util.List;
31
32 public class ImportUtils{
33
34     private ImportUtils(){
35     }
36
37     public static boolean nameCanBeImported(@NotNull String fqName,
38                                             @NotNull PsiElement context){
39         final PsiClass containingClass = PsiTreeUtil.getParentOfType(context, PsiClass.class);
40         if (containingClass != null) {
41             if (fqName.equals(containingClass.getQualifiedName())) {
42                 return true;
43             }
44             final String shortName = ClassUtil.extractClassName(fqName);
45             final PsiClass[] innerClasses = containingClass.getAllInnerClasses();
46             for (PsiClass innerClass : innerClasses) {
47                 if (innerClass.hasModifierProperty(PsiModifier.PRIVATE)) {
48                     continue;
49                 }
50                 if (innerClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
51                     if (!inSamePackage(innerClass, containingClass)) {
52                         continue;
53                     }
54                 }
55                 final String className = innerClass.getName();
56                 if (shortName.equals(className)) {
57                     return false;
58                 }
59             }
60         }
61         final PsiJavaFile file =
62                 PsiTreeUtil.getParentOfType(context, PsiJavaFile.class);
63         if (file == null) {
64             return false;
65         }
66         if(hasExactImportConflict(fqName, file)){
67             return false;
68         }
69         if(hasOnDemandImportConflict(fqName, file, true)){
70             return false;
71         }
72         if(containsConflictingClass(fqName, file)){
73             return false;
74         }
75         return !containsConflictingClassName(fqName, file);
76     }
77
78     public static boolean inSamePackage(@Nullable PsiElement element1,
79                                         @Nullable PsiElement element2) {
80         if (element1 == null || element2==null) {
81             return false;
82         }
83         final PsiFile containingFile1 = element1.getContainingFile();
84         if (!(containingFile1 instanceof PsiClassOwner)) {
85             return false;
86         }
87         final PsiClassOwner containingJavaFile1 =
88                 (PsiClassOwner)containingFile1;
89         final String packageName1 = containingJavaFile1.getPackageName();
90         final PsiFile containingFile2 = element2.getContainingFile();
91         if (!(containingFile2 instanceof PsiClassOwner)) {
92             return false;
93         }
94         final PsiClassOwner containingJavaFile2 =
95                 (PsiClassOwner)containingFile2;
96         final String packageName2 = containingJavaFile2.getPackageName();
97         return packageName1.equals(packageName2);
98     }
99
100     private static boolean containsConflictingClassName(String fqName,
101                                                         PsiJavaFile file){
102         final int lastDotIndex = fqName.lastIndexOf((int) '.');
103         final String shortName = fqName.substring(lastDotIndex + 1);
104         final PsiClass[] classes = file.getClasses();
105         for(PsiClass aClass : classes){
106             if(shortName.equals(aClass.getName())){
107                 return true;
108             }
109         }
110         return false;
111     }
112
113     private static boolean hasExactImportConflict(String fqName,
114                                                   PsiJavaFile file){
115         final PsiImportList imports = file.getImportList();
116         if(imports == null){
117             return false;
118         }
119         final PsiImportStatement[] importStatements = imports
120                 .getImportStatements();
121         final int lastDotIndex = fqName.lastIndexOf((int) '.');
122         final String shortName = fqName.substring(lastDotIndex + 1);
123         final String dottedShortName = '.' + shortName;
124         for(final PsiImportStatement importStatement : importStatements){
125             if(!importStatement.isOnDemand()){
126                 final String importName = importStatement.getQualifiedName();
127                 if (importName ==  null){
128                     return false;
129                 }
130                 if(!importName.equals(fqName)){
131                     if(importName.endsWith(dottedShortName)){
132                         return true;
133                     }
134                 }
135             }
136         }
137         return false;
138     }
139
140     public static boolean hasOnDemandImportConflict(@NotNull String fqName,
141                                                     @NotNull PsiJavaFile file){
142         return hasOnDemandImportConflict(fqName, file, false);
143     }
144
145     /**
146      * @param strict  if strict is true this method checks if the conflicting
147      * class which is imported is actually used in the file. If it isn't the
148      * on demand import can be overridden with an exact import for the fqName
149      * without breaking stuff.
150      */
151     private static boolean hasOnDemandImportConflict(@NotNull String fqName,
152                                                      @NotNull PsiJavaFile file,
153                                                      boolean strict) {
154         final PsiImportList imports = file.getImportList();
155         if(imports == null){
156             return false;
157         }
158         final PsiImportStatement[] importStatements =
159                 imports.getImportStatements();
160         final String shortName = ClassUtil.extractClassName(fqName);
161         final String packageName = ClassUtil.extractPackageName(fqName);
162         for(final PsiImportStatement importStatement : importStatements){
163             if (!importStatement.isOnDemand()) {
164                 continue;
165             }
166             final PsiJavaCodeReferenceElement importReference =
167                     importStatement.getImportReference();
168             if(importReference == null){
169                 continue;
170             }
171             final String packageText = importReference.getText();
172             if(packageText.equals(packageName)){
173                 continue;
174             }
175             final PsiElement element = importReference.resolve();
176             if (element == null || !(element instanceof PsiPackage)) {
177                 continue;
178             }
179             final PsiPackage aPackage = (PsiPackage) element;
180             final PsiClass[] classes = aPackage.getClasses();
181             for(final PsiClass aClass : classes){
182                 final String className = aClass.getName();
183                 if (!shortName.equals(className)) {
184                     continue;
185                 }
186                 if (!strict) {
187                     return true;
188                 }
189                 final String qualifiedClassName = aClass.getQualifiedName();
190                 final ClassReferenceVisitor visitor =
191                         new ClassReferenceVisitor(qualifiedClassName);
192                 file.accept(visitor);
193                 return visitor.isReferenceFound();
194             }
195         }
196         return hasJavaLangImportConflict(fqName, file);
197     }
198
199     public static boolean hasDefaultImportConflict(String fqName,
200                                                    PsiJavaFile file) {
201         final String shortName = ClassUtil.extractClassName(fqName);
202         final String packageName = ClassUtil.extractPackageName(fqName);
203         final String filePackageName = file.getPackageName();
204         if (filePackageName.equals(packageName)) {
205             return false;
206         }
207         final Project project = file.getProject();
208         final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
209         final PsiPackage filePackage =
210                 psiFacade.findPackage(filePackageName);
211         if (filePackage == null) {
212             return false;
213         }
214         final PsiClass[] classes = filePackage.getClasses();
215         for (PsiClass aClass : classes) {
216             final String className = aClass.getName();
217             if(shortName.equals(className)){
218                 return true;
219             }
220         }
221         return false;
222     }
223
224     public static boolean hasJavaLangImportConflict(String fqName,
225                                                     PsiJavaFile file) {
226         final String shortName = ClassUtil.extractClassName(fqName);
227         final String packageName = ClassUtil.extractPackageName(fqName);
228         if ("java.lang".equals(packageName)) {
229             return false;
230         }
231         final Project project = file.getProject();
232         final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
233         final PsiPackage javaLangPackage = psiFacade.findPackage("java.lang");
234         if(javaLangPackage == null){
235             return false;
236         }
237         final PsiClass[] classes = javaLangPackage.getClasses();
238         for(final PsiClass aClass : classes){
239             final String className = aClass.getName();
240             if(shortName.equals(className)){
241                 return true;
242             }
243         }
244         return false;
245     }
246
247     private static boolean containsConflictingClass(String fqName,
248                                                     PsiJavaFile file){
249         final PsiClass[] classes = file.getClasses();
250         for(PsiClass aClass : classes){
251             if (containsConflictingInnerClass(fqName, aClass)) {
252                 return true;
253             }
254         }
255         //return false;
256         final ClassReferenceVisitor visitor =
257                 new ClassReferenceVisitor(fqName);
258         file.accept(visitor);
259         return visitor.isReferenceFound();
260     }
261
262     /**
263      * ImportUtils currently checks all inner classes, even those that are
264      * contained in inner classes themselves, because it doesn't know the
265      * location of the original fully qualified reference. It should really only
266      * check if the containing class of the fully qualified reference has any
267      * conflicting inner classes.
268      */
269     private static boolean containsConflictingInnerClass(String fqName,
270                                                          PsiClass aClass){
271         final String shortName = ClassUtil.extractClassName(fqName);
272         if(shortName.equals(aClass.getName())){
273             if(!fqName.equals(aClass.getQualifiedName())){
274                 return true;
275             }
276         }
277         final PsiClass[] classes = aClass.getInnerClasses();
278         for (PsiClass innerClass : classes) {
279             if (containsConflictingInnerClass(fqName, innerClass)) {
280                 return true;
281             }
282         }
283         return false;
284     }
285
286     public static void addStaticImport(PsiElement context, String qualifierClass, String memberName)
287             throws IncorrectOperationException {
288         final PsiFile psiFile = context.getContainingFile();
289         if (!(psiFile instanceof PsiJavaFile)) {
290             return;
291         }
292         final Project project = context.getProject();
293         final GlobalSearchScope scope = context.getResolveScope();
294         final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
295         final PsiClass aClass = psiFacade.findClass(qualifierClass, scope);
296         if (aClass == null) {
297             return;
298         }
299         final PsiJavaFile javaFile = (PsiJavaFile)psiFile;
300         final PsiImportList importList = javaFile.getImportList();
301         if (importList == null) {
302             return;
303         }
304         final String qualifiedName  = aClass.getQualifiedName();
305         if (qualifiedName == null) {
306             return;
307         }
308         final List<PsiJavaCodeReferenceElement> imports =
309                 getImportsFromClass(importList, qualifiedName);
310         final CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(project);
311         final PsiElementFactory elementFactory = psiFacade.getElementFactory();
312         if (imports.size() < codeStyleSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND) {
313             importList.add(elementFactory.createImportStaticStatement(aClass, memberName));
314         } else {
315             for (PsiJavaCodeReferenceElement ref : imports) {
316                 final PsiImportStaticStatement importStatement =
317                         PsiTreeUtil.getParentOfType(ref, PsiImportStaticStatement.class);
318                 if (importStatement != null) {
319                     importStatement.delete();
320                 }
321             }
322             importList.add(elementFactory.createImportStaticStatement(aClass, "*"));
323         }
324     }
325
326     private static List<PsiJavaCodeReferenceElement> getImportsFromClass(
327             @NotNull PsiImportList importList, @NotNull String className){
328         final List<PsiJavaCodeReferenceElement> imports =
329                 new ArrayList<PsiJavaCodeReferenceElement>();
330         for (PsiImportStaticStatement staticStatement : importList.getImportStaticStatements()) {
331             final PsiClass psiClass = staticStatement.resolveTargetClass();
332             if (psiClass == null) {
333                 continue;
334             }
335             if (!className.equals(psiClass.getQualifiedName())) {
336                 continue;
337             }
338             imports.add(staticStatement.getImportReference());
339         }
340         return imports;
341     }
342
343     private static class ClassReferenceVisitor
344             extends JavaRecursiveElementVisitor{
345
346         private final String m_name;
347         private final String fullyQualifiedName;
348         private boolean m_referenceFound = false;
349
350         private ClassReferenceVisitor(String fullyQualifiedName) {
351             super();
352             m_name = ClassUtil.extractClassName(fullyQualifiedName);
353             this.fullyQualifiedName = fullyQualifiedName;
354         }
355
356         @Override public void visitReferenceElement(
357                 PsiJavaCodeReferenceElement reference) {
358             super.visitReferenceElement(reference);
359             if (m_referenceFound) {
360                 return;
361             }
362             final String text = reference.getText();
363             if (text.indexOf((int)'.') >= 0 || !m_name.equals(text)) {
364                 return;
365             }
366             final PsiElement element = reference.resolve();
367             if (!(element instanceof PsiClass)
368                 || element instanceof PsiTypeParameter) {
369                 return;
370             }
371             final PsiClass aClass = (PsiClass) element;
372             final String testClassName = aClass.getName();
373             final String testClassQualifiedName = aClass.getQualifiedName();
374             if (testClassQualifiedName == null || testClassName == null
375                 || testClassQualifiedName.equals(fullyQualifiedName) ||
376                 !testClassName.equals(m_name)) {
377                 return;
378             }
379             m_referenceFound = true;
380         }
381
382         public boolean isReferenceFound(){
383             return m_referenceFound;
384         }
385     }
386 }