package org.marketcetera.photon.messagehistory;
import java.util.HashMap;
import java.util.Map;
import org.marketcetera.core.ClassVersion;
import org.marketcetera.messagehistory.ReportHolder;
import org.marketcetera.photon.FIXFieldLocalizer;
import org.marketcetera.quickfix.CurrentFIXDataDictionary;
import quickfix.DataDictionary;
import quickfix.FieldNotFound;
import quickfix.Message;
import quickfix.field.MsgType;
import quickfix.field.OrdStatus;
import quickfix.field.OrdType;
import quickfix.field.Side;
import ca.odell.glazedlists.matchers.Matcher;
/* $License$ */
/**
* <code>Matcher</code> implementation for FIX messages.
*
* <p>This class is immutable as suggested by {@link Matcher}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: FIXMatcher.java 16841 2014-02-20 19:59:04Z colin $
* @since 0.7.0
*/
@ClassVersion("$Id: FIXMatcher.java 16841 2014-02-20 19:59:04Z colin $")
public abstract class FIXMatcher<T>
implements Matcher<ReportHolder>
{
/**
* collection of converters keyed by FIX field
*/
private static final Map<Integer, FIXConverter> sFIXConverters;
/**
* Initializes the converters.
*
* <p>Note, extra converters should be added to this class if new groups of messages are added
* to <code>photon_fix_messages.properties</code>
*/
static {
// initialize the converter collection here in order to guarantee the order of the two static constructors (the variable and population)
sFIXConverters = new HashMap<Integer, FIXConverter>();
// create converters for FIX fields that use codes to represent longer values
// FIX field 35 - MsgType
sFIXConverters.put(MsgType.FIELD,
new FIXConverter("MsgType", //$NON-NLS-1$
MsgType.FIELD));
// FIX field 39 - OrdStatus
sFIXConverters.put(OrdStatus.FIELD,
new FIXConverter("OrdStatus", //$NON-NLS-1$
OrdStatus.FIELD));
// FIX field 40 - OrdType
sFIXConverters.put(OrdType.FIELD,
new FIXConverter("OrdType", //$NON-NLS-1$
OrdType.FIELD));
// FIX field 54 - Side
sFIXConverters.put(Side.FIELD,
new FIXConverter("Side", //$NON-NLS-1$
Side.FIELD));
}
/**
* the field against which to match
*/
private final int mMatcherFIXField;
/**
* the value against which to match
*/
private final T mMatcherValue;
/**
* indicates whether to include or exclude the match criteria
*/
private final boolean mShouldInclude;
/**
* Create a new FIXMatcher instance.
*
* @param inFixField an <code>int</code> value containing the FIX field tag
* @param inValue a <code>T</code> value containing the FIX value
* @throws IllegalArgumentException if <code>inFixField</code> is less than or equal to zero
* @throws NullPointerException if <code>inValue</code> is null
*/
protected FIXMatcher(int inFixField,
T inValue)
{
this(inFixField,
inValue,
true);
}
/**
* Create a new FIXMatcher instance.
*
* @param inFixField an <code>int</code> value containing the FIX field tag
* @param inValue a <code>T</code> value containing the FIX value
* @param inShouldInclude a <code>boolean</code> value indicating whether the matcher should include or exclude
* the given criteria
* @throws IllegalArgumentException if <code>inFixField</code> is less than or equal to zero
* @throws NullPointerException if <code>inValue</code> is null
*/
protected FIXMatcher(int inFixField,
T inValue,
boolean inShouldInclude)
{
validateField(inFixField);
if(inValue == null) {
throw new NullPointerException();
}
mMatcherFIXField = inFixField;
mMatcherValue = inValue;
mShouldInclude = inShouldInclude;
}
/**
* Retrieves the specified field from the given FIX message as a <code>String</code>.
*
* @param inMessage a <code>Message</code> value
* @param inFieldNum an <code>int</code> value
* @return a <code>String</code> value
* @throws FieldNotFound if the given <code>Message</code> does not contain the specified field
*/
protected static String getFieldValueString(Message inMessage,
int inFieldNum)
throws FieldNotFound
{
validateMessage(inMessage);
validateField(inFieldNum);
DataDictionary dictionary = CurrentFIXDataDictionary.getCurrentFIXDataDictionary().getDictionary();
if (dictionary.isHeaderField(inFieldNum)) {
return inMessage.getHeader().getString(inFieldNum);
} else if (dictionary.isTrailerField(inFieldNum)) {
return inMessage.getTrailer().getString(inFieldNum);
} else {
return inMessage.getString(inFieldNum);
}
}
/**
* Get the matcherFIXField value.
*
* @return a <code>FIXMatcher</code> value
*/
protected final int getMatcherFIXField()
{
return mMatcherFIXField;
}
/**
* Get the matcherValue value.
*
* @return a <code>FIXMatcher</code> value
*/
protected final T getMatcherValue()
{
return mMatcherValue;
}
/**
* Get the shouldInclude value.
*
* @return a <code>FIXMatcher</code> value
*/
protected final boolean getShouldInclude()
{
return mShouldInclude;
}
/**
* Converts the given FIX-encoded value to a human-readable value, if appropriate.
*
* <p>If the given FIX field is one of the fields for which Photon stores human-readable
* translations, this method will return the human-readable translation for the given
* value. Note that the value returned is the corresponding entry in <code>photon_fix_messages.properties</code>.
*
* @param inValue a <code>String</code> value containing the FIX value to convert
* @param inFIXField an <code>int</code> value containing the FIX field corresponding to the given value
* @return a <code>String</code> value containing the converted value
*/
protected static String convertFIXValueToHumanString(String inValue,
int inFIXField)
{
if(inValue == null) {
throw new NullPointerException();
}
validateField(inFIXField);
// check to see if there is a converter for the FIX field
FIXConverter converter = sFIXConverters.get(inFIXField);
if (converter == null) {
// no converter is available, return the field as it was passed in
return inValue;
}
// there *is* a converter available, so return the converted value
return converter.convert(inValue);
}
/**
* Validation routine for a <code>Message</code> parameter.
*
* @param inMessage a <code>Message</code> value
* @throws NullPointerException if <code>inMessage</code> is null
*/
private static void validateMessage(Message inMessage)
{
if(inMessage == null) {
throw new NullPointerException();
}
}
/**
* Validation routine for an <code>int</code> parameter used as a FIX field.
*
* @param inFixField an <code>int</code> value
* @throws IllegalArgumentException if <code>inFixField</code> is not valid
*/
private static void validateField(int inFixField)
{
if(inFixField <= 0) {
throw new IllegalArgumentException();
}
}
/**
* Converts a value to the short FIX value defined in the Photon FIX message catalog.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: FIXMatcher.java 16841 2014-02-20 19:59:04Z colin $
* @since 0.7.0
*/
@ClassVersion("$Id: FIXMatcher.java 16841 2014-02-20 19:59:04Z colin $")//$NON-NLS-1$
private static class FIXConverter
{
/**
* the name of the field - this is not localizable, but is merely the key in photon's FIX message catalog
*/
private final String mFieldName;
/**
* the FIX field tag
*/
private final int mFixField;
/**
* Create a new FIXConverter instance.
*
* @param inFieldName a <code>String</code> value
* @param inFIXField an <code>int</code> value
*/
private FIXConverter(String inFieldName,
int inFIXField)
{
mFieldName = inFieldName;
mFixField = inFIXField;
}
/**
* Converts the given value to the short FIX value, if possible.
*
* @param inValue a <code>String</code> value containing a FIX value
* @return a <code>String</code> value
*/
private String convert(String inValue)
{
String conversion = FIXFieldLocalizer.getLocalizedFIXValueName(mFieldName,
CurrentFIXDataDictionary.getCurrentFIXDataDictionary().getHumanFieldValue(mFixField,
inValue));
// if the conversion comes up empty, that means that the converter exists, but there is no translation
// for this particular value. that's ok, just return the passed value (this is what Photon would display)
if(conversion == null) {
return inValue;
}
return conversion;
}
}
}