highlighting fix
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / ImportClassFixBase.java
blob8803aa9b312379999f0437b55a1b21fcffbacb6a
1 /*
2 * Copyright 2000-2009 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.CodeInsightSettings;
19 import com.intellij.codeInsight.daemon.QuickFixBundle;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
23 import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
24 import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
25 import com.intellij.codeInsight.completion.JavaCompletionUtil;
26 import com.intellij.codeInsight.CodeInsightUtil;
27 import com.intellij.codeInsight.CodeInsightUtilBase;
28 import com.intellij.codeInsight.hint.QuestionAction;
29 import com.intellij.codeInsight.hint.HintManager;
30 import com.intellij.codeInspection.HintAction;
31 import com.intellij.psi.*;
32 import com.intellij.psi.search.PsiShortNamesCache;
33 import com.intellij.psi.search.GlobalSearchScope;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.editor.Editor;
36 import com.intellij.openapi.command.CommandProcessor;
37 import com.intellij.openapi.util.TextRange;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.packageDependencies.DependencyValidationManager;
40 import com.intellij.packageDependencies.DependencyRule;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
44 import java.util.List;
45 import java.util.Collections;
46 import java.util.ArrayList;
47 import java.util.regex.Pattern;
48 import java.util.regex.Matcher;
49 import java.util.regex.PatternSyntaxException;
51 /**
52 * @author peter
54 public abstract class ImportClassFixBase<T extends PsiElement & PsiReference> implements HintAction {
55 private final T myRef;
57 protected ImportClassFixBase(T ref) {
58 myRef = ref;
61 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
62 return myRef.isValid() && file.getManager().isInProject(file) && !getClassesToImport().isEmpty();
65 @Nullable
66 protected abstract String getReferenceName(T reference);
68 protected abstract boolean hasTypeParameters(T reference);
70 public List<PsiClass> getClassesToImport() {
71 PsiManager manager = PsiManager.getInstance(myRef.getProject());
72 PsiShortNamesCache cache = JavaPsiFacade.getInstance(manager.getProject()).getShortNamesCache();
73 String name = getReferenceName(myRef);
74 GlobalSearchScope scope = myRef.getResolveScope();
75 if (name == null) {
76 return Collections.emptyList();
78 boolean referenceHasTypeParameters = hasTypeParameters(myRef);
79 PsiClass[] classes = cache.getClassesByName(name, scope);
80 if (classes.length == 0) return Collections.emptyList();
81 ArrayList<PsiClass> classList = new ArrayList<PsiClass>(classes.length);
82 boolean isAnnotationReference = myRef.getParent() instanceof PsiAnnotation;
83 for (PsiClass aClass : classes) {
84 if (isAnnotationReference && !aClass.isAnnotationType()) continue;
85 if (JavaCompletionUtil.isInExcludedPackage(aClass)) continue;
86 if (referenceHasTypeParameters && !aClass.hasTypeParameters()) continue;
87 String qName = aClass.getQualifiedName();
88 if (qName != null) { //filter local classes
89 if (qName.indexOf('.') == -1) continue; //do not show classes from default package)
90 if (qName.endsWith(name)) {
91 if (isAccessible(aClass, myRef)) {
92 classList.add(aClass);
97 return classList;
100 protected abstract boolean isAccessible(PsiClass aClass, T reference);
102 protected abstract String getQualifiedName(T reference);
104 public enum Result {
105 POPUP_SHOWN,
106 CLASS_IMPORTED,
107 POPUP_NOT_SHOWN
110 public Result doFix(@NotNull final Editor editor, boolean doShow, final boolean allowCaretNearRef) {
111 List<PsiClass> classesToImport = getClassesToImport();
112 if (classesToImport.isEmpty()) return Result.POPUP_NOT_SHOWN;
114 try {
115 String name = getQualifiedName(myRef);
116 if (name != null) {
117 Pattern pattern = Pattern.compile(DaemonCodeAnalyzerSettings.getInstance().NO_AUTO_IMPORT_PATTERN);
118 Matcher matcher = pattern.matcher(name);
119 if (matcher.matches()) {
120 return Result.POPUP_NOT_SHOWN;
124 catch (PatternSyntaxException e) {
125 //ignore
127 final PsiFile psiFile = myRef.getContainingFile();
128 if (classesToImport.size() > 1) {
129 reduceSuggestedClassesBasedOnDependencyRuleViolation(psiFile, classesToImport);
131 PsiClass[] classes = classesToImport.toArray(new PsiClass[classesToImport.size()]);
132 final Project project = myRef.getProject();
133 CodeInsightUtil.sortIdenticalShortNameClasses(classes, psiFile);
135 final QuestionAction action = createAddImportAction(classes, project, editor);
137 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(project);
139 boolean canImportHere = true;
141 if (classes.length == 1
142 && (canImportHere = canImportHere(allowCaretNearRef, editor, psiFile, classes[0].getName()))
143 && CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY
144 && codeAnalyzer.canChangeFileSilently(psiFile)) {
145 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
146 public void run() {
147 action.execute();
150 return Result.CLASS_IMPORTED;
153 if (doShow && canImportHere) {
154 String hintText = ShowAutoImportPass.getMessage(classes.length > 1, classes[0].getQualifiedName());
155 HintManager.getInstance().showQuestionHint(editor, hintText, myRef.getTextOffset(), myRef.getTextRange().getEndOffset(), action);
156 return Result.POPUP_SHOWN;
158 return Result.POPUP_NOT_SHOWN;
161 private boolean canImportHere(boolean allowCaretNearRef, Editor editor, PsiFile psiFile, String exampleClassName) {
162 return (allowCaretNearRef || !isCaretNearRef(editor, myRef)) &&
163 !JspPsiUtil.isInJspFile(psiFile) &&
164 !hasUnresolvedImportWhichCanImport(psiFile, exampleClassName);
167 protected abstract boolean isQualified(T reference);
169 public boolean showHint(final Editor editor) {
170 if (isQualified(myRef)) {
171 return false;
173 Result result = doFix(editor, true, false);
174 return result == Result.POPUP_SHOWN || result == Result.CLASS_IMPORTED;
177 @NotNull
178 public String getText() {
179 return QuickFixBundle.message("import.class.fix");
182 @NotNull
183 public String getFamilyName() {
184 return QuickFixBundle.message("import.class.fix");
187 public boolean startInWriteAction() {
188 return false;
191 protected abstract boolean hasUnresolvedImportWhichCanImport(PsiFile psiFile, String name);
193 private static void reduceSuggestedClassesBasedOnDependencyRuleViolation(PsiFile file, List<PsiClass> availableClasses) {
194 final Project project = file.getProject();
195 final DependencyValidationManager validationManager = DependencyValidationManager.getInstance(project);
196 for (int i = availableClasses.size() - 1; i >= 0; i--) {
197 PsiClass psiClass = availableClasses.get(i);
198 PsiFile targetFile = psiClass.getContainingFile();
199 if (targetFile == null) continue;
200 final DependencyRule[] violated = validationManager.getViolatorDependencyRules(file, targetFile);
201 if (violated.length != 0) {
202 availableClasses.remove(i);
203 if (availableClasses.size() == 1) break;
208 private static boolean isCaretNearRef(Editor editor, PsiElement ref) {
209 TextRange range = ref.getTextRange();
210 int offset = editor.getCaretModel().getOffset();
212 return offset == range.getEndOffset();
215 public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) {
216 if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
217 ApplicationManager.getApplication().runWriteAction(new Runnable() {
218 public void run() {
219 List<PsiClass> classesToImport = getClassesToImport();
220 PsiClass[] classes = classesToImport.toArray(new PsiClass[classesToImport.size()]);
221 CodeInsightUtil.sortIdenticalShortNameClasses(classes, file);
222 if (classes.length == 0) return;
224 AddImportAction action = createAddImportAction(classes, project, editor);
225 action.execute();
230 protected void bindReference(T reference, PsiClass targetClass) {
231 reference.bindToElement(targetClass);
234 protected AddImportAction createAddImportAction(PsiClass[] classes, Project project, Editor editor) {
235 return new AddImportAction(project, myRef, editor, classes) {
236 @Override
237 protected void bindReference(PsiReference ref, PsiClass targetClass) {
238 ImportClassFixBase.this.bindReference((T)ref, targetClass);