IDEADEV-40882 add static imports when needed
[idea/community.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / pattern / compiler / AnnotationBasedInstrumentingCompiler.java
1 /*
2  * Copyright 2006 Sascha Weinreuter
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.intellij.plugins.intelliLang.pattern.compiler;
18
19 import com.intellij.compiler.PsiClassWriter;
20 import com.intellij.lang.StdLanguages;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.compiler.*;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.progress.ProgressIndicator;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.projectRoots.Sdk;
28 import com.intellij.openapi.roots.CompilerModuleExtension;
29 import com.intellij.openapi.roots.ModuleRootManager;
30 import com.intellij.openapi.roots.ProjectFileIndex;
31 import com.intellij.openapi.roots.ProjectRootManager;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.psi.*;
35 import com.intellij.psi.search.GlobalSearchScope;
36 import com.intellij.psi.search.PsiSearchHelper;
37 import com.intellij.util.Processor;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.objectweb.asm.ClassReader;
41 import org.objectweb.asm.ClassWriter;
42
43 import java.io.DataInput;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.util.ArrayList;
47 import java.util.HashSet;
48 import java.util.Set;
49
50 /**
51  * Based on NotNullVerifyingCompiler, kindly provided by JetBrains for reference.
52  */
53 public abstract class AnnotationBasedInstrumentingCompiler implements ClassInstrumentingCompiler {
54   private static final Logger LOG = Logger.getInstance("org.intellij.lang.pattern.compiler.AnnotationBasedInstrumentingCompiler");
55
56   public AnnotationBasedInstrumentingCompiler() {
57   }
58
59   @NotNull
60   public ProcessingItem[] getProcessingItems(final CompileContext context) {
61     if (!isEnabled()) {
62       return ProcessingItem.EMPTY_ARRAY;
63     }
64
65     final Project project = context.getProject();
66     final Set<InstrumentationItem> result = new HashSet<InstrumentationItem>();
67     final PsiSearchHelper searchHelper = PsiManager.getInstance(context.getProject()).getSearchHelper();
68     ApplicationManager.getApplication().runReadAction(new Runnable() {
69       public void run() {
70         final String[] names = getAnnotationNames(project);
71         for (String name : names) {
72           final GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
73
74           final PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(name, GlobalSearchScope.allScope(project));
75           if (psiClass == null) {
76             context.addMessage(CompilerMessageCategory.ERROR, "Cannot find class " + name, null, -1, -1);
77             continue;
78           }
79
80           // wow, this is a sweet trick... ;)
81           searchHelper.processAllFilesWithWord(StringUtil.getShortName(name), scope, new Processor<PsiFile>() {
82             public boolean process(PsiFile psifile) {
83               if (StdLanguages.JAVA == psifile.getLanguage() && psifile.getVirtualFile() != null && psifile instanceof PsiJavaFile) {
84                 addClassFiles((PsiJavaFile)psifile, result, project);
85               }
86               return true;
87             }
88           }, true);
89         }
90       }
91     });
92     return result.toArray(new ProcessingItem[result.size()]);
93   }
94
95   private static void addClassFiles(PsiJavaFile srcFile, Set<InstrumentationItem> result, final Project project) {
96
97     final VirtualFile sourceFile = srcFile.getVirtualFile();
98     assert sourceFile != null;
99
100     final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
101     final Module module = index.getModuleForFile(sourceFile);
102     if (module != null) {
103       final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
104       final boolean jdk6;
105       if (jdk != null) {
106         final String versionString = jdk.getVersionString();
107         jdk6 = versionString != null && (versionString.contains("1.6") || versionString.contains("6.0"));
108       }
109       else {
110         jdk6 = false;
111       }
112
113       final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
114       final VirtualFile compilerOutputPath = extension != null ? extension.getCompilerOutputPath() : null;
115       if (compilerOutputPath != null) {
116         final String packageName = srcFile.getPackageName();
117         final VirtualFile packageDir =
118             packageName.length() > 0 ? compilerOutputPath.findFileByRelativePath(packageName.replace('.', '/')) : compilerOutputPath;
119
120         if (packageDir != null && packageDir.isDirectory()) {
121           final PsiClass[] classes = srcFile.getClasses();
122           final VirtualFile[] children = packageDir.getChildren();
123           for (VirtualFile classFile : children) {
124             if (classFile.isDirectory() || !"class".equals(classFile.getExtension())) {
125               // no point in looking at directories or non-class files
126               continue;
127             }
128             final String name = classFile.getName();
129             for (PsiClass clazz : classes) {
130               final String className = clazz.getName();
131               if (className != null && name.startsWith(className)) {
132                 result.add(new InstrumentationItem(classFile, jdk6));
133               }
134             }
135           }
136         }
137       }
138     }
139   }
140
141   public ProcessingItem[] process(final CompileContext context, final ProcessingItem[] items) {
142     final ProgressIndicator progressIndicator = context.getProgressIndicator();
143     progressIndicator.setText(getProgressMessage());
144
145     final Project project = context.getProject();
146     final ArrayList<ProcessingItem> result = new ArrayList<ProcessingItem>(items.length);
147     ApplicationManager.getApplication().runReadAction(new Runnable() {
148
149       public void run() {
150         int filesProcessed = 0;
151
152         for (ProcessingItem pi : items) {
153           final InstrumentationItem item = ((InstrumentationItem)pi);
154           final VirtualFile classFile = item.getClassFile();
155
156           try {
157             final byte[] bytes = classFile.contentsToByteArray();
158             final ClassReader classreader;
159             try {
160               classreader = new ClassReader(bytes, 0, bytes.length);
161             }
162             catch (Exception e) {
163               LOG.debug("ASM failed to read class file <" + classFile.getPresentableUrl() + ">", e);
164               continue;
165             }
166
167             final ClassWriter classwriter = new PsiClassWriter(project, item.isJDK6());
168             final Instrumenter instrumenter = createInstrumenter(classwriter);
169
170             classreader.accept(instrumenter, 0);
171
172             if (instrumenter.instrumented()) {
173               // only dump the class if it has actually been instrumented
174               final FileOutputStream out = new FileOutputStream(classFile.getPath());
175               try {
176                 out.write(classwriter.toByteArray());
177               }
178               finally {
179                 out.close();
180               }
181             }
182
183             result.add(item);
184             progressIndicator.setFraction(++filesProcessed / (double)items.length);
185           }
186           catch (InstrumentationException e) {
187             context.addMessage(CompilerMessageCategory.ERROR, "[" + getDescription() + "]: " + e.getLocalizedMessage(), null, -1, -1);
188           }
189           catch (IOException e) {
190             context.addMessage(CompilerMessageCategory.ERROR, "[" + getDescription() + "]: " + e.getLocalizedMessage(), null, -1, -1);
191           }
192         }
193       }
194     });
195     return result.toArray(new ProcessingItem[result.size()]);
196   }
197
198   protected abstract boolean isEnabled();
199
200   protected abstract String[] getAnnotationNames(Project project);
201
202   protected abstract Instrumenter createInstrumenter(ClassWriter classwriter);
203
204   protected abstract String getProgressMessage();
205
206   public boolean validateConfiguration(CompileScope compilescope) {
207     return true;
208   }
209
210   @Nullable
211   public ValidityState createValidityState(DataInput datainputstream) throws IOException {
212 //        return TimestampValidityState.load(datainputstream);
213     return null;
214   }
215 }