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