disable on demand static imports for trivial cases
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / intention / impl / AddOnDemandStaticImportAction.java
1 /*
2  * Copyright 2000-2009 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.intention.impl;
17
18 import com.intellij.codeInsight.CodeInsightBundle;
19 import com.intellij.codeInsight.CodeInsightUtilBase;
20 import com.intellij.codeInsight.highlighting.HighlightManager;
21 import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.colors.EditorColors;
25 import com.intellij.openapi.editor.colors.EditorColorsManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.psi.*;
28 import com.intellij.psi.util.PsiTreeUtil;
29 import com.intellij.psi.util.PsiUtil;
30 import com.intellij.util.IncorrectOperationException;
31 import gnu.trove.TIntArrayList;
32 import gnu.trove.TIntProcedure;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 /**
37  * @author ven
38  */
39 public class AddOnDemandStaticImportAction extends PsiElementBaseIntentionAction {
40   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.AddOnDemandStaticImportAction");
41
42   @NotNull
43   public String getFamilyName() {
44     return CodeInsightBundle.message("intention.add.on.demand.static.import.family");
45   }
46
47   /**
48    * Allows to check if static import may be performed for the given element.
49    * 
50    * @param element     element to check
51    * @return            target class that may be statically imported if any; <code>null</code> otherwise
52    */
53   @Nullable
54   public static PsiClass getClassToPerformStaticImport(@NotNull PsiElement element) {
55     if (!PsiUtil.isLanguageLevel5OrHigher(element)) return null;
56     if (!(element instanceof PsiIdentifier) || !(element.getParent() instanceof PsiJavaCodeReferenceElement)) {
57       return null;
58     }
59     PsiJavaCodeReferenceElement refExpr = (PsiJavaCodeReferenceElement)element.getParent();
60     if (!(refExpr.getParent() instanceof PsiJavaCodeReferenceElement) ||
61         isParameterizedReference((PsiJavaCodeReferenceElement)refExpr.getParent())) return null;
62
63     PsiElement resolved = refExpr.resolve();
64     if (!(resolved instanceof PsiClass)) {
65       return null;
66     }
67     PsiClass psiClass = (PsiClass)resolved;
68     PsiFile file = refExpr.getContainingFile();
69     if (!(file instanceof PsiJavaFile)) return null;
70     PsiImportList importList = ((PsiJavaFile)file).getImportList();
71     if (importList == null) return null;
72     for (PsiImportStaticStatement statement : importList.getImportStaticStatements()) {
73       PsiClass staticResolve = statement.resolveTargetClass();
74       if (psiClass == staticResolve) return null; //already imported
75     }
76     
77     return psiClass;
78   }
79   
80   public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
81     PsiClass classToImport = getClassToPerformStaticImport(element);
82     if (classToImport != null) {
83       String text = CodeInsightBundle.message("intention.add.on.demand.static.import.text", classToImport.getQualifiedName());
84       setText(text);
85     }
86     return classToImport != null;
87   }
88
89   public static void invoke(final Project project, PsiFile file, final Editor editor, PsiElement element) {
90     if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
91     
92     final PsiJavaCodeReferenceElement refExpr = (PsiJavaCodeReferenceElement)element.getParent();
93     final PsiClass aClass = (PsiClass)refExpr.resolve();
94     if (aClass == null) {
95       return;
96     }
97     PsiImportStaticStatement importStaticStatement =
98       JavaPsiFacade.getInstance(file.getProject()).getElementFactory().createImportStaticStatement(aClass, "*");
99     PsiImportList importList = ((PsiJavaFile)file).getImportList();
100     if (importList == null) {
101       return;
102     }
103     importList.add(importStaticStatement);
104
105     PsiFile[] roots = file.getPsiRoots();
106     for (final PsiFile root : roots) {
107       PsiElement copy = root.copy();
108       final PsiManager manager = root.getManager();
109
110       final TIntArrayList expressionToDequalifyOffsets = new TIntArrayList();
111       copy.accept(new JavaRecursiveElementWalkingVisitor() {
112         int delta = 0;
113         @Override
114         public void visitReferenceElement(PsiJavaCodeReferenceElement expression) {
115           if (isParameterizedReference(expression)) return;
116           PsiElement qualifierExpression = expression.getQualifier();
117           if (qualifierExpression instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifierExpression).isReferenceTo(aClass)) {
118             try {
119               PsiElement resolved = expression.resolve();
120               int end = expression.getTextRange().getEndOffset();
121               qualifierExpression.delete();
122               delta += end - expression.getTextRange().getEndOffset();
123               PsiElement after = expression.resolve();
124               if (manager.areElementsEquivalent(after, resolved)) {
125                 expressionToDequalifyOffsets.add(expression.getTextRange().getStartOffset() + delta);
126               }
127             }
128             catch (IncorrectOperationException e) {
129               LOG.error(e);
130             }
131           }
132           super.visitElement(expression);
133         }
134       });
135
136       expressionToDequalifyOffsets.forEachDescending(new TIntProcedure() {
137         public boolean execute(int offset) {
138           PsiJavaCodeReferenceElement expression = PsiTreeUtil.findElementOfClassAtOffset(root, offset, PsiJavaCodeReferenceElement.class, false);
139           if (expression == null) {
140             return false;
141           }
142           PsiElement qualifierExpression = expression.getQualifier();
143           if (qualifierExpression instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifierExpression).isReferenceTo(aClass)) {
144             qualifierExpression.delete();
145             HighlightManager.getInstance(project)
146               .addRangeHighlight(editor, expression.getTextRange().getStartOffset(), expression.getTextRange().getEndOffset(),
147                                  EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES),
148                                  false, null);
149           }
150
151           return true;
152         }
153       });
154     }
155   }
156   
157   public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
158     PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
159     invoke(project, file, editor, element);
160   }
161
162   private static boolean isParameterizedReference(final PsiJavaCodeReferenceElement expression) {
163     if (expression.getParameterList() == null) {
164       return false;
165     }
166     PsiReferenceParameterList parameterList = expression.getParameterList();
167     return parameterList != null && parameterList.getFirstChild() != null;
168   }
169 }