package org.marketcetera.photon.views; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jface.action.ControlContribution; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Text; import org.marketcetera.core.ClassVersion; import org.marketcetera.messagehistory.ReportHolder; import org.marketcetera.messagehistory.TradeReportsHistory; import org.marketcetera.photon.FIXFieldLocalizer; import org.marketcetera.photon.Messages; import org.marketcetera.photon.PhotonPlugin; import org.marketcetera.photon.actions.AddExecutionReportAction; import org.marketcetera.photon.messagehistory.FIXRegexMatcher; import org.marketcetera.photon.messagehistory.FIXStringMatcher; import org.marketcetera.photon.ui.BrokerSupportTableFormat; import org.marketcetera.photon.ui.ContextMenuFactory; import org.marketcetera.photon.ui.EventListContentProvider; import org.marketcetera.photon.ui.FIXMessageTableFormat; import org.marketcetera.photon.ui.FIXMessageTableRefresher; import org.marketcetera.photon.ui.IndexedTableViewer; import org.marketcetera.photon.ui.TextContributionItem; import org.marketcetera.quickfix.FIXMessageUtil; import quickfix.Field; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.matchers.AbstractMatcherEditor; import ca.odell.glazedlists.matchers.Matchers; import ca.odell.glazedlists.util.concurrent.Lock; /* $License$ */ /** * A view for FIX messages that uses a FIXMessageTableFormat and ensures the * columns are refreshed when preferences change. * * @author michael.lossos@softwaregoodness.com * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> */ @ClassVersion("$Id: AbstractFIXMessagesView.java 16543 2013-04-11 16:02:00Z colin $") public abstract class AbstractFIXMessagesView extends MessagesViewBase<ReportHolder> { /** * refresher for the view table */ private FIXMessageTableRefresher tableRefresher; /** * filter matcher editor used to dynamically filter the table contents */ private final FilterMatcherEditor filterMatcherEditor = new FilterMatcherEditor(); /** * default matcher matches all rows */ private static final ca.odell.glazedlists.matchers.Matcher<ReportHolder> DEFAULT_MATCHER = Matchers.trueMatcher(); /** * regex determining the search pattern for the view filter */ private static final String FILTER_PATTERN = "([^~=]*)(=|~=)(.*)"; //$NON-NLS-1$ /** * regex pattern object used to split the filter text entered by the user (this is *not* the regex the user enters) */ private final Pattern mFilterPattern; /** * text contents of the filter widget */ private String mFilterText = ""; //$NON-NLS-1$ /** * Create a new AbstractFIXMessagesView instance. */ public AbstractFIXMessagesView() { mFilterPattern = Pattern.compile(FILTER_PATTERN); } /* (non-Javadoc) * @see org.marketcetera.photon.views.MessagesViewBase#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(Composite inParent) { super.createPartControl(inParent); TradeReportsHistory messageHistory = PhotonPlugin.getDefault().getTradeReportsHistory(); if (messageHistory != null) { setInput(messageHistory); } } /** * Sets the input object upon which the message view is based. * * @param inHistory a <code>FIXMessageHistory</code> value containing the messages to display */ public void setInput(TradeReportsHistory inHistory) { EventList<ReportHolder> list = getMessageList(inHistory); // Get the write lock since it is needed for sorting (see PN-416) Lock writeLock = list.getReadWriteLock().writeLock(); writeLock.lock(); try { super.setInput(new FilterList<ReportHolder>(list, getFilterMatcherEditor())); } finally { writeLock.unlock(); } } /* (non-Javadoc) * @see org.marketcetera.photon.views.MessagesViewBase#dispose() */ @Override public void dispose() { super.dispose(); if (tableRefresher != null) { tableRefresher.dispose(); tableRefresher = null; } } /** * Gets the unique ID of the view. * * @return a <code>String</code> value */ protected abstract String getViewID(); /** * Gets the subset of the given <code>Message</code> history that applies to the * implementing subclass. * * @param inHistory a <code>FIXMessageHistory</code> value * @return the event list of report holders */ protected abstract EventList<ReportHolder> getMessageList(TradeReportsHistory inHistory); /** * Gets the current value of the filter widget. * * @return a <code>String</code> value */ protected final String getFilterText() { return mFilterText; } /** * Gets the dynamic match generator. * * @return a <code>FilterMatcherEditor</code> value */ protected final FilterMatcherEditor getFilterMatcherEditor() { return filterMatcherEditor; } /* * (non-Javadoc) * * @see org.marketcetera.photon.views.MessagesViewBase#initializeToolBar(org.eclipse.jface.action.IToolBarManager) */ @Override protected void initializeToolBar(IToolBarManager inTheToolBarManager) { inTheToolBarManager.add(new ControlContribution(null) { @Override protected Control createControl(Composite parent) { // surround in composite to be able to control the layout Composite composite = new Composite(parent, SWT.NONE); GridLayoutFactory.swtDefaults().applyTo(composite); Label label = new Label(composite, SWT.NONE); label.setText(Messages.FIX_MESSAGE_VIEW_FILTER_LABEL.getText()); GridDataFactory.defaultsFor(label).applyTo(label); return composite; } }); TextContributionItem filterTextContributionItem = new TextContributionItem(""); //$NON-NLS-1$ filterTextContributionItem.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { Text theText = (Text) e.widget; setFilterText(theText.getText()); if ('\r' == e.character) { try { handleFilter(mFilterText); } catch (Throwable t) { t.printStackTrace(); } } } }); inTheToolBarManager.add(filterTextContributionItem); // Add execution report button inTheToolBarManager.add(new AddExecutionReportAction()); } /** * The FIXMessageTableFormat manages the addition/removal of columns. The * Enum[] columns feature should not be used to create columns. * * @return always null */ @Override protected Enum<?>[] getEnumValues() { return null; } /** * Creates the table format for the view. * * @param inMessageTable a <code>Table</code> value * @return a <code>FIXMessageTableFormat<MessageHolder></code> value */ protected FIXMessageTableFormat<ReportHolder> createFIXMessageTableFormat( Table inMessageTable) { return new BrokerSupportTableFormat(inMessageTable, getViewID()); } /* (non-Javadoc) * @see org.marketcetera.photon.views.MessagesViewBase#createTableViewer(org.eclipse.swt.widgets.Table, java.lang.Enum<?>[]) */ @Override protected IndexedTableViewer createTableViewer(Table inMessageTable, Enum<?>[] inEnums) { IndexedTableViewer aMessagesViewer = new IndexedTableViewer(inMessageTable); getSite().setSelectionProvider(aMessagesViewer); aMessagesViewer.setContentProvider(new EventListContentProvider<ReportHolder>()); FIXMessageTableFormat<ReportHolder> tableFormat = createFIXMessageTableFormat(inMessageTable); aMessagesViewer.setLabelProvider(tableFormat); tableRefresher = new FIXMessageTableRefresher(aMessagesViewer, tableFormat); createContextMenu(inMessageTable); return aMessagesViewer; } /** * Creates the <code>Matcher</code> to use for the <code>String</code> match filter. * * @param inFixField an <code>int</code> value containing the FIX field against which to match * @param inValue a <code>String</code> value containing the value to match against the table rows * @return a {@link ca.odell.glazedlists.matchers.Matcher}<{@link ReportHolder}> value */ protected ca.odell.glazedlists.matchers.Matcher<ReportHolder> createStringMatcher(int inFixField, String inValue) { return new FIXStringMatcher(inFixField, inValue); } /** * Creates the <code>Matcher</code> to use for the <code>Regex</code> match filter. * * @param inFixField an <code>int</code> value containing the FIX field against which to match * @param inValue a <code>String</code> value containing the value to match against the table rows * @return a <code>ca.odell.glazedlists.matchers.Matcher<MessageHolder></code> value */ protected ca.odell.glazedlists.matchers.Matcher<ReportHolder> createRegexMatcher(int inFixField, String inValue) { return new FIXRegexMatcher(inFixField, inValue); } /** * Processes a change in the contents of the filter widget. * * @param inValue a <code>String</code> value containing the text of the filter widget */ protected final void handleFilter(String inValue) { // inValue contains the full text of what the user entered in the filter widget, e.g. Side=B if (inValue.length() > 0) { // some filter value has been entered, process that value Matcher regexMatcher = mFilterPattern.matcher(inValue); // if the value is meaningful, continue, otherwise, exit and do nothing (no filtering changes) if (regexMatcher.matches()) { // the first match of the regex must be the field against which to match String fieldSpecifier = regexMatcher.group(1); try { // the second is the operator (either '=' or '~=' as dictated by the regex pattern) String operator = regexMatcher.group(2); // the third is the value to match String value = regexMatcher.group(3); // the field name may be one that we've translated to a shorter or more readable version // try to translate it back first String fieldNameToUse = FIXFieldLocalizer.readFIXFieldNameFromCache(fieldSpecifier); // dig out the field object indicated by the fieldSpecified (a failure will throw a CoreException) Field<?> fixField = FIXMessageUtil.getQuickFixFieldFromName(fieldNameToUse); // this is the int value of the FIX field which we now know is valid int fixFieldInt = fixField.getField(); // create a matcher depending on the operator entered by the user if ("=".equals(operator)) { //$NON-NLS-1$ // use a straight string matcher getFilterMatcherEditor().setMatcher(createStringMatcher(fixFieldInt, value)); } else { // use a regex matcher assert "~=".equals(operator); //$NON-NLS-1$ getFilterMatcherEditor().setMatcher(createRegexMatcher(fixFieldInt, value)); } } catch (Throwable t) { PhotonPlugin.getMainConsoleLogger().error(UNRECOGNIZED_FIELD.getText(fieldSpecifier)); } } } else { // the value entered by the user is of zero length, use the default matcher getFilterMatcherEditor().setMatcher(getDefaultMatcher()); } } /** * Gets the default <code>Matcher</code> to use when no filter is specified. * * @return a <code>ca.odell.glazedlists.matchers.Matcher<MessageHolder></code> value */ protected ca.odell.glazedlists.matchers.Matcher<ReportHolder> getDefaultMatcher() { return DEFAULT_MATCHER; } /** * Sets the filter text contents. * * @param inText a <code>String</code> value */ private void setFilterText(String inText) { mFilterText = inText; } /** * Creates the context menu for the given <code>Table</code>. * * @param inTable a <code>Table</code> value */ private void createContextMenu(Table inTable) { ContextMenuFactory contextMenuFactory = new ContextMenuFactory(); contextMenuFactory.createContextMenu("fixMessageContextMenu", //$NON-NLS-1$ inTable, getSite()); } /** * Coordinates application of <code>Matcher</code> objects to create a filter. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractFIXMessagesView.java 16543 2013-04-11 16:02:00Z colin $ * @since 0.7.0 */ @ClassVersion("$Id: AbstractFIXMessagesView.java 16543 2013-04-11 16:02:00Z colin $")//$NON-NLS-1$ protected final static class FilterMatcherEditor extends AbstractMatcherEditor<ReportHolder> { /** * Sets the <code>Matcher</code> used in the editor. * * @param inMatcher a <code>ca.odell.glazedlists.matchers.Matcher<MessageHolder></code> value */ protected final void setMatcher(ca.odell.glazedlists.matchers.Matcher<ReportHolder> inMatcher) { fireChanged(inMatcher); } } }