Merge branch 'master' of git@git.labs.intellij.net:idea/community
[idea/community.git] / java / java-impl / src / com / intellij / psi / impl / source / codeStyle / ImportHelper.java
1 /*
2  * Copyright 2000-2009 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.impl.source.codeStyle;
17
18 import com.intellij.lang.ASTNode;
19 import com.intellij.lang.StdLanguages;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.fileTypes.StdFileTypes;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.psi.*;
25 import com.intellij.psi.codeStyle.CodeStyleManager;
26 import com.intellij.psi.codeStyle.CodeStyleSettings;
27 import com.intellij.psi.codeStyle.PackageEntry;
28 import com.intellij.psi.codeStyle.PackageEntryTable;
29 import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
30 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
31 import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement;
32 import com.intellij.psi.impl.source.resolve.ResolveClassUtil;
33 import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference;
34 import com.intellij.psi.jsp.JspFile;
35 import com.intellij.psi.jsp.JspSpiUtil;
36 import com.intellij.psi.search.GlobalSearchScope;
37 import com.intellij.psi.search.LocalSearchScope;
38 import com.intellij.psi.search.searches.ReferencesSearch;
39 import com.intellij.util.ArrayUtil;
40 import com.intellij.util.IncorrectOperationException;
41 import gnu.trove.THashSet;
42 import gnu.trove.TObjectIntHashMap;
43 import gnu.trove.TObjectIntProcedure;
44 import org.jetbrains.annotations.NonNls;
45 import org.jetbrains.annotations.NotNull;
46
47 import java.util.*;
48
49 public class ImportHelper{
50   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.ImportHelper");
51
52   private final CodeStyleSettings mySettings;
53   @NonNls private static final String JAVA_LANG_PACKAGE = "java.lang";
54
55   public ImportHelper(@NotNull CodeStyleSettings settings){
56     mySettings = settings;
57   }
58
59   public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) {
60     // Note: this array may contain "<packageOrClassName>.*" for unresolved imports!
61     List<Pair<String, Boolean>> names = new ArrayList<Pair<String, Boolean>>(collectNamesToImport(file));
62     Collections.sort(names, new Comparator<Pair<String, Boolean>>() {
63       public int compare(Pair<String, Boolean> o1, Pair<String, Boolean> o2) {
64         return o1.getFirst().compareTo(o2.getFirst());
65       }
66     });
67
68     int[] entryForName = ArrayUtil.newIntArray(names.size());
69     PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries();
70     for(int i = 0; i < names.size(); i++){
71       Pair<String, Boolean> pair = names.get(i);
72       String packageName = pair.getFirst();
73       Boolean isStatic = pair.getSecond();
74       entryForName[i] = findEntryIndex(packageName, isStatic, entries);
75     }
76
77     List<Pair<String, Boolean>> resultList = new ArrayList<Pair<String, Boolean>>(names.size());
78     for(int i = 0; i < entries.length; i++){
79       for(int j = 0; j < names.size(); j++){
80         if (entryForName[j] == i){
81           resultList.add(names.get(j));
82           names.set(j, null);
83         }
84       }
85     }
86     for (Pair<String, Boolean> name : names) {
87       if (name != null) resultList.add(name);
88     }
89
90     TObjectIntHashMap<String> packageToCountMap = new TObjectIntHashMap<String>();
91     TObjectIntHashMap<String> classToCountMap = new TObjectIntHashMap<String>();
92     for (Pair<String, Boolean> pair : resultList) {
93       String name = pair.getFirst();
94       Boolean isStatic = pair.getSecond();
95       String packageOrClassName = getPackageOrClassName(name);
96       if (packageOrClassName.length() == 0) continue;
97       if (isStatic) {
98         int count = classToCountMap.get(packageOrClassName);
99         classToCountMap.put(packageOrClassName, count + 1);
100       }
101       else {
102         int count = packageToCountMap.get(packageOrClassName);
103         packageToCountMap.put(packageOrClassName, count + 1);
104       }
105     }
106
107     final Set<String> classesOrPackagesToImportOnDemand = new THashSet<String>();
108     class MyVisitorProcedure implements TObjectIntProcedure<String> {
109       private final boolean myIsVisitingPackages;
110
111       MyVisitorProcedure(boolean isVisitingPackages) {
112         myIsVisitingPackages = isVisitingPackages;
113       }
114
115       public boolean execute(final String packageOrClassName, final int count) {
116         if (isToUseImportOnDemand(packageOrClassName, count, !myIsVisitingPackages)){
117           classesOrPackagesToImportOnDemand.add(packageOrClassName);
118         }
119         return true;
120       }
121     }
122     classToCountMap.forEachEntry(new MyVisitorProcedure(false));
123     packageToCountMap.forEachEntry(new MyVisitorProcedure(true));
124
125     Set<String> classesToUseSingle = findSingleImports(file, resultList, classesOrPackagesToImportOnDemand);
126
127     try {
128       StringBuilder text = buildImportListText(resultList, classesOrPackagesToImportOnDemand, classesToUseSingle);
129       String ext = StdFileTypes.JAVA.getDefaultExtension();
130       PsiFileFactory factory = PsiFileFactory.getInstance(file.getProject());
131       final PsiJavaFile dummyFile = (PsiJavaFile)factory.createFileFromText("_Dummy_." + ext, StdFileTypes.JAVA, text);
132       CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(file.getProject());
133       codeStyleManager.reformat(dummyFile);
134
135       PsiImportList result = dummyFile.getImportList();
136       PsiImportList oldList = file.getImportList();
137       if (oldList.isReplaceEquivalent(result)) return null;
138       return result;
139     }
140     catch(IncorrectOperationException e) {
141       LOG.error(e);
142       return null;
143     }
144   }
145
146   @NotNull
147   private static Set<String> findSingleImports(@NotNull final PsiJavaFile file,
148                                                @NotNull List<Pair<String,Boolean>> names,
149                                                @NotNull final Set<String> onDemandImports
150                                                ) {
151     final GlobalSearchScope resolveScope = file.getResolveScope();
152     Set<String> namesToUseSingle = new THashSet<String>();
153     final String thisPackageName = file.getPackageName();
154     final Set<String> implicitlyImportedPackages = new THashSet<String>(Arrays.asList(file.getImplicitlyImportedPackages()));
155     final PsiManager manager = file.getManager();
156     for (Pair<String, Boolean> pair : names) {
157       String name = pair.getFirst();
158       Boolean isStatic = pair.getSecond();
159       String prefix = getPackageOrClassName(name);
160       if (prefix.length() == 0) continue;
161       final boolean isImplicitlyImported = implicitlyImportedPackages.contains(prefix);
162       if (!onDemandImports.contains(prefix) && !isImplicitlyImported) continue;
163       String shortName = PsiNameHelper.getShortClassName(name);
164
165       String thisPackageClass = thisPackageName.length() > 0 ? thisPackageName + "." + shortName : shortName;
166       if (JavaPsiFacade.getInstance(manager.getProject()).findClass(thisPackageClass, resolveScope) != null) {
167         namesToUseSingle.add(name);
168         continue;
169       }
170       if (!isImplicitlyImported) {
171         String langPackageClass = JAVA_LANG_PACKAGE + "." + shortName; //TODO : JSP!
172         if (JavaPsiFacade.getInstance(manager.getProject()).findClass(langPackageClass, resolveScope) != null) {
173           namesToUseSingle.add(name);
174           continue;
175         }
176       }
177       for (String onDemandName : onDemandImports) {
178         if (prefix.equals(onDemandName)) continue;
179         if (isStatic) {
180           PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(onDemandName, resolveScope);
181           if (aClass != null) {
182             PsiField field = aClass.findFieldByName(shortName, true);
183             if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
184               namesToUseSingle.add(name);
185             }
186             else {
187               PsiClass inner = aClass.findInnerClassByName(shortName, true);
188               if (inner != null && inner.hasModifierProperty(PsiModifier.STATIC)) {
189                 namesToUseSingle.add(name);
190               }
191               else {
192                 PsiMethod[] methods = aClass.findMethodsByName(shortName, true);
193                 for (PsiMethod method : methods) {
194                   if (method.hasModifierProperty(PsiModifier.STATIC)) {
195                     namesToUseSingle.add(name);
196                   }
197                 }
198               }
199             }
200           }
201         }
202         else {
203           PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(onDemandName + "." + shortName, resolveScope);
204           if (aClass != null) {
205             namesToUseSingle.add(name);
206           }
207         }
208       }
209     }
210     return namesToUseSingle;
211   }
212
213   @NotNull
214   private static StringBuilder buildImportListText(@NotNull List<Pair<String, Boolean>> names,
215                                                    @NotNull final Set<String> packagesOrClassesToImportOnDemand,
216                                                    @NotNull final Set<String> namesToUseSingle) {
217     final Set<String> importedPackagesOrClasses = new THashSet<String>();
218     @NonNls final StringBuilder buffer = new StringBuilder();
219     for (Pair<String, Boolean> pair : names) {
220       String name = pair.getFirst();
221       Boolean isStatic = pair.getSecond();
222       String packageOrClassName = getPackageOrClassName(name);
223       final boolean implicitlyImported = JAVA_LANG_PACKAGE.equals(packageOrClassName);
224       boolean useOnDemand = implicitlyImported || packagesOrClassesToImportOnDemand.contains(packageOrClassName);
225       if (useOnDemand && namesToUseSingle.contains(name)) {
226         useOnDemand = false;
227       }
228       if (useOnDemand && (importedPackagesOrClasses.contains(packageOrClassName) || implicitlyImported)) continue;
229       buffer.append("import ");
230       if (isStatic) buffer.append("static ");
231       if (useOnDemand) {
232         importedPackagesOrClasses.add(packageOrClassName);
233         buffer.append(packageOrClassName);
234         buffer.append(".*");
235       }
236       else {
237         buffer.append(name);
238       }
239       buffer.append(";\n");
240     }
241
242     return buffer;
243   }
244
245   /**
246    * Adds import if it is needed.
247    * @return false when the FQ-name have to be used in code (e.g. when conflicting imports already exist)
248    */
249   public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass){
250     final JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject());
251     PsiElementFactory factory = facade.getElementFactory();
252     PsiResolveHelper helper = facade.getResolveHelper();
253
254     String className = refClass.getQualifiedName();
255     if (className == null) return true;
256     String packageName = getPackageOrClassName(className);
257     String shortName = PsiNameHelper.getShortClassName(className);
258
259     PsiClass conflictSingleRef = findSingleImportByShortName(file, shortName);
260     if (conflictSingleRef != null){
261       return className.equals(conflictSingleRef.getQualifiedName());
262     }
263
264     PsiClass curRefClass = helper.resolveReferencedClass(shortName, file);
265     if (file.getManager().areElementsEquivalent(refClass, curRefClass)) {
266       return true;
267     }
268
269     boolean useOnDemand = true;
270     if (packageName.length() == 0){
271       useOnDemand = false;
272     }
273
274     PsiElement conflictPackageRef = findImportOnDemand(file, packageName);
275     if (conflictPackageRef != null) {
276       useOnDemand = false;
277     }
278
279     List<PsiElement> classesToReimport = new ArrayList<PsiElement>();
280
281     List<PsiJavaCodeReferenceElement> importRefs = getImportsFromPackage(file, packageName);
282     if (useOnDemand){
283       if (mySettings.USE_SINGLE_CLASS_IMPORTS &&
284           importRefs.size() + 1 < mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND &&
285           !mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.contains(packageName)) {
286         useOnDemand = false;
287       }
288       // name of class we try to import is the same as of the class defined in this file
289       if (curRefClass != null) {
290         useOnDemand = true;
291       }
292       // check conflicts
293       if (useOnDemand){
294         PsiElement[] onDemandRefs = file.getOnDemandImports(false, true);
295         if (onDemandRefs.length > 0){
296           PsiPackage aPackage = facade.findPackage(packageName);
297           if (aPackage != null){
298             PsiDirectory[] dirs = aPackage.getDirectories();
299             for (PsiDirectory dir : dirs) {
300               PsiFile[] files = dir.getFiles(); // do not iterate classes - too slow when not loaded
301               for (PsiFile aFile : files) {
302                 if (aFile instanceof PsiJavaFile) {
303                   String name = aFile.getVirtualFile().getNameWithoutExtension();
304                   for (PsiElement ref : onDemandRefs) {
305                     String refName = ref instanceof PsiClass ? ((PsiClass)ref).getQualifiedName() : ((PsiPackage)ref).getQualifiedName();
306                     String conflictClassName = refName + "." + name;
307                     GlobalSearchScope resolveScope = file.getResolveScope();
308                     PsiClass conflictClass = facade.findClass(conflictClassName, resolveScope);
309                     if (conflictClass != null && helper.isAccessible(conflictClass, file, null)) {
310                       String conflictClassName2 = aPackage.getQualifiedName() + "." + name;
311                       PsiClass conflictClass2 = facade.findClass(conflictClassName2, resolveScope);
312                       if (conflictClass2 != null && helper.isAccessible(conflictClass2, file, null)) {
313                         if (ReferencesSearch.search(conflictClass, new LocalSearchScope(file), false).findFirst()  != null) {
314                         classesToReimport.add(conflictClass);
315                         }
316                       }
317                     }
318                   }
319                 }
320               }
321             }
322           }
323         }
324       }
325     }
326
327     try{
328       PsiImportList importList = file.getImportList();
329       PsiImportStatement statement;
330       if (useOnDemand) {
331         statement = factory.createImportStatementOnDemand(packageName);
332       }
333       else {
334         statement = factory.createImportStatement(refClass);
335       }
336       importList.add(statement);
337       if (useOnDemand) {
338         for (PsiJavaCodeReferenceElement ref : importRefs) {
339           LOG.assertTrue(ref.getParent() instanceof PsiImportStatement);
340           if (!ref.isValid()) continue; // todo[dsl] Q?
341           classesToReimport.add(ref.resolve());
342           PsiImportStatement importStatement = (PsiImportStatement) ref.getParent();
343           importStatement.delete();
344         }
345       }
346
347       for (PsiElement aClassesToReimport : classesToReimport) {
348         PsiClass aClass = (PsiClass)aClassesToReimport;
349         if (aClass != null) {
350           addImport(file, aClass);
351         }
352       }
353     }
354     catch(IncorrectOperationException e){
355       LOG.error(e);
356     }
357     return true;
358   }
359
360   @NotNull
361   private static List<PsiJavaCodeReferenceElement> getImportsFromPackage(@NotNull PsiJavaFile file, @NotNull String packageName){
362     PsiClass[] refs = file.getSingleClassImports(true);
363     List<PsiJavaCodeReferenceElement> array = new ArrayList<PsiJavaCodeReferenceElement>();
364     for (PsiClass ref1 : refs) {
365       String className = ref1.getQualifiedName();
366       if (getPackageOrClassName(className).equals(packageName)) {
367         final PsiJavaCodeReferenceElement ref = file.findImportReferenceTo(ref1);
368         if (ref != null) {
369           array.add(ref);
370         }
371       }
372     }
373     return array;
374   }
375
376   private static PsiClass findSingleImportByShortName(@NotNull PsiJavaFile file, @NotNull String shortClassName){
377     PsiClass[] refs = file.getSingleClassImports(true);
378     for (PsiClass ref : refs) {
379       String className = ref.getQualifiedName();
380       if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) {
381         return ref;
382       }
383     }
384     for (PsiClass aClass : file.getClasses()) {
385       String className = aClass.getQualifiedName();
386       if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) {
387         return aClass;
388       }
389     }
390     return null;
391   }
392
393   private static PsiPackage findImportOnDemand(@NotNull PsiJavaFile file, @NotNull String packageName){
394     PsiElement[] refs = file.getOnDemandImports(false, true);
395     for (PsiElement ref : refs) {
396       if (ref instanceof PsiPackage && ((PsiPackage)ref).getQualifiedName().equals(packageName)) {
397         return (PsiPackage)ref;
398       }
399     }
400     return null;
401   }
402
403   public ASTNode getDefaultAnchor(@NotNull PsiImportList list, @NotNull PsiImportStatementBase statement){
404     PsiJavaCodeReferenceElement ref = statement.getImportReference();
405     if (ref == null) return null;
406
407     int entryIndex = findEntryIndex(statement);
408     PsiImportStatementBase[] allStatements = list.getAllImportStatements();
409     int[] entries = ArrayUtil.newIntArray(allStatements.length);
410     List<PsiImportStatementBase> statements = new ArrayList<PsiImportStatementBase>();
411     for(int i = 0; i < allStatements.length; i++){
412       PsiImportStatementBase statement1 = allStatements[i];
413       int entryIndex1 = findEntryIndex(statement1);
414       entries[i] = entryIndex1;
415       if (entryIndex1 == entryIndex){
416         statements.add(statement1);
417       }
418     }
419
420     if (statements.isEmpty()){
421       int index;
422       for(index = entries.length - 1; index >= 0; index--){
423         if (entries[index] < entryIndex) break;
424       }
425       index++;
426       return index < entries.length ? SourceTreeToPsiMap.psiElementToTree(allStatements[index]) : null;
427     }
428     else {
429       String refText = ref.getCanonicalText();
430       if (statement.isOnDemand()){
431         refText += ".";
432       }
433
434       PsiImportStatementBase insertBefore = null;
435       PsiImportStatementBase insertAfter = null;
436       for (PsiImportStatementBase statement1 : statements) {
437         PsiJavaCodeReferenceElement ref1 = statement1.getImportReference();
438         if (ref1 == null) {
439           continue;
440         }
441         String refTextThis = ref1.getCanonicalText();
442         if (statement1.isOnDemand()) {
443           refTextThis += ".";
444         }
445
446         int comp = Comparing.compare(refText, refTextThis);
447         if (comp < 0 && insertBefore == null) {
448           insertBefore = statement1;
449         }
450         if (comp > 0) {
451           insertAfter = statement1;
452         }
453       }
454
455       if (insertBefore != null) return insertBefore.getNode();
456       if (insertAfter != null) return insertAfter.getNode().getTreeNext();
457       return null;
458     }
459   }
460
461   public int getEmptyLinesBetween(@NotNull PsiImportStatementBase statement1, @NotNull PsiImportStatementBase statement2){
462     int index1 = findEntryIndex(statement1);
463     int index2 = findEntryIndex(statement2);
464     if (index1 == index2) return 0;
465     if (index1 > index2) {
466       int t = index1;
467       index1 = index2;
468       index2 = t;
469     }
470     PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries();
471     int maxSpace = 0;
472     for(int i = index1 + 1; i < index2; i++){
473       if (entries[i] == PackageEntry.BLANK_LINE_ENTRY){
474         int space = 0;
475         do{
476           space++;
477         } while(entries[++i] == PackageEntry.BLANK_LINE_ENTRY);
478         maxSpace = Math.max(maxSpace, space);
479       }
480     }
481     return maxSpace;
482   }
483
484   private boolean isToUseImportOnDemand(@NotNull String packageName, int classCount, boolean isStaticImportNeeded){
485     if (!mySettings.USE_SINGLE_CLASS_IMPORTS) return true;
486     int limitCount = isStaticImportNeeded ? mySettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND :
487                      mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND;
488     if (classCount >= limitCount) return true;
489     if (packageName.length() == 0) return false;
490     PackageEntryTable table = mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
491     return table != null && table.contains(packageName);
492   }
493
494   private static int findEntryIndex(@NotNull String packageName, boolean isStatic, @NotNull PackageEntry[] entries) {
495     PackageEntry bestEntry = null;
496     int bestEntryIndex = -1;
497     int allOtherStaticIndex = -1;
498     int allOtherIndex = -1;
499     for(int i = 0; i < entries.length; i++){
500       PackageEntry entry = entries[i];
501       if (entry == PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY) {
502         allOtherStaticIndex = i;
503       }
504       if (entry == PackageEntry.ALL_OTHER_IMPORTS_ENTRY) {
505         allOtherIndex = i;
506       }
507       if (entry.isBetterMatchForPackageThan(bestEntry, packageName, isStatic)) {
508         bestEntry = entry;
509         bestEntryIndex = i;
510       }
511     }
512     if (bestEntryIndex == -1 && isStatic && allOtherStaticIndex == -1 && allOtherIndex != -1) {
513       // if no layout for static imports specified, put them among all others
514       bestEntryIndex = allOtherIndex;
515     }
516     return bestEntryIndex;
517   }
518
519   public int findEntryIndex(@NotNull PsiImportStatementBase statement){
520     PsiJavaCodeReferenceElement ref = statement.getImportReference();
521     if (ref == null) return -1;
522     String packageName;
523     if (statement.isOnDemand()){
524       packageName = ref.getCanonicalText();
525     }
526     else{
527       String className = ref.getCanonicalText();
528       packageName = getPackageOrClassName(className);
529     }
530     return findEntryIndex(packageName, statement instanceof PsiImportStaticStatement, mySettings.IMPORT_LAYOUT_TABLE.getEntries());
531   }
532
533   @NotNull
534   // returns list of (name, isImportStatic) pairs
535   private static Collection<Pair<String,Boolean>> collectNamesToImport(@NotNull PsiJavaFile file){
536     Set<Pair<String,Boolean>> names = new THashSet<Pair<String,Boolean>>();
537
538     final JspFile jspFile = JspPsiUtil.getJspFile(file);
539     collectNamesToImport(names, file, jspFile);
540     if (jspFile != null) {
541       PsiFile[] files = ArrayUtil.mergeArrays(JspSpiUtil.getIncludingFiles(jspFile), JspSpiUtil.getIncludedFiles(jspFile), PsiFile.class);
542       for (PsiFile includingFile : files) {
543         final PsiFile javaRoot = includingFile.getViewProvider().getPsi(StdLanguages.JAVA);
544         if (javaRoot instanceof PsiJavaFile && file != javaRoot) {
545           collectNamesToImport(names, (PsiJavaFile)javaRoot, jspFile);
546         }
547       }
548     }
549
550     addUnresolvedImportNames(names, file);
551
552     return names;
553   }
554
555   private static void collectNamesToImport(@NotNull final Set<Pair<String, Boolean>> names,
556                                            @NotNull final PsiJavaFile file,
557                                            PsiFile context) {
558     String packageName = file.getPackageName();
559
560     final PsiElement[] roots = file.getPsiRoots();
561     for (PsiElement root : roots) {
562       addNamesToImport(names, root, packageName, context);
563     }
564   }
565
566   private static void addNamesToImport(@NotNull Set<Pair<String, Boolean>> names,
567                                        @NotNull PsiElement scope,
568                                        @NotNull String thisPackageName,
569                                        PsiFile context){
570     if (scope instanceof PsiImportList) return;
571
572     final LinkedList<PsiElement> stack = new LinkedList<PsiElement>();
573     stack.add(scope);
574     while (!stack.isEmpty()) {
575       final PsiElement child = stack.removeFirst();
576       if (child instanceof PsiImportList) continue;
577       stack.addAll(Arrays.asList(child.getChildren()));
578
579       for(final PsiReference reference : child.getReferences()){
580         if (!(reference instanceof PsiJavaReference)) continue;
581         final PsiJavaReference javaReference = (PsiJavaReference)reference;
582         if (javaReference instanceof JavaClassReference && ((JavaClassReference)javaReference).getContextReference() != null) continue;
583         PsiJavaCodeReferenceElement referenceElement = null;
584         if (reference instanceof PsiJavaCodeReferenceElement) {
585           referenceElement = (PsiJavaCodeReferenceElement)child;
586           if (referenceElement.getQualifier() != null) {
587             continue;
588           }
589           if (reference instanceof PsiJavaCodeReferenceElementImpl
590               && ((PsiJavaCodeReferenceElementImpl)reference).getKind() == PsiJavaCodeReferenceElementImpl.CLASS_IN_QUALIFIED_NEW_KIND) {
591             continue;
592           }
593         }
594
595         final JavaResolveResult resolveResult = javaReference.advancedResolve(true);
596         PsiElement refElement = resolveResult.getElement();
597         if (refElement == null && referenceElement != null) {
598           refElement = ResolveClassUtil.resolveClass(referenceElement); // might be uncomplete code
599         }
600         if (refElement == null) continue;
601
602         PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope();
603         if (!(currentFileResolveScope instanceof PsiImportStatementBase)) continue;
604         if (context != null && currentFileResolveScope instanceof JspxImportStatement && context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile()) {
605           continue;
606         }
607
608         if (referenceElement != null) {
609           if (currentFileResolveScope instanceof PsiImportStaticStatement) {
610             PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)currentFileResolveScope;
611             String name = importStaticStatement.getImportReference().getCanonicalText();
612             if (importStaticStatement.isOnDemand()) {
613               String refName = referenceElement.getReferenceName();
614               if (refName != null) name = name + "." + refName;
615             }
616             names.add(Pair.create(name, Boolean.TRUE));
617             continue;
618           }
619         }
620
621         if (refElement instanceof PsiClass) {
622           String qName = ((PsiClass)refElement).getQualifiedName();
623           if (hasPackage(qName, thisPackageName)) continue;
624           names.add(Pair.create(qName, Boolean.FALSE));
625         }
626       }
627     }
628   }
629
630   private static void addUnresolvedImportNames(@NotNull Set<Pair<String, Boolean>> names, @NotNull PsiJavaFile file) {
631     PsiImportStatementBase[] imports = file.getImportList().getAllImportStatements();
632     for (PsiImportStatementBase anImport : imports) {
633       PsiJavaCodeReferenceElement ref = anImport.getImportReference();
634       if (ref == null) continue;
635       JavaResolveResult[] results = ref.multiResolve(false);
636       if (results.length == 0) {
637         String text = ref.getCanonicalText();
638         if (anImport.isOnDemand()) {
639           text += ".*";
640         }
641         names.add(Pair.create(text, anImport instanceof PsiImportStaticStatement));
642       }
643     }
644   }
645
646   public static boolean isImplicitlyImported(@NotNull String className, @NotNull PsiJavaFile file) {
647     String[] packageNames = file.getImplicitlyImportedPackages();
648     for (String packageName : packageNames) {
649       if (hasPackage(className, packageName)) return true;
650     }
651     return false;
652   }
653
654   public static boolean hasPackage(@NotNull String className, @NotNull String packageName){
655     if (!className.startsWith(packageName)) return false;
656     if (className.length() == packageName.length()) return false;
657     if (packageName.length() > 0 && className.charAt(packageName.length()) != '.') return false;
658     return className.indexOf('.', packageName.length() + 1) < 0;
659   }
660
661   @NotNull
662   private static String getPackageOrClassName(@NotNull String className){
663     int dotIndex = className.lastIndexOf('.');
664     return dotIndex < 0 ? "" : className.substring(0, dotIndex);
665   }
666
667 }