blob: db04a80a786203ebd08b0b0ec86ee6402b8d495e [file] [log] [blame]
/*
* 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<ByteCodeViewerComponent> {
private static final ExtensionPointName<ClassSearcher> 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;
}
}