package org.marketcetera.photon.ui; import java.math.BigDecimal; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.marketcetera.messagehistory.ReportHolder; import org.marketcetera.photon.FIXFieldLocalizer; import org.marketcetera.photon.PhotonPlugin; import org.marketcetera.photon.preferences.FIXMessageColumnPreferenceParser; import org.marketcetera.quickfix.FIXDataDictionary; import org.marketcetera.quickfix.FIXMessageFactory; import org.marketcetera.quickfix.FIXMessageUtil; import org.marketcetera.quickfix.FIXValueExtractor; import org.marketcetera.quickfix.FIXVersion; import quickfix.DataDictionary; import quickfix.FieldMap; import quickfix.FieldType; import ca.odell.glazedlists.gui.TableFormat; /** * A table format and label provider for FIX message based tables. Listens to * preference changes for the assigned view ID and updates the visible columns. * * @author michael.lossos@softwaregoodness.com */ public class FIXMessageTableFormat<T> implements TableFormat<T>, ITableLabelProvider { private static final int INVALID_FIELD_ID = -1; // todo: This constant is duplicated from EnumTableFormat. private static final DateFormat TIME_FORMAT = new SimpleDateFormat( "HH:mm:ss"); //$NON-NLS-1$ // todo: This constant is duplicated from EnumTableFormat. private static final DateFormat DATE_FORMAT = new SimpleDateFormat( "yyyy-MM-dd"); //$NON-NLS-1$ private static final Class<?>[] NUMERIC_TYPES = { Number.class, Date.class, Calendar.class }; private static final int NUM_DIGITS = 6; private final String assignedViewID; private FIXMessageColumnPreferenceParser prefsParser; private Table underlyingTable; private FIXValueExtractor valueExtractor; private Class<T> underlyingClass; private ColumnTracker columnTracker = new ColumnTracker(); public FIXMessageTableFormat(Table table, final String assignedViewID, Class<T> underlyingClass) { this.underlyingTable = table; this.assignedViewID = assignedViewID; this.underlyingClass = underlyingClass; prefsParser = new FIXMessageColumnPreferenceParser(); updateColumnsFromPreferences(); } protected static class ColumnTracker { protected HashMap<Integer, TableColumn> fieldToColumnMap = new HashMap<Integer, TableColumn>(); protected HashMap<TableColumn, Integer> columnToFieldMap = new HashMap<TableColumn, Integer>(); protected HashMap<Integer, Integer> columnIndexToFieldMap = new HashMap<Integer, Integer>(); protected HashMap<Integer, Integer> fieldToColumnIndexMap = new HashMap<Integer, Integer>(); public void add(int fieldNum, TableColumn column, int columnIndex) { fieldToColumnMap.put(fieldNum, column); columnToFieldMap.put(column, fieldNum); columnIndexToFieldMap.put(columnIndex, fieldNum); fieldToColumnIndexMap.put(fieldNum, columnIndex); } public void remove(TableColumn column) { Integer fieldNum = columnToFieldMap.get(column); if (fieldNum != null) { int columnIndex = fieldToColumnIndexMap.get(fieldNum); fieldToColumnMap.remove(fieldNum); columnToFieldMap.remove(column); columnIndexToFieldMap.remove(columnIndex); fieldToColumnIndexMap.remove(fieldNum); } } public boolean containsFieldNumber(int fieldNum) { return fieldToColumnMap.containsKey(fieldNum); } public int getFieldNumber(int columnIndex) { int fieldNum = INVALID_FIELD_ID; if (columnIndexToFieldMap.containsKey(columnIndex)) { fieldNum = columnIndexToFieldMap.get(columnIndex); } return fieldNum; } } public String getAssignedViewID() { return assignedViewID; } protected void createColumn(int fieldNum) { createColumn(fieldNum, null, null); } protected void createColumn(int fieldNum, FIXDataDictionary fixDictionary, DataDictionary dictionary) { int alignment; if (isNumericColumn(fieldNum, dictionary)) { alignment = SWT.RIGHT; } else { alignment = SWT.LEFT; } TableColumn tableColumn = new TableColumn(underlyingTable, alignment); String columnName = getFIXFieldColumnName(fieldNum, fixDictionary); String localizedName = ""; //$NON-NLS-1$ if (columnName != null) { localizedName = FIXFieldLocalizer.getLocalizedFIXFieldName(columnName); } tableColumn.setText(localizedName); tableColumn.setResizable(true); tableColumn.pack(); /** * todo: Allow column moving to change the order in the column * preferences. See FIXMessageColumnPreferencePage for what needs to be * set. */ tableColumn.setMoveable(false); int columnIndex = underlyingTable.getColumnCount() - 1; columnTracker.add(fieldNum, tableColumn, columnIndex); } protected void removeColumn(TableColumn whichColumn) { columnTracker.remove(whichColumn); whichColumn.dispose(); } protected void createAllMissingColumns(List<Integer> fieldsToShow) { // todo: Handle columns that are not FIX fields. // todo: Handle adding custom FIX fields as columns. FIXDataDictionary fixDictionary = getFIXDataDictionary(); DataDictionary dictionary = getDataDictionary(); if (fieldsToShow.isEmpty()) { for (int fieldNum = 1; fieldNum < FIXMessageUtil.getMaxFIXFields(); ++fieldNum) { if (dictionary.isField(fieldNum)) { if (!columnTracker.containsFieldNumber(fieldNum)) { createColumn(fieldNum, fixDictionary, dictionary); } } } } else { for (int fieldNum : fieldsToShow) { if (!columnTracker.containsFieldNumber(fieldNum)) { createColumn(fieldNum, fixDictionary, dictionary); } } } } /** * Derived classes can override this method to add columns that are always * present. Call the createColumn() methods to create the columns and * override getFIXFieldColumnName(), getColumnValue(), and isNumericColumn() * methods to provide information about them. */ protected void createExtraColumns() { // Do nothing } protected boolean isNumericColumn(int fieldNum, DataDictionary dict) { if (dict == null) { return false; } try { FieldType fieldTypeEnum = dict.getFieldTypeEnum(fieldNum); Class<?> javaType; if (fieldTypeEnum == null){ javaType = String.class; } else { javaType = fieldTypeEnum.getJavaType(); } for (Class<?> type : NUMERIC_TYPES) { if (type.isAssignableFrom(javaType)) { return true; } } } catch (Exception anyException) { // Ignore } return false; } public void addListener(ILabelProviderListener listener) { } public void dispose() { } public void removeListener(ILabelProviderListener listener) { } public boolean isLabelProperty(Object element, String property) { return true; } public Image getColumnImage(Object element, int columnIndex) { return null; } protected void removeAllColumns() { TableColumn[] columns = underlyingTable.getColumns(); for (TableColumn column : columns) { removeColumn(column); } } protected void recreateAllColumns(List<Integer> fieldsToShow) { removeAllColumns(); // Additional static columns come first. createExtraColumns(); // Columns chosen by the user come after. createAllMissingColumns(fieldsToShow); } public void updateColumnsFromPreferences() { // todo: This should also dictate column order. List<Integer> fieldsToShowList = prefsParser .getFieldsToShow(assignedViewID); long begin = 0, end = 0; try { underlyingTable.getParent().setRedraw(false); underlyingTable.setRedraw(false); begin = System.nanoTime(); recreateAllColumns(fieldsToShowList); } finally { end = System.nanoTime(); underlyingTable.getParent().setRedraw(true); underlyingTable.setRedraw(true); } long elapsedMillis = (end - begin) / 1000000L; PhotonPlugin.getMainConsoleLogger().debug( "Rendered table: " + elapsedMillis + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ } public int getColumnCount() { return underlyingTable.getColumnCount(); } public String getFIXFieldColumnName(int fixFieldNum, FIXDataDictionary fixDataDictionary) { if (fixDataDictionary == null) { return null; } String fieldName = null; try { fieldName = fixDataDictionary.getHumanFieldName(fixFieldNum); } catch (Exception anyException) { // Ignore } if (fieldName == null || fieldName.trim().length() == 0) { fieldName = "(" + fixFieldNum + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } return fieldName; } public String getColumnName(int column) { return getFIXFieldColumnName(column, PhotonPlugin.getDefault() .getFIXDataDictionary()); } public Object getColumnValue(T baseObject, int columnIndex) { int fieldNum = columnTracker.getFieldNumber(columnIndex); Object columnValue = null; if (fieldNum > INVALID_FIELD_ID) { columnValue = extractValue(fieldNum, baseObject, columnIndex); } return columnValue; } @SuppressWarnings("unchecked") //$NON-NLS-1$ public String getColumnText(Object element, int columnIndex) { String columnText = ""; //$NON-NLS-1$ if (element != null && underlyingClass.isAssignableFrom(element.getClass())) { T elementAsT = (T) element; columnText = convertColumnValueToText(elementAsT, columnIndex); } return columnText; } protected String convertColumnValueToText(T baseObject, int columnIndex) { Object objValue = getColumnValue(baseObject, columnIndex); String textValue = null; if (objValue != null) { DataDictionary dictionary = getDataDictionary(); int fieldNum = columnTracker.getFieldNumber(columnIndex); FieldType fieldType = dictionary.getFieldTypeEnum(fieldNum); if (objValue instanceof Date) { if (fieldType.equals(FieldType.UtcTimeOnly) || fieldType.equals(FieldType.UtcTimeStamp)) { textValue = TIME_FORMAT.format((Date) objValue); } else if (fieldType.equals(FieldType.UtcDateOnly) || fieldType.equals(FieldType.UtcDate)) { textValue = DATE_FORMAT.format((Date) objValue); } } /* * Exclude field 201 since it is PutOrCall, and we want to use the localizer below. */ else if (objValue instanceof BigDecimal && fieldNum != 201) { BigDecimal n = (BigDecimal)objValue; if (n.scale() <= NUM_DIGITS) { return n.toPlainString(); } else { return n.setScale(NUM_DIGITS, BigDecimal.ROUND_DOWN).toPlainString() + "..."; //$NON-NLS-1$ } } if (textValue == null) { textValue = objValue.toString(); String fieldName = dictionary.getFieldName(fieldNum); textValue = FIXFieldLocalizer.getLocalizedFIXValueName(fieldName, textValue); } } if (textValue == null) { textValue = ""; //$NON-NLS-1$ } return textValue; } public FieldMap getFieldMap(T element, int columnIndex) { FieldMap fieldMap = null; // todo: This specialization should be in a derived class. if (element instanceof ReportHolder) { fieldMap = ((ReportHolder) element).getMessage(); } return fieldMap; } protected FIXDataDictionary getFIXDataDictionary() { return PhotonPlugin.getDefault().getFIXDataDictionary(); } protected DataDictionary getDataDictionary() { return getFIXDataDictionary().getDictionary(); } protected Object extractValue(int fieldNum, T element, int columnIndex) { if (valueExtractor == null) { // Lazily initialize the FIXValueExtractor DataDictionary dictionary = getDataDictionary(); FIXMessageFactory messageFactory = FIXVersion.getFIXVersion( dictionary.getVersion()).getMessageFactory(); valueExtractor = new FIXValueExtractor(dictionary, messageFactory); } // todo: Handle repeating groups. int groupID = 0; int groupDiscriminatorID = 0; Object groupDiscriminatorValue = null; FieldMap fieldMap = getFieldMap(element, columnIndex); Object value = valueExtractor.extractValue(fieldMap, fieldNum, groupID, groupDiscriminatorID, groupDiscriminatorValue, true); return value; } /** * Returns the table being formatted by this class. * * @return the table being formatted by this class */ protected final Table getTable() { return underlyingTable; } }