2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.daemon.impl.quickfix;
18 import com.intellij.codeInsight.AnnotationUtil;
19 import com.intellij.codeInsight.daemon.QuickFixActionRegistrar;
20 import com.intellij.codeInsight.daemon.QuickFixBundle;
21 import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
22 import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryDescriptor;
23 import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver;
24 import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver.ExternalClassResolveResult;
25 import com.intellij.codeInsight.daemon.quickFix.MissingDependencyFixProvider;
26 import com.intellij.codeInsight.intention.IntentionAction;
27 import com.intellij.codeInspection.LocalQuickFix;
28 import com.intellij.codeInspection.ProblemDescriptor;
29 import com.intellij.openapi.application.PathManager;
30 import com.intellij.openapi.application.Result;
31 import com.intellij.openapi.command.WriteCommandAction;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.module.EffectiveLanguageLevelUtil;
34 import com.intellij.openapi.module.Module;
35 import com.intellij.openapi.project.DumbService;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.roots.*;
38 import com.intellij.openapi.roots.impl.OrderEntryUtil;
39 import com.intellij.openapi.roots.libraries.Library;
40 import com.intellij.openapi.vfs.LocalFileSystem;
41 import com.intellij.openapi.vfs.VfsUtil;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.packageDependencies.DependencyValidationManager;
44 import com.intellij.pom.java.LanguageLevel;
45 import com.intellij.psi.*;
46 import com.intellij.psi.search.GlobalSearchScope;
47 import com.intellij.psi.search.PsiShortNamesCache;
48 import com.intellij.psi.util.PsiTreeUtil;
49 import com.intellij.psi.util.PsiUtil;
50 import com.intellij.util.Function;
51 import com.intellij.util.ObjectUtils;
52 import com.intellij.util.ThreeState;
53 import com.intellij.util.containers.ContainerUtil;
54 import gnu.trove.THashSet;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
60 import java.util.ArrayList;
61 import java.util.Collections;
62 import java.util.List;
65 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.findFixes;
66 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.provideFix;
71 public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
72 public static final String JUNIT4_LIBRARY_NAME = "JUnit4";
74 protected OrderEntryFix() {
78 public boolean startInWriteAction() {
84 public String getName() {
89 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
90 invoke(project, null, descriptor.getPsiElement().getContainingFile());
94 public static List<LocalQuickFix> registerFixes(@NotNull final QuickFixActionRegistrar registrar, @NotNull final PsiReference reference) {
95 final PsiElement psiElement = reference.getElement();
96 @NonNls final String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText());
98 Project project = psiElement.getProject();
99 PsiFile containingFile = psiElement.getContainingFile();
100 if (containingFile == null) return null;
102 final VirtualFile classVFile = containingFile.getVirtualFile();
103 if (classVFile == null) return null;
105 final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
106 final Module currentModule = fileIndex.getModuleForFile(classVFile);
107 if (currentModule == null) return null;
109 final List<LocalQuickFix> providedFixes = findFixes(new Function<MissingDependencyFixProvider, List<LocalQuickFix>>() {
111 public List<LocalQuickFix> fun(MissingDependencyFixProvider provider) {
112 return provider.registerFixes(registrar, reference);
115 if (providedFixes != null) {
116 return providedFixes;
119 List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
120 JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
121 String fullReferenceText = reference.getCanonicalText();
122 for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensions()) {
123 final ExternalClassResolveResult resolveResult = resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement));
124 OrderEntryFix fix = null;
125 if (resolveResult != null && psiFacade.findClass(resolveResult.getQualifiedClassName(), currentModule.getModuleWithDependenciesAndLibrariesScope(true)) == null) {
126 fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, resolveResult.getLibrary(), reference, resolveResult.getQualifiedClassName());
128 else if (!fullReferenceText.equals(shortReferenceName)) {
129 ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText);
130 if (descriptor != null) {
131 fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null);
135 registrar.register(fix);
139 if (!result.isEmpty()) {
143 Set<Object> librariesToAdd = new THashSet<Object>();
144 final JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject());
145 PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project));
146 List<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
147 if (allowedDependencies.isEmpty()) {
150 classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
151 OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
153 final PsiClass[] finalClasses = classes;
154 final OrderEntryFix finalModuleDependencyFix = moduleDependencyFix;
155 final OrderEntryFix providedModuleDependencyFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
157 public OrderEntryFix fun(MissingDependencyFixProvider provider) {
158 return provider.getAddModuleDependencyFix(reference, finalModuleDependencyFix, currentModule, classVFile, finalClasses);
161 moduleDependencyFix = ObjectUtils.notNull(providedModuleDependencyFix, moduleDependencyFix);
163 registrar.register(moduleDependencyFix);
164 result.add(moduleDependencyFix);
165 for (final PsiClass aClass : classes) {
166 if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
167 PsiFile psiFile = aClass.getContainingFile();
168 if (psiFile == null) continue;
169 VirtualFile virtualFile = psiFile.getVirtualFile();
170 if (virtualFile == null) continue;
171 ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
172 for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
173 if (orderEntry instanceof LibraryOrderEntry) {
174 final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry;
175 final Library library = libraryEntry.getLibrary();
176 if (library == null) continue;
177 VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);
178 if (files.length == 0) continue;
179 final VirtualFile jar = files[0];
181 if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) continue;
182 OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile);
183 if (entryForFile != null &&
184 !(entryForFile instanceof ExportableOrderEntry &&
185 ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
186 !ModuleRootManager.getInstance(currentModule).getFileIndex().isInTestSourceContent(classVFile))) {
189 final OrderEntryFix platformFix = new OrderEntryFix() {
192 public String getText() {
193 return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", libraryEntry.getPresentableName());
198 public String getFamilyName() {
199 return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath");
203 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
204 return !project.isDisposed() && !currentModule.isDisposed() && libraryEntry.isValid();
208 public void invoke(@NotNull final Project project, @Nullable final Editor editor, PsiFile file) {
209 OrderEntryUtil.addLibraryToRoots(libraryEntry, currentModule);
210 if (editor != null) {
211 DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
214 new AddImportAction(project, reference, editor, aClass).execute();
221 final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
223 public OrderEntryFix fun(MissingDependencyFixProvider provider) {
224 return provider.getAddLibraryToClasspathFix(reference, platformFix, currentModule, libraryEntry, aClass);
227 final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
228 registrar.register(fix);
236 private static List<PsiClass> filterAllowedDependencies(PsiElement element, PsiClass[] classes) {
237 DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject());
238 PsiFile fromFile = element.getContainingFile();
239 List<PsiClass> result = new ArrayList<PsiClass>();
240 for (PsiClass psiClass : classes) {
241 if (dependencyValidationManager.getViolatorDependencyRule(fromFile, psiClass.getContainingFile()) == null) {
242 result.add(psiClass);
248 private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) {
249 if (!PsiUtil.isLanguageLevel5OrHigher(psiElement)) {
250 return ThreeState.NO;
252 if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) {
253 return ThreeState.YES;
255 if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) {
256 return ThreeState.UNSURE;
258 return ThreeState.NO;
261 public static void addJarsToRootsAndImportClass(@NotNull List<String> jarPaths,
262 final String libraryName,
263 @NotNull final Module currentModule, @Nullable final Editor editor,
264 @Nullable final PsiReference reference,
265 @Nullable @NonNls final String className) {
266 addJarsToRoots(jarPaths, libraryName, currentModule, reference != null ? reference.getElement() : null);
268 final Project project = currentModule.getProject();
269 if (editor != null && reference != null && className != null) {
270 DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
273 GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(currentModule);
274 PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, scope);
275 if (aClass != null) {
276 new AddImportAction(project, reference, editor, aClass).execute();
283 public static void addJarToRoots(@NotNull String jarPath, final @NotNull Module module, @Nullable PsiElement location) {
284 addJarsToRoots(Collections.singletonList(jarPath), null, module, location);
287 public static void addJarsToRoots(@NotNull final List<String> jarPaths, @Nullable final String libraryName,
288 @NotNull final Module module, @Nullable final PsiElement location) {
289 final Boolean isAdded = provideFix(new Function<MissingDependencyFixProvider, Boolean>() {
291 public Boolean fun(MissingDependencyFixProvider provider) {
292 return provider.addJarsToRoots(jarPaths, libraryName, module, location);
295 if (Boolean.TRUE.equals(isAdded)) return;
297 List<String> urls = ContainerUtil.map(jarPaths, new Function<String, String>() {
299 public String fun(String path) {
300 return refreshAndConvertToUrl(path);
303 boolean inTests = false;
304 if (location != null) {
305 final VirtualFile vFile = location.getContainingFile().getVirtualFile();
306 if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) {
310 ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.<String>emptyList(),
311 inTests ? DependencyScope.TEST : DependencyScope.COMPILE);
315 private static String refreshAndConvertToUrl(String jarPath) {
316 final File libraryRoot = new File(jarPath);
317 LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot);
318 return VfsUtil.getUrlForLibraryRoot(libraryRoot);
321 public static boolean ensureAnnotationsJarInPath(final Module module) {
322 if (isAnnotationsJarInPath(module)) return true;
323 if (module == null) return false;
324 final String libraryPath = locateAnnotationsJar(module);
325 if (libraryPath != null) {
326 new WriteCommandAction(module.getProject()) {
328 protected void run(@NotNull final Result result) throws Throwable {
329 addJarToRoots(libraryPath, module, null);
338 public static String locateAnnotationsJar(@NotNull Module module) {
341 if (EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_8)) {
342 jarName = "annotations-java8.jar";
343 libPath = new File(PathManager.getHomePath(), "redist").getAbsolutePath();
346 jarName = "annotations.jar";
347 libPath = PathManager.getLibPath();
349 final LocateLibraryDialog dialog = new LocateLibraryDialog(module, libPath, jarName, QuickFixBundle.message("add.library.annotations.description"));
350 return dialog.showAndGet() ? dialog.getResultingLibraryPath() : null;
353 public static boolean isAnnotationsJarInPath(Module module) {
354 if (module == null) return false;
355 return JavaPsiFacade.getInstance(module.getProject())
356 .findClass(AnnotationUtil.LANGUAGE, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module)) != null;