001: package de.loskutov.bco.editors;
002:
003: import org.eclipse.core.runtime.CoreException;
004: import org.eclipse.core.runtime.IStatus;
005: import org.eclipse.jdt.core.IBuffer;
006: import org.eclipse.jdt.core.IClassFile;
007: import org.eclipse.jdt.core.JavaModelException;
008: import org.eclipse.jdt.internal.core.BufferManager;
009: import org.eclipse.jdt.internal.ui.javaeditor.ClassFileDocumentProvider;
010: import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
011: import org.eclipse.jface.text.AbstractDocument;
012: import org.eclipse.jface.text.BadLocationException;
013: import org.eclipse.jface.text.IDocument;
014: import org.eclipse.jface.text.IRegion;
015: import org.eclipse.ui.IEditorInput;
016: import org.eclipse.ui.IEditorPart;
017: import org.eclipse.ui.PlatformUI;
018:
019: import de.loskutov.bco.BytecodeOutlinePlugin;
020:
021: /**
022: * Overriden to get control over document content for bytecode editors
023: * @author Andrei
024: */
025: public class BytecodeDocumentProvider extends ClassFileDocumentProvider {
026:
027: public BytecodeDocumentProvider(
028: BytecodeClassFileEditor classFileEditor) {
029: super ();
030: }
031:
032: /*
033: * Overriden to get control over document content for bytecode editors
034: * @see StorageDocumentProvider#setDocumentContent(IDocument, IEditorInput)
035: */
036: protected boolean setDocumentContent(IDocument document,
037: IEditorInput editorInput, String encoding)
038: throws CoreException {
039:
040: if (editorInput instanceof IClassFileEditorInput) {
041: IClassFile classFile = ((IClassFileEditorInput) editorInput)
042: .getClassFile();
043:
044: String source = null;
045: try {
046: source = classFile.getSource();
047: } catch (JavaModelException e) {
048: // ignore, this may happen if *class* file is not on class path but inside
049: // of source tree without associated source
050: }
051: if (source == null) {
052: // this could be the case for class files which are not on the class path
053: // buffer should be already opened and created in our editor->doOpenBuffer
054: // method
055: IBuffer buffer = BufferManager
056: .getDefaultBufferManager().getBuffer(classFile);
057: if (buffer != null) {
058: source = buffer.getContents();
059: }
060: }
061: document.set(source);
062: return true;
063: }
064: return super
065: .setDocumentContent(document, editorInput, encoding);
066: }
067:
068: /**
069: *
070: * During debug session, debugger tries to get line information for the current line
071: * in the stack, and then uses this info to set cursor and select text in editor.
072: *
073: * The problem is, that Java debugger knows only "source" - based lines, but our editor
074: * contains "bytecode" text, where the lines are NOT aligned to the source code lines.
075: * (it is simply not possible).
076: *
077: * So if we do not change the default implementation, the selected bytecode text will
078: * never match requested sourcecode lines.
079: *
080: * As workaround we "just" pass to the debugger another document, as one we use to
081: * represent the text in our editor. This document is a proxy and just replaces
082: * the implementation of "getLineInformation" method. Our implementation mapps the
083: * requested sourcecode line to the bytecode line in editor.
084: *
085: * All other clients of this method shouldn't be affected and should receive always
086: * the original document.
087: */
088: public IDocument getDocument(Object element) {
089: IDocument document = super .getDocument(element);
090: if (element instanceof IClassFileEditorInput
091: && isDebuggerCall()) {
092: IClassFileEditorInput input = (IClassFileEditorInput) element;
093:
094: return new DocumentProxy4Debugger(document, input
095: .getClassFile());
096: }
097: return document;
098: }
099:
100: /**
101: * We are looking for two stack patterns, which both are related to debug session and
102: * coming from SourceLookupFacility.display(ISourceLookupResult result, IWorkbenchPage page):
103: * first is the highlighting the editor current line, corresponding to
104: * the line in the bytecode stack (light gray color),
105: * and second is the annotation the current debugger position (same line as before) in
106: * editor (light green color)
107: *
108: * This is a VERY BAD and VERY DIRTY hack, but it works.
109: * @return
110: */
111: private boolean isDebuggerCall() {
112: Exception e = new Exception();
113: StackTraceElement[] stackTrace = e.getStackTrace();
114: boolean stackOk = true;
115: // at 0 is our method name, and 1 id the "getDocument" call, so we start with 2
116: for (int i = 2; i < stackTrace.length; i++) {
117: StackTraceElement elt = stackTrace[i];
118: switch (i) {
119: case 2:
120: stackOk = "getLineInformation".equals(elt
121: .getMethodName())
122: || "addAnnotation".equals(elt.getMethodName());
123: break;
124: case 3:
125: stackOk = "positionEditor".equals(elt.getMethodName())
126: || "display".equals(elt.getMethodName());
127: break;
128: default:
129: break;
130: }
131: if (!stackOk || i > 3) {
132: return false;
133: }
134:
135: if (stackOk && i == 3) {
136: IEditorPart activeEditor = PlatformUI.getWorkbench()
137: .getActiveWorkbenchWindow().getActivePage()
138: .getActiveEditor();
139: if (activeEditor instanceof BytecodeClassFileEditor) {
140: BytecodeClassFileEditor editor = (BytecodeClassFileEditor) activeEditor;
141: return editor.isDecompiled();
142: }
143: }
144: }
145: return false;
146: }
147:
148: public IRegion getDecompiledLineInfo(IEditorInput input,
149: int decompiledLine) {
150: IDocument document = getDocument(input);
151: try {
152: return document.getLineInformation(decompiledLine);
153: } catch (BadLocationException e) {
154: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
155: }
156: return null;
157: }
158:
159: /*
160: * SourceLookupFacility.class: The code is responsible for the
161: * positioning of current debugger line during debugging Positions the text editor for
162: * the given stack frame :
163: * private void positionEditor(ITextEditor editor, IStackFrame frame)
164: * private IRegion getLineInformation(ITextEditor editor, int lineNumber)
165: *
166: * Other place for debug instruction pointer with the "wrong line" is InstructionPointerManager
167: * public void addAnnotation(ITextEditor textEditor, IStackFrame frame, Annotation annotation)
168: */
169:
170: /**
171: * This class is non-functional replacement for IDocument. The only one purpose is to
172: * override getLineInformation() implementation for debug purposes
173: */
174: private static final class DocumentProxy4Debugger extends
175: AbstractDocument {
176:
177: private final IDocument delegate;
178: private final IClassFile cf;
179:
180: public DocumentProxy4Debugger(IDocument delegate, IClassFile cf) {
181: super ();
182: this .delegate = delegate;
183: this .cf = cf;
184: }
185:
186: public IRegion getLineInformation(int line)
187: throws BadLocationException {
188: BytecodeSourceMapper mapper = BytecodeClassFileEditor
189: .getSourceMapper();
190:
191: int decompiledLine;
192: if (line < -1) {
193: /* this is the case if debugger does not have line information in bytecode
194: * if bytecode does not contain line info, we should at least
195: * return the line with the first method instruction.
196: */
197: decompiledLine = mapper.mapDebuggerToDecompiled(cf);
198: } else {
199: // SourceLookupFacility decrement source line by 1
200: decompiledLine = mapper.mapToDecompiled(line + 1, cf);
201: if (decompiledLine == -1) {
202: /*
203: * The line is from inner class (it is in another class file)
204: * the mapping does not work for inner/anon. classes, as the debugger
205: * expect that their source code is in our editor which is not the case for
206: * bytecode of inner classes => for inner classes we use another strategy
207: */
208: return BytecodeClassFileEditor.checkForInnerClass(
209: line, cf);
210: }
211: }
212: // editor start lines with 1
213: return delegate.getLineInformation(decompiledLine + 1);
214: }
215: }
216:
217: }
|