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
;
54 public abstract class ImportClassFixBase
<T
extends PsiElement
& PsiReference
> implements HintAction
{
55 private final T myRef
;
57 protected ImportClassFixBase(T ref
) {
61 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
62 return myRef
.isValid() && file
.getManager().isInProject(file
) && !getClassesToImport().isEmpty();
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();
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
);
100 protected abstract boolean isAccessible(PsiClass aClass
, T reference
);
102 protected abstract String
getQualifiedName(T reference
);
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
;
115 String name
= getQualifiedName(myRef
);
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
) {
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() {
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
)) {
173 Result result
= doFix(editor
, true, false);
174 return result
== Result
.POPUP_SHOWN
|| result
== Result
.CLASS_IMPORTED
;
178 public String
getText() {
179 return QuickFixBundle
.message("import.class.fix");
183 public String
getFamilyName() {
184 return QuickFixBundle
.message("import.class.fix");
187 public boolean startInWriteAction() {
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() {
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
);
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
) {
237 protected void bindReference(PsiReference ref
, PsiClass targetClass
) {
238 ImportClassFixBase
.this.bindReference((T
)ref
, targetClass
);