class moved
[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.daemon.QuickFixActionRegistrar;
19 import com.intellij.codeInsight.daemon.QuickFixBundle;
20 import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
21 import com.intellij.openapi.roots.ExternalLibraryDescriptor;
22 import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver;
23 import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver.ExternalClassResolveResult;
24 import com.intellij.codeInsight.daemon.quickFix.MissingDependencyFixProvider;
25 import com.intellij.codeInsight.intention.IntentionAction;
26 import com.intellij.codeInspection.LocalQuickFix;
27 import com.intellij.codeInspection.ProblemDescriptor;
28 import com.intellij.openapi.editor.Editor;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.project.DumbService;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.*;
33 import com.intellij.openapi.roots.impl.OrderEntryUtil;
34 import com.intellij.openapi.roots.libraries.Library;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VfsUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.packageDependencies.DependencyValidationManager;
39 import com.intellij.psi.*;
40 import com.intellij.psi.search.GlobalSearchScope;
41 import com.intellij.psi.search.PsiShortNamesCache;
42 import com.intellij.psi.util.PsiTreeUtil;
43 import com.intellij.psi.util.PsiUtil;
44 import com.intellij.util.Function;
45 import com.intellij.util.ObjectUtils;
46 import com.intellij.util.ThreeState;
47 import com.intellij.util.containers.ContainerUtil;
48 import gnu.trove.THashSet;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52
53 import java.io.File;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.Set;
58
59 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.findFixes;
60 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.provideFix;
61
62 /**
63  * @author cdr
64  */
65 public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
66   protected OrderEntryFix() {
67   }
68
69   @Override
70   public boolean startInWriteAction() {
71     return true;
72   }
73
74   @Override
75   @NotNull
76   public String getName() {
77     return getText();
78   }
79
80   @Override
81   public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
82     invoke(project, null, descriptor.getPsiElement().getContainingFile());
83   }
84
85   @Nullable
86   public static List<LocalQuickFix> registerFixes(@NotNull final QuickFixActionRegistrar registrar, @NotNull final PsiReference reference) {
87     final PsiElement psiElement = reference.getElement();
88     @NonNls final String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText());
89
90     Project project = psiElement.getProject();
91     PsiFile containingFile = psiElement.getContainingFile();
92     if (containingFile == null) return null;
93
94     final VirtualFile classVFile = containingFile.getVirtualFile();
95     if (classVFile == null) return null;
96
97     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
98     final Module currentModule = fileIndex.getModuleForFile(classVFile);
99     if (currentModule == null) return null;
100
101     final List<LocalQuickFix> providedFixes = findFixes(new Function<MissingDependencyFixProvider, List<LocalQuickFix>>() {
102       @Override
103       public List<LocalQuickFix> fun(MissingDependencyFixProvider provider) {
104         return provider.registerFixes(registrar, reference);
105       }
106     });
107     if (providedFixes != null) {
108       return providedFixes;
109     }
110
111     List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
112     JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
113     String fullReferenceText = reference.getCanonicalText();
114     for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensions()) {
115       final ExternalClassResolveResult resolveResult = resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement), currentModule);
116       OrderEntryFix fix = null;
117       if (resolveResult != null && psiFacade.findClass(resolveResult.getQualifiedClassName(), currentModule.getModuleWithDependenciesAndLibrariesScope(true)) == null) {
118         fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, resolveResult.getLibrary(), reference, resolveResult.getQualifiedClassName());
119       }
120       else if (!fullReferenceText.equals(shortReferenceName)) {
121         ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText);
122         if (descriptor != null) {
123           fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null);
124         }
125       }
126       if (fix != null) {
127         registrar.register(fix);
128         result.add(fix);
129       }
130     }
131     if (!result.isEmpty()) {
132       return result;
133     }
134
135     Set<Object> librariesToAdd = new THashSet<Object>();
136     final JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject());
137     PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project));
138     List<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
139     if (allowedDependencies.isEmpty()) {
140       return result;
141     }
142     classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
143     OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
144
145     final PsiClass[] finalClasses = classes;
146     final OrderEntryFix finalModuleDependencyFix = moduleDependencyFix;
147     final OrderEntryFix providedModuleDependencyFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
148       @Override
149       public OrderEntryFix fun(MissingDependencyFixProvider provider) {
150         return provider.getAddModuleDependencyFix(reference, finalModuleDependencyFix, currentModule, classVFile, finalClasses);
151       }
152     });
153     moduleDependencyFix = ObjectUtils.notNull(providedModuleDependencyFix, moduleDependencyFix);
154
155     registrar.register(moduleDependencyFix);
156     result.add(moduleDependencyFix);
157     for (final PsiClass aClass : classes) {
158       if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
159       PsiFile psiFile = aClass.getContainingFile();
160       if (psiFile == null) continue;
161       VirtualFile virtualFile = psiFile.getVirtualFile();
162       if (virtualFile == null) continue;
163       ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
164       for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
165         if (orderEntry instanceof LibraryOrderEntry) {
166           final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry;
167           final Library library = libraryEntry.getLibrary();
168           if (library == null) continue;
169           VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);
170           if (files.length == 0) continue;
171           final VirtualFile jar = files[0];
172
173           if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) continue;
174           OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile);
175           if (entryForFile != null &&
176               !(entryForFile instanceof ExportableOrderEntry &&
177                 ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
178                 !ModuleRootManager.getInstance(currentModule).getFileIndex().isInTestSourceContent(classVFile))) {
179             continue;
180           }
181           final OrderEntryFix platformFix = new OrderEntryFix() {
182             @Override
183             @NotNull
184             public String getText() {
185               return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", libraryEntry.getPresentableName());
186             }
187
188             @Override
189             @NotNull
190             public String getFamilyName() {
191               return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath");
192             }
193
194             @Override
195             public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
196               return !project.isDisposed() && !currentModule.isDisposed() && libraryEntry.isValid();
197             }
198
199             @Override
200             public void invoke(@NotNull final Project project, @Nullable final Editor editor, PsiFile file) {
201               OrderEntryUtil.addLibraryToRoots(libraryEntry, currentModule);
202               if (editor != null) {
203                 DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
204                   @Override
205                   public void run() {
206                     new AddImportAction(project, reference, editor, aClass).execute();
207                   }
208                 });
209               }
210             }
211           };
212
213           final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
214             @Override
215             public OrderEntryFix fun(MissingDependencyFixProvider provider) {
216               return provider.getAddLibraryToClasspathFix(reference, platformFix, currentModule, libraryEntry, aClass);
217             }
218           });
219           final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
220           registrar.register(fix);
221           result.add(fix);
222         }
223       }
224     }
225     return result;
226   }
227
228   private static List<PsiClass> filterAllowedDependencies(PsiElement element, PsiClass[] classes) {
229     DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject());
230     PsiFile fromFile = element.getContainingFile();
231     List<PsiClass> result = new ArrayList<PsiClass>();
232     for (PsiClass psiClass : classes) {
233       if (dependencyValidationManager.getViolatorDependencyRule(fromFile, psiClass.getContainingFile()) == null) {
234         result.add(psiClass);
235       }
236     }
237     return result;
238   }
239
240   private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) {
241     if (!PsiUtil.isLanguageLevel5OrHigher(psiElement)) {
242       return ThreeState.NO;
243     }
244     if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) {
245       return ThreeState.YES;
246     }
247     if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) {
248       return ThreeState.UNSURE;
249     }
250     return ThreeState.NO;
251   }
252
253   public static void importClass(@NotNull final Module currentModule,
254                                  @Nullable final Editor editor,
255                                  @Nullable final PsiReference reference,
256                                  @Nullable @NonNls final String className) {
257     final Project project = currentModule.getProject();
258     if (editor != null && reference != null && className != null) {
259       DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
260         @Override
261         public void run() {
262           GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(currentModule);
263           PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, scope);
264           if (aClass != null) {
265             new AddImportAction(project, reference, editor, aClass).execute();
266           }
267         }
268       });
269     }
270   }
271
272   public static void addJarToRoots(@NotNull String jarPath, final @NotNull Module module, @Nullable PsiElement location) {
273     addJarsToRoots(Collections.singletonList(jarPath), null, module, location);
274   }
275
276   public static void addJarsToRoots(@NotNull final List<String> jarPaths, @Nullable final String libraryName,
277                                     @NotNull final Module module, @Nullable final PsiElement location) {
278     final Boolean isAdded = provideFix(new Function<MissingDependencyFixProvider, Boolean>() {
279       @Override
280       public Boolean fun(MissingDependencyFixProvider provider) {
281         return provider.addJarsToRoots(jarPaths, libraryName, module, location);
282       }
283     });
284     if (Boolean.TRUE.equals(isAdded)) return;
285
286     List<String> urls = refreshAndConvertToUrls(jarPaths);
287     DependencyScope scope = suggestScopeByLocation(module, location);
288     ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.<String>emptyList(),
289                                                 scope);
290   }
291
292   @NotNull
293   public static List<String> refreshAndConvertToUrls(@NotNull List<String> jarPaths) {
294     return ContainerUtil.map(jarPaths, new Function<String, String>() {
295         @Override
296         public String fun(String path) {
297           return refreshAndConvertToUrl(path);
298         }
299       });
300   }
301
302   @NotNull
303   public static DependencyScope suggestScopeByLocation(@NotNull Module module, @Nullable PsiElement location) {
304     if (location != null) {
305       final VirtualFile vFile = location.getContainingFile().getVirtualFile();
306       if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) {
307         return DependencyScope.TEST;
308       }
309     }
310     return DependencyScope.COMPILE;
311   }
312
313   @NotNull
314   private static String refreshAndConvertToUrl(String jarPath) {
315     final File libraryRoot = new File(jarPath);
316     LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot);
317     return VfsUtil.getUrlForLibraryRoot(libraryRoot);
318   }
319
320   @NotNull
321   public static List<String> locateAnnotationsJars(@NotNull Module module) {
322     List<String> defaultPaths = JetBrainsAnnotationsExternalLibraryResolver.getAnnotationsLibraryDescriptor(module).getLibraryClassesRoots();
323     final LocateLibraryDialog dialog = new LocateLibraryDialog(module, defaultPaths, "JetBrains Annotations");
324     return dialog.showAndGetResult();
325   }
326 }