Commit 13e652d8 authored by ekuleshov's avatar ekuleshov

experimental read-only class bytecode editor based on jadclipse

parent bc863485
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.views">
<view
name="Bytecode"
icon="icons/bytecodeview.gif"
category="org.eclipse.jdt.ui.java"
class="de.loskutov.bco.views.BytecodeOutlineView"
id="de.loskutov.bco.views.BytecodeOutlineView"/>
</extension>
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.jdt.core.IOpenable"
id="de.loskutov.bco.CompareBytecodeActionContribution1">
<visibility>
<or>
<objectClass name="org.eclipse.jdt.core.IClassFile"/>
<objectClass name="org.eclipse.jdt.core.ICompilationUnit"/>
</or>
</visibility>
<menu
label="Compare With ..."
path="additions"
id="compareWithMenu">
<separator
name="compareWithGroup">
</separator>
</menu>
<action
enablesFor="1"
label="Another Class Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.OpenAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.CompareBytecodeAction">
</action>
<action
enablesFor="2"
label="Each Other Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.CompareBytecodeAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.OpenAction">
</action>
</objectContribution>
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
nameFilter="*.class"
id="de.loskutov.bco.CompareBytecodeActionContribution2">
<visibility>
<not>
<or>
<objectClass name="org.eclipse.jdt.core.IClassFile"/>
<objectClass name="org.eclipse.jdt.core.ICompilationUnit"/>
</or>
</not>
</visibility>
<menu
label="Compare With ..."
path="additions"
id="compareWithMenu">
<separator
name="compareWithGroup">
</separator>
</menu>
<action
enablesFor="1"
label="Another Class Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.OpenAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.CompareBytecodeAction">
</action>
<action
enablesFor="2"
label="Each Other Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.CompareBytecodeAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.OpenAction">
</action>
</objectContribution>
</extension>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.views">
<view
name="Bytecode"
icon="icons/bytecodeview.gif"
category="org.eclipse.jdt.ui.java"
class="de.loskutov.bco.views.BytecodeOutlineView"
id="de.loskutov.bco.views.BytecodeOutlineView"/>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="de.loskutov.bco.editors.BytecodeClassFileEditor"
icon="icons/bytecodeview.gif"
contributorClass="de.loskutov.bco.editors.BytecodeActionBarContributor"
name="Class File Bytecode Viewer"
id="de.loskutov.bco.editors.BytecodeClassFileEditor"
extensions="class,java">
</editor>
</extension>
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.jdt.core.IOpenable"
id="de.loskutov.bco.CompareBytecodeActionContribution1">
<visibility>
<or>
<objectClass name="org.eclipse.jdt.core.IClassFile"/>
<objectClass name="org.eclipse.jdt.core.ICompilationUnit"/>
</or>
</visibility>
<menu
label="Compare With ..."
path="additions"
id="compareWithMenu">
<separator
name="compareWithGroup">
</separator>
</menu>
<action
enablesFor="1"
label="Another Class Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.OpenAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.CompareBytecodeAction">
</action>
<action
enablesFor="2"
label="Each Other Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.CompareBytecodeAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.OpenAction">
</action>
</objectContribution>
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
nameFilter="*.class"
id="de.loskutov.bco.CompareBytecodeActionContribution2">
<visibility>
<not>
<or>
<objectClass name="org.eclipse.jdt.core.IClassFile"/>
<objectClass name="org.eclipse.jdt.core.ICompilationUnit"/>
</or>
</not>
</visibility>
<menu
label="Compare With ..."
path="additions"
id="compareWithMenu">
<separator
name="compareWithGroup">
</separator>
</menu>
<action
enablesFor="1"
label="Another Class Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.OpenAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.CompareBytecodeAction">
</action>
<action
enablesFor="2"
label="Each Other Bytecode"
icon="icons/bytecodeview.gif"
class="de.loskutov.bco.ui.actions.CompareBytecodeAction"
menubarPath="compareWithMenu/compareWithGroup"
id="de.loskutov.bco.OpenAction">
</action>
</objectContribution>
</extension>
</plugin>
package de.loskutov.bco.editors;
import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditorActionContributor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import de.loskutov.bco.BytecodeOutlinePlugin;
/**
* Adds "Show Bytecode" action to tool/menu bars
*
* @author V. Grishchenko, Eugene Kuleshov
*/
public class BytecodeActionBarContributor extends ClassFileEditorActionContributor {
private BytecodeClassFileEditor editor;
private ShowBytecodeAction dAction;
public BytecodeActionBarContributor() {
String symbolicName = BytecodeOutlinePlugin.getDefault().getBundle().getSymbolicName();
ImageDescriptor actionIcon = AbstractUIPlugin.imageDescriptorFromPlugin(symbolicName, "icons/bytecodeview.gif");
dAction = new ShowBytecodeAction(actionIcon);
}
public void contributeToToolBar(IToolBarManager toolBarManager) {
super.contributeToToolBar(toolBarManager);
toolBarManager.add(dAction);
// toolBarManager.add(new Separator(JadclipsePlugin.PID_JADCLIPSE));
// toolBarManager.appendToGroup(JadclipsePlugin.PID_JADCLIPSE, dAction);
}
public void contributeToMenu(IMenuManager menu) {
super.contributeToMenu(menu);
IMenuManager edit = menu.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT);
if (edit != null) {
edit.add(dAction);
}
}
public void setActiveEditor(IEditorPart targetEditor) {
if (targetEditor instanceof BytecodeClassFileEditor) {
editor = (BytecodeClassFileEditor) targetEditor;
editor.doSetInput(false);
} else
editor = null;
super.setActiveEditor(targetEditor);
}
private class ShowBytecodeAction extends Action {
protected ShowBytecodeAction(ImageDescriptor actionIcon) {
super("Show Bytecode@Ctrl+Shift+B", actionIcon);
// setDescription("xxx");
setToolTipText("Show Bytecode");
setAccelerator(SWT.CTRL | SWT.SHIFT | 'B');
}
public void run() {
if (editor != null)
editor.doSetInput(true);
}
}
}
package de.loskutov.bco.editors;
import java.util.Enumeration;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.BufferManager;
import de.loskutov.bco.BytecodeOutlinePlugin;
/**
* This class is a hack that replaces JDT <code>BufferManager</code> in order
* to make <code>addBuffer()</code> and <code>removeBuffer()</code>
* accessible.
*
* @author V.Grishchenko
*/
public class BytecodeBufferManager extends BufferManager {
/**
* Constructor for JadclipseBufferManager.
*/
public BytecodeBufferManager(BufferManager manager) {
super();
synchronized (BufferManager.class) {
Enumeration en = manager.getOpenBuffers();
while (en.hasMoreElements()) {
addBuffer((IBuffer) en.nextElement());
}
BufferManager.DEFAULT_BUFFER_MANAGER = this;
}
}
/**
* Closes buffers open by jadclipse
*
* @param all
* close all buffers including those that have no real source
*/
public static void closeJadclipseBuffers(boolean all) {
BufferManager manager = BufferManager.getDefaultBufferManager();
if (manager instanceof BytecodeBufferManager) {
Enumeration en = manager.getOpenBuffers();
while (en.hasMoreElements()) {
IBuffer buffer = (IBuffer) en.nextElement();
IOpenable owner = buffer.getOwner();
if (owner instanceof IClassFile &&
buffer.getContents().startsWith( BytecodeClassFileEditor.MARK)) {
BytecodeBufferManager jManager = (BytecodeBufferManager) manager;
jManager.removeBuffer(buffer);
if (!all) { // restore buffers for files without source
IClassFile cf = (IClassFile) owner;
String realSource = null;
try {
realSource = cf.getSource();
} catch (JavaModelException e) {
IStatus err = new Status(
IStatus.ERROR, BytecodeClassFileEditor.ID,
0,
"failed to get source while flushing buffers",
e);
BytecodeOutlinePlugin.getDefault().getLog().log(err);
}
if (realSource == null)
jManager.addBuffer(buffer);
}
}
}
}
}
/**
* @see BufferManager#addBuffer(IBuffer)
*/
public void addBuffer(IBuffer buffer) {
super.addBuffer(buffer);
}
/**
* @see BufferManager#removeBuffer(IBuffer)
*/
public void removeBuffer(IBuffer buffer) {
super.removeBuffer(buffer);
}
}
/* $Id: BytecodeClassFileEditor.java,v 1.1 2005-01-09 10:08:28 ekuleshov Exp $ */
package de.loskutov.bco.editors;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.BufferManager;
import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import de.loskutov.bco.BytecodeOutlinePlugin;
/**
* A "better" way to hook into JDT...
*
* @author Eugene Kuleshov, V. Grishchenko, Jochen Klein
*/
public class BytecodeClassFileEditor extends ClassFileEditor {
public static final String ID = "de.loskutov.bco.editors.BytecodeClassFileEditor";
public static final String MARK = "// class";
// private static final char[] MARK_ARRAY = MARK.toCharArray();
private BytecodeSourceMapper sourceMapper;
/**
* Constructor for JadclipseClassFileEditor.
*/
public BytecodeClassFileEditor() {
super();
}
protected BytecodeBufferManager getBufferManager() {
BufferManager defManager = BufferManager.getDefaultBufferManager();
if (defManager instanceof BytecodeBufferManager) {
return (BytecodeBufferManager) defManager;
}
return new BytecodeBufferManager(defManager);
}
/*
* @see IEditorPart#init(IEditorSite, IEditorInput)
*/
public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
doOpenBuffer(input, false);
super.init(site, input);
}
/**
* Sets edditor input only if buffer was actually opened.
*
* @param force
* if <code>true</code> initialize no matter what
*/
public void doSetInput(boolean force) {
IEditorInput input = getEditorInput();
if (doOpenBuffer(input, force)) {
try {
doSetInput(input);
} catch (Exception e) {
BytecodeOutlinePlugin.logError(e);
}
}
}
/**
* @return <code>true</code> if this editor displays decompiled source,
* <code>false</code> otherwise
*/
public boolean containsDecompiled() {
return (sourceMapper != null);
}
private boolean doOpenBuffer(IEditorInput input, boolean force) {
if (input instanceof IClassFileEditorInput) {
try {
boolean opened = false;
IClassFile cf = ((IClassFileEditorInput) input).getClassFile();
// IPreferenceStore prefs = JadclipsePlugin.getDefault().getPreferenceStore();
// boolean reuseBuf = prefs.getBoolean(JadclipsePlugin.REUSE_BUFFER);
// boolean always = prefs.getBoolean(JadclipsePlugin.IGNORE_EXISTING);
boolean reuseBuf = false;
boolean always = false;
String origSrc = cf.getSource();
// have to check our mark since all line comments are stripped
// in debug align mode
if (origSrc == null
|| always && !origSrc.startsWith(MARK)
|| (origSrc.startsWith(MARK) && (!reuseBuf || force))) {
if (sourceMapper == null)
sourceMapper = new BytecodeSourceMapper();
char[] src = sourceMapper.findSource(cf.getType());
if (src == null) {
src = new char[]{'\n', '/', '/', 'E', 'r', 'r', 'o', 'r', '!'};
}
// char[] markedSrc = new char[MARK_ARRAY.length + src.length];
// // next time we know this is decompiled source
// System.arraycopy(MARK_ARRAY, 0, markedSrc, 0, MARK_ARRAY.length);
// System.arraycopy(src, 0, markedSrc, MARK_ARRAY.length, src.length);
IBuffer buffer = getBufferManager().createBuffer(cf);
buffer.setContents(src);
getBufferManager().addBuffer(buffer);
// buffer.addBufferChangedListener((IBufferChangedListener)cf);
sourceMapper.mapSource(cf.getType(), src, true);
opened = true;
}
return opened;
} catch (Exception e) {
BytecodeOutlinePlugin.logError(e);
}
}
return false;
}
/*
* @see JavaEditor#getElementAt(int)
*/
protected IJavaElement getElementAt(int offset) {
IJavaElement result = super.getElementAt(offset);
if (result == null && getEditorInput() instanceof IClassFileEditorInput
&& containsDecompiled()) {
try {
IClassFileEditorInput input = (IClassFileEditorInput) getEditorInput();
result = sourceMapper.findElement(input.getClassFile().getType(), offset);
} catch (JavaModelException x) {
BytecodeOutlinePlugin.logError(x);
}
}
return result;
}
// protected IClassFile findClassFileParent(IJavaElement jElement)
// {
// IJavaElement parent = jElement.getParent();
// if (parent == null)
// return null;
// else if (parent instanceof IClassFile)
// return (IClassFile)parent;
// else
// return findClassFileParent(jElement);
// }
protected void setSelection(ISourceReference reference, boolean moveCursor) {
if (reference != null) {
try {
ISourceRange range = null;
if ((reference instanceof IJavaElement) && containsDecompiled())
range = sourceMapper.getSourceRange((IJavaElement) reference);
else
range = reference.getSourceRange();
int offset = range.getOffset();
int length = range.getLength();
if (offset > -1 && length > 0)
setHighlightRange(offset, length, moveCursor);
if (moveCursor && (reference instanceof IMember)) {
IMember member = (IMember) reference;
range = containsDecompiled()
? sourceMapper.getNameRange(member)
: member.getNameRange();
offset = range.getOffset();
length = range.getLength();
if (range != null && offset > -1 && length > 0) {
if (getSourceViewer() != null) {
getSourceViewer().revealRange(offset, length);
getSourceViewer().setSelectedRange(offset, length);
}
}
}
return;
} catch (Exception e) {
BytecodeOutlinePlugin.error("", e);
}
}
if (moveCursor)
resetHighlightRange();
}
}
package de.loskutov.bco.editors;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jdt.internal.core.SourceMapper;
import de.loskutov.bco.asm.DecompiledClass;
import de.loskutov.bco.asm.DecompilerClassVisitor;
/**
* Yet "better" way to hook into JDT...
*
* using a source mapper enables us to use the outline!
*
* Sorry, still ne better news for debuging.
*
* @author Jochen Klein
*/
public class BytecodeSourceMapper extends SourceMapper {
public BytecodeSourceMapper() {
// values are never used, as we have overwritten
// findSource()
// super(new Path("."), "");
super(new Path("."), "", new HashMap()); // per Rene's e-mail
}
/*
* (non-Javadoc) R2.1 fix
*
* @see org.eclipse.jdt.internal.core.SourceMapper#findSource(org.eclipse.jdt.core.IType)
*/
public char[] findSource(IType type) {
if (!type.isBinary()) {
return null;
}
BinaryType parent = (BinaryType) type.getDeclaringType();
BinaryType declType = (BinaryType) type;
while (parent != null) {
declType = parent;
parent = (BinaryType) declType.getDeclaringType();
}
IBinaryType info = null;
try {
info = (IBinaryType) declType.getElementInfo();
} catch (JavaModelException e) {
return null;
}
if (info == null) {
return null;
}
return findSource(type, info);
}
/**
*/
public char[] findSource(IType type, IBinaryType info) {
Collection exceptions = new LinkedList();
IPackageFragment pkgFrag = type.getPackageFragment();
IPackageFragmentRoot root = (IPackageFragmentRoot) pkgFrag.getParent();
String pkg = type.getPackageFragment().getElementName().replace( '.', '/');
String location = "\tDECOMPILED FROM: ";
String classFile = new String(info.getFileName());
int p = classFile.lastIndexOf('/');
classFile = classFile.substring(p + 1);
StringBuffer source = new StringBuffer();
if (root.isArchive()) {
String archivePath = getArchivePath(root);
location += archivePath;
decompileFromArchive( source, archivePath, pkg, classFile);
} else {
try {
location += root.getUnderlyingResource().getLocation().toOSString() + "/" + pkg + "/" + classFile;
decompile( source, root.getUnderlyingResource().getLocation().toOSString(), pkg, classFile);
} catch (JavaModelException e) {
exceptions.add(e);
}
}
source.append("\n\n");
source.append(location).append("\n");
// source.append(decompiler.getLog());
// exceptions.addAll(decompiler.getExceptions());
// logExceptions(exceptions, source);
return source.toString().toCharArray();
}
public void decompile(StringBuffer source, String root, String packege, String className) {
try {
decompile( source, new FileInputStream( root+"/"+packege+"/"+className));
} catch (IOException e) {
source.append( e.toString());
e.printStackTrace();
}
}
public void decompileFromArchive(StringBuffer source, String archivePath, String packege, String className) {
try {
ZipFile zf = new ZipFile( archivePath);
// TODO implement better entry resolution (inner classes?)
ZipEntry ze = zf.getEntry( packege+"/"+className);
decompile(source, zf.getInputStream(ze));
} catch( IOException e) {
source.append( e.toString());
e.printStackTrace();
}
}
private void decompile( StringBuffer source, InputStream is) throws IOException {
boolean raw = true;
boolean asmify = false;
boolean verify = false;
DecompiledClass decompiledClass = DecompilerClassVisitor.getDecompiledClass(
is, null, null, raw, asmify, verify, null);
source.append( decompiledClass.getText());
}
private void logExceptions(Collection exceptions, StringBuffer buffer) {
buffer.append("\n\tCAUGHT EXCEPTIONS:\n");
if (exceptions == null || exceptions.size() == 0)
return; // nothing to do
StringWriter stackTraces = new