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.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;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
63 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.findFixes;
64 import static com.intellij.codeInsight.daemon.impl.quickfix.MissingDependencyFixUtil.provideFix;
69 public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
70 public static final String JUNIT4_LIBRARY_NAME = "JUnit4";
72 protected OrderEntryFix() {
76 public boolean startInWriteAction() {
82 public String getName() {
87 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
88 invoke(project, null, descriptor.getPsiElement().getContainingFile());
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());
96 Project project = psiElement.getProject();
97 PsiFile containingFile = psiElement.getContainingFile();
98 if (containingFile == null) return null;
100 final VirtualFile classVFile = containingFile.getVirtualFile();
101 if (classVFile == null) return null;
103 final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
104 final Module currentModule = fileIndex.getModuleForFile(classVFile);
105 if (currentModule == null) return null;
107 final List<LocalQuickFix> providedFixes = findFixes(new Function<MissingDependencyFixProvider, List<LocalQuickFix>>() {
109 public List<LocalQuickFix> fun(MissingDependencyFixProvider provider) {
110 return provider.registerFixes(registrar, reference);
113 if (providedFixes != null) {
114 return providedFixes;
117 if (isAnnotation(psiElement) && AnnotationUtil.isJetbrainsAnnotation(referenceName)) {
118 @NonNls final String className = "org.jetbrains.annotations." + referenceName;
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() {
125 public String getText() {
126 return QuickFixBundle.message("orderEntry.fix.add.annotations.jar.to.classpath");
131 public String getFamilyName() {
136 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
137 return !project.isDisposed() && !currentModule.isDisposed();
141 public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) {
142 ApplicationManager.getApplication().invokeLater(new Runnable() {
145 final String libraryPath = locateAnnotationsJar(currentModule);
146 if (libraryPath != null) {
147 new WriteCommandAction(project) {
149 protected void run(@NotNull final Result result) throws Throwable {
150 addJarsToRootsAndImportClass(Collections.singletonList(libraryPath), null, currentModule, editor, reference,
151 "org.jetbrains.annotations." + referenceName);
160 final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
162 public OrderEntryFix fun(MissingDependencyFixProvider provider) {
163 return provider.getJetbrainsAnnotationFix(reference, platformFix, currentModule);
166 final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
168 registrar.register(fix);
169 return Collections.singletonList((LocalQuickFix)fix);
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()) {
180 classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
181 OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
183 final PsiClass[] finalClasses = classes;
184 final OrderEntryFix finalModuleDependencyFix = moduleDependencyFix;
185 final OrderEntryFix providedModuleDependencyFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
187 public OrderEntryFix fun(MissingDependencyFixProvider provider) {
188 return provider.getAddModuleDependencyFix(reference, finalModuleDependencyFix, currentModule, classVFile, finalClasses);
191 moduleDependencyFix = ObjectUtils.notNull(providedModuleDependencyFix, moduleDependencyFix);
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];
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))) {
219 final OrderEntryFix platformFix = new OrderEntryFix() {
222 public String getText() {
223 return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", libraryEntry.getPresentableName());
228 public String getFamilyName() {
229 return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath");
233 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
234 return !project.isDisposed() && !currentModule.isDisposed() && libraryEntry.isValid();
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() {
244 new AddImportAction(project, reference, editor, aClass).execute();
251 final OrderEntryFix providedFix = provideFix(new Function<MissingDependencyFixProvider, OrderEntryFix>() {
253 public OrderEntryFix fun(MissingDependencyFixProvider provider) {
254 return provider.getAddLibraryToClasspathFix(reference, platformFix, currentModule, libraryEntry, aClass);
257 final OrderEntryFix fix = ObjectUtils.notNull(providedFix, platformFix);
258 registrar.register(fix);
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);
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);
286 private static boolean isAnnotation(final PsiElement psiElement) {
287 return PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null && PsiUtil.isLanguageLevel5OrHigher(psiElement);
291 * @deprecated use {@link #addJarsToRootsAndImportClass} instead
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);
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);
307 final Project project = currentModule.getProject();
308 if (editor != null && reference != null && className != null) {
309 DumbService.getInstance(project).withAlternativeResolveEnabled(new Runnable() {
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();
322 public static void addJarToRoots(@NotNull String jarPath, final @NotNull Module module, @Nullable PsiElement location) {
323 addJarsToRoots(Collections.singletonList(jarPath), null, module, location);
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>() {
330 public Boolean fun(MissingDependencyFixProvider provider) {
331 return provider.addJarsToRoots(jarPaths, libraryName, module, location);
334 if (Boolean.TRUE.equals(isAdded)) return;
336 List<String> urls = ContainerUtil.map(jarPaths, new Function<String, String>() {
338 public String fun(String path) {
339 return refreshAndConvertToUrl(path);
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)) {
349 ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.<String>emptyList(),
350 inTests ? DependencyScope.TEST : DependencyScope.COMPILE);
354 private static String refreshAndConvertToUrl(String jarPath) {
355 final File libraryRoot = new File(jarPath);
356 LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot);
357 return VfsUtil.getUrlForLibraryRoot(libraryRoot);
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()) {
367 protected void run(@NotNull final Result result) throws Throwable {
368 addJarToRoots(libraryPath, module, null);
377 public static String locateAnnotationsJar(@NotNull Module module) {
380 if (EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_8)) {
381 jarName = "annotations-java8.jar";
382 libPath = new File(PathManager.getHomePath(), "redist").getAbsolutePath();
385 jarName = "annotations.jar";
386 libPath = PathManager.getLibPath();
388 final LocateLibraryDialog dialog = new LocateLibraryDialog(module, libPath, jarName, QuickFixBundle.message("add.library.annotations.description"));
389 return dialog.showAndGet() ? dialog.getResultingLibraryPath() : null;
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;