001: /* $Id: BytecodeClassFileEditor.java,v 1.7 2007-10-05 22:05:41 andrei Exp $ */
002:
003: package de.loskutov.bco.editors;
004:
005: import java.lang.reflect.Constructor;
006: import java.util.BitSet;
007:
008: import org.eclipse.core.resources.IFile;
009: import org.eclipse.core.runtime.CoreException;
010: import org.eclipse.core.runtime.IStatus;
011: import org.eclipse.debug.core.DebugException;
012: import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
013: import org.eclipse.jdt.core.IBuffer;
014: import org.eclipse.jdt.core.IBufferChangedListener;
015: import org.eclipse.jdt.core.IClassFile;
016: import org.eclipse.jdt.core.IInitializer;
017: import org.eclipse.jdt.core.IJavaElement;
018: import org.eclipse.jdt.core.IJavaModelStatusConstants;
019: import org.eclipse.jdt.core.IMember;
020: import org.eclipse.jdt.core.IMethod;
021: import org.eclipse.jdt.core.ISourceRange;
022: import org.eclipse.jdt.core.ISourceReference;
023: import org.eclipse.jdt.core.IType;
024: import org.eclipse.jdt.core.JavaModelException;
025: import org.eclipse.jdt.core.dom.CompilationUnit;
026: import org.eclipse.jdt.debug.core.IJavaReferenceType;
027: import org.eclipse.jdt.internal.ui.JavaPlugin;
028: import org.eclipse.jdt.internal.ui.JavaUIStatus;
029: import org.eclipse.jdt.internal.ui.javaeditor.ClassFileDocumentProvider;
030: import org.eclipse.jdt.internal.ui.javaeditor.ExternalClassFileEditorInput;
031: import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
032: import org.eclipse.jdt.internal.ui.javaeditor.InternalClassFileEditorInput;
033: import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
034: import org.eclipse.jface.text.BadLocationException;
035: import org.eclipse.jface.text.IDocument;
036: import org.eclipse.jface.text.IRegion;
037: import org.eclipse.jface.text.ITextSelection;
038: import org.eclipse.jface.text.ITextViewerExtension5;
039: import org.eclipse.jface.text.Region;
040: import org.eclipse.jface.text.TextSelection;
041: import org.eclipse.jface.text.source.ISourceViewer;
042: import org.eclipse.swt.SWT;
043: import org.eclipse.swt.custom.StackLayout;
044: import org.eclipse.swt.custom.StyledText;
045: import org.eclipse.swt.layout.FillLayout;
046: import org.eclipse.swt.widgets.Composite;
047: import org.eclipse.ui.IEditorInput;
048: import org.eclipse.ui.IEditorPart;
049: import org.eclipse.ui.IEditorReference;
050: import org.eclipse.ui.IEditorSite;
051: import org.eclipse.ui.IFileEditorInput;
052: import org.eclipse.ui.PartInitException;
053: import org.eclipse.ui.PlatformUI;
054: import org.eclipse.ui.part.FileEditorInput;
055: import org.eclipse.ui.texteditor.IDocumentProvider;
056:
057: import de.loskutov.bco.BytecodeOutlinePlugin;
058: import de.loskutov.bco.asm.DecompiledClass;
059: import de.loskutov.bco.preferences.BCOConstants;
060: import de.loskutov.bco.ui.JdtUtils;
061:
062: /**
063: * A "better" way to hook into JDT...
064: * @author Eugene Kuleshov, V. Grishchenko, Jochen Klein, Andrei Loskutov
065: */
066: public class BytecodeClassFileEditor extends JavaEditor implements
067: ClassFileDocumentProvider.InputChangeListener {
068:
069: private StackLayout fStackLayout;
070: private Composite fParent;
071: private Composite fViewerComposite;
072: private InputUpdater fInputUpdater;
073: public static final String ID = "de.loskutov.bco.editors.BytecodeClassFileEditor";
074: public static final String MARK = "// class version ";
075: /** the modes (flags) for the decompiler */
076: private BitSet decompilerFlags;
077: /** is not null only on class files with decompiled source */
078: private static BytecodeSourceMapper sourceMapper;
079: private BytecodeDocumentProvider fClassFileDocumentProvider;
080: private boolean hasMappedSource;
081: private boolean decompiled;
082: private boolean initDone;
083:
084: /**
085: * Constructor for JadclipseClassFileEditor.
086: */
087: public BytecodeClassFileEditor() {
088: super ();
089: if (sourceMapper == null) {
090: sourceMapper = new BytecodeSourceMapper();
091: }
092: fInputUpdater = new InputUpdater();
093: setDocumentProvider(getClassFileDocumentProvider());
094: setEditorContextMenuId("#ClassFileEditorContext"); //$NON-NLS-1$
095: setRulerContextMenuId("#ClassFileRulerContext"); //$NON-NLS-1$
096: setOutlinerContextMenuId("#ClassFileOutlinerContext"); //$NON-NLS-1$
097: // don't set help contextId, we install our own help context
098:
099: decompilerFlags = new BitSet();
100: // TODO take from preferences and/or last editor memento
101: decompilerFlags.set(BCOConstants.F_SHOW_LINE_INFO, true);
102: decompilerFlags.set(BCOConstants.F_SHOW_VARIABLES, true);
103: decompilerFlags.set(BCOConstants.F_SHOW_RAW_BYTECODE, false);
104: }
105:
106: /**
107: * @return the hasMappedSource
108: */
109: protected boolean hasMappedSource() {
110: return hasMappedSource;
111: }
112:
113: /**
114: * @param hasMappedSource the hasMappedSource to set
115: */
116: protected void setHasMappedSource(boolean hasMappedSource) {
117: this .hasMappedSource = hasMappedSource;
118: }
119:
120: private ClassFileDocumentProvider getClassFileDocumentProvider() {
121: if (fClassFileDocumentProvider == null) {
122: fClassFileDocumentProvider = new BytecodeDocumentProvider(
123: this );
124: }
125: return fClassFileDocumentProvider;
126: }
127:
128: public void setDecompilerFlag(int flag, boolean value) {
129: decompilerFlags.set(flag, value);
130: }
131:
132: public boolean getDecompilerFlag(int flag) {
133: return decompilerFlags.get(flag);
134: }
135:
136: /*
137: * @see IEditorPart#init(IEditorSite, IEditorInput)
138: */
139: public void init(IEditorSite site, IEditorInput input)
140: throws PartInitException {
141: input = doOpenBuffer(input, false, true);
142: super .init(site, input);
143: }
144:
145: /**
146: * Sets editor input only if buffer was actually opened.
147: * @param force if <code>true</code> initialize no matter what
148: * @param reuseSource true to show source code if available
149: */
150: public void doSetInput(boolean force, boolean reuseSource) {
151: IEditorInput input = getEditorInput();
152: input = doOpenBuffer(input, force, reuseSource);
153: if (input != null) {
154: try {
155: doSetInput(input);
156: } catch (Exception e) {
157: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
158: }
159: }
160: }
161:
162: /*
163: * @see AbstractTextEditor#doSetInput(IEditorInput)
164: * ClassFileDocumentProvider.setDocumentContent(IDocument, IEditorInput, String) line:
165: * 202 ClassFileDocumentProvider(StorageDocumentProvider).createDocument(Object) line:
166: * 228 ClassFileDocumentProvider.createDocument(Object) line: 247
167: * ClassFileDocumentProvider.createElementInfo(Object) line: 275
168: * ClassFileDocumentProvider(AbstractDocumentProvider).connect(Object) line: 398
169: * BytecodeClassFileEditor(AbstractTextEditor).doSetInput(IEditorInput) line: 3063
170: * BytecodeClassFileEditor(StatusTextEditor).doSetInput(IEditorInput) line: 173
171: * BytecodeClassFileEditor(AbstractDecoratedTextEditor).doSetInput(IEditorInput) line:
172: * 1511 BytecodeClassFileEditor(JavaEditor).internalDoSetInput(IEditorInput) line:
173: * 2370 BytecodeClassFileEditor(JavaEditor).doSetInput(IEditorInput) line: 2343
174: * BytecodeClassFileEditor.doSetInput(IEditorInput) line: 201
175: * AbstractTextEditor$17.run(IProgressMonitor) line: 2396
176: */
177: protected void doSetInput(IEditorInput input) throws CoreException {
178:
179: input = transformEditorInput(input);
180: if (!(input instanceof IClassFileEditorInput)) {
181: throw new CoreException(JavaUIStatus.createError(
182: IJavaModelStatusConstants.INVALID_RESOURCE_TYPE,
183: "invalid input", // JavaEditorMessages.ClassFileEditor_error_invalid_input_message,
184: null));
185: }
186:
187: IDocumentProvider documentProvider = getDocumentProvider();
188: if (documentProvider instanceof ClassFileDocumentProvider) {
189: ((ClassFileDocumentProvider) documentProvider)
190: .removeInputChangeListener(this );
191: }
192:
193: super .doSetInput(input);
194:
195: documentProvider = getDocumentProvider();
196: if (documentProvider instanceof ClassFileDocumentProvider) {
197: ((ClassFileDocumentProvider) documentProvider)
198: .addInputChangeListener(this );
199: }
200: }
201:
202: /**
203: * @return <code>true</code> if this editor displays decompiled source,
204: * <code>false</code> otherwise
205: */
206: public boolean isDecompiled() {
207: return decompiled;
208: }
209:
210: private IEditorInput doOpenBuffer(IJavaReferenceType type,
211: IClassFile parent, boolean externalClass) {
212: IClassFile classFile = null;
213: try {
214: classFile = JdtUtils.getInnerType(parent, getSourceMapper()
215: .getDecompiledClass(parent), type.getSignature());
216: } catch (DebugException e) {
217: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
218: }
219: return doOpenBuffer(classFile, externalClass);
220: }
221:
222: private IEditorInput doOpenBuffer(IClassFile classFile,
223: boolean externalClass) {
224: IEditorInput input = null;
225: if (classFile == null) {
226: return null;
227: }
228: if (externalClass) {
229: // TODO create external input, but we need a file object here...
230: } else {
231: input = transformEditorInput(classFile);
232: }
233: return doOpenBuffer(input, false, true);
234: }
235:
236: private IEditorInput doOpenBuffer(IEditorInput input,
237: boolean force, boolean reuseSource) {
238: if (input instanceof IClassFileEditorInput) {
239: IClassFile cf = ((IClassFileEditorInput) input)
240: .getClassFile();
241: String origSrc = getAttachedJavaSource(cf, force);
242: if (origSrc != null && !hasMappedSource) {
243: // remember, that the JDT knows where the real source is and can show it
244: setHasMappedSource(true);
245: }
246:
247: if (origSrc == null || (force && !reuseSource)) {
248: setDecompiled(true);
249: char[] src;
250: if (input instanceof ExternalClassFileEditorInput) {
251: ExternalClassFileEditorInput extInput = (ExternalClassFileEditorInput) input;
252: src = getSourceMapper().getSource(
253: extInput.getFile(), cf, decompilerFlags);
254: } else {
255: src = getSourceMapper().getSource(cf,
256: decompilerFlags);
257: }
258: changeBufferContent(cf, src);
259: } else {
260: setDecompiled(false);
261: }
262: } else if (input instanceof FileEditorInput) {
263: FileEditorInput fileEditorInput = (FileEditorInput) input;
264: // make class file from that
265: IClassFileEditorInput cfi = transformEditorInput(input);
266: // return changed reference
267: input = cfi;
268: setDecompiled(true);
269: IClassFile cf = cfi.getClassFile();
270: char[] src = getSourceMapper().getSource(
271: fileEditorInput.getFile(), cf, decompilerFlags);
272: changeBufferContent(cf, src);
273: }
274: return input;
275: }
276:
277: private void setDecompiled(boolean decompiled) {
278: boolean oldDecompiled = this .decompiled;
279: this .decompiled = decompiled;
280: if (initDone && oldDecompiled != decompiled) {
281: if (decompiled) {
282: // prevent multiple errors in ASTProvider which fails to work without source
283: uninstallOccurrencesFinder();
284: } else {
285: // install again if source is available
286: installOccurrencesFinder(true);
287: }
288: }
289: }
290:
291: private String getAttachedJavaSource(IClassFile cf, boolean force) {
292: String origSrc = null;
293: if (force) {
294: IBuffer buffer = BytecodeBufferManager.getBuffer(cf);
295: if (buffer != null) {
296: BytecodeBufferManager.removeBuffer(buffer);
297: }
298: }
299: try {
300: origSrc = cf.getSource();
301: if (origSrc != null && origSrc.startsWith(MARK)) {
302: // this is NOT orig. sourse, but cached content
303: origSrc = null;
304: }
305: } catch (JavaModelException e) {
306: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
307: }
308: return origSrc;
309: }
310:
311: private void changeBufferContent(IClassFile cf, char[] src) {
312: IBuffer buffer = BytecodeBufferManager.getBuffer(cf);
313:
314: // i'm not sure if we need to create buffer each time -
315: // couldn't we reuse existing one (if any)?
316: // - seems that without "create" some listener didn't get notifications about
317: // changed content
318: boolean addBuffer = false;
319:
320: // if(buffer == null) {
321: buffer = BytecodeBufferManager.createBuffer(cf);
322: addBuffer = true;
323: // }
324: if (src == null) {
325: src = new char[] { '\n', '/', '/', 'E', 'r', 'r', 'o', 'r' };
326: }
327: buffer.setContents(src);
328: if (addBuffer) {
329: BytecodeBufferManager.addBuffer(buffer);
330: buffer
331: .addBufferChangedListener((IBufferChangedListener) cf);
332: }
333: }
334:
335: /*
336: * @see JavaEditor#getElementAt(int)
337: */
338: protected IJavaElement getElementAt(int offset) {
339:
340: IClassFile classFile = getClassFile();
341: if (classFile == null) {
342: return null;
343: }
344: IJavaElement result = null;
345: if (isDecompiled()) {
346: IDocument document = getDocumentProvider().getDocument(
347: getEditorInput());
348: try {
349: int lineAtOffset = document.getLineOfOffset(offset);
350: // get DecompiledMethod from line, then get JavaElement with same
351: // signature, because we do not have offsets or lines in the class file,
352: // only java elements...
353: result = getSourceMapper().findElement(classFile,
354: lineAtOffset);
355: } catch (BadLocationException e) {
356: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
357: }
358: } else {
359: try {
360: result = classFile.getElementAt(offset);
361: } catch (JavaModelException e) {
362: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
363: }
364: }
365:
366: return result;
367: }
368:
369: public IClassFile getClassFile() {
370: IEditorInput editorInput = getEditorInput();
371: if (!(editorInput instanceof IClassFileEditorInput)) {
372: return null;
373: }
374: return ((IClassFileEditorInput) editorInput).getClassFile();
375: }
376:
377: protected void setSelection(ISourceReference reference,
378: boolean moveCursor) {
379: if (reference == null) {
380: if (moveCursor) {
381: resetHighlightRange();
382: }
383: return;
384: }
385: try {
386: ISourceRange range = null;
387: int offset;
388: int length;
389: if (isDecompiled() && isSupportedMember(reference)) {
390:
391: // document lines count starts with 1 and not with 0
392: int decompLine = -1
393: + getSourceMapper().getDecompiledLine(
394: (IMember) reference, getClassFile());
395: if (decompLine < 0) {
396: return;
397: }
398: IRegion region = ((BytecodeDocumentProvider) getDocumentProvider())
399: .getDecompiledLineInfo(getEditorInput(),
400: decompLine);
401: if (region == null) {
402: return;
403: }
404: offset = region.getOffset();
405: length = region.getLength();
406: } else if (!isDecompiled()) {
407: range = reference.getSourceRange();
408: if (range == null) {
409: return;
410: }
411: offset = range.getOffset();
412: length = range.getLength();
413: } else {
414: return;
415: }
416:
417: if (offset > -1 && length > 0) {
418: setHighlightRange(offset, length, moveCursor);
419: }
420:
421: if ((reference instanceof IMember) && !isDecompiled()) {
422: IMember member = (IMember) reference;
423: range = member.getNameRange();
424: if (range != null) {
425: offset = range.getOffset();
426: length = range.getLength();
427: }
428: }
429: if (moveCursor && offset > -1 && length > 0) {
430: ISourceViewer sourceViewer = getSourceViewer();
431: if (sourceViewer != null) {
432: sourceViewer.revealRange(offset, length);
433: sourceViewer.setSelectedRange(offset, length);
434: }
435: }
436: return;
437: } catch (Exception e) {
438: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
439: }
440:
441: if (moveCursor) {
442: resetHighlightRange();
443: }
444: }
445:
446: private boolean isSupportedMember(ISourceReference reference) {
447: // TODO this condition is not enough. We could have inner/anon. classes
448: // as selection source (they are displayed in the outline),
449: // but they are not in the decompiled class,
450: // so we need to filter "external" elements too, even if they are methods...
451:
452: // we could also later point to the IField's, but currently it is not supported
453: return reference instanceof IMethod
454: || reference instanceof IInitializer;
455: }
456:
457: public Object getAdapter(Class required) {
458: if (IToggleBreakpointsTarget.class == required) {
459:
460: // TODO implement own adapter for toggle breakpoints, because the default one
461: // could not find java elements in our document and therefore could not
462: // create a Java breakpoint.
463: // see org.eclipse.jdt.internal.debug.ui.actions.ToggleBreakpointAdapter
464: // IMember member =
465: // ActionDelegateHelper.getDefault().getCurrentMember(selection);
466: // and the ActionDelegateHelper looks with the given offset at classfile or
467: // compilation unit, but our offset is completely different to Java source
468: // code
469: super .getAdapter(required);
470: }
471:
472: return super .getAdapter(required);
473: }
474:
475: /*
476: * Overriden to prevent NPE on SourceReference objects without associated source
477: * ranges, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=142936
478: * @see JavaEditor#adjustHighlightRange(int, int)
479: */
480: protected void adjustHighlightRange(int offset, int length) {
481: try {
482:
483: IJavaElement element = getElementAt(offset);
484: while (element instanceof ISourceReference) {
485: ISourceRange range = ((ISourceReference) element)
486: .getSourceRange();
487: // range != null is the only one change we need here
488: if (range != null
489: && (offset < range.getOffset()
490: + range.getLength() && range
491: .getOffset() < offset + length)) {
492:
493: ISourceViewer viewer = getSourceViewer();
494: if (viewer instanceof ITextViewerExtension5) {
495: ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
496: extension.exposeModelRange(new Region(range
497: .getOffset(), range.getLength()));
498: }
499:
500: setHighlightRange(range.getOffset(), range
501: .getLength(), true);
502: if (fOutlinePage != null) {
503: fOutlineSelectionChangedListener
504: .uninstall(fOutlinePage);
505: fOutlinePage.select((ISourceReference) element);
506: fOutlineSelectionChangedListener
507: .install(fOutlinePage);
508: }
509:
510: return;
511: }
512: element = element.getParent();
513: }
514:
515: } catch (JavaModelException x) {
516: JavaPlugin.log(x.getStatus());
517: }
518:
519: ISourceViewer viewer = getSourceViewer();
520: if (viewer instanceof ITextViewerExtension5) {
521: ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
522: extension.exposeModelRange(new Region(offset, length));
523: } else {
524: resetHighlightRange();
525: }
526:
527: }
528:
529: /*
530: * (non-Javadoc)
531: * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor#computeHighlightRangeSourceReference()
532: */
533: protected ISourceReference computeHighlightRangeSourceReference() {
534: if (!isDecompiled()) {
535: return super .computeHighlightRangeSourceReference();
536: }
537:
538: ISourceViewer sourceViewer = getSourceViewer();
539: if (sourceViewer == null) {
540: return null;
541: }
542: StyledText styledText = sourceViewer.getTextWidget();
543: if (styledText == null) {
544: return null;
545: }
546:
547: int caret = 0;
548: if (sourceViewer instanceof ITextViewerExtension5) {
549: ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer;
550: caret = extension.widgetOffset2ModelOffset(styledText
551: .getCaretOffset());
552: } else {
553: int offset = sourceViewer.getVisibleRegion().getOffset();
554: caret = offset + styledText.getCaretOffset();
555: }
556:
557: IJavaElement element = getElementAt(caret);
558:
559: if (!(element instanceof ISourceReference)) {
560: return null;
561: }
562: return (ISourceReference) element;
563: }
564:
565: public void showHighlightRangeOnly(boolean showHighlightRangeOnly) {
566: // disabled as we currently do not support "partial" view on selected
567: // elements
568: // super.showHighlightRangeOnly(showHighlightRangeOnly);
569: }
570:
571: protected void updateOccurrenceAnnotations(
572: ITextSelection selection, CompilationUnit astRoot) {
573: // disabled for bytecode as we currently do not support "occurencies" highlighting
574: if (hasMappedSource()) {
575: super .updateOccurrenceAnnotations(selection, astRoot);
576: }
577: }
578:
579: /**
580: * Updater that takes care of minimizing changes of the editor input.
581: */
582: private class InputUpdater implements Runnable {
583:
584: /** Has the runnable already been posted? */
585: private boolean fPosted = false;
586: /** Editor input */
587: private IClassFileEditorInput fClassFileEditorInput;
588:
589: public InputUpdater() {
590: //
591: }
592:
593: /*
594: * @see Runnable#run()
595: */
596: public void run() {
597:
598: IClassFileEditorInput input;
599: synchronized (this ) {
600: input = fClassFileEditorInput;
601: }
602:
603: try {
604: if (getSourceViewer() != null) {
605: setInput(input);
606: }
607: } finally {
608: synchronized (this ) {
609: fPosted = false;
610: }
611: }
612: }
613:
614: /**
615: * Posts this runnable into the event queue if not already there.
616: * @param input the input to be set when executed
617: */
618: public void post(IClassFileEditorInput input) {
619:
620: synchronized (this ) {
621: if (fPosted) {
622: if (input != null
623: && input.equals(fClassFileEditorInput)) {
624: fClassFileEditorInput = input;
625: }
626: return;
627: }
628: }
629:
630: if (input != null && input.equals(getEditorInput())) {
631: ISourceViewer viewer = getSourceViewer();
632: if (viewer != null) {
633: StyledText textWidget = viewer.getTextWidget();
634: if (textWidget != null && !textWidget.isDisposed()) {
635: synchronized (this ) {
636: fPosted = true;
637: fClassFileEditorInput = input;
638: }
639: textWidget.getDisplay().asyncExec(this );
640: }
641: }
642: }
643: }
644: }
645:
646: /*
647: * @see JavaEditor#getCorrespondingElement(IJavaElement)
648: */
649: protected IJavaElement getCorrespondingElement(IJavaElement element) {
650: IClassFile classFile = getClassFile();
651: if (classFile == null) {
652: return null;
653: }
654: if (classFile.equals(element
655: .getAncestor(IJavaElement.CLASS_FILE))) {
656: return element;
657: }
658: return null;
659: }
660:
661: /*
662: * 1GEPKT5: ITPJUI:Linux - Source in editor for external classes is editable Removed
663: * methods isSaveOnClosedNeeded and isDirty. Added method isEditable.
664: */
665: /*
666: * @see org.eclipse.ui.texteditor.AbstractTextEditor#isEditable()
667: */
668: public boolean isEditable() {
669: return isDecompiled();
670: }
671:
672: /*
673: * @see org.eclipse.ui.texteditor.AbstractTextEditor#isEditorInputReadOnly()
674: * @since 3.2
675: */
676: public boolean isEditorInputReadOnly() {
677: return !isDecompiled();
678: }
679:
680: /*
681: * @see ITextEditorExtension2#isEditorInputModifiable()
682: * @since 2.1
683: */
684: public boolean isEditorInputModifiable() {
685: return isDecompiled();
686: }
687:
688: public boolean isSaveAsAllowed() {
689: return isDecompiled();
690: }
691:
692: /**
693: * Translates the given object into an <code>IClassFileEditorInput</code>
694: * @param input the object to be transformed if necessary
695: * @return the transformed editor input
696: */
697: protected IClassFileEditorInput transformEditorInput(Object input) {
698:
699: if (input instanceof IFileEditorInput) {
700: IFile file = ((IFileEditorInput) input).getFile();
701: Constructor cons;
702: try {
703: cons = ExternalClassFileEditorInput.class
704: .getDeclaredConstructor(new Class[] { IFile.class });
705: cons.setAccessible(true);
706: IClassFileEditorInput classFileInput = (IClassFileEditorInput) cons
707: .newInstance(new Object[] { file });
708: return classFileInput;
709: } catch (Exception e) {
710: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
711: }
712: } else if (input instanceof IClassFileEditorInput) {
713: return (IClassFileEditorInput) input;
714: } else if (input instanceof IClassFile) {
715: return new InternalClassFileEditorInput((IClassFile) input);
716: }
717:
718: return null;
719: }
720:
721: /*
722: * @see IWorkbenchPart#createPartControl(Composite)
723: */
724: public void createPartControl(Composite parent) {
725:
726: fParent = new Composite(parent, SWT.NONE);
727: fStackLayout = new StackLayout();
728: fParent.setLayout(fStackLayout);
729:
730: fViewerComposite = new Composite(fParent, SWT.NONE);
731: fViewerComposite.setLayout(new FillLayout());
732:
733: super .createPartControl(fViewerComposite);
734:
735: fStackLayout.topControl = fViewerComposite;
736: fParent.layout();
737: initDone = true;
738: }
739:
740: /*
741: * @see ClassFileDocumentProvider.InputChangeListener#inputChanged(IClassFileEditorInput)
742: */
743: public void inputChanged(IClassFileEditorInput input) {
744: fInputUpdater.post(input);
745: IClassFile cf = input.getClassFile();
746: String source;
747: try {
748: source = cf.getSource();
749: setDecompiled(source != null && source.startsWith(MARK));
750: } catch (JavaModelException e) {
751: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
752: }
753:
754: }
755:
756: /*
757: * @see org.eclipse.ui.IWorkbenchPart#dispose()
758: */
759: public void dispose() {
760: // http://bugs.eclipse.org/bugs/show_bug.cgi?id=18510
761: IDocumentProvider documentProvider = getDocumentProvider();
762: if (documentProvider instanceof ClassFileDocumentProvider) {
763: ((ClassFileDocumentProvider) documentProvider)
764: .removeInputChangeListener(this );
765: }
766:
767: IClassFile classFile = getClassFile();
768: BytecodeBufferManager.removeBuffer(BytecodeBufferManager
769: .getBuffer(classFile));
770: super .dispose();
771: }
772:
773: public static BytecodeSourceMapper getSourceMapper() {
774: return sourceMapper;
775: }
776:
777: /**
778: * Check if we can show an inner class, which was declared in the source code of the
779: * given parent class and which could have the bytecode for the given source line.
780: *
781: * If both are true, then this method changes the input of the bytecode editor
782: * with the given class file (if any) to the inner class, or opens a new editor with
783: * the inner class.
784: *
785: * @param sourceLine requested source line (from debugger)
786: * @param parent expected parent class file
787: * @return The region in the inner class (if inner class could be found), or an empty
788: * zero-based region.
789: */
790: public static IRegion checkForInnerClass(int sourceLine,
791: IClassFile parent) {
792: IRegion region = new Region(0, 0);
793:
794: // get the editor with given class file, if any
795: BytecodeClassFileEditor editor = getBytecodeEditor(parent);
796: if (editor == null) {
797: return region;
798: }
799:
800: // get the inner class type according to the debugger stack frame, if any
801: IJavaReferenceType debugType = sourceMapper
802: .getLastTypeInDebugger();
803: if (debugType == null) {
804: return region;
805: }
806:
807: boolean externalClass = editor.getEditorInput() instanceof ExternalClassFileEditorInput;
808: IEditorInput input = null;
809: // check if it is a inner class from the class in editor
810: if (!hasInnerClass(debugType, parent)) {
811: // not only inner classes could be defined in the same source file, but also
812: // local types (non public non inner classes in the same source file)
813: IClassFile classFile = getLocalTypeClass(debugType, parent);
814: if (classFile != null) {
815: input = editor.doOpenBuffer(classFile, externalClass);
816: }
817: } else {
818: // if both exists, replace the input to the inner class
819: input = editor.doOpenBuffer(debugType, parent,
820: externalClass);
821: }
822:
823: if (input == null) {
824: return region;
825: }
826:
827: /*
828: * Now we change editor input from parent class to child class.
829: * It will change editor title too, so the user will see a new class.
830: * After this change, we could finally compute right source line for current stack
831: */
832: try {
833: editor.doSetInput(input);
834: } catch (CoreException e) {
835: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
836: return region;
837: }
838:
839: // and then map given source line to the decompiled code
840: int decompiledLine = sourceMapper.mapToDecompiled(
841: sourceLine + 1, editor.getClassFile());
842:
843: if (decompiledLine >= 0) {
844: // and get the requested line information
845: try {
846: region = editor.getDocumentProvider()
847: .getDocument(input).getLineInformation(
848: decompiledLine);
849: } catch (BadLocationException e) {
850: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
851: }
852: }
853: return region;
854: }
855:
856: /**
857: * @param debugType
858: * @param parent
859: * @return non public class (local type class) with the name from "debugType" and
860: * which source was in the "parent" class. Null if no class file could be found.
861: */
862: private static IClassFile getLocalTypeClass(
863: IJavaReferenceType debugType, IClassFile parent) {
864: try {
865: IType type = parent.getType();
866: if ((type.isLocal()) || (type.isMember())) {
867: // local type could not be defined in local or inner classes
868: return null;
869: }
870: // debugType.getSignature() == Lpackage/name/with/slashes/className;
871: String binarySignature = debugType.getSignature();
872: // get only type name from binary signature
873: int idx = binarySignature.lastIndexOf('/');
874: if (idx > 0 && idx < binarySignature.length() - 1) {
875: String name = binarySignature.substring(idx + 1);
876: if (name.charAt(name.length() - 1) == ';') {
877: name = name.substring(0, name.length() - 1);
878: }
879: return type.getPackageFragment().getClassFile(
880: name + ".class");
881: }
882: } catch (Exception e) {
883: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
884: }
885: return null;
886: }
887:
888: private static boolean hasInnerClass(IJavaReferenceType debugType,
889: IClassFile parent) {
890: try {
891: String parentName = parent.getType()
892: .getFullyQualifiedName();
893: String childName = debugType.getName();
894: return childName != null
895: && childName.startsWith(parentName + "$");
896: } catch (Exception e) {
897: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
898: }
899: return false;
900: }
901:
902: private static BytecodeClassFileEditor getBytecodeEditor(
903: IClassFile parent) {
904: IEditorReference[] editorReferences = PlatformUI.getWorkbench()
905: .getActiveWorkbenchWindow().getActivePage()
906: .getEditorReferences();
907: for (int i = 0; i < editorReferences.length; i++) {
908: IEditorPart editor = editorReferences[i].getEditor(false);
909: if (editor instanceof BytecodeClassFileEditor) {
910: BytecodeClassFileEditor bytecodeEditor = (BytecodeClassFileEditor) editor;
911: if (parent.equals((bytecodeEditor).getClassFile())) {
912: return bytecodeEditor;
913: }
914: }
915: }
916: return null;
917: }
918:
919: public ITextSelection convertSelection(
920: ITextSelection textSelection, boolean toDecompiled) {
921: int startLine = textSelection.getStartLine();
922: int newLine;
923: if (toDecompiled) {
924: newLine = sourceMapper.mapToDecompiled(startLine + 1,
925: getClassFile()) + 1;
926: } else {
927: newLine = sourceMapper.mapToSource(startLine,
928: getClassFile()) - 1;
929: }
930: if (newLine < 0) {
931: return null;
932: }
933: IDocument document = getDocumentProvider().getDocument(
934: getEditorInput());
935: try {
936: int lineOffset = document.getLineOffset(newLine);
937: return new TextSelection(lineOffset, 0);
938: } catch (BadLocationException e) {
939: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
940: }
941: return null;
942: }
943:
944: public int getSourceLine(ITextSelection bytecodeSelection) {
945: int startLine = bytecodeSelection.getStartLine();
946: return sourceMapper.mapToSource(startLine, getClassFile()) - 1;
947: }
948:
949: public ITextSelection convertLine(int sourceLine) {
950: int newLine = sourceMapper.mapToDecompiled(sourceLine + 1,
951: getClassFile()) + 1;
952: IDocument document = getDocumentProvider().getDocument(
953: getEditorInput());
954: try {
955: int lineOffset = document.getLineOffset(newLine);
956: return new TextSelection(lineOffset, 0);
957: } catch (BadLocationException e) {
958: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
959: }
960: return null;
961: }
962:
963: public int getBytecodeInstructionAtLine(int line) {
964: if (isDecompiled()) {
965: DecompiledClass decompiledClass = sourceMapper
966: .getDecompiledClass(getClassFile());
967: if (line > 0 && decompiledClass != null) {
968: return decompiledClass.getBytecodeInsn(line);
969: }
970: }
971: return -1;
972: }
973:
974: }
|