91e4b9456f9bffd0eb948094b96624faa62ee495
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / OrderEntryFix.java
1 /*
2  * Copyright 2000-2015 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.codeInsight.daemon.impl.quickfix;
17
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.projectRoots.ex.JavaSdkUtil;
38 import com.intellij.openapi.roots.*;
39 import com.intellij.openapi.roots.impl.OrderEntryUtil;
40 import com.intellij.openapi.roots.libraries.Library;
41 import com.intellij.openapi.vfs.LocalFileSystem;
42 import com.intellij.openapi.vfs.VfsUtil;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.packageDependencies.DependencyValidationManager;
45 import com.intellij.pom.java.LanguageLevel;
46 import com.intellij.psi.*;
47 import com.intellij.psi.search.GlobalSearchScope;
48 import com.intellij.psi.search.PsiShortNamesCache;
49 import com.intellij.psi.util.PsiTreeUtil;
50 import com.intellij.psi.util.PsiUtil;
51 import com.intellij.util.Function;
52 import com.intellij.util.ObjectUtils;
53 import com.intellij.util.ThreeState;
54 import com.intellij.util.containers.ContainerUtil;
55 import gnu.trove.THashSet;
56 import org.jetbrains.annotations.NonNls;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import java.io.File;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Set;
65
66 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.findFixes;
67 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.provideFix;
68
69 /**
70  * @author cdr
71  */
72 public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
73   public static final String JUNIT4_LIBRARY_NAME = "JUnit4";
74
75   protected OrderEntryFix() {
76   }
77
78   @Override
79   public boolean startInWriteAction() {
80     return true;
81   }
82
83   @Override
84   @NotNull
85   public String getName() {
86     return getText();
87   }
88
89   @Override
90   public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
91     invoke(project, null, descriptor.getPsiElement().getContainingFile());
92   }
93
94   @Nullable
95   public static List<LocalQuickFix> registerFixes(@NotNull final QuickFixActionRegistrar registrar, @NotNull final PsiReference reference) {
96     final PsiElement psiElement = reference.getElement();
97     @NonNls final String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText());
98
99     Project project = psiElement.getProject();
100     PsiFile containingFile = psiElement.getContainingFile();
101     if (containingFile == null) return null;
102
103     final VirtualFile classVFile = containingFile.getVirtualFile();
104     if (classVFile == null) return null;
105
106     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
107     final Module currentModule = fileIndex.getModuleForFile(classVFile);
108     if (currentModule == null) return null;
109
110     final List<LocalQuickFix> providedFixes = findFixes(new Function<MissingDependencyFixProvider, List<LocalQuickFix>>() {
111       @Override
112       public List<LocalQuickFix> fun(MissingDependencyFixProvider provider) {
113         return provider.registerFixes(registrar, reference);
114       }
115     });
116     if (providedFixes != null) {
117       return providedFixes;
118     }
119
120     List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
121     JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
122     String fullReferenceText = reference.getCanonicalText();
123     for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensions()) {
124       final ExternalClassResolveResult resolveResult = resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement));
125       OrderEntryFix fix = null;
126       if (resolveResult != null && psiFacade.findClass(resolveResult.getQualifiedClassName(), currentModule.getModuleWithDependenciesAndLibrariesScope(true)) == null) {
127         fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, resolveResult.getLibrary(), reference, resolveResult.getQualifiedClassName());
128       }
129       else if (!fullReferenceText.equals(shortReferenceName)) {
130         ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText);
131         if (descriptor != null) {
132           fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null);
133         }
134       }
135       if (fix != null) {
136         registrar.register(fix);
137         result.add(fix);
138       }
139     }
140     if (!result.isEmpty()) {
141       return result;
142     }
143
144     Set<Object> librariesToAdd = new THashSet<Object>();
145     final JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject());
146     PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project));
147     List<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
148     if (allowedDependencies.isEmpty()) {
149       return result;
150     }
151     classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
152     OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
153
154     final PsiClass[] finalClasses = classes;
155     final OrderEntryFix finalModuleDependencyFix = moduleDependencyFix;
156     final OrderEntryFix providedModuleDependencyFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
157       @Override
158       public OrderEntryFix fun(MissingDependencyFixProvider provider) {
159         return provider.getAddModuleDependencyFix(reference, finalModuleDependencyFix, currentModule, classVFile, finalClasses);
160       }
161     });
162     moduleDependencyFix = ObjectUtils.notNull(providedModuleDependencyFix, moduleDependencyFix);
163
164     registrar.register(moduleDependencyFix);
165     result.add(moduleDependencyFix);
166     for (final PsiClass aClass : classes) {
167       if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
168       PsiFile psiFile = aClass.getContainingFile();
169       if (psiFile == null) continue;
170       VirtualFile virtualFile = psiFile.getVirtualFile();
171       if (virtualFile == null) continue;
172       ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
173       for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
174         if (orderEntry instanceof LibraryOrderEntry) {
175           final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry;
176           final Library library = libraryEntry.getLibrary();
177           if (library == null) continue;
178           VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);
179           if (files.length == 0) continue;
180           final VirtualFile jar = files[0];
181
182           if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) continue;
183           OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile);
184           if (entryForFile != null &&
185               !(entryForFile instanceof ExportableOrderEntry &&
186                 ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
187                 !ModuleRootManager.getInstance(currentModule).getFileIndex().isInTestSourceContent(classVFile))) {
188             continue;
189           }
190           final OrderEntryFix platformFix = new OrderEntryFix() {
191             @Override
192             @NotNull
193             public String getText() {
194               return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", libraryEntry.getPresentableName());
195             }
196
197             @Override
198             @NotNull
199             public String getFamilyName() {
200               return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath");
201             }
202
203             @Override
204             public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
205               return !project.isDisposed() && !currentModule.isDisposed() && libraryEntry.isValid();
206             }
207
208             @Override
209             public void invoke(@NotNull final Project project, @Nullable final Editor editor, PsiFile file) {
210               OrderEntryUtil.addLibraryToRoots(libraryEntry, currentModule);
211               if (editor != null) {
212                 DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
213                   @Override
214                   public void run() {
215                     new AddImportAction(project, reference, editor, aClass).execute();
216                   }
217                 });
218               }
219             }
220           };
221
222           final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
223             @Override
224             public OrderEntryFix fun(MissingDependencyFixProvider provider) {
225               return provider.getAddLibraryToClasspathFix(reference, platformFix, currentModule, libraryEntry, aClass);
226             }
227           });
228           final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
229           registrar.register(fix);
230           result.add(fix);
231         }
232       }
233     }
234     return result;
235   }
236
237   /**
238    * @deprecated
239    */
240   public static void addJUnit4Library(boolean inTests, Module currentModule) throws ClassNotFoundException {
241     final List<String> junit4Paths = JavaSdkUtil.getJUnit4JarPaths();
242     addJarsToRoots(junit4Paths, JUNIT4_LIBRARY_NAME, currentModule, null);
243   }
244
245   private static List<PsiClass> filterAllowedDependencies(PsiElement element, PsiClass[] classes) {
246     DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject());
247     PsiFile fromFile = element.getContainingFile();
248     List<PsiClass> result = new ArrayList<PsiClass>();
249     for (PsiClass psiClass : classes) {
250       if (dependencyValidationManager.getViolatorDependencyRule(fromFile, psiClass.getContainingFile()) == null) {
251         result.add(psiClass);
252       }
253     }
254     return result;
255   }
256
257   private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) {
258     if (!PsiUtil.isLanguageLevel5OrHigher(psiElement)) {
259       return ThreeState.NO;
260     }
261     if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) {
262       return ThreeState.YES;
263     }
264     if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) {
265       return ThreeState.UNSURE;
266     }
267     return ThreeState.NO;
268   }
269
270   /**
271    * @deprecated use {@link #addJarsToRootsAndImportClass} instead
272    */
273   public static void addBundledJarToRoots(final Project project, @Nullable final Editor editor, final Module currentModule,
274                                           @Nullable final PsiReference reference,
275                                           @NonNls final String className,
276                                           @NonNls final String libVirtFile) {
277     addJarsToRootsAndImportClass(Collections.singletonList(libVirtFile), null, currentModule, editor, reference, className);
278   }
279
280   public static void addJarsToRootsAndImportClass(@NotNull List<String> jarPaths,
281                                                   final String libraryName,
282                                                   @NotNull final Module currentModule, @Nullable final Editor editor,
283                                                   @Nullable final PsiReference reference,
284                                                   @Nullable @NonNls final String className) {
285     addJarsToRoots(jarPaths, libraryName, currentModule, reference != null ? reference.getElement() : null);
286
287     final Project project = currentModule.getProject();
288     if (editor != null && reference != null && className != null) {
289       DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
290         @Override
291         public void run() {
292           GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(currentModule);
293           PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, scope);
294           if (aClass != null) {
295             new AddImportAction(project, reference, editor, aClass).execute();
296           }
297         }
298       });
299     }
300   }
301
302   public static void addJarToRoots(@NotNull String jarPath, final @NotNull Module module, @Nullable PsiElement location) {
303     addJarsToRoots(Collections.singletonList(jarPath), null, module, location);
304   }
305
306   public static void addJarsToRoots(@NotNull final List<String> jarPaths, @Nullable final String libraryName,
307                                     @NotNull final Module module, @Nullable final PsiElement location) {
308     final Boolean isAdded = provideFix(new Function<MissingDependencyFixProvider, Boolean>() {
309       @Override
310       public Boolean fun(MissingDependencyFixProvider provider) {
311         return provider.addJarsToRoots(jarPaths, libraryName, module, location);
312       }
313     });
314     if (Boolean.TRUE.equals(isAdded)) return;
315
316     List<String> urls = ContainerUtil.map(jarPaths, new Function<String, String>() {
317       @Override
318       public String fun(String path) {
319         return refreshAndConvertToUrl(path);
320       }
321     });
322     boolean inTests = false;
323     if (location != null) {
324       final VirtualFile vFile = location.getContainingFile().getVirtualFile();
325       if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) {
326         inTests = true;
327       }
328     }
329     ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.<String>emptyList(),
330                                                 inTests ? DependencyScope.TEST : DependencyScope.COMPILE);
331   }
332
333   @NotNull
334   private static String refreshAndConvertToUrl(String jarPath) {
335     final File libraryRoot = new File(jarPath);
336     LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot);
337     return VfsUtil.getUrlForLibraryRoot(libraryRoot);
338   }
339
340   public static boolean ensureAnnotationsJarInPath(final Module module) {
341     if (isAnnotationsJarInPath(module)) return true;
342     if (module == null) return false;
343     final String libraryPath = locateAnnotationsJar(module);
344     if (libraryPath != null) {
345       new WriteCommandAction(module.getProject()) {
346         @Override
347         protected void run(@NotNull final Result result) throws Throwable {
348           addJarToRoots(libraryPath, module, null);
349         }
350       }.execute();
351       return true;
352     }
353     return false;
354   }
355
356   @Nullable
357   public static String locateAnnotationsJar(@NotNull Module module) {
358     String jarName;
359     String libPath;
360     if (EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_8)) {
361       jarName = "annotations-java8.jar";
362       libPath = new File(PathManager.getHomePath(), "redist").getAbsolutePath();
363     }
364     else {
365       jarName = "annotations.jar";
366       libPath = PathManager.getLibPath();
367     }
368     final LocateLibraryDialog dialog = new LocateLibraryDialog(module, libPath, jarName, QuickFixBundle.message("add.library.annotations.description"));
369     return dialog.showAndGet() ? dialog.getResultingLibraryPath() : null;
370   }
371
372   public static boolean isAnnotationsJarInPath(Module module) {
373     if (module == null) return false;
374     return JavaPsiFacade.getInstance(module.getProject())
375              .findClass(AnnotationUtil.LANGUAGE, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module)) != null;
376   }
377 }