better painting for bread crumbs
[idea/community.git] / plugins / ByteCodeViewer / src / com / intellij / byteCodeViewer / ByteCodeViewerManager.java
1 /*
2  * Copyright 2000-2013 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.byteCodeViewer;
17
18 import com.intellij.codeInsight.documentation.DockablePopupManager;
19 import com.intellij.ide.util.JavaAnonymousClassesHelper;
20 import com.intellij.openapi.components.ServiceManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.extensions.ExtensionPointName;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.module.ModuleUtilCore;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.roots.CompilerModuleExtension;
28 import com.intellij.openapi.roots.ProjectRootManager;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.*;
33 import com.intellij.psi.presentation.java.SymbolPresentationUtil;
34 import com.intellij.psi.util.ClassUtil;
35 import com.intellij.psi.util.PsiTreeUtil;
36 import com.intellij.psi.util.PsiUtilCore;
37 import com.intellij.ui.content.Content;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.org.objectweb.asm.ClassReader;
41 import org.jetbrains.org.objectweb.asm.util.Textifier;
42 import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor;
43
44 import java.io.File;
45 import java.io.IOException;
46 import java.io.PrintWriter;
47 import java.io.StringWriter;
48
49 /**
50  * @author anna
51  * @since 5/7/12
52  */
53 public class ByteCodeViewerManager extends DockablePopupManager<ByteCodeViewerComponent> {
54   private static final ExtensionPointName<ClassSearcher> CLASS_SEARCHER_EP = ExtensionPointName.create("ByteCodeViewer.classSearcher");
55
56   private static final Logger LOG = Logger.getInstance("#" + ByteCodeViewerManager.class.getName());
57
58   public static final String TOOLWINDOW_ID = "Byte Code Viewer";
59   private static final String SHOW_BYTECODE_IN_TOOL_WINDOW = "BYTE_CODE_TOOL_WINDOW";
60   private static final String BYTECODE_AUTO_UPDATE_ENABLED = "BYTE_CODE_AUTO_UPDATE_ENABLED";
61
62   public static ByteCodeViewerManager getInstance(Project project) {
63     return ServiceManager.getService(project, ByteCodeViewerManager.class);
64   }
65
66   public ByteCodeViewerManager(Project project) {
67     super(project);
68   }
69
70   @Override
71   public String getShowInToolWindowProperty() {
72     return SHOW_BYTECODE_IN_TOOL_WINDOW;
73   }
74
75   @Override
76   public String getAutoUpdateEnabledProperty() {
77     return BYTECODE_AUTO_UPDATE_ENABLED;
78   }
79
80   @Override
81   protected String getToolwindowId() {
82     return TOOLWINDOW_ID;
83   }
84
85   @Override
86   protected String getAutoUpdateTitle() {
87     return "Auto Show Byte Code for Selected Element";
88   }
89
90   @Override
91   protected String getAutoUpdateDescription() {
92     return "Show byte code for current element automatically";
93   }
94
95   @Override
96   protected String getRestorePopupDescription() {
97     return "Restore byte code popup behavior";
98   }
99
100   @Override
101   protected ByteCodeViewerComponent createComponent() {
102     return new ByteCodeViewerComponent(myProject, createActions());
103   }
104
105   @Nullable
106   protected String getTitle(PsiElement element) {
107     PsiClass aClass = getContainingClass(element);
108     if (aClass == null) return null;
109     return SymbolPresentationUtil.getSymbolPresentableText(aClass);
110   }
111
112   private void updateByteCode(PsiElement element, ByteCodeViewerComponent component, Content content) {
113     updateByteCode(element, component, content, getByteCode(element));
114   }
115
116   public void updateByteCode(PsiElement element,
117                              ByteCodeViewerComponent component,
118                              Content content,
119                              final String byteCode) {
120     if (!StringUtil.isEmpty(byteCode)) {
121       component.setText(byteCode, element);
122     } else {
123       PsiElement presentableElement = getContainingClass(element);
124       if (presentableElement == null) {
125         presentableElement = element.getContainingFile();
126         if (presentableElement == null && element instanceof PsiNamedElement) {
127           presentableElement = element;
128         }
129         if (presentableElement == null) {
130           component.setText("No bytecode found");
131           return;
132         }
133       }
134       component.setText("No bytecode found for " + SymbolPresentationUtil.getSymbolPresentableText(presentableElement));
135     }
136     content.setDisplayName(getTitle(element));
137   }
138
139   @Override
140   protected void doUpdateComponent(PsiElement element, PsiElement originalElement, ByteCodeViewerComponent component) {
141     final Content content = myToolWindow.getContentManager().getSelectedContent();
142     if (content != null && element != null) {
143       updateByteCode(element, component, content);
144     }
145   }
146
147
148   @Override
149   protected void doUpdateComponent(Editor editor, PsiFile psiFile) {
150     final Content content = myToolWindow.getContentManager().getSelectedContent();
151     if (content != null) {
152       final ByteCodeViewerComponent component = (ByteCodeViewerComponent)content.getComponent();
153       PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());
154       if (element != null) {
155         updateByteCode(element, component, content);
156       }
157     }
158   }
159
160   @Override
161   protected void doUpdateComponent(@NotNull PsiElement element) {
162     doUpdateComponent(element, getByteCode(element));
163   }
164
165   protected void doUpdateComponent(@NotNull PsiElement element, final String newText) {
166     final Content content = myToolWindow.getContentManager().getSelectedContent();
167     if (content != null) {
168       updateByteCode(element, (ByteCodeViewerComponent)content.getComponent(), content, newText);
169     }
170   }
171
172   @Nullable
173   public static String getByteCode(@NotNull PsiElement psiElement) {
174     PsiClass containingClass = getContainingClass(psiElement);
175     //todo show popup
176     if (containingClass == null) return null;
177     final String classVMName = getClassVMName(containingClass);
178     if (classVMName == null) return null;
179
180     Module module = ModuleUtilCore.findModuleForPsiElement(psiElement);
181     if (module == null){
182       final Project project = containingClass.getProject();
183       final PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(classVMName, psiElement.getResolveScope());
184       if (aClass != null) {
185         final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(aClass);
186         if (virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInLibraryClasses(virtualFile)) {
187           try {
188             return processClassFile(virtualFile.contentsToByteArray());
189           }
190           catch (IOException e) {
191             LOG.error(e);
192           }
193           return null;
194         }
195       }
196       return null;
197     }
198
199     try {
200       final PsiFile containingFile = containingClass.getContainingFile();
201       final VirtualFile virtualFile = containingFile.getVirtualFile();
202       if (virtualFile == null) return null;
203       final CompilerModuleExtension moduleExtension = CompilerModuleExtension.getInstance(module);
204       if (moduleExtension == null) return null;
205       String classPath;
206       if (ProjectRootManager.getInstance(module.getProject()).getFileIndex().isInTestSourceContent(virtualFile)) {
207         final VirtualFile pathForTests = moduleExtension.getCompilerOutputPathForTests();
208         if (pathForTests == null) return null;
209         classPath = pathForTests.getPath();
210       } else {
211         final VirtualFile compilerOutputPath = moduleExtension.getCompilerOutputPath();
212         if (compilerOutputPath == null) return null;
213         classPath = compilerOutputPath.getPath();
214       }
215
216       classPath += "/" + classVMName.replace('.', '/') + ".class";
217
218       final File classFile = new File(classPath);
219       if (!classFile.exists()) {
220         LOG.info("search in: " + classPath);
221         return null;
222       }
223       return processClassFile(FileUtil.loadFileBytes(classFile));
224     }
225     catch (Exception e1) {
226       LOG.error(e1);
227     }
228     return null;
229   }
230
231   private static String processClassFile(byte[] bytes) {
232     final ClassReader classReader = new ClassReader(bytes);
233     final StringWriter writer = new StringWriter();
234     final PrintWriter printWriter = new PrintWriter(writer);
235     try {
236       classReader.accept(new TraceClassVisitor(null, new Textifier(), printWriter), 0);
237     }
238     finally {
239       printWriter.close();
240     }
241     return writer.toString();
242   }
243
244   @Nullable
245   private static String getClassVMName(PsiClass containingClass) {
246     if (containingClass instanceof PsiAnonymousClass) {
247       return getClassVMName(PsiTreeUtil.getParentOfType(containingClass, PsiClass.class)) +
248              JavaAnonymousClassesHelper.getName((PsiAnonymousClass)containingClass);
249     }
250     return ClassUtil.getJVMClassName(containingClass);
251   }
252
253   public static PsiClass getContainingClass(PsiElement psiElement) {
254     for (ClassSearcher searcher : CLASS_SEARCHER_EP.getExtensions()) {
255       PsiClass aClass = searcher.findClass(psiElement);
256       if (aClass != null) {
257         return aClass;
258       }
259     }
260     return findClass(psiElement);
261   }
262
263   public static PsiClass findClass(@NotNull PsiElement psiElement) {
264     PsiClass containingClass = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class, false);
265     while (containingClass instanceof PsiTypeParameter) {
266       containingClass = PsiTreeUtil.getParentOfType(containingClass, PsiClass.class);
267     }
268     if (containingClass == null) return null;
269
270     return containingClass;
271   }
272 }