/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.byteCodeViewer; import com.intellij.codeInsight.documentation.DockablePopupManager; import com.intellij.ide.util.JavaAnonymousClassesHelper; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.CompilerModuleExtension; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.psi.util.ClassUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.content.Content; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.ClassReader; import org.jetbrains.asm4.util.Textifier; import org.jetbrains.asm4.util.TraceClassVisitor; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; /** * @author anna * @since 5/7/12 */ public class ByteCodeViewerManager extends DockablePopupManager { private static final ExtensionPointName CLASS_SEARCHER_EP = ExtensionPointName.create("ByteCodeViewer.classSearcher"); private static final Logger LOG = Logger.getInstance("#" + ByteCodeViewerManager.class.getName()); public static final String TOOLWINDOW_ID = "Byte Code Viewer"; private static final String SHOW_BYTECODE_IN_TOOL_WINDOW = "BYTE_CODE_TOOL_WINDOW"; private static final String BYTECODE_AUTO_UPDATE_ENABLED = "BYTE_CODE_AUTO_UPDATE_ENABLED"; public static ByteCodeViewerManager getInstance(Project project) { return ServiceManager.getService(project, ByteCodeViewerManager.class); } public ByteCodeViewerManager(Project project) { super(project); } @Override public String getShowInToolWindowProperty() { return SHOW_BYTECODE_IN_TOOL_WINDOW; } @Override public String getAutoUpdateEnabledProperty() { return BYTECODE_AUTO_UPDATE_ENABLED; } @Override protected String getToolwindowId() { return TOOLWINDOW_ID; } @Override protected String getAutoUpdateTitle() { return "Auto Show Byte Code for Selected Element"; } @Override protected String getAutoUpdateDescription() { return "Show byte code for current element automatically"; } @Override protected String getRestorePopupDescription() { return "Restore byte code popup behavior"; } @Override protected ByteCodeViewerComponent createComponent() { return new ByteCodeViewerComponent(myProject, createActions()); } @Nullable protected String getTitle(PsiElement element) { PsiClass aClass = getContainingClass(element); if (aClass == null) return null; return SymbolPresentationUtil.getSymbolPresentableText(aClass); } private void updateByteCode(PsiElement element, ByteCodeViewerComponent component, Content content) { updateByteCode(element, component, content, getByteCode(element)); } public void updateByteCode(PsiElement element, ByteCodeViewerComponent component, Content content, final String byteCode) { if (!StringUtil.isEmpty(byteCode)) { component.setText(byteCode, element); } else { PsiElement presentableElement = getContainingClass(element); if (presentableElement == null) { presentableElement = element.getContainingFile(); if (presentableElement == null && element instanceof PsiNamedElement) { presentableElement = element; } if (presentableElement == null) { component.setText("No bytecode found"); return; } } component.setText("No bytecode found for " + SymbolPresentationUtil.getSymbolPresentableText(presentableElement)); } content.setDisplayName(getTitle(element)); } @Override protected void doUpdateComponent(PsiElement element, PsiElement originalElement, ByteCodeViewerComponent component) { final Content content = myToolWindow.getContentManager().getSelectedContent(); if (content != null && element != null) { updateByteCode(element, component, content); } } @Override protected void doUpdateComponent(Editor editor, PsiFile psiFile) { final Content content = myToolWindow.getContentManager().getSelectedContent(); if (content != null) { final ByteCodeViewerComponent component = (ByteCodeViewerComponent)content.getComponent(); PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset()); if (element != null) { updateByteCode(element, component, content); } } } @Override protected void doUpdateComponent(@NotNull PsiElement element) { doUpdateComponent(element, getByteCode(element)); } protected void doUpdateComponent(@NotNull PsiElement element, final String newText) { final Content content = myToolWindow.getContentManager().getSelectedContent(); if (content != null) { updateByteCode(element, (ByteCodeViewerComponent)content.getComponent(), content, newText); } } @Nullable public static String getByteCode(@NotNull PsiElement psiElement) { PsiClass containingClass = getContainingClass(psiElement); //todo show popup if (containingClass == null) return null; final String classVMName = getClassVMName(containingClass); if (classVMName == null) return null; Module module = ModuleUtilCore.findModuleForPsiElement(psiElement); if (module == null){ final Project project = containingClass.getProject(); final PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(classVMName, psiElement.getResolveScope()); if (aClass != null) { final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(aClass); if (virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInLibraryClasses(virtualFile)) { try { return processClassFile(virtualFile.contentsToByteArray()); } catch (IOException e) { LOG.error(e); } return null; } } return null; } try { final PsiFile containingFile = containingClass.getContainingFile(); final VirtualFile virtualFile = containingFile.getVirtualFile(); if (virtualFile == null) return null; final CompilerModuleExtension moduleExtension = CompilerModuleExtension.getInstance(module); if (moduleExtension == null) return null; String classPath; if (ProjectRootManager.getInstance(module.getProject()).getFileIndex().isInTestSourceContent(virtualFile)) { final VirtualFile pathForTests = moduleExtension.getCompilerOutputPathForTests(); if (pathForTests == null) return null; classPath = pathForTests.getPath(); } else { final VirtualFile compilerOutputPath = moduleExtension.getCompilerOutputPath(); if (compilerOutputPath == null) return null; classPath = compilerOutputPath.getPath(); } classPath += "/" + classVMName.replace('.', '/') + ".class"; final File classFile = new File(classPath); if (!classFile.exists()) { LOG.info("search in: " + classPath); return null; } return processClassFile(FileUtil.loadFileBytes(classFile)); } catch (Exception e1) { LOG.error(e1); } return null; } private static String processClassFile(byte[] bytes) { final ClassReader classReader = new ClassReader(bytes); final StringWriter writer = new StringWriter(); final PrintWriter printWriter = new PrintWriter(writer); try { classReader.accept(new TraceClassVisitor(null, new Textifier(), printWriter), 0); } finally { printWriter.close(); } return writer.toString(); } @Nullable private static String getClassVMName(PsiClass containingClass) { if (containingClass instanceof PsiAnonymousClass) { return getClassVMName(PsiTreeUtil.getParentOfType(containingClass, PsiClass.class)) + JavaAnonymousClassesHelper.getName((PsiAnonymousClass)containingClass); } return ClassUtil.getJVMClassName(containingClass); } public static PsiClass getContainingClass(PsiElement psiElement) { for (ClassSearcher searcher : CLASS_SEARCHER_EP.getExtensions()) { PsiClass aClass = searcher.findClass(psiElement); if (aClass != null) { return aClass; } } return findClass(psiElement); } public static PsiClass findClass(@NotNull PsiElement psiElement) { PsiClass containingClass = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class, false); while (containingClass instanceof PsiTypeParameter) { containingClass = PsiTreeUtil.getParentOfType(containingClass, PsiClass.class); } if (containingClass == null) return null; return containingClass; } }