blob: 80aea9579ea710accbcb26a851bc25d21dda0995 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Boeing.
* 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:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.ote.ui.message.watch;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IRegistryEventListener;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osee.framework.logging.OseeLog;
import org.eclipse.osee.framework.ui.swt.Displays;
import org.eclipse.osee.framework.ui.swt.ImageManager;
import org.eclipse.osee.ote.message.Message;
import org.eclipse.osee.ote.message.data.MessageData;
import org.eclipse.osee.ote.message.elements.ArrayElement;
import org.eclipse.osee.ote.message.elements.Element;
import org.eclipse.ote.ui.message.internal.WatchImages;
import org.eclipse.ote.ui.message.search.MessageInfoComposite;
import org.eclipse.ote.ui.message.search.MessageInfoSelectionListener;
import org.eclipse.ote.ui.message.tree.AbstractTreeNode;
import org.eclipse.ote.ui.message.tree.ElementNode;
import org.eclipse.ote.ui.message.tree.INodeVisitor;
import org.eclipse.ote.ui.message.tree.MessageNode;
import org.eclipse.ote.ui.message.tree.RootNode;
import org.eclipse.ote.ui.message.tree.WatchedElementNode;
import org.eclipse.ote.ui.message.tree.WatchedMessageNode;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.osgi.framework.Bundle;
/**
* @author Ken J. Aguilar
*/
public class DetailsBox implements IRegistryEventListener {
private static final String EXTENSION_POINT_ID = "org.eclipse.ote.ui.message.detailsProvider";
private static final String ELEMENT = "TabProvider";
private static final String PAYLOAD_TXT = "\npayload:\n 0: ";
private static final String HEADER_TXT = "header:\n 0: ";
private static final int HEX_DUMP_BYTES_PER_ROW = 16;
private static final int HEX_DUMP_CHARS_PER_BYTE = 3;
/**
* number of characters that lead each row in the hex dump, these characters represent the byte offset indicator for
* each hex row
*/
private static int HEX_DUMP_PREFIX_CHARS = 8;
private static int HEX_DUMP_NON_PREFIX_CHAR = HEX_DUMP_BYTES_PER_ROW * HEX_DUMP_CHARS_PER_BYTE;
/**
* total number of characters per hex dump row including the newline character
*/
private static int HEX_DUMP_LINE_WIDTH = HEX_DUMP_PREFIX_CHARS + HEX_DUMP_NON_PREFIX_CHAR + 1;
private final TabFolder infoFolder;
private final TabItem hexDumpTab;
private final TabItem databaseTab;
private final StyledText hexDumpTxt;
private final Font courier;
private final Image hexImg;
private final MessageInfoComposite databaseComposite;
private final StringBuilder strBuilder = new StringBuilder(1024*128);
private TabItem selectedTab;
private final HashMap<String, TabItem> detailsProviderMap = new HashMap<String, TabItem>();
private AbstractTreeNode lastDatabaseNode;
private static final String[] hexTbl = new String[256];
static {
for (int i = 0; i < 256; i++) {
hexTbl[i] = String.format("%02X ", i);
}
}
public DetailsBox(Composite parent) {
hexImg = ImageManager.getImage(WatchImages.HEX);
courier = new Font(parent.getDisplay(), "Courier", 10, SWT.NORMAL);
/* Create Text box to display values of selected messages */
infoFolder = new TabFolder(parent, SWT.BORDER);
hexDumpTab = new TabItem(infoFolder, SWT.NONE);
hexDumpTab.setText("Hex Dump");
hexDumpTab.setImage(hexImg);
hexDumpTab.setToolTipText("displays hex dump of currently selected message");
hexDumpTxt = new StyledText(infoFolder, SWT.DOUBLE_BUFFERED | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.READ_ONLY);
hexDumpTxt.setFont(courier);
hexDumpTab.setControl(hexDumpTxt);
databaseTab = new TabItem(infoFolder, SWT.NONE);
databaseTab.setText("Database Info");
databaseTab.setToolTipText("Displays static info about a selected message");
databaseComposite = new MessageInfoComposite(infoFolder);
databaseTab.setControl(databaseComposite);
lastDatabaseNode = null;
installExtensionRegistryListener();
infoFolder.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
selectedTab = (TabItem) e.item;
}
});
selectedTab = infoFolder.getItem(infoFolder.getSelectionIndex());
}
/**
* display details about specified node
*
* @param node node whose details will be displayed in the detail window of the GUI
*/
public void setDetailText(final AbstractTreeNode node) {
if (selectedTab == null) {
return;
}
if (selectedTab == hexDumpTab) {
renderHex(node);
}
else if (selectedTab == databaseTab) {
updateDatabaseInfo(node);
} else {
DetailsProvider provider = (DetailsProvider) selectedTab.getControl();
provider.render(node);
}
}
public void dispose() {
IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
if (extensionRegistry != null) {
extensionRegistry.removeListener(this);
}
if(!courier.isDisposed()){
courier.dispose();
}
}
public void selectNode(AbstractTreeNode node) {
if (node instanceof WatchedElementNode) {
WatchedElementNode elementNode = (WatchedElementNode) node;
WatchedMessageNode msgNode = (WatchedMessageNode) elementNode.getMessageNode();
int offset = elementNode.getElement().getByteOffset() + elementNode.getElement().getMsb() / 8;
hexDumpTxt.setTopIndex(msgNode.getSubscription().getMessage().getHeaderSize() / HEX_DUMP_BYTES_PER_ROW + offset / HEX_DUMP_BYTES_PER_ROW + 2);
}
setDetailText(node);
}
private void renderHex(AbstractTreeNode node) {
if (!node.isEnabled()) {
hexDumpTxt.setText(node.getName() + "\nDISABLED: " + node.getDisabledReason());
return;
}
Point selection = hexDumpTxt.getSelection();
int horizontalPixel = hexDumpTxt.getHorizontalPixel();
int verticalPixel = hexDumpTxt.getTopPixel();
int caret = hexDumpTxt.getCaretOffset();
hexDumpTxt.setRedraw(false);
final INodeVisitor<Object> visitor = new INodeVisitor<Object>() {
@Override
public Object elementNode(final ElementNode node) {
hexDumpTxt.setStyleRange(null);
WatchedMessageNode msgNode = (WatchedMessageNode) node.getMessageNode();
if (!msgNode.getSubscription().isResolved()) {
hexDumpTxt.setText(node.getName() + " not found in library");
return null;
}
final Message<?, ?, ?> msg = msgNode.getSubscription().getMessage();
if (msg.isDestroyed()) {
return null;
}
int payloadStart = printByteDump(msg);
Element e = ((WatchedElementNode) node).getElement();
if (e != null) {
if (!e.isNonMappingElement()) {
MessageData data = msg.getActiveDataSource();
int headerSize = data.getMsgHeader() == null ? 0 : data.getMsgHeader().getHeaderSize();
if (e.getByteOffset() >= data.getCurrentLength() - headerSize) {
hexDumpTxt.setText("element outside of current message size");
return null;
}
StyleRange range = new StyleRange();
range.background = Displays.getSystemColor(SWT.COLOR_GRAY);
range.foreground = Displays.getSystemColor(SWT.COLOR_BLACK);
int offset = e.getByteOffset() + e.getMsb() / 8;
range.length = (e.getLsb() - e.getMsb() + 8) / 8 * HEX_DUMP_CHARS_PER_BYTE - 1;
int line = offset / HEX_DUMP_BYTES_PER_ROW * HEX_DUMP_LINE_WIDTH;
int lineIndent = offset % HEX_DUMP_BYTES_PER_ROW * HEX_DUMP_CHARS_PER_BYTE;
range.start = line + lineIndent + payloadStart;
if(!(e instanceof ArrayElement)){
if (HEX_DUMP_PREFIX_CHARS + lineIndent + range.length >= HEX_DUMP_LINE_WIDTH) {
int remaining = range.length - (HEX_DUMP_LINE_WIDTH - lineIndent - 9);
int numberOfRanges =remaining / HEX_DUMP_NON_PREFIX_CHAR + 2;
StyleRange[] existing = hexDumpTxt.getStyleRanges();
final StyleRange[] ranges;
if (existing.length != numberOfRanges) {
ranges = new StyleRange[numberOfRanges];
} else {
ranges = existing;
}
ranges[0] = range;
range.length -= remaining;
int c = 1;
while (remaining > 0) {
StyleRange newRange = ranges[c];
if (newRange == null) {
newRange = new StyleRange();
ranges[c] = newRange;
}
newRange.background = range.background;
newRange.foreground = range.foreground;
newRange.start = line + c * HEX_DUMP_LINE_WIDTH + payloadStart;
newRange.length = remaining < HEX_DUMP_NON_PREFIX_CHAR ? remaining : HEX_DUMP_NON_PREFIX_CHAR;
remaining -= newRange.length;
c++;
}
try{
hexDumpTxt.setStyleRanges(ranges);
} catch (Throwable th){
th.printStackTrace();
}
} else {
try{
hexDumpTxt.setStyleRange(range);
} catch (Throwable th){
th.printStackTrace();
}
}
}
}
}
return node;
}
@Override
public Object messageNode(final MessageNode node) {
WatchedMessageNode msgNode = (WatchedMessageNode) node;
final Message<?, ?, ?> msg = msgNode.getSubscription().getMessage();
if (msg != null && !msg.isDestroyed()) {
printByteDump(msg);
hexDumpTxt.setStyleRange(null);
}
return node;
}
@Override
public Object rootNode(RootNode node) {
return node;
}
};
node.visit(visitor);
hexDumpTxt.setCaretOffset(caret);
if (caret == selection.x) {
selection.x = selection.y;
selection.y = caret;
}
hexDumpTxt.setSelection(selection);
hexDumpTxt.setTopPixel(verticalPixel);
hexDumpTxt.setHorizontalPixel(horizontalPixel);
hexDumpTxt.setRedraw(true);
}
private void updateDatabaseInfo(AbstractTreeNode node) {
if (lastDatabaseNode == node) {
return;
}
lastDatabaseNode = node;
if (node == null) {
databaseComposite.search("");
return;
}
final INodeVisitor<Object> visitor = new INodeVisitor<Object>() {
@Override
public Object elementNode(final ElementNode node) {
WatchedMessageNode msgNode = (WatchedMessageNode) node.getMessageNode();
databaseComposite.search(msgNode.getMessageClassName());
return node;
}
@Override
public Object messageNode(final MessageNode node) {
WatchedMessageNode msgNode = (WatchedMessageNode) node;
databaseComposite.search(msgNode.getMessageClassName());
return node;
}
@Override
public Object rootNode(RootNode node) {
return node;
}
};
node.visit(visitor);
}
/**
* writes message data to a buffer in hex format
*/
private int printByteDump(Message<?, ?, ?> msg) {
strBuilder.setLength(0);
final byte[] data = msg.getData();
int columnCount = 0;
strBuilder.append(HEADER_TXT);
for (int i = 0; i < msg.getHeaderSize(); i++) {
if (columnCount == HEX_DUMP_BYTES_PER_ROW) {
strBuilder.append('\n').append(String.format("%5d: ", i));
columnCount = 0;
}
strBuilder.append(hexTbl[data[i] & 0xFF]);
columnCount++;
}
strBuilder.append(PAYLOAD_TXT);
int payloadStart = strBuilder.length();
columnCount = 0;
for (int i = msg.getHeaderSize(); i < data.length; i++) {
if (columnCount == HEX_DUMP_BYTES_PER_ROW) {
strBuilder.append('\n').append(String.format("%5d: ", i - msg.getHeaderSize()));
columnCount = 0;
}
strBuilder.append(hexTbl[data[i] & 0xFF]);
columnCount++;
}
strBuilder.append('\n');
hexDumpTxt.setText(strBuilder.toString());
return payloadStart;
}
private void installExtensionRegistryListener() {
IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
if (extensionRegistry == null) {
throw new IllegalStateException("The extension registry is unavailable");
}
IExtensionPoint point = extensionRegistry.getExtensionPoint(EXTENSION_POINT_ID);
if (point == null) {
return;
}
added(point.getExtensions());
extensionRegistry.addListener(this, EXTENSION_POINT_ID);
}
@Override
public void added(IExtension[] extensions) {
final List<IConfigurationElement> newElements = new LinkedList<IConfigurationElement>();
for (IExtension extension : extensions) {
for (IConfigurationElement element : extension.getConfigurationElements()) {
if (element.getName().equals(ELEMENT)) {
newElements.add(element);
}
}
}
Displays.ensureInDisplayThread(new Runnable() {
@Override
public void run() {
for (IConfigurationElement element : newElements) {
String className = element.getAttribute("className");
String bundleName = element.getContributor().getName();
Bundle bundle = Platform.getBundle(bundleName);
if (bundle == null) {
OseeLog.logf(DetailsBox.class, Level.SEVERE,
"no bundle found for name %s while handling extension element %s", bundleName, element.getName());
return;
}
try {
Class<?> clazz = bundle.loadClass(className);
Class<? extends DetailsProvider> detailsClazz = clazz.asSubclass(DetailsProvider.class);
Constructor<? extends DetailsProvider> constructor =
detailsClazz.getConstructor(Composite.class, int.class);
try {
DetailsProvider provider = constructor.newInstance(infoFolder, SWT.NONE);
TabItem newTab = new TabItem(infoFolder, SWT.NONE);
newTab.setText(provider.getTabText());
newTab.setToolTipText(provider.getTabToolTipText());
newTab.setControl(provider);
detailsProviderMap.put(element.getDeclaringExtension().getUniqueIdentifier(), newTab);
} catch (Exception ex) {
OseeLog.logf(DetailsBox.class, Level.SEVERE, "failed to install details provider");
}
} catch (ClassCastException ex) {
OseeLog.logf(
DetailsBox.class,
Level.SEVERE,
"the class named %s is not a subclass of %s", className,
DetailsProvider.class.getName());
} catch (ClassNotFoundException ex) {
OseeLog.logf(DetailsBox.class, Level.SEVERE,
"no class found named %s in bundle %s", className, bundleName);
} catch (NoSuchMethodException ex) {
OseeLog.logf(DetailsBox.class, Level.SEVERE,
"can't find appropriate constructor for %s", className);
}
}
}
});
}
@Override
public void added(IExtensionPoint[] points) {
}
@Override
public void removed(IExtension[] extensions) {
final List<TabItem> removedElements = new LinkedList<TabItem>();
for (IExtension extension : extensions) {
TabItem item = detailsProviderMap.get(extension.getUniqueIdentifier());
if (item != null) {
removedElements.add(item);
}
}
Displays.ensureInDisplayThread(new Runnable() {
@Override
public void run() {
if (!infoFolder.isDisposed()){
for (TabItem item : removedElements) {
if (selectedTab == item) {
selectedTab = null;
infoFolder.setSelection(hexDumpTab);
}
item.dispose();
}
}
}
});
}
@Override
public void removed(IExtensionPoint[] arg0) {
}
public void setMessageInfoSelectionListener(MessageInfoSelectionListener listener) {
databaseComposite.setSelectionListener(listener);
}
}