/*******************************************************************************
* Copyright (c) 2007, 2010 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tm.internal.tcf.debug.ui.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.tm.internal.tcf.debug.model.TCFContextState;
import org.eclipse.tm.internal.tcf.debug.ui.ImageCache;
import org.eclipse.tm.tcf.protocol.IToken;
import org.eclipse.tm.tcf.services.IRegisters;
import org.eclipse.tm.tcf.util.TCFDataCache;
import org.eclipse.tm.tcf.util.TCFTask;
public class TCFNodeRegister extends TCFNode implements IElementEditor, IWatchInExpressions, IDetailsProvider {
private final TCFChildrenRegisters children;
private final TCFData<IRegisters.RegistersContext> context;
private final TCFData<String> expression_text;
private final TCFData<byte[]> value;
private final boolean is_stack_frame_register;
private byte[] prev_value;
private byte[] next_value;
private static final RGB
rgb_error = new RGB(192, 0, 0),
rgb_highlight = new RGB(255, 255, 128);
private int index;
TCFNodeRegister(TCFNode parent, final String id) {
super(parent, id);
if (parent instanceof TCFNodeStackFrame) is_stack_frame_register = true;
else if (parent instanceof TCFNodeRegister) is_stack_frame_register = ((TCFNodeRegister)parent).is_stack_frame_register;
else is_stack_frame_register = false;
children = new TCFChildrenRegisters(this);
context = new TCFData<IRegisters.RegistersContext>(channel) {
@Override
protected boolean startDataRetrieval() {
IRegisters regs = launch.getService(IRegisters.class);
command = regs.getContext(id, new IRegisters.DoneGetContext() {
public void doneGetContext(IToken token, Exception error, IRegisters.RegistersContext context) {
set(token, error, context);
}
});
return false;
}
};
expression_text = new TCFData<String>(channel) {
@Override
protected boolean startDataRetrieval() {
Throwable err = null;
TCFNodeRegister n = TCFNodeRegister.this;
ArrayList<String> names = new ArrayList<String>();
for (;;) {
if (!n.context.validate(this)) return false;
IRegisters.RegistersContext ctx = n.context.getData();
if (ctx == null) {
err = n.context.getError();
break;
}
String s = ctx.getName();
if (s == null) break;
names.add(s);
if (!(n.parent instanceof TCFNodeRegister)) break;
n = (TCFNodeRegister)n.parent;
}
if (names.size() == 0 || err != null) {
set(null, err, null);
}
else {
StringBuffer bf = new StringBuffer();
boolean first = true;
int m = names.size();
while (m > 0) {
String s = names.get(--m);
boolean need_quotes = false;
int l = s.length();
for (int i = 0; i < l; i++) {
char ch = s.charAt(i);
if (ch >= 'A' && ch <= 'Z') continue;
if (ch >= 'a' && ch <= 'z') continue;
if (ch >= '0' && ch <= '9') continue;
need_quotes = true;
break;
}
if (!first) bf.append('.');
if (need_quotes) bf.append("$\"");
if (first) bf.append('$');
bf.append(s);
if (need_quotes) bf.append('"');
first = false;
}
set(null, null, bf.toString());
}
return true;
}
};
value = new TCFData<byte[]>(channel) {
@Override
protected boolean startDataRetrieval() {
Boolean b = usePrevValue(this);
if (b == null) return false;
if (b) {
set(null, null, prev_value);
return true;
}
if (!context.validate(this)) return false;
IRegisters.RegistersContext ctx = context.getData();
if (ctx == null || ctx.getSize() <= 0) {
set(null, null, null);
return true;
}
command = ctx.get(new IRegisters.DoneGet() {
public void doneGet(IToken token, Exception error, byte[] value) {
if (error != null) {
Boolean b = usePrevValue(null);
if (b != null && b) {
set(token, null, prev_value);
return;
}
}
set(token, error, value);
}
});
return false;
}
};
}
public TCFDataCache<IRegisters.RegistersContext> getContext() {
return context;
}
public TCFDataCache<byte[]> getValue() {
return value;
}
public TCFChildren getChildren() {
return children;
}
public TCFDataCache<String> getExpressionText() {
return expression_text;
}
void setIndex(int index) {
this.index = index;
}
private Boolean usePrevValue(Runnable done) {
// Check if view should show old value.
// Old value is shown if context is running or
// stack trace does not contain expression parent frame.
// Return null if waiting for cache update.
if (prev_value == null) return false;
if (parent instanceof TCFNodeStackFrame) {
TCFNodeExecContext exe = (TCFNodeExecContext)parent.parent;
TCFDataCache<TCFContextState> state_cache = exe.getState();
if (!state_cache.validate(done)) return null;
TCFContextState state = state_cache.getData();
if (state == null || !state.is_suspended) return true;
TCFChildrenStackTrace stack_trace_cache = exe.getStackTrace();
if (!stack_trace_cache.validate(done)) return null;
if (stack_trace_cache.getData().get(parent.id) == null) return true;
}
else if (parent instanceof TCFNodeExecContext) {
TCFNodeExecContext exe = (TCFNodeExecContext)parent;
TCFDataCache<TCFContextState> state_cache = exe.getState();
if (!state_cache.validate(done)) return null;
TCFContextState state = state_cache.getData();
if (state == null || !state.is_suspended) return true;
}
return false;
}
private void appendErrorText(StringBuffer bf, Throwable error) {
if (error == null) return;
bf.append("Exception: ");
bf.append(TCFModel.getErrorMessage(error, true));
}
public boolean getDetailText(StyledStringBuffer bf, Runnable done) {
if (!context.validate(done)) return false;
if (!value.validate(done)) return false;
int pos = bf.length();
appendErrorText(bf.getStringBuffer(), context.getError());
if (bf.length() == 0) appendErrorText(bf.getStringBuffer(), value.getError());
if (bf.length() > pos) {
bf.append(pos, 0, null, rgb_error);
}
else {
IRegisters.RegistersContext ctx = context.getData();
if (ctx != null) {
if (ctx.getDescription() != null) {
bf.append(ctx.getDescription());
bf.append('\n');
}
int l = bf.length();
if (ctx.isReadable()) {
bf.append("readable");
}
if (ctx.isReadOnce()) {
if (l < bf.length()) bf.append(", ");
bf.append("read once");
}
if (ctx.isWriteable()) {
if (l < bf.length()) bf.append(", ");
bf.append("writable");
}
if (ctx.isWriteOnce()) {
if (l < bf.length()) bf.append(", ");
bf.append("write once");
}
if (ctx.hasSideEffects()) {
if (l < bf.length()) bf.append(", ");
bf.append("side effects");
}
if (l < bf.length()) bf.append('\n');
}
byte[] v = value.getData();
if (v != null) {
bf.append("Hex: ", SWT.BOLD);
bf.append(toNumberString(16));
bf.append(", ");
bf.append("Dec: ", SWT.BOLD);
bf.append(toNumberString(10));
bf.append(", ");
bf.append("Oct: ", SWT.BOLD);
bf.append(toNumberString(8));
bf.append('\n');
bf.append("Bin: ", SWT.BOLD);
bf.append(toNumberString(2));
bf.append('\n');
}
}
return true;
}
@Override
protected boolean getData(IHasChildrenUpdate result, Runnable done) {
if (!children.validate(done)) return false;
result.setHasChilren(children.size() > 0);
return true;
}
@Override
protected boolean getData(IChildrenCountUpdate result, Runnable done) {
if (!children.validate(done)) return false;
result.setChildCount(children.size());
return true;
}
@Override
protected boolean getData(IChildrenUpdate result, Runnable done) {
return children.getData(result, done);
}
@Override
protected boolean getData(ILabelUpdate result, Runnable done) {
TCFDataCache<?> pending = null;
if (!context.validate()) pending = context;
if (!value.validate()) pending = value;
if (pending != null) {
pending.wait(done);
return false;
}
String[] cols = result.getColumnIds();
if (cols == null) {
setLabel(result, -1, 16);
}
else {
IRegisters.RegistersContext ctx = context.getData();
for (int i = 0; i < cols.length; i++) {
String c = cols[i];
if (ctx == null) {
result.setForeground(rgb_error, i);
result.setLabel("N/A", i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_NAME)) {
result.setLabel(ctx.getName(), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_HEX_VALUE)) {
setLabel(result, i, 16);
}
else if (c.equals(TCFColumnPresentationRegister.COL_DEC_VALUE)) {
setLabel(result, i, 10);
}
else if (c.equals(TCFColumnPresentationRegister.COL_DESCRIPTION)) {
result.setLabel(ctx.getDescription(), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_READBLE)) {
result.setLabel(bool(ctx.isReadable()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_READ_ONCE)) {
result.setLabel(bool(ctx.isReadOnce()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_WRITEABLE)) {
result.setLabel(bool(ctx.isWriteable()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_WRITE_ONCE)) {
result.setLabel(bool(ctx.isWriteOnce()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_SIDE_EFFECTS)) {
result.setLabel(bool(ctx.hasSideEffects()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_VOLATILE)) {
result.setLabel(bool(ctx.isVolatile()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_FLOAT)) {
result.setLabel(bool(ctx.isFloat()), i);
}
else if (c.equals(TCFColumnPresentationRegister.COL_MNEMONIC)) {
result.setLabel(getMnemonic(ctx), i);
}
}
}
boolean changed = false;
next_value = value.getData();
if (prev_value != null && next_value != null) {
if (prev_value.length != next_value.length) {
changed = true;
}
else {
for (int i = 0; i < prev_value.length; i++) {
if (prev_value[i] != next_value[i]) changed = true;
}
}
}
if (changed) {
result.setBackground(rgb_highlight, 0);
if (cols != null) {
for (int i = 1; i < cols.length; i++) {
result.setBackground(rgb_highlight, i);
}
}
}
result.setImageDescriptor(ImageCache.getImageDescriptor(ImageCache.IMG_REGISTER), 0);
return true;
}
private void setLabel(ILabelUpdate result, int col, int radix) {
IRegisters.RegistersContext ctx = context.getData();
Throwable error = context.getError();
if (error == null) error = value.getError();
byte[] data = value.getData();
if (error != null || ctx == null) {
result.setForeground(rgb_error, col);
result.setLabel("N/A", col);
}
else if (data != null) {
String s = toNumberString(radix);
if (col >= 0) {
result.setLabel(s, col);
}
else {
result.setLabel(ctx.getName() + " = " + s, 0);
}
}
}
private String toNumberString(int radix) {
IRegisters.RegistersContext ctx = context.getData();
byte[] data = value.getData();
if (ctx == null || data == null) return "N/A";
if (radix == 2) {
StringBuffer bf = new StringBuffer();
int i = data.length * 8;
while (i > 0) {
if (i % 4 == 0 && bf.length() > 0) bf.append(',');
i--;
int j = i / 8;
if (ctx.isBigEndian()) j = data.length - j - 1;
if ((data[j] & (1 << (i % 8))) != 0) {
bf.append('1');
}
else {
bf.append('0');
}
}
return bf.toString();
}
if (radix == 10 && ctx.isFloat()) {
String s = TCFNumberFormat.toFPString(data, 0, data.length, ctx.isBigEndian());
if (s != null) return s;
}
BigInteger b = TCFNumberFormat.toBigInteger(data, 0, data.length, ctx.isBigEndian(), false);
String s = b.toString(radix);
switch (radix) {
case 8:
if (!s.startsWith("0")) s = "0" + s;
break;
case 16:
int l = data.length * 2 - s.length();
if (l < 0) l = 0;
if (l > 16) l = 16;
s = "0000000000000000".substring(0, l) + s;
break;
}
return s;
}
private String bool(boolean b) {
return b ? "yes" : "no";
}
private String getMnemonic(IRegisters.RegistersContext ctx) {
if (value.getData() != null) {
IRegisters.NamedValue[] arr = ctx.getNamedValues();
if (arr != null) {
for (IRegisters.NamedValue n : arr) {
if (Arrays.equals(n.getValue(), value.getData())) return n.getName();
}
}
}
return "";
}
private void postStateChangedDelta() {
for (TCFModelProxy p : model.getModelProxies()) {
if (!IDebugUIConstants.ID_REGISTER_VIEW.equals(p.getPresentationContext().getId())) continue;
p.addDelta(this, IModelDelta.STATE);
}
}
void onValueChanged() {
prev_value = next_value;
value.reset();
TCFNode n = parent;
while (n != null) {
if (n instanceof TCFNodeExecContext) {
((TCFNodeExecContext)n).onRegisterValueChanged();
break;
}
else if (n instanceof TCFNodeRegister) {
TCFNodeRegister r = (TCFNodeRegister)n;
if (r.value.isValid() && r.value.getData() != null) {
r.value.reset();
r.postStateChangedDelta();
}
}
n = n.parent;
}
children.onParentValueChanged();
postStateChangedDelta();
}
void onParentValueChanged() {
value.reset();
children.onParentValueChanged();
postStateChangedDelta();
}
void onSuspended() {
prev_value = next_value;
value.reset();
children.onSuspended();
// Unlike thread registers, stack frame register list must be retrieved on every suspend
if (is_stack_frame_register) children.reset();
// No need to post delta: parent posted CONTENT
}
void onRegistersChanged() {
children.onRegistersChanged();
expression_text.reset();
context.reset();
value.reset();
// No need to post delta: parent posted CONTENT
}
public CellEditor getCellEditor(IPresentationContext context, String column_id, Object element, Composite parent) {
assert element == this;
if (TCFColumnPresentationRegister.COL_HEX_VALUE.equals(column_id)) {
return new TextCellEditor(parent);
}
if (TCFColumnPresentationRegister.COL_DEC_VALUE.equals(column_id)) {
return new TextCellEditor(parent);
}
return null;
}
private static final ICellModifier cell_modifier = new ICellModifier() {
public boolean canModify(Object element, final String property) {
final TCFNodeRegister node = (TCFNodeRegister)element;
return new TCFTask<Boolean>() {
public void run() {
if (!node.context.validate(this)) return;
IRegisters.RegistersContext ctx = node.context.getData();
if (ctx != null && ctx.isWriteable()) {
if (!ctx.isReadable()) {
done(Boolean.TRUE);
return;
}
if (!node.value.validate(this)) return;
if (node.value.getError() == null) {
if (TCFColumnPresentationRegister.COL_HEX_VALUE.equals(property)) {
done(TCFNumberFormat.isValidHexNumber(node.toNumberString(16)) == null);
return;
}
if (TCFColumnPresentationRegister.COL_DEC_VALUE.equals(property)) {
done(TCFNumberFormat.isValidDecNumber(true, node.toNumberString(10)) == null);
return;
}
}
}
done(Boolean.FALSE);
}
}.getE();
}
public Object getValue(Object element, final String property) {
final TCFNodeRegister node = (TCFNodeRegister)element;
return new TCFTask<String>() {
public void run() {
if (!node.context.validate(this)) return;
IRegisters.RegistersContext ctx = node.context.getData();
if (!ctx.isReadable()) {
done("0");
return;
}
if (!node.value.validate(this)) return;
if (node.value.getError() == null) {
if (TCFColumnPresentationRegister.COL_HEX_VALUE.equals(property)) {
done(node.toNumberString(16));
return;
}
if (TCFColumnPresentationRegister.COL_DEC_VALUE.equals(property)) {
done(node.toNumberString(10));
return;
}
}
done(null);
}
}.getE();
}
public void modify(Object element, final String property, final Object value) {
if (value == null) return;
final TCFNodeRegister node = (TCFNodeRegister)element;
new TCFTask<Boolean>() {
public void run() {
try {
if (!node.context.validate(this)) return;
IRegisters.RegistersContext ctx = node.context.getData();
if (ctx != null && ctx.isWriteable()) {
byte[] bf = null;
boolean is_float = ctx.isFloat();
int size = ctx.getSize();
boolean big_endian = ctx.isBigEndian();
String input = (String)value;
String error = null;
if (TCFColumnPresentationRegister.COL_HEX_VALUE.equals(property)) {
error = TCFNumberFormat.isValidHexNumber(input);
if (error == null) bf = TCFNumberFormat.toByteArray(input, 16, false, size, false, big_endian);
}
else if (TCFColumnPresentationRegister.COL_DEC_VALUE.equals(property)) {
error = TCFNumberFormat.isValidDecNumber(is_float, input);
if (error == null) bf = TCFNumberFormat.toByteArray(input, 10, is_float, size, is_float, big_endian);
}
if (error != null) throw new Exception("Invalid value: " + value, new Exception(error));
if (bf != null) {
ctx.set(bf, new IRegisters.DoneSet() {
public void doneSet(IToken token, Exception error) {
if (error != null) {
node.model.showMessageBox("Cannot modify register value", error);
done(Boolean.FALSE);
}
else {
node.value.reset();
node.postStateChangedDelta();
done(Boolean.TRUE);
}
}
});
return;
}
}
done(Boolean.FALSE);
}
catch (Throwable x) {
node.model.showMessageBox("Cannot modify register value", x);
done(Boolean.FALSE);
}
}
}.getE();
}
};
public ICellModifier getCellModifier(IPresentationContext context, Object element) {
assert element == this;
return cell_modifier;
}
@Override
public int compareTo(TCFNode n) {
if (n instanceof TCFNodeRegister) {
TCFNodeRegister r = (TCFNodeRegister)n;
if (index < r.index) return -1;
if (index > r.index) return +1;
}
return id.compareTo(n.id);
}
}