/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.github.sdbg.debug.ui.internal.presentation;
import com.github.sdbg.debug.core.SDBGDebugCorePlugin;
import com.github.sdbg.debug.core.breakpoints.SDBGBreakpoint;
import com.github.sdbg.debug.core.model.IExceptionStackFrame;
import com.github.sdbg.debug.core.model.ISDBGLogicalStructureTypeExtensions;
import com.github.sdbg.debug.core.model.ISDBGStackFrame;
import com.github.sdbg.debug.core.model.ISDBGValue;
import com.github.sdbg.debug.core.model.ISDBGVariable;
import com.github.sdbg.debug.core.util.SDBGNoSourceFoundElement;
import com.github.sdbg.debug.ui.internal.DartUtil;
import com.github.sdbg.debug.ui.internal.SDBGDebugUIPlugin;
import com.github.sdbg.debug.ui.internal.util.DebuggerEditorInput;
import com.github.sdbg.debug.ui.internal.util.StorageEditorInput;
import com.github.sdbg.utilities.Streams;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Method;
import java.net.URI;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILogicalStructureType;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.ILineBreakpoint;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage;
import org.eclipse.debug.core.sourcelookup.containers.ZipEntryStorage;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.views.variables.VariablesView;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.IInstructionPointerPresentation;
import org.eclipse.debug.ui.ISourcePresentation;
import org.eclipse.debug.ui.IValueDetailListener;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
/**
* A debug model presentation is responsible for providing labels, images, and editors associated
* with debug elements in a specific debug model.
*/
@SuppressWarnings("restriction")
public class SDBGDebugModelPresentation implements IDebugModelPresentation,
IInstructionPointerPresentation {
private static final String BREAK_ON_EXCEPTION_ANNOTAION = "org.eclipse.debug.ui.currentIPEx";
private static final LogicalValueProvider LOGICAL_VALUE_PROVIDER = new LogicalValueProvider();
private List<ILabelProviderListener> listeners = new ArrayList<ILabelProviderListener>();
private Collection<ISourcePresentation> sourcePresentations;
public SDBGDebugModelPresentation() {
}
@Override
public void addListener(ILabelProviderListener listener) {
listeners.add(listener);
}
/**
* Computes a detailed description of the given value, reporting the result to the specified
* listener. This allows a presentation to provide extra details about a selected value in the
* variable detail portion of the variables view. Since this can be a long-running operation, the
* details are reported back to the specified listener asynchronously. If <code>null</code> is
* reported, the value's value string is displayed (<code>IValue.getValueString()</code>).
*
* @param value the value for which a detailed description is required
* @param listener the listener to report the details to asynchronously
*/
@Override
public void computeDetail(final IValue value, final IValueDetailListener listener) {
if (value instanceof ISDBGValue) {
ISDBGValue debugValue = (ISDBGValue) value;
debugValue.computeDetail(new ISDBGValue.IValueCallback() {
@Override
public void detailComputed(String stringValue) {
listener.detailComputed(value, stringValue);
}
});
} else {
listener.detailComputed(value, null);
}
}
@Override
public void dispose() {
}
@Override
public String getEditorId(IEditorInput input, Object element) {
if (input instanceof SDBGSourceNotFoundEditorInput) {
return SDBGSourceNotFoundEditor.EDITOR_ID;
}
for (ISourcePresentation sourcePresentation : getSourcePresentations()) {
String editorId = sourcePresentation.getEditorId(input, element);
if (editorId != null) {
return editorId;
}
}
try {
return IDE.getEditorDescriptor(input.getName()).getId();
} catch (PartInitException e) {
return null;
}
}
@Override
public IEditorInput getEditorInput(Object element) {
for (ISourcePresentation sourcePresentation : getSourcePresentations()) {
IEditorInput result = sourcePresentation.getEditorInput(element);
if (result != null) {
return result;
}
}
if (element instanceof IMarker) {
element = DebugPlugin.getDefault().getBreakpointManager().getBreakpoint((IMarker) element);
}
if (element instanceof SDBGBreakpoint) {
IFile file = ((SDBGBreakpoint) element).getFile();
if (file != null) {
return new FileEditorInput(file);
}
return new FileStoreEditorInput(EFS.getLocalFileSystem().getStore(
new Path(((SDBGBreakpoint) element).getFilePath())));
}
if (element instanceof ILineBreakpoint) {
return new FileEditorInput((IFile) ((ILineBreakpoint) element).getMarker().getResource());
}
if (element instanceof IFile) {
return new FileEditorInput((IFile) element);
}
if (element instanceof LocalFileStorage) {
try {
URI fileUri = ((LocalFileStorage) element).getFile().toURI();
return new DebuggerEditorInput(EFS.getStore(fileUri));
} catch (CoreException e) {
DartUtil.logError(e);
}
}
if (element instanceof ZipEntryStorage) {
return new StorageEditorInput((IStorage) element) {
@Override
public boolean exists() {
return true;
}
};
}
if (element instanceof SDBGNoSourceFoundElement) {
return new SDBGSourceNotFoundEditorInput((SDBGNoSourceFoundElement) element);
}
return null;
}
/**
* This method allows us to customize images for Dart objects that are displayed in the debugger.
*/
@Override
public Image getImage(Object element) {
return getImage(element, null);
}
public Image getImage(Object element, IPresentationContext context) {
try {
if (element instanceof ISDBGVariable) {
ISDBGVariable variable = (ISDBGVariable) element;
if (variable.isThrownException()) {
return SDBGDebugUIPlugin.getImage("obj16/object_exception.png");
} else if (variable.isThisObject()) {
return SDBGDebugUIPlugin.getImage("obj16/object_this.png");
} else if (variable.isLibraryObject()) {
return SDBGDebugUIPlugin.getImage("obj16/object_library.png");
} else if (variable.isStatic() || variable.isScope() && "global".equals(variable.getName())) {
return SDBGDebugUIPlugin.getImage("obj16/object_static.png");
} else if (variable.isLocal() || variable.isScope() && "local".equals(variable.getName())) {
return SDBGDebugUIPlugin.getImage("obj16/object_local.gif");
} else {
return SDBGDebugUIPlugin.getImage("obj16/object_obj.png");
}
} else if (element instanceof ISDBGStackFrame) {
ISDBGStackFrame frame = (ISDBGStackFrame) element;
//&&&!!!
Image image = SDBGDebugUIPlugin.getImage("obj16/field_public.png"); // TODO: Copy over the images for methods
//&&&
// Image image = DartDebugUIPlugin.getImage(DartElementImageProvider.getMethodImageDescriptor(
// false,
// frame.isPrivate()));
if (frame.isUsingSourceMaps()) {
DecorationOverlayIcon overlayDescriptor = new DecorationOverlayIcon(
image,
SDBGDebugUIPlugin.getImageDescriptor("ovr16/mapped.png"),
IDecoration.BOTTOM_RIGHT);
image = SDBGDebugUIPlugin.getImage(overlayDescriptor);
}
return image;
} else {
return null;
}
} catch (Throwable t) {
SDBGDebugUIPlugin.logError(t);
return null;
}
}
@Override
public Annotation getInstructionPointerAnnotation(IEditorPart editorPart, IStackFrame frame) {
return null;
}
@Override
public String getInstructionPointerAnnotationType(IEditorPart editorPart, IStackFrame frame) {
if (frame instanceof IExceptionStackFrame) {
IExceptionStackFrame f = (IExceptionStackFrame) frame;
if (f.hasException()) {
return BREAK_ON_EXCEPTION_ANNOTAION;
}
}
return null;
}
@Override
public Image getInstructionPointerImage(IEditorPart editorPart, IStackFrame frame) {
if (frame instanceof IExceptionStackFrame) {
IExceptionStackFrame f = (IExceptionStackFrame) frame;
if (f.hasException()) {
return SDBGDebugUIPlugin.getImage("obj16/inst_ptr_exception.png");
}
}
try {
IStackFrame topOfStack = frame.getThread().getTopStackFrame();
if (frame.equals(topOfStack)) {
return SDBGDebugUIPlugin.getImage("obj16/inst_ptr_current.png");
}
} catch (DebugException de) {
}
return SDBGDebugUIPlugin.getImage("obj16/inst_ptr_normal.png");
}
@Override
public String getInstructionPointerText(IEditorPart editorPart, IStackFrame frame) {
if (frame instanceof IExceptionStackFrame) {
IExceptionStackFrame f = (IExceptionStackFrame) frame;
if (f.hasException()) {
try {
return f.getExceptionDisplayText();
} catch (DebugException e) {
DartUtil.logError(e);
}
}
}
return null;
}
@Override
public String getText(Object element) {
return getText(element, null);
}
public String getText(Object element, IPresentationContext context) {
if (element instanceof IBreakpoint) {
return getBreakpointText((IBreakpoint) element, context);
} else if (element instanceof IVariable) {
return getVariableDetailText((IVariable) element, context);
} else if (element instanceof IValue) {
return getValueDetailText((IValue) element, context);
}
return null;
}
public String getVariableDetailText(IVariable var, IPresentationContext context) {
try {
StringBuilder buff = new StringBuilder(getVariableName(var, context));
String valueString = getValueDetailText(var.getValue(), context);
if (valueString != null && valueString.length() != 0) {
buff.append(" = ");
buff.append(valueString);
}
return buff.toString();
} catch (DebugException e) {
return null;
}
}
public String getVariableName(IVariable var, IPresentationContext context) {
try {
if (var instanceof ISDBGVariable) {
ISDBGVariable svar = (ISDBGVariable) var;
if (svar.isScope()) {
return "(" + svar.getName() + ")";
}
}
ISDBGLogicalStructureTypeExtensions lstExtensions = getLogicalStructureTypeExtensions(
var.getValue(),
context);
if (lstExtensions != null) {
return lstExtensions.getVariableName(var);
} else {
return var.getName();
}
} catch (CoreException e) {
return null;
}
}
@Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
@Override
public void removeListener(ILabelProviderListener listener) {
listeners.remove(listener);
}
@Override
public void setAttribute(String attribute, Object value) {
}
/**
* Return a textual description of the breakpoint. It looks something like:
* <p>
* <code>project-name, path/to/file.dart, line 123, 'text.of.line();'</code>
*
* @param bp
* @return
*/
protected String getBreakpointText(IBreakpoint bp, IPresentationContext context) {
try {
String text;
if (bp instanceof SDBGBreakpoint) {
SDBGBreakpoint sdbgBreakpoint = (SDBGBreakpoint) bp;
IFile file = sdbgBreakpoint.getFile();
if (file != null) {
text = file.getProject().getName() + ", "
+ file.getProjectRelativePath().toPortableString() + ", line "
+ NumberFormat.getNumberInstance().format(sdbgBreakpoint.getLine());
} else {
text = sdbgBreakpoint.getName() + ", line "
+ NumberFormat.getNumberInstance().format(sdbgBreakpoint.getLine());
}
} else {
text = bp.getMarker().getResource().getProject().getName() + ", "
+ bp.getMarker().getResource().getProjectRelativePath().toPortableString();
if (bp instanceof ILineBreakpoint) {
text += ", line "
+ NumberFormat.getNumberInstance().format(((ILineBreakpoint) bp).getLineNumber());
String lineInfo = getLineExtract((ILineBreakpoint) bp);
if (lineInfo != null) {
text = text + ", '" + lineInfo + "'";
}
}
}
return text;
} catch (CoreException e) {
throw new RuntimeException(e);
}
}
/**
* Build the text for an {@link IValue}. This can be a long running call since we wait for the
* toString call to get back with the value.
*/
protected String getValueDetailText(IValue value, IPresentationContext context) {
try {
if (value == null) {
return "<unknown value>";
}
ISDBGLogicalStructureTypeExtensions lstExtensions = getLogicalStructureTypeExtensions(
value,
context);
if (lstExtensions != null
&& lstExtensions.isValueDetailStringComputedByLogicalStructure(value)) {
value = getLogicalValue(value, context);
}
if (!(value instanceof ISDBGValue) || !((ISDBGValue) value).isPrimitive()) {
final CountDownLatch latch = new CountDownLatch(1);
final String valueString[] = new String[1];
computeDetail(value, new IValueDetailListener() {
@Override
public void detailComputed(IValue value, String result) {
valueString[0] = result;
latch.countDown();
}
});
try {
latch.await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return null;
}
if (value instanceof ISDBGValue && ((ISDBGValue) value).isListValue()) {
valueString[0] = "[" + valueString[0] + "]";
}
return valueString[0];
} else {
return value.getValueString();
}
} catch (CoreException e) {
return "<unknown value>";
}
}
protected String getValueText(IValue value, IPresentationContext context) throws CoreException {
ISDBGLogicalStructureTypeExtensions lstExtensions = getLogicalStructureTypeExtensions(
value,
context);
if (lstExtensions != null && lstExtensions.isValueStringComputedByLogicalStructure(value)) {
value = getLogicalValue(value, context);
}
return value.getValueString();
}
private String getLineExtract(ILineBreakpoint bp) {
try {
Reader r;
if (bp instanceof SDBGBreakpoint) {
r = new InputStreamReader(
((SDBGBreakpoint) bp).getContents(),
((SDBGBreakpoint) bp).getCharset());
} else {
r = new InputStreamReader(
((IFile) bp.getMarker().getResource()).getContents(),
((IFile) bp.getMarker().getResource()).getCharset());
}
List<String> lines = Streams.loadLinesAndClose(r);
int line = bp.getLineNumber() - 1;
if (line > 0 && line < lines.size()) {
String lineStr = lines.get(line).trim();
return lineStr.length() == 0 ? null : lineStr;
}
} catch (IOException ioe) {
return null;
} catch (CoreException ce) {
return null;
}
return null;
}
private ISDBGLogicalStructureTypeExtensions getLogicalStructureTypeExtensions(IValue value,
IPresentationContext context) {
if (context != null && isShowLogicalStructure(context)) {
ILogicalStructureType[] types = DebugPlugin.getLogicalStructureTypes(value);
if (types.length > 0) {
ILogicalStructureType type = DebugPlugin.getDefaultStructureType(types);
if (type instanceof ISDBGLogicalStructureTypeExtensions) {
return (ISDBGLogicalStructureTypeExtensions) type;
} else if (type instanceof IAdaptable) {
return (ISDBGLogicalStructureTypeExtensions) ((IAdaptable) type).getAdapter(ISDBGLogicalStructureTypeExtensions.class);
} else if (type.getClass().getName().equals(
"org.eclipse.debug.internal.core.LogicalStructureType")) {
// This is really nasty now, but LogicalStructureType is unfortunately not IAdaptable...
try {
Method method = type.getClass().getDeclaredMethod("getDelegate");
method.setAccessible(true);
Object delegateType = method.invoke(type);
if (delegateType instanceof ISDBGLogicalStructureTypeExtensions) {
return (ISDBGLogicalStructureTypeExtensions) delegateType;
} else if (delegateType instanceof IAdaptable) {
return (ISDBGLogicalStructureTypeExtensions) ((IAdaptable) delegateType).getAdapter(ISDBGLogicalStructureTypeExtensions.class);
}
} catch (Exception e) {
SDBGDebugUIPlugin.logError(e);
return null;
}
}
}
}
return null;
}
private IValue getLogicalValue(IValue value, IPresentationContext context) throws CoreException {
return LOGICAL_VALUE_PROVIDER.getLogicalValue(value, context);
}
private Collection<ISourcePresentation> getSourcePresentations() {
if (sourcePresentations == null) {
sourcePresentations = new ArrayList<ISourcePresentation>();
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(
"com.github.sdbg.debug.ui.sourcePresentation");
for (IConfigurationElement element : extensionPoint.getConfigurationElements()) {
try {
sourcePresentations.add((ISourcePresentation) element.createExecutableExtension("class"));
} catch (CoreException e) {
SDBGDebugCorePlugin.logError(e);
}
}
}
return sourcePresentations;
}
/**
* Return whether to show compute a logical structure or a raw structure in the specified context
*
* @return whether to show compute a logical structure or a raw structure in the specified context
*/
private boolean isShowLogicalStructure(IPresentationContext context) {
Boolean show = (Boolean) context.getProperty(VariablesView.PRESENTATION_SHOW_LOGICAL_STRUCTURES);
return show != null && show.booleanValue();
}
}