blob: 9c1f93547b1f3041f049bd67e6cd24d70bebe4a8 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.idea.quickfix;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.parser.GradleBuildFile;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.QuickFixActionRegistrar;
import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
import com.intellij.jarFinder.FindJarFix;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packageDependencies.DependencyValidationManager;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.android.tools.idea.gradle.util.GradleUtil.getGradleBuildFile;
import static com.intellij.openapi.module.ModuleUtilCore.findModuleForPsiElement;
public class AndroidUnresolvedReferenceQuickFixProvider extends UnresolvedReferenceQuickFixProvider<PsiJavaCodeReferenceElement> {
@Override
public void registerFixes(final @NotNull PsiJavaCodeReferenceElement reference, @NotNull QuickFixActionRegistrar registrar) {
Module contextModule = findModuleForPsiElement(reference);
if (contextModule == null) {
return;
}
AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(contextModule);
if (gradleFacet == null) {
return;
}
GradleBuildFile gradleBuildFile = GradleBuildFile.get(contextModule);
if (gradleBuildFile == null) {
return;
}
PsiFile contextFile = reference.getContainingFile();
if (contextFile == null) {
return;
}
final VirtualFile classVFile = contextFile.getVirtualFile();
if (classVFile == null) {
return;
}
// Since this is a gradle android project, we need to unregister:
// "add module dependency fix",
// "add junit to module quick fix",
// "add library to module quick fix",
// "add jar from web quick fix",
// since those quick fixes would make the iml file and the gradle file out of sync.
registrar.unregister(new Condition<IntentionAction>() {
@Override
public boolean value(IntentionAction intentionAction) {
return intentionAction instanceof OrderEntryFix || intentionAction instanceof FindJarFix;
}
});
// Currently our API doesn't address the case that gradle.build file does not exist at the module folder, so just skip for now.
if (getGradleBuildFile(contextModule) == null) {
return;
}
PsiElement psiElement = reference.getElement();
String referenceName = reference.getRangeInElement().substring(psiElement.getText());
Project project = psiElement.getProject();
// Check if it is a JUnit class reference.
if ("TestCase".equals(referenceName) || isAnnotation(psiElement) && isJunitAnnotationName(referenceName, psiElement)) {
final boolean isJunit4 = !referenceName.equals("TestCase");
String className = isJunit4 ? "org.junit." + referenceName : "junit.framework.TestCase";
PsiClass found =
JavaPsiFacade.getInstance(project).findClass(className, contextModule.getModuleWithDependenciesAndLibrariesScope(true));
if (found == null) {
registrar.register(new AddGradleJUnitDependencyFix(contextModule, reference, className, isJunit4));
}
}
// Check if it is a JetBrains annotation class reference.
if (isAnnotation(psiElement) && AnnotationUtil.isJetbrainsAnnotation(referenceName)) {
String className = "org.jetbrains.annotations." + referenceName;
PsiClass found =
JavaPsiFacade.getInstance(project).findClass(className, contextModule.getModuleWithDependenciesAndLibrariesScope(true));
if (found == null) {
registrar.register(new AddGradleJetbrainsAnnotationFix(contextModule, reference, className));
}
}
// Check if we could fix it by introduce gradle dependency.
PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(referenceName, GlobalSearchScope.allScope(project));
List<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
if (!allowedDependencies.isEmpty()) {
classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
registrar.register(new AddGradleProjectDependencyFix(contextModule, classVFile, classes, reference));
}
// Check if we could fix it by introduce other library dependency.
JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject());
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
Set<Object> librariesToAdd = Sets.newHashSet();
for (PsiClass aClass : classes) {
if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) {
continue;
}
PsiFile psiFile = aClass.getContainingFile();
if (psiFile == null) {
continue;
}
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) {
continue;
}
ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(contextModule).getFileIndex();
for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
if (orderEntry instanceof LibraryOrderEntry) {
LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry;
Library library = libraryEntry.getLibrary();
if (library == null) {
continue;
}
VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);
if (files.length == 0) {
continue;
}
final VirtualFile jar = files[0];
if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) {
continue;
}
OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile);
if (entryForFile != null) {
if (entryForFile instanceof ExportableOrderEntry &&
((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
!ModuleRootManager.getInstance(contextModule).getFileIndex().isInTestSourceContent(classVFile)) {
}
else {
continue;
}
}
registrar.register(new AddGradleLibraryDependencyFix(libraryEntry, contextModule, aClass, reference));
}
}
}
}
// Duplicated from com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix.filterAllowedDependencies
@NotNull
private static List<PsiClass> filterAllowedDependencies(@NotNull PsiElement element, @NotNull PsiClass[] classes) {
DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject());
PsiFile fromFile = element.getContainingFile();
List<PsiClass> result = new ArrayList<PsiClass>();
for (PsiClass psiClass : classes) {
if (dependencyValidationManager.getViolatorDependencyRule(fromFile, psiClass.getContainingFile()) == null) {
result.add(psiClass);
}
}
return result;
}
// Duplicated from com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix.isAnnotation
private static boolean isAnnotation(@NotNull final PsiElement psiElement) {
return PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null && PsiUtil.isLanguageLevel5OrHigher(psiElement);
}
// Duplicated from com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix.isJunitAnnotationName
private static boolean isJunitAnnotationName(@NonNls final String referenceName, @NotNull final PsiElement psiElement) {
if ("Test".equals(referenceName) || "Ignore".equals(referenceName) || "RunWith".equals(referenceName) ||
"Before".equals(referenceName) || "BeforeClass".equals(referenceName) ||
"After".equals(referenceName) || "AfterClass".equals(referenceName)) {
return true;
}
final PsiElement parent = psiElement.getParent();
if (parent != null && !(parent instanceof PsiAnnotation)) {
final PsiReference reference = parent.getReference();
if (reference != null) {
final String referenceText = parent.getText();
if (isJunitAnnotationName(reference.getRangeInElement().substring(referenceText), parent)) {
final int lastDot = referenceText.lastIndexOf('.');
return lastDot > -1 && referenceText.substring(0, lastDot).equals("org.junit");
}
}
}
return false;
}
@NotNull
@Override
public Class<PsiJavaCodeReferenceElement> getReferenceClass() {
return PsiJavaCodeReferenceElement.class;
}
}