blob: 2fda915be0f2ae61c6908ccaab1e3ec3efbd7937 [file] [log] [blame]
/*
* 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.impl.source.codeStyle;
import com.intellij.codeInsight.ImportFilter;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.PackageEntry;
import com.intellij.psi.codeStyle.PackageEntryTable;
import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement;
import com.intellij.psi.impl.source.resolve.ResolveClassUtil;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaJspElementType;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.jsp.JspSpiUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectIntHashMap;
import gnu.trove.TObjectIntProcedure;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class ImportHelper{
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.ImportHelper");
private final CodeStyleSettings mySettings;
@NonNls private static final String JAVA_LANG_PACKAGE = "java.lang";
public ImportHelper(@NotNull CodeStyleSettings settings){
mySettings = settings;
}
public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) {
// Java parser works in a way that comments may be included to the import list, e.g.:
// import a;
// /* comment */
// import b;
// We want to preserve those comments then.
List<PsiElement> nonImports = new ArrayList<PsiElement>();
// Note: this array may contain "<packageOrClassName>.*" for unresolved imports!
List<Pair<String, Boolean>> names = new ArrayList<Pair<String, Boolean>>(collectNamesToImport(file, nonImports));
Collections.sort(names, new Comparator<Pair<String, Boolean>>() {
@Override
public int compare(Pair<String, Boolean> o1, Pair<String, Boolean> o2) {
return o1.getFirst().compareTo(o2.getFirst());
}
});
List<Pair<String, Boolean>> resultList = sortItemsAccordingToSettings(names, mySettings);
final Set<String> classesOrPackagesToImportOnDemand = new THashSet<String>();
collectOnDemandImports(resultList, classesOrPackagesToImportOnDemand, this.mySettings);
Set<String> classesToUseSingle = findSingleImports(file, resultList, classesOrPackagesToImportOnDemand);
Set<String> toReimport = new THashSet<String>();
calcClassesConflictingViaOnDemandImports(file, classesOrPackagesToImportOnDemand, file.getResolveScope(), toReimport);
classesToUseSingle.addAll(toReimport);
try {
StringBuilder text = buildImportListText(resultList, classesOrPackagesToImportOnDemand, classesToUseSingle);
for (PsiElement nonImport : nonImports) {
text.append("\n").append(nonImport.getText());
}
String ext = StdFileTypes.JAVA.getDefaultExtension();
PsiFileFactory factory = PsiFileFactory.getInstance(file.getProject());
final PsiJavaFile dummyFile = (PsiJavaFile)factory.createFileFromText("_Dummy_." + ext, StdFileTypes.JAVA, text);
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(file.getProject());
codeStyleManager.reformat(dummyFile);
PsiImportList newImportList = dummyFile.getImportList();
PsiImportList result = (PsiImportList)newImportList.copy();
PsiImportList oldList = file.getImportList();
if (oldList.isReplaceEquivalent(result)) return null;
if (!nonImports.isEmpty()) {
PsiElement firstPrevious = newImportList.getPrevSibling();
while (firstPrevious != null && firstPrevious.getPrevSibling() != null) {
firstPrevious = firstPrevious.getPrevSibling();
}
for (PsiElement element = firstPrevious; element != null && element != newImportList; element = element.getNextSibling()) {
result.add(element.copy());
}
for (PsiElement element = newImportList.getNextSibling(); element != null; element = element.getNextSibling()) {
result.add(element.copy());
}
}
return result;
}
catch(IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
public static void collectOnDemandImports(List<Pair<String, Boolean>> resultList,
final Set<String> classesOrPackagesToImportOnDemand,
final CodeStyleSettings settings) {
TObjectIntHashMap<String> packageToCountMap = new TObjectIntHashMap<String>();
TObjectIntHashMap<String> classToCountMap = new TObjectIntHashMap<String>();
for (Pair<String, Boolean> pair : resultList) {
String name = pair.getFirst();
Boolean isStatic = pair.getSecond();
String packageOrClassName = getPackageOrClassName(name);
if (packageOrClassName.isEmpty()) continue;
if (isStatic) {
int count = classToCountMap.get(packageOrClassName);
classToCountMap.put(packageOrClassName, count + 1);
}
else {
int count = packageToCountMap.get(packageOrClassName);
packageToCountMap.put(packageOrClassName, count + 1);
}
}
class MyVisitorProcedure implements TObjectIntProcedure<String> {
private final boolean myIsVisitingPackages;
MyVisitorProcedure(boolean isVisitingPackages) {
myIsVisitingPackages = isVisitingPackages;
}
@Override
public boolean execute(final String packageOrClassName, final int count) {
if (isToUseImportOnDemand(packageOrClassName, count, !myIsVisitingPackages, settings)){
classesOrPackagesToImportOnDemand.add(packageOrClassName);
}
return true;
}
}
classToCountMap.forEachEntry(new MyVisitorProcedure(false));
packageToCountMap.forEachEntry(new MyVisitorProcedure(true));
}
public static List<Pair<String, Boolean>> sortItemsAccordingToSettings(List<Pair<String, Boolean>> names, final CodeStyleSettings settings) {
int[] entryForName = ArrayUtil.newIntArray(names.size());
PackageEntry[] entries = settings.IMPORT_LAYOUT_TABLE.getEntries();
for(int i = 0; i < names.size(); i++){
Pair<String, Boolean> pair = names.get(i);
String packageName = pair.getFirst();
Boolean isStatic = pair.getSecond();
entryForName[i] = findEntryIndex(packageName, isStatic, entries);
}
List<Pair<String, Boolean>> resultList = new ArrayList<Pair<String, Boolean>>(names.size());
for(int i = 0; i < entries.length; i++){
for(int j = 0; j < names.size(); j++){
if (entryForName[j] == i){
resultList.add(names.get(j));
names.set(j, null);
}
}
}
for (Pair<String, Boolean> name : names) {
if (name != null) resultList.add(name);
}
return resultList;
}
@NotNull
private static Set<String> findSingleImports(@NotNull final PsiJavaFile file,
@NotNull List<Pair<String,Boolean>> names,
@NotNull final Set<String> onDemandImports
) {
final GlobalSearchScope resolveScope = file.getResolveScope();
Set<String> namesToUseSingle = new THashSet<String>();
final String thisPackageName = file.getPackageName();
final Set<String> implicitlyImportedPackages = new THashSet<String>(Arrays.asList(file.getImplicitlyImportedPackages()));
final PsiManager manager = file.getManager();
JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
List<PsiElement> onDemandElements = new ArrayList<PsiElement>(onDemandImports.size());
List<String> onDemandImportsList = new ArrayList<String>(onDemandImports);
for (String onDemandName : onDemandImportsList) {
PsiElement aClass = facade.findClass(onDemandName, resolveScope);
onDemandElements.add(aClass);
}
for (Pair<String, Boolean> pair : names) {
String name = pair.getFirst();
Boolean isStatic = pair.getSecond();
String prefix = getPackageOrClassName(name);
if (prefix.isEmpty()) continue;
final boolean isImplicitlyImported = implicitlyImportedPackages.contains(prefix);
if (!onDemandImports.contains(prefix) && !isImplicitlyImported) continue;
String shortName = PsiNameHelper.getShortClassName(name);
String thisPackageClass = !thisPackageName.isEmpty() ? thisPackageName + "." + shortName : shortName;
if (JavaPsiFacade.getInstance(manager.getProject()).findClass(thisPackageClass, resolveScope) != null) {
namesToUseSingle.add(name);
continue;
}
if (!isImplicitlyImported) {
String langPackageClass = JAVA_LANG_PACKAGE + "." + shortName; //TODO : JSP!
if (facade.findClass(langPackageClass, resolveScope) != null) {
namesToUseSingle.add(name);
continue;
}
}
for (int i = 0; i < onDemandImportsList.size(); i++) {
String onDemandName = onDemandImportsList.get(i);
if (prefix.equals(onDemandName)) continue;
if (isStatic) {
PsiElement element = onDemandElements.get(i);
PsiClass aClass = (PsiClass)element;
if (aClass != null) {
PsiField field = aClass.findFieldByName(shortName, true);
if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
namesToUseSingle.add(name);
}
else {
PsiClass inner = aClass.findInnerClassByName(shortName, true);
if (inner != null && inner.hasModifierProperty(PsiModifier.STATIC)) {
namesToUseSingle.add(name);
}
else {
PsiMethod[] methods = aClass.findMethodsByName(shortName, true);
for (PsiMethod method : methods) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
namesToUseSingle.add(name);
}
}
}
}
}
}
else {
PsiClass aClass = facade.findClass(onDemandName + "." + shortName, resolveScope);
if (aClass != null) {
namesToUseSingle.add(name);
}
}
}
}
return namesToUseSingle;
}
private static void calcClassesConflictingViaOnDemandImports(PsiJavaFile file, Collection<String> onDemandImportsList,
GlobalSearchScope resolveScope, final Set<String> namesToUseSingle) {
List<String> onDemands = new ArrayList<String>(Arrays.asList(file.getImplicitlyImportedPackages()));
for (String onDemand : onDemandImportsList) {
if (!onDemands.contains(onDemand)) {
onDemands.add(onDemand);
}
}
if (onDemands.size() < 2) return;
Map<String, Set<String>> classNames = new THashMap<String, Set<String>>();
JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject());
for (int i = onDemands.size()-1; i>=0; i--) {
String onDemand = onDemands.get(i);
PsiPackage aPackage = facade.findPackage(onDemand);
if (aPackage == null) {
onDemands.remove(i);
continue;
}
PsiClass[] psiClasses = aPackage.getClasses(resolveScope);
Set<String> set = new THashSet<String>(psiClasses.length);
for (PsiClass psiClass : psiClasses) {
set.add(psiClass.getName());
}
classNames.put(onDemand, set);
}
final Set<String> conflicts = new THashSet<String>();
for (int i = 0; i < onDemands.size(); i++) {
String on1 = onDemands.get(i);
for (int j = i+1; j < onDemands.size(); j++) {
String on2 = onDemands.get(j);
Set<String> inter = new THashSet<String>(classNames.get(on1));
inter.retainAll(classNames.get(on2));
conflicts.addAll(inter);
}
}
if (!conflicts.isEmpty() && !(file instanceof PsiCompiledElement)) {
file.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
if (reference.getQualifier() != null) return;
PsiElement element = reference.resolve();
if (element instanceof PsiClass && conflicts.contains(((PsiClass)element).getName())) {
String fqn = ((PsiClass)element).getQualifiedName();
namesToUseSingle.add(fqn);
}
}
});
}
}
@NotNull
private static StringBuilder buildImportListText(@NotNull List<Pair<String, Boolean>> names,
@NotNull final Set<String> packagesOrClassesToImportOnDemand,
@NotNull final Set<String> namesToUseSingle) {
final Set<Pair<String, Boolean>> importedPackagesOrClasses = new THashSet<Pair<String, Boolean>>();
@NonNls final StringBuilder buffer = new StringBuilder();
for (Pair<String, Boolean> pair : names) {
String name = pair.getFirst();
Boolean isStatic = pair.getSecond();
String packageOrClassName = getPackageOrClassName(name);
final boolean implicitlyImported = JAVA_LANG_PACKAGE.equals(packageOrClassName);
boolean useOnDemand = implicitlyImported || packagesOrClassesToImportOnDemand.contains(packageOrClassName);
if (useOnDemand && namesToUseSingle.remove(name)) {
useOnDemand = false;
}
final Pair<String, Boolean> current = Pair.create(packageOrClassName, isStatic);
if (useOnDemand && (importedPackagesOrClasses.contains(current) || implicitlyImported)) continue;
buffer.append("import ");
if (isStatic) buffer.append("static ");
if (useOnDemand) {
importedPackagesOrClasses.add(current);
buffer.append(packageOrClassName);
buffer.append(".*");
}
else {
buffer.append(name);
}
buffer.append(";\n");
}
for (String remainingSingle : namesToUseSingle) {
buffer.append("import ");
buffer.append(remainingSingle);
buffer.append(";\n");
}
return buffer;
}
/**
* Adds import if it is needed.
* @return false when the FQ-name have to be used in code (e.g. when conflicting imports already exist)
*/
public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass){
final JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject());
PsiElementFactory factory = facade.getElementFactory();
PsiResolveHelper helper = facade.getResolveHelper();
String className = refClass.getQualifiedName();
if (className == null) return true;
if (!ImportFilter.shouldImport(file, className)) {
return false;
}
String packageName = getPackageOrClassName(className);
String shortName = PsiNameHelper.getShortClassName(className);
PsiClass conflictSingleRef = findSingleImportByShortName(file, shortName);
if (conflictSingleRef != null){
return className.equals(conflictSingleRef.getQualifiedName());
}
PsiClass curRefClass = helper.resolveReferencedClass(shortName, file);
if (file.getManager().areElementsEquivalent(refClass, curRefClass)) {
return true;
}
boolean useOnDemand = true;
if (packageName.isEmpty()){
useOnDemand = false;
}
PsiElement conflictPackageRef = findImportOnDemand(file, packageName);
if (conflictPackageRef != null) {
useOnDemand = false;
}
List<PsiClass> classesToReimport = new ArrayList<PsiClass>();
List<PsiJavaCodeReferenceElement> importRefs = getImportsFromPackage(file, packageName);
if (useOnDemand){
if (mySettings.USE_SINGLE_CLASS_IMPORTS &&
importRefs.size() + 1 < mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND &&
!mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.contains(packageName)) {
useOnDemand = false;
}
// name of class we try to import is the same as of the class defined in this file
if (curRefClass != null) {
useOnDemand = true;
}
// check conflicts
if (useOnDemand){
PsiElement[] onDemandRefs = file.getOnDemandImports(false, true);
List<String> refTexts = new ArrayList<String>(onDemandRefs.length);
for (PsiElement ref : onDemandRefs) {
String refName = ref instanceof PsiClass ? ((PsiClass)ref).getQualifiedName() : ((PsiPackage)ref).getQualifiedName();
refTexts.add(refName);
}
calcClassesToReimport(file, facade, helper, packageName, classesToReimport, refTexts);
}
}
if (useOnDemand &&
curRefClass != null &&
refClass.getContainingClass() != null &&
mySettings.INSERT_INNER_CLASS_IMPORTS &&
"java.lang".equals(StringUtil.getPackageName(curRefClass.getQualifiedName()))) {
return false;
}
try {
PsiImportList importList = file.getImportList();
PsiImportStatement statement = useOnDemand ? factory.createImportStatementOnDemand(packageName) : factory.createImportStatement(refClass);
importList.add(statement);
if (useOnDemand) {
for (PsiJavaCodeReferenceElement ref : importRefs) {
LOG.assertTrue(ref.getParent() instanceof PsiImportStatement);
if (!ref.isValid()) continue; // todo[dsl] Q?
PsiImportStatement importStatement = (PsiImportStatement) ref.getParent();
if (importStatement.isForeignFileImport()) {
// we should not optimize imports from include files
continue;
}
classesToReimport.add((PsiClass)ref.resolve());
importStatement.delete();
}
}
for (PsiClass aClass : classesToReimport) {
if (aClass != null) {
addImport(file, aClass);
}
}
}
catch(IncorrectOperationException e){
LOG.error(e);
}
return true;
}
private static void calcClassesToReimport(PsiJavaFile file, JavaPsiFacade facade, PsiResolveHelper helper, String packageName, List<PsiClass> classesToReimport,
Collection<String> onDemandRefs) {
if (onDemandRefs.isEmpty()) {
return;
}
PsiPackage aPackage = facade.findPackage(packageName);
if (aPackage != null) {
PsiDirectory[] dirs = aPackage.getDirectories();
GlobalSearchScope resolveScope = file.getResolveScope();
for (PsiDirectory dir : dirs) {
PsiFile[] files = dir.getFiles(); // do not iterate classes - too slow when not loaded
for (PsiFile aFile : files) {
if (!(aFile instanceof PsiJavaFile)) continue;
String name = aFile.getVirtualFile().getNameWithoutExtension();
for (String refName : onDemandRefs) {
String conflictClassName = refName + "." + name;
PsiClass conflictClass = facade.findClass(conflictClassName, resolveScope);
if (conflictClass == null || !helper.isAccessible(conflictClass, file, null)) continue;
String conflictClassName2 = packageName + "." + name;
PsiClass conflictClass2 = facade.findClass(conflictClassName2, resolveScope);
if (conflictClass2 != null &&
helper.isAccessible(conflictClass2, file, null) &&
ReferencesSearch.search(conflictClass, new LocalSearchScope(file), false).findFirst() != null) {
classesToReimport.add(conflictClass);
}
}
}
}
}
}
@NotNull
private static List<PsiJavaCodeReferenceElement> getImportsFromPackage(@NotNull PsiJavaFile file, @NotNull String packageName){
PsiClass[] refs = file.getSingleClassImports(true);
List<PsiJavaCodeReferenceElement> array = new ArrayList<PsiJavaCodeReferenceElement>();
for (PsiClass ref1 : refs) {
String className = ref1.getQualifiedName();
if (getPackageOrClassName(className).equals(packageName)) {
final PsiJavaCodeReferenceElement ref = file.findImportReferenceTo(ref1);
if (ref != null) {
array.add(ref);
}
}
}
return array;
}
private static PsiClass findSingleImportByShortName(@NotNull final PsiJavaFile file, @NotNull String shortClassName){
PsiClass[] refs = file.getSingleClassImports(true);
for (PsiClass ref : refs) {
String className = ref.getQualifiedName();
if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) {
return ref;
}
}
for (PsiClass aClass : file.getClasses()) {
String className = aClass.getQualifiedName();
if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) {
return aClass;
}
}
// there maybe a class imported implicitly from current package
String packageName = file.getPackageName();
if (!StringUtil.isEmptyOrSpaces(packageName)) {
String fqn = packageName + "." + shortClassName;
final PsiClass aClass = JavaPsiFacade.getInstance(file.getProject()).findClass(fqn, file.getResolveScope());
if (aClass != null) {
final boolean[] foundRef = {false};
// check if that short name referenced in the file
file.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
if (foundRef[0]) return;
super.visitElement(element);
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
if (file.getManager().areElementsEquivalent(reference.resolve(), aClass)) {
foundRef[0] = true;
}
super.visitReferenceElement(reference);
}
});
if (foundRef[0]) return aClass;
}
}
return null;
}
private static PsiPackage findImportOnDemand(@NotNull PsiJavaFile file, @NotNull String packageName){
PsiElement[] refs = file.getOnDemandImports(false, true);
for (PsiElement ref : refs) {
if (ref instanceof PsiPackage && ((PsiPackage)ref).getQualifiedName().equals(packageName)) {
return (PsiPackage)ref;
}
}
return null;
}
public ASTNode getDefaultAnchor(@NotNull PsiImportList list, @NotNull PsiImportStatementBase statement){
PsiJavaCodeReferenceElement ref = statement.getImportReference();
if (ref == null) return null;
int entryIndex = findEntryIndex(statement);
PsiImportStatementBase[] allStatements = list.getAllImportStatements();
int[] entries = ArrayUtil.newIntArray(allStatements.length);
List<PsiImportStatementBase> statements = new ArrayList<PsiImportStatementBase>();
for(int i = 0; i < allStatements.length; i++){
PsiImportStatementBase statement1 = allStatements[i];
int entryIndex1 = findEntryIndex(statement1);
entries[i] = entryIndex1;
if (entryIndex1 == entryIndex){
statements.add(statement1);
}
}
if (statements.isEmpty()){
int index;
for(index = entries.length - 1; index >= 0; index--){
if (entries[index] < entryIndex) break;
}
index++;
return index < entries.length ? SourceTreeToPsiMap.psiElementToTree(allStatements[index]) : null;
}
else {
String refText = ref.getCanonicalText();
if (statement.isOnDemand()){
refText += ".";
}
PsiImportStatementBase insertBefore = null;
PsiImportStatementBase insertAfter = null;
for (PsiImportStatementBase statement1 : statements) {
PsiJavaCodeReferenceElement ref1 = statement1.getImportReference();
if (ref1 == null) {
continue;
}
String refTextThis = ref1.getCanonicalText();
if (statement1.isOnDemand()) {
refTextThis += ".";
}
int comp = Comparing.compare(refText, refTextThis);
if (comp < 0 && insertBefore == null) {
insertBefore = statement1;
}
if (comp > 0) {
insertAfter = statement1;
}
}
if (insertBefore != null) return insertBefore.getNode();
if (insertAfter != null) return insertAfter.getNode().getTreeNext();
return null;
}
}
public int getEmptyLinesBetween(@NotNull PsiImportStatementBase statement1, @NotNull PsiImportStatementBase statement2){
int index1 = findEntryIndex(statement1);
int index2 = findEntryIndex(statement2);
if (index1 == index2) return 0;
if (index1 > index2) {
int t = index1;
index1 = index2;
index2 = t;
}
PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries();
int maxSpace = 0;
for(int i = index1 + 1; i < index2; i++){
if (entries[i] == PackageEntry.BLANK_LINE_ENTRY){
int space = 0;
do{
space++;
} while(entries[++i] == PackageEntry.BLANK_LINE_ENTRY);
maxSpace = Math.max(maxSpace, space);
}
}
return maxSpace;
}
private static boolean isToUseImportOnDemand(@NotNull String packageName,
int classCount,
boolean isStaticImportNeeded,
final CodeStyleSettings settings){
if (!settings.USE_SINGLE_CLASS_IMPORTS) return true;
int limitCount = isStaticImportNeeded ? settings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND :
settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND;
if (classCount >= limitCount) return true;
if (packageName.isEmpty()) return false;
PackageEntryTable table = settings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
return table != null && table.contains(packageName);
}
private static int findEntryIndex(@NotNull String packageName, boolean isStatic, @NotNull PackageEntry[] entries) {
PackageEntry bestEntry = null;
int bestEntryIndex = -1;
int allOtherStaticIndex = -1;
int allOtherIndex = -1;
for(int i = 0; i < entries.length; i++){
PackageEntry entry = entries[i];
if (entry == PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY) {
allOtherStaticIndex = i;
}
if (entry == PackageEntry.ALL_OTHER_IMPORTS_ENTRY) {
allOtherIndex = i;
}
if (entry.isBetterMatchForPackageThan(bestEntry, packageName, isStatic)) {
bestEntry = entry;
bestEntryIndex = i;
}
}
if (bestEntryIndex == -1 && isStatic && allOtherStaticIndex == -1 && allOtherIndex != -1) {
// if no layout for static imports specified, put them among all others
bestEntryIndex = allOtherIndex;
}
return bestEntryIndex;
}
public int findEntryIndex(@NotNull PsiImportStatementBase statement){
PsiJavaCodeReferenceElement ref = statement.getImportReference();
if (ref == null) return -1;
String packageName;
if (statement.isOnDemand()){
packageName = ref.getCanonicalText();
}
else{
String className = ref.getCanonicalText();
packageName = getPackageOrClassName(className);
}
return findEntryIndex(packageName, statement instanceof PsiImportStaticStatement, mySettings.IMPORT_LAYOUT_TABLE.getEntries());
}
@NotNull
// returns list of (name, isImportStatic) pairs
private static Collection<Pair<String,Boolean>> collectNamesToImport(@NotNull PsiJavaFile file, List<PsiElement> comments){
Set<Pair<String,Boolean>> names = new THashSet<Pair<String,Boolean>>();
final JspFile jspFile = JspPsiUtil.getJspFile(file);
collectNamesToImport(names, comments, file, jspFile);
if (jspFile != null) {
PsiFile[] files = ArrayUtil.mergeArrays(JspSpiUtil.getIncludingFiles(jspFile), JspSpiUtil.getIncludedFiles(jspFile));
for (PsiFile includingFile : files) {
final PsiFile javaRoot = includingFile.getViewProvider().getPsi(JavaLanguage.INSTANCE);
if (javaRoot instanceof PsiJavaFile && file != javaRoot) {
collectNamesToImport(names, comments, (PsiJavaFile)javaRoot, jspFile);
}
}
}
addUnresolvedImportNames(names, file);
return names;
}
private static void collectNamesToImport(@NotNull final Set<Pair<String, Boolean>> names,
@NotNull List<PsiElement> comments,
@NotNull final PsiJavaFile file,
PsiFile context) {
String packageName = file.getPackageName();
final List<PsiFile> roots = file.getViewProvider().getAllFiles();
for (PsiElement root : roots) {
addNamesToImport(names, comments, root, packageName, context);
}
}
private static void addNamesToImport(@NotNull Set<Pair<String, Boolean>> names,
@NotNull List<PsiElement> comments,
@NotNull PsiElement scope,
@NotNull String thisPackageName,
PsiFile context){
if (scope instanceof PsiImportList) return;
final LinkedList<PsiElement> stack = new LinkedList<PsiElement>();
stack.add(scope);
while (!stack.isEmpty()) {
final PsiElement child = stack.removeFirst();
if (child instanceof PsiImportList) {
for (PsiElement element : child.getChildren()) {
if (element == null) {
continue;
}
ASTNode node = element.getNode();
if (node == null) {
continue;
}
IElementType elementType = node.getElementType();
if (elementType != null &&!ElementType.IMPORT_STATEMENT_BASE_BIT_SET.contains(elementType)
&& !JavaJspElementType.WHITE_SPACE_BIT_SET.contains(elementType))
{
comments.add(element);
}
}
continue;
}
if (child instanceof PsiLiteralExpression) continue;
ContainerUtil.addAll(stack, child.getChildren());
for (final PsiReference reference : child.getReferences()) {
if (!(reference instanceof PsiJavaReference)) continue;
final PsiJavaReference javaReference = (PsiJavaReference)reference;
if (javaReference instanceof JavaClassReference && ((JavaClassReference)javaReference).getContextReference() != null) continue;
PsiJavaCodeReferenceElement referenceElement = null;
if (reference instanceof PsiJavaCodeReferenceElement) {
referenceElement = (PsiJavaCodeReferenceElement)child;
if (referenceElement.getQualifier() != null) {
continue;
}
if (reference instanceof PsiJavaCodeReferenceElementImpl
&& ((PsiJavaCodeReferenceElementImpl)reference).getKind(((PsiJavaCodeReferenceElementImpl)reference).getContainingFile()) == PsiJavaCodeReferenceElementImpl.CLASS_IN_QUALIFIED_NEW_KIND) {
continue;
}
}
final JavaResolveResult resolveResult = javaReference.advancedResolve(true);
PsiElement refElement = resolveResult.getElement();
if (refElement == null && referenceElement != null) {
refElement = ResolveClassUtil.resolveClass(referenceElement, referenceElement.getContainingFile()); // might be uncomplete code
}
if (refElement == null) continue;
PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope();
if (!(currentFileResolveScope instanceof PsiImportStatementBase)) continue;
if (context != null &&
(!currentFileResolveScope.isValid() ||
currentFileResolveScope instanceof JspxImportStatement &&
context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile())) {
continue;
}
if (referenceElement != null) {
if (currentFileResolveScope instanceof PsiImportStaticStatement) {
PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)currentFileResolveScope;
String name = importStaticStatement.getImportReference().getCanonicalText();
if (importStaticStatement.isOnDemand()) {
String refName = referenceElement.getReferenceName();
if (refName != null) name = name + "." + refName;
}
names.add(Pair.create(name, Boolean.TRUE));
continue;
}
}
if (refElement instanceof PsiClass) {
String qName = ((PsiClass)refElement).getQualifiedName();
if (hasPackage(qName, thisPackageName)) continue;
names.add(Pair.create(qName, Boolean.FALSE));
}
}
}
}
private static void addUnresolvedImportNames(@NotNull final Set<Pair<String, Boolean>> namesToImport, @NotNull PsiJavaFile file) {
final PsiImportList importList = file.getImportList();
PsiImportStatementBase[] imports = importList == null ? PsiImportStatementBase.EMPTY_ARRAY : importList.getAllImportStatements();
final Map<String, Pair<String, Boolean>> unresolvedNames = new THashMap<String, Pair<String, Boolean>>();
@NotNull Set<Pair<String, Boolean>> unresolvedOnDemand = new THashSet<Pair<String, Boolean>>();
for (PsiImportStatementBase anImport : imports) {
PsiJavaCodeReferenceElement ref = anImport.getImportReference();
if (ref == null) continue;
JavaResolveResult[] results = ref.multiResolve(false);
if (results.length == 0) {
String text = ref.getCanonicalText();
if (anImport.isOnDemand()) {
text += ".*";
}
Pair<String, Boolean> pair = Pair.create(text, anImport instanceof PsiImportStaticStatement);
if (anImport.isOnDemand()) {
unresolvedOnDemand.add(pair);
}
else {
unresolvedNames.put(ref.getReferenceName(), pair);
}
}
}
// do not optimize unresolved imports for things like JSP (IDEA-41814)
if (file.getViewProvider().getLanguages().size() > 1 && file.getViewProvider().getBaseLanguage() != JavaLanguage.INSTANCE) {
namesToImport.addAll(unresolvedOnDemand);
namesToImport.addAll(unresolvedNames.values());
return;
}
final boolean[] hasResolveProblem = {false};
// do not visit imports
for (PsiClass aClass : file.getClasses()) {
if (!(aClass instanceof PsiCompiledElement)) {
aClass.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
String name = reference.getReferenceName();
Pair<String, Boolean> pair = unresolvedNames.get(name);
if (reference.multiResolve(false).length == 0) {
hasResolveProblem[0] = true;
if (pair != null) {
namesToImport.add(pair);
unresolvedNames.remove(name);
}
}
super.visitReferenceElement(reference);
}
});
}
}
if (hasResolveProblem[0]) {
namesToImport.addAll(unresolvedOnDemand);
}
// otherwise, optimize out all red on demand imports for green file
}
public static boolean isImplicitlyImported(@NotNull String className, @NotNull PsiJavaFile file) {
String[] packageNames = file.getImplicitlyImportedPackages();
for (String packageName : packageNames) {
if (hasPackage(className, packageName)) return true;
}
return false;
}
public static boolean hasPackage(@NotNull String className, @NotNull String packageName){
if (!className.startsWith(packageName)) return false;
if (className.length() == packageName.length()) return false;
if (!packageName.isEmpty() && className.charAt(packageName.length()) != '.') return false;
return className.indexOf('.', packageName.length() + 1) < 0;
}
@NotNull
private static String getPackageOrClassName(@NotNull String className){
int dotIndex = className.lastIndexOf('.');
return dotIndex < 0 ? "" : className.substring(0, dotIndex);
}
}