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
.daemon
.QuickFixBundle
;
19 import com
.intellij
.codeInsight
.daemon
.DaemonCodeAnalyzerSettings
;
20 import com
.intellij
.codeInsight
.daemon
.DaemonCodeAnalyzer
;
21 import com
.intellij
.codeInsight
.daemon
.impl
.actions
.AddImportAction
;
22 import com
.intellij
.codeInsight
.daemon
.impl
.DaemonCodeAnalyzerImpl
;
23 import com
.intellij
.codeInsight
.daemon
.impl
.ShowAutoImportPass
;
24 import com
.intellij
.codeInsight
.completion
.JavaCompletionUtil
;
25 import com
.intellij
.codeInsight
.CodeInsightUtil
;
26 import com
.intellij
.codeInsight
.CodeInsightUtilBase
;
27 import com
.intellij
.codeInsight
.hint
.QuestionAction
;
28 import com
.intellij
.codeInsight
.hint
.HintManager
;
29 import com
.intellij
.codeInspection
.HintAction
;
30 import com
.intellij
.psi
.*;
31 import com
.intellij
.psi
.search
.PsiShortNamesCache
;
32 import com
.intellij
.psi
.search
.GlobalSearchScope
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.editor
.Editor
;
35 import com
.intellij
.openapi
.command
.CommandProcessor
;
36 import com
.intellij
.openapi
.util
.TextRange
;
37 import com
.intellij
.openapi
.application
.ApplicationManager
;
38 import com
.intellij
.packageDependencies
.DependencyValidationManager
;
39 import com
.intellij
.packageDependencies
.DependencyRule
;
40 import org
.jetbrains
.annotations
.NotNull
;
41 import org
.jetbrains
.annotations
.Nullable
;
43 import java
.util
.List
;
44 import java
.util
.Collections
;
45 import java
.util
.ArrayList
;
46 import java
.util
.regex
.Pattern
;
47 import java
.util
.regex
.Matcher
;
48 import java
.util
.regex
.PatternSyntaxException
;
53 public abstract class ImportClassFixBase
<T
extends PsiElement
& PsiReference
> implements HintAction
{
54 private final T myRef
;
56 protected ImportClassFixBase(T ref
) {
60 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
61 return myRef
.isValid() && file
.getManager().isInProject(file
) && !getClassesToImport().isEmpty();
65 protected abstract String
getReferenceName(T reference
);
67 protected abstract boolean hasTypeParameters(T reference
);
69 public List
<PsiClass
> getClassesToImport() {
70 PsiManager manager
= PsiManager
.getInstance(myRef
.getProject());
71 PsiShortNamesCache cache
= JavaPsiFacade
.getInstance(manager
.getProject()).getShortNamesCache();
72 String name
= getReferenceName(myRef
);
73 GlobalSearchScope scope
= myRef
.getResolveScope();
75 return Collections
.emptyList();
77 boolean referenceHasTypeParameters
= hasTypeParameters(myRef
);
78 PsiClass
[] classes
= cache
.getClassesByName(name
, scope
);
79 if (classes
.length
== 0) return Collections
.emptyList();
80 ArrayList
<PsiClass
> classList
= new ArrayList
<PsiClass
>(classes
.length
);
81 boolean isAnnotationReference
= myRef
.getParent() instanceof PsiAnnotation
;
82 for (PsiClass aClass
: classes
) {
83 if (isAnnotationReference
&& !aClass
.isAnnotationType()) continue;
84 if (JavaCompletionUtil
.isInExcludedPackage(aClass
)) continue;
85 if (referenceHasTypeParameters
&& !aClass
.hasTypeParameters()) continue;
86 String qName
= aClass
.getQualifiedName();
87 if (qName
!= null) { //filter local classes
88 if (qName
.indexOf('.') == -1) continue; //do not show classes from default package)
89 if (qName
.endsWith(name
)) {
90 if (isAccessible(aClass
, myRef
)) {
91 classList
.add(aClass
);
99 protected abstract boolean isAccessible(PsiClass aClass
, T reference
);
101 protected abstract String
getQualifiedName(T reference
);
103 public boolean doFix(@NotNull final Editor editor
, boolean doShow
, final boolean allowCaretNearRef
) {
104 List
<PsiClass
> classesToImport
= getClassesToImport();
105 if (classesToImport
.isEmpty()) return false;
108 String name
= getQualifiedName(myRef
);
110 Pattern pattern
= Pattern
.compile(DaemonCodeAnalyzerSettings
.getInstance().NO_AUTO_IMPORT_PATTERN
);
111 Matcher matcher
= pattern
.matcher(name
);
112 if (matcher
.matches()) {
117 catch (PatternSyntaxException e
) {
120 final PsiFile psiFile
= myRef
.getContainingFile();
121 if (classesToImport
.size() > 1) {
122 reduceSuggestedClassesBasedOnDependencyRuleViolation(psiFile
, classesToImport
);
124 PsiClass
[] classes
= classesToImport
.toArray(new PsiClass
[classesToImport
.size()]);
125 final Project project
= myRef
.getProject();
126 CodeInsightUtil
.sortIdenticalShortNameClasses(classes
, psiFile
);
128 final QuestionAction action
= createAddImportAction(classes
, project
, editor
);
130 DaemonCodeAnalyzerImpl codeAnalyzer
= (DaemonCodeAnalyzerImpl
)DaemonCodeAnalyzer
.getInstance(project
);
132 if (classes
.length
== 1
133 && com
.intellij
.codeInsight
.CodeInsightSettings
.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY
134 && (allowCaretNearRef
|| !isCaretNearRef(editor
, myRef
))
135 && !JspPsiUtil
.isInJspFile(psiFile
)
136 && codeAnalyzer
.canChangeFileSilently(psiFile
)
137 && !hasUnresolvedImportWhichCanImport(psiFile
, classes
[0].getName())) {
138 CommandProcessor
.getInstance().runUndoTransparentAction(new Runnable() {
146 String hintText
= ShowAutoImportPass
.getMessage(classes
.length
> 1, classes
[0].getQualifiedName());
147 HintManager
.getInstance().showQuestionHint(editor
, hintText
, myRef
.getTextOffset(), myRef
.getTextRange().getEndOffset(), action
);
152 protected abstract boolean isQualified(T reference
);
154 public boolean showHint(final Editor editor
) {
155 return !isQualified(myRef
) && doFix(editor
, true, false);
159 public String
getText() {
160 return QuickFixBundle
.message("import.class.fix");
164 public String
getFamilyName() {
165 return QuickFixBundle
.message("import.class.fix");
168 public boolean startInWriteAction() {
172 protected abstract boolean hasUnresolvedImportWhichCanImport(PsiFile psiFile
, String name
);
174 private static void reduceSuggestedClassesBasedOnDependencyRuleViolation(PsiFile file
, List
<PsiClass
> availableClasses
) {
175 final Project project
= file
.getProject();
176 final DependencyValidationManager validationManager
= DependencyValidationManager
.getInstance(project
);
177 for (int i
= availableClasses
.size() - 1; i
>= 0; i
--) {
178 PsiClass psiClass
= availableClasses
.get(i
);
179 PsiFile targetFile
= psiClass
.getContainingFile();
180 if (targetFile
== null) continue;
181 final DependencyRule
[] violated
= validationManager
.getViolatorDependencyRules(file
, targetFile
);
182 if (violated
.length
!= 0) {
183 availableClasses
.remove(i
);
184 if (availableClasses
.size() == 1) break;
189 private static boolean isCaretNearRef(Editor editor
, PsiElement ref
) {
190 TextRange range
= ref
.getTextRange();
191 int offset
= editor
.getCaretModel().getOffset();
193 return range
.grown(1).contains(offset
);
196 public void invoke(@NotNull final Project project
, final Editor editor
, final PsiFile file
) {
197 if (!CodeInsightUtilBase
.prepareFileForWrite(file
)) return;
198 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
200 List
<PsiClass
> classesToImport
= getClassesToImport();
201 PsiClass
[] classes
= classesToImport
.toArray(new PsiClass
[classesToImport
.size()]);
202 CodeInsightUtil
.sortIdenticalShortNameClasses(classes
, file
);
203 if (classes
.length
== 0) return;
205 AddImportAction action
= createAddImportAction(classes
, project
, editor
);
211 protected void bindReference(T reference
, PsiClass targetClass
) {
212 reference
.bindToElement(targetClass
);
215 protected AddImportAction
createAddImportAction(PsiClass
[] classes
, Project project
, Editor editor
) {
216 return new AddImportAction(project
, myRef
, editor
, classes
) {
218 protected void bindReference(PsiReference ref
, PsiClass targetClass
) {
219 ImportClassFixBase
.this.bindReference((T
)ref
, targetClass
);