unused deprecated methods removed
[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.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;
58
59 import java.io.File;
60 import java.util.ArrayList;
61 import java.util.Collections;
62 import java.util.List;
63 import java.util.Set;
64
65 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.findFixes;
66 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.provideFix;
67
68 /**
69  * @author cdr
70  */
71 public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
72   public static final String JUNIT4_LIBRARY_NAME = "JUnit4";
73
74   protected OrderEntryFix() {
75   }
76
77   @Override
78   public boolean startInWriteAction() {
79     return true;
80   }
81
82   @Override
83   @NotNull
84   public String getName() {
85     return getText();
86   }
87
88   @Override
89   public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
90     invoke(project, null, descriptor.getPsiElement().getContainingFile());
91   }
92
93   @Nullable
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());
97
98     Project project = psiElement.getProject();
99     PsiFile containingFile = psiElement.getContainingFile();
100     if (containingFile == null) return null;
101
102     final VirtualFile classVFile = containingFile.getVirtualFile();
103     if (classVFile == null) return null;
104
105     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
106     final Module currentModule = fileIndex.getModuleForFile(classVFile);
107     if (currentModule == null) return null;
108
109     final List<LocalQuickFix> providedFixes = findFixes(new Function<MissingDependencyFixProvider, List<LocalQuickFix>>() {
110       @Override
111       public List<LocalQuickFix> fun(MissingDependencyFixProvider provider) {
112         return provider.registerFixes(registrar, reference);
113       }
114     });
115     if (providedFixes != null) {
116       return providedFixes;
117     }
118
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());
127       }
128       else if (!fullReferenceText.equals(shortReferenceName)) {
129         ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText);
130         if (descriptor != null) {
131           fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null);
132         }
133       }
134       if (fix != null) {
135         registrar.register(fix);
136         result.add(fix);
137       }
138     }
139     if (!result.isEmpty()) {
140       return result;
141     }
142
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()) {
148       return result;
149     }
150     classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
151     OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
152
153     final PsiClass[] finalClasses = classes;
154     final OrderEntryFix finalModuleDependencyFix = moduleDependencyFix;
155     final OrderEntryFix providedModuleDependencyFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
156       @Override
157       public OrderEntryFix fun(MissingDependencyFixProvider provider) {
158         return provider.getAddModuleDependencyFix(reference, finalModuleDependencyFix, currentModule, classVFile, finalClasses);
159       }
160     });
161     moduleDependencyFix = ObjectUtils.notNull(providedModuleDependencyFix, moduleDependencyFix);
162
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];
180
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))) {
187             continue;
188           }
189           final OrderEntryFix platformFix = new OrderEntryFix() {
190             @Override
191             @NotNull
192             public String getText() {
193               return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", libraryEntry.getPresentableName());
194             }
195
196             @Override
197             @NotNull
198             public String getFamilyName() {
199               return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath");
200             }
201
202             @Override
203             public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
204               return !project.isDisposed() && !currentModule.isDisposed() && libraryEntry.isValid();
205             }
206
207             @Override
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() {
212                   @Override
213                   public void run() {
214                     new AddImportAction(project, reference, editor, aClass).execute();
215                   }
216                 });
217               }
218             }
219           };
220
221           final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
222             @Override
223             public OrderEntryFix fun(MissingDependencyFixProvider provider) {
224               return provider.getAddLibraryToClasspathFix(reference, platformFix, currentModule, libraryEntry, aClass);
225             }
226           });
227           final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
228           registrar.register(fix);
229           result.add(fix);
230         }
231       }
232     }
233     return result;
234   }
235
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);
243       }
244     }
245     return result;
246   }
247
248   private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) {
249     if (!PsiUtil.isLanguageLevel5OrHigher(psiElement)) {
250       return ThreeState.NO;
251     }
252     if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) {
253       return ThreeState.YES;
254     }
255     if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) {
256       return ThreeState.UNSURE;
257     }
258     return ThreeState.NO;
259   }
260
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);
267
268     final Project project = currentModule.getProject();
269     if (editor != null && reference != null && className != null) {
270       DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
271         @Override
272         public void run() {
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();
277           }
278         }
279       });
280     }
281   }
282
283   public static void addJarToRoots(@NotNull String jarPath, final @NotNull Module module, @Nullable PsiElement location) {
284     addJarsToRoots(Collections.singletonList(jarPath), null, module, location);
285   }
286
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>() {
290       @Override
291       public Boolean fun(MissingDependencyFixProvider provider) {
292         return provider.addJarsToRoots(jarPaths, libraryName, module, location);
293       }
294     });
295     if (Boolean.TRUE.equals(isAdded)) return;
296
297     List<String> urls = ContainerUtil.map(jarPaths, new Function<String, String>() {
298       @Override
299       public String fun(String path) {
300         return refreshAndConvertToUrl(path);
301       }
302     });
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)) {
307         inTests = true;
308       }
309     }
310     ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.<String>emptyList(),
311                                                 inTests ? DependencyScope.TEST : DependencyScope.COMPILE);
312   }
313
314   @NotNull
315   private static String refreshAndConvertToUrl(String jarPath) {
316     final File libraryRoot = new File(jarPath);
317     LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot);
318     return VfsUtil.getUrlForLibraryRoot(libraryRoot);
319   }
320
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()) {
327         @Override
328         protected void run(@NotNull final Result result) throws Throwable {
329           addJarToRoots(libraryPath, module, null);
330         }
331       }.execute();
332       return true;
333     }
334     return false;
335   }
336
337   @Nullable
338   public static String locateAnnotationsJar(@NotNull Module module) {
339     String jarName;
340     String libPath;
341     if (EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_8)) {
342       jarName = "annotations-java8.jar";
343       libPath = new File(PathManager.getHomePath(), "redist").getAbsolutePath();
344     }
345     else {
346       jarName = "annotations.jar";
347       libPath = PathManager.getLibPath();
348     }
349     final LocateLibraryDialog dialog = new LocateLibraryDialog(module, libPath, jarName, QuickFixBundle.message("add.library.annotations.description"));
350     return dialog.showAndGet() ? dialog.getResultingLibraryPath() : null;
351   }
352
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;
357   }
358 }