818f4badb3d375c0d6a99a7b4d4ef81f93f87824
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / codeInspection / local / GroovyUnusedImportPass.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
17 package org.jetbrains.plugins.groovy.codeInspection.local;
18
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.impl.*;
23 import com.intellij.codeInsight.intention.IntentionAction;
24 import com.intellij.codeInspection.ProblemHighlightType;
25 import com.intellij.lang.annotation.Annotation;
26 import com.intellij.lang.annotation.AnnotationHolder;
27 import com.intellij.lang.annotation.AnnotationSession;
28 import com.intellij.lang.annotation.HighlightSeverity;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.progress.ProgressIndicator;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.roots.ProjectRootManager;
36 import com.intellij.openapi.util.TextRange;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.psi.PsiElement;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
41 import com.intellij.util.Processor;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
44 import org.jetbrains.plugins.groovy.lang.editor.GroovyImportOptimizer;
45 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
46 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
47 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
48 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
49 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
50
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Set;
55
56 /**
57  * @author ilyas
58  */
59 public class GroovyUnusedImportPass extends TextEditorHighlightingPass {
60   private final GroovyFile myFile;
61   private final Editor myEditor;
62   public static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.codeInspection.local.GroovyUnusedImportsPass");
63   private volatile Set<GrImportStatement> myUnusedImports;
64   private volatile Runnable myOptimizeRunnable;
65
66   public GroovyUnusedImportPass(GroovyFile file, Editor editor) {
67     super(file.getProject(), editor.getDocument(), true);
68     myFile = file;
69     myEditor = editor;
70   }
71
72   public void doCollectInformation(ProgressIndicator progress) {
73     final Set<GrImportStatement> unusedImports = new HashSet<GrImportStatement>(GroovyImportOptimizer.getValidImportStatements(myFile));
74     myFile.accept(new PsiRecursiveElementWalkingVisitor() {
75       @Override
76       public void visitElement(PsiElement element) {
77         if (element instanceof GrReferenceElement) {
78           for (GroovyResolveResult result : ((GrReferenceElement)element).multiResolve(true)) {
79             GroovyPsiElement context = result.getCurrentFileResolveContext();
80             if (context instanceof GrImportStatement) {
81               GrImportStatement importStatement = (GrImportStatement)context;
82               unusedImports.remove(importStatement);
83             }
84           }
85         }
86         super.visitElement(element);
87       }
88     });
89     myUnusedImports = unusedImports;
90     if (!unusedImports.isEmpty() && CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
91       final VirtualFile vfile = myFile.getVirtualFile();
92       if (vfile != null && ProjectRootManager.getInstance(myFile.getProject()).getFileIndex().isInSource(vfile)) {
93         final GrImportStatement[] imports = myFile.getImportStatements();
94         if (imports.length > 0) {
95           final int offset = myEditor.getCaretModel().getOffset();
96           if (imports[0].getTextRange().getStartOffset() <= offset && offset <= imports[imports.length - 1].getTextRange().getEndOffset()) {
97             return;
98           }
99         }
100
101         myOptimizeRunnable = new GroovyImportOptimizer().processFile(myFile);
102       }
103     }
104
105   }
106
107   private static IntentionAction createUnusedImportIntention() {
108     return new IntentionAction() {
109
110       @NotNull
111       public String getText() {
112         return GroovyInspectionBundle.message("optimize.all.imports");
113       }
114
115       @NotNull
116       public String getFamilyName() {
117         return GroovyInspectionBundle.message("optimize.imports");
118       }
119
120       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
121         return true;
122       }
123
124       public void invoke(@NotNull final Project project, Editor editor, PsiFile file) {
125         optimizeImports(project, file);
126       }
127
128       public boolean startInWriteAction() {
129         return true;
130       }
131     };
132   }
133
134   public static void optimizeImports(final Project project, PsiFile file) {
135     GroovyImportOptimizer optimizer = new GroovyImportOptimizer();
136     final Runnable runnable = optimizer.processFile(file);
137     ApplicationManager.getApplication().runWriteAction(new Runnable() {
138       public void run() {
139         CommandProcessor.getInstance().executeCommand(project, runnable, "optimize imports", this);
140       }
141     });
142   }
143
144   public void doApplyInformationToEditor() {
145     AnnotationHolder annotationHolder = new AnnotationHolderImpl(new AnnotationSession(myFile));
146     List<HighlightInfo> infos = new ArrayList<HighlightInfo>(myUnusedImports.size());
147     for (GrImportStatement unusedImport : myUnusedImports) {
148       Annotation annotation = annotationHolder.createWarningAnnotation(unusedImport, GroovyInspectionBundle.message("unused.import"));
149       annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
150       annotation.registerFix(createUnusedImportIntention());
151       infos.add(HighlightInfo.fromAnnotation(annotation));
152     }
153
154     UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), infos, getColorsScheme(), getId());
155
156     final Runnable optimize = myOptimizeRunnable;
157     if (optimize != null && timeToOptimizeImports()) {
158       PostHighlightingPass.invokeOnTheFlyImportOptimizer(new Runnable() {
159         @Override
160         public void run() {
161           optimize.run();
162         }
163       },myFile,myEditor);
164     }
165   }
166
167   private boolean timeToOptimizeImports() {
168     if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
169
170     DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
171     if (!codeAnalyzer.isHighlightingAvailable(myFile)) return false;
172
173     if (!codeAnalyzer.isErrorAnalyzingFinished(myFile)) return false;
174     boolean errors = containsErrorsPreventingOptimize();
175
176     return !errors && codeAnalyzer.canChangeFileSilently(myFile);
177   }
178
179   private boolean containsErrorsPreventingOptimize() {
180     // ignore unresolved imports errors
181     final TextRange ignoreRange;
182     final GrImportStatement[] imports = myFile.getImportStatements();
183     if (imports.length != 0) {
184       final int start = imports[0].getTextRange().getStartOffset();
185       final int end = imports[imports.length - 1].getTextRange().getEndOffset();
186       ignoreRange = new TextRange(start, end);
187     } else {
188       ignoreRange = TextRange.EMPTY_RANGE;
189     }
190
191     return !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
192       public boolean process(HighlightInfo error) {
193         int infoStart = error.getActualStartOffset();
194         int infoEnd = error.getActualEndOffset();
195
196         return ignoreRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
197       }
198     });
199   }
200
201
202 }