/******************************************************************************* * Copyright (c) quickfixengine.org All rights reserved. * * This file is part of the QuickFIX FIX Engine * * This file may be distributed under the terms of the quickfixengine.org * license as defined by quickfixengine.org and appearing in the file * LICENSE included in the packaging of this file. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. * * See http://www.quickfixengine.org/LICENSE for licensing information. * * Contact ask@quickfixengine.org if any conditions of this licensing * are not clear to you. ******************************************************************************/ package quickfix; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import quickfix.field.BeginString; import quickfix.field.BodyLength; import quickfix.field.CheckSum; import quickfix.field.SessionRejectReason; import quickfix.field.converter.BooleanConverter; import quickfix.field.converter.CharConverter; import quickfix.field.converter.DecimalConverter; import quickfix.field.converter.DoubleConverter; import quickfix.field.converter.IntConverter; import quickfix.field.converter.UtcDateOnlyConverter; import quickfix.field.converter.UtcTimeOnlyConverter; import quickfix.field.converter.UtcTimestampConverter; /** * Field container used by messages, groups, and composites. */ public abstract class FieldMap implements Serializable { static final long serialVersionUID = -3193357271891865972L; private final int[] fieldOrder; private final TreeMap<Integer, Field<?>> fields; private final TreeMap<Integer, List<Group>> groups = new TreeMap<Integer, List<Group>>(); protected FieldMap(int[] fieldOrder) { this.fieldOrder = fieldOrder; fields = new TreeMap<Integer, Field<?>>(fieldOrder != null ? new FieldOrderComparator() : null); } protected FieldMap() { this(null); } public int[] getFieldOrder() { return fieldOrder; } public void clear() { fields.clear(); groups.clear(); } public boolean isEmpty() { return fields.size() == 0; } private class FieldOrderComparator implements Comparator<Integer>, Serializable { static final long serialVersionUID = 3416006398018829270L; public int compare(Integer tag1, Integer tag2) { final int index1 = indexOf(tag1.intValue(), getFieldOrder()); final int index2 = indexOf(tag2.intValue(), getFieldOrder()); if ((index1 != Integer.MAX_VALUE) && (index2 != Integer.MAX_VALUE)) { // We manage two ordered fields return index1 != index2 ? (index1 < index2 ? -1 : 1) : 0; } else if ((index1 == Integer.MAX_VALUE) || (index2 == Integer.MAX_VALUE)) { if (index1 != index2) { return (index1 == Integer.MAX_VALUE ? 1 : -1); } else { // index1 and index2 equals to Integer.MAX_VALUE so use the // tags return tag1.intValue() != tag2.intValue() ? (tag1.intValue() < tag2.intValue() ? -1 : 1) : 0; } } else { return tag1.intValue() != tag2.intValue() ? (tag1.intValue() < tag2.intValue() ? -1 : 1) : 0; } } private int indexOf(int tag, int[] order) { if (order != null) { for (int i = 0; i < order.length; i++) { if (tag == order[i]) { return i; } } } return Integer.MAX_VALUE; } } public void setFields(FieldMap fieldMap) { fields.clear(); fields.putAll(fieldMap.fields); } protected void setComponent(MessageComponent component) { component.copyTo(this); } protected void getComponent(MessageComponent component) { component.clear(); component.copyFrom(this); } public void setGroups(FieldMap fieldMap) { groups.clear(); groups.putAll(fieldMap.groups); } protected void setGroups(int key, List<Group> groupList) { groups.put(key, groupList); } public void setString(int field, String value) { setField(new StringField(field, value)); } public void setBytes(int field, byte[] value) { setField(field, new BytesField(field, value)); } public void setBoolean(int field, boolean value) { final String s = BooleanConverter.convert(value); setField(new StringField(field, s)); } public void setChar(int field, char value) { final String s = CharConverter.convert(value); setField(new StringField(field, s)); } public void setInt(int field, int value) { final String s = IntConverter.convert(value); setField(new StringField(field, s)); } public void setDouble(int field, double value) { setDouble(field, value, 0); } public void setDouble(int field, double value, int padding) { final String s = DoubleConverter.convert(value, padding); setField(new StringField(field, s)); } public void setDecimal(int field, BigDecimal value) { setField(new StringField(field, DecimalConverter.convert(value))); } public void setDecimal(int field, BigDecimal value, int padding) { final String s = DecimalConverter.convert(value, padding); setField(new StringField(field, s)); } public void setUtcTimeStamp(int field, Date value) { setUtcTimeStamp(field, value, false); } public void setUtcTimeStamp(int field, Date value, boolean includeMilliseconds) { final String s = UtcTimestampConverter.convert(value, includeMilliseconds); setField(new StringField(field, s)); } public void setUtcTimeOnly(int field, Date value) { setUtcTimeOnly(field, value, false); } public void setUtcTimeOnly(int field, Date value, boolean includeMillseconds) { final String s = UtcTimeOnlyConverter.convert(value, includeMillseconds); setField(new StringField(field, s)); } public void setUtcDateOnly(int field, Date value) { final String s = UtcDateOnlyConverter.convert(value); setField(new StringField(field, s)); } public String getString(int field) throws FieldNotFound { return getField(field).getObject(); } StringField getField(int field) throws FieldNotFound { final StringField f = (StringField) fields.get(field); if (f == null) { throw new FieldNotFound(field); } return f; } Field<?> getField(int field, Field<?> defaultValue) { final Field<?> f = fields.get(field); if (f == null) { return defaultValue; } return f; } public boolean getBoolean(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return BooleanConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public char getChar(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return CharConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public int getInt(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return IntConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public double getDouble(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return DoubleConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public BigDecimal getDecimal(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return DecimalConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public Date getUtcTimeStamp(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return UtcTimestampConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public Date getUtcTimeOnly(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return UtcTimeOnlyConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public Date getUtcDateOnly(int field) throws FieldNotFound { final String value = getField(field).getValue(); try { return UtcDateOnlyConverter.convert(value); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field); } } public void setField(int key, Field<?> field) { fields.put(key, field); } public void setField(StringField field) { if (field.getValue() == null) { throw new NullPointerException("Null field values are not allowed."); } fields.put(field.getField(), field); } public void setField(BooleanField field) { setBoolean(field.getField(), field.getValue()); } public void setField(CharField field) { setChar(field.getField(), field.getValue()); } public void setField(IntField field) { setInt(field.getField(), field.getValue()); } public void setField(DoubleField field) { setDouble(field.getField(), field.getValue()); } public void setField(DecimalField field) { setDecimal(field.getField(), field.getValue()); } public void setField(UtcTimeStampField field) { setUtcTimeStamp(field.getField(), field.getValue(), field.showMilliseconds()); } public void setField(UtcTimeOnlyField field) { setUtcTimeOnly(field.getField(), field.getValue(), field.showMilliseconds()); } public void setField(UtcDateOnlyField field) { setUtcDateOnly(field.getField(), field.getValue()); } public void setField(BytesField field) { setBytes(field.getField(), field.getObject()); } public StringField getField(StringField field) throws FieldNotFound { return (StringField) getFieldInternal(field); } private Field<String> getFieldInternal(Field<String> field) throws FieldNotFound { field.setObject(getField(field.getField()).getObject()); return field; } public BytesField getField(BytesField field) throws FieldNotFound { final Field<?> returnField = fields.get(field.getField()); if (returnField == null) { throw new FieldNotFound(field.getField()); } else if (!(returnField instanceof BytesField)) { throw new FieldException(SessionRejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE, field.getField()); } else { return (BytesField) returnField; } } public BooleanField getField(BooleanField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(Boolean.valueOf(BooleanConverter.convert(value))); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public CharField getField(CharField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(Character.valueOf(CharConverter.convert(value))); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public IntField getField(IntField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(IntConverter.convert(value)); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public DoubleField getField(DoubleField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(new Double(DoubleConverter.convert(value))); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public DecimalField getField(DecimalField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(DecimalConverter.convert(value)); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public UtcTimeStampField getField(UtcTimeStampField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(UtcTimestampConverter.convert(value)); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public UtcTimeOnlyField getField(UtcTimeOnlyField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(UtcTimeOnlyConverter.convert(value)); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } public UtcDateOnlyField getField(UtcDateOnlyField field) throws FieldNotFound { try { final String value = getField(field.getField()).getValue(); field.setObject(UtcDateOnlyConverter.convert(value)); } catch (final FieldConvertError e) { throw newIncorrectDataException(e, field.getField()); } catch (final FieldNotFound e) { throw e; } return field; } private FieldException newIncorrectDataException(FieldConvertError e, int tag) { return new FieldException(SessionRejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE, e.getMessage(), tag); } public boolean isSetField(int field) { return fields.containsKey(field); } public boolean isSetField(Field<?> field) { return isSetField(field.getField()); } public void removeField(int field) { fields.remove(field); } public Iterator<Field<?>> iterator() { return fields.values().iterator(); } protected void initializeFrom(FieldMap source) { fields.clear(); fields.putAll(source.fields); final Iterator<Map.Entry<Integer, List<Group>>> groupItr = source.groups.entrySet() .iterator(); while (groupItr.hasNext()) { final Map.Entry<Integer, List<Group>> entry = groupItr.next(); final List<Group> clonedMembers = new ArrayList<Group>(); final List<Group> groupMembers = entry.getValue(); for (int i = 0; i < groupMembers.size(); i++) { final Group originalGroup = groupMembers.get(i); final Group clonedGroup = new Group(originalGroup.getFieldTag(), originalGroup.delim(), originalGroup.getFieldOrder()); clonedGroup.initializeFrom(originalGroup); clonedMembers.add(clonedGroup); } groups.put(entry.getKey(), clonedMembers); } } private boolean isGroupField(int field) { return groups.containsKey(field); } protected void calculateString(StringBuffer buffer, int[] preFields, int[] postFields) { if (preFields != null) { for (final int preField : preFields) { final Field<?> field = getField(preField, null); if (field != null) { field.toString(buffer); buffer.append('\001'); } } } for (final Field<?> field2 : fields.values()) { final Field<?> field = field2; final int tag = field.getField(); if (!isOrderedField(tag, preFields) && !isOrderedField(tag, postFields) && !isGroupField(tag)) { field.toString(buffer); buffer.append('\001'); } else if (isGroupField(tag) && isOrderedField(tag, fieldOrder) && getGroupCount(tag) > 0) { final List<Group> groups = getGroups(tag); field.toString(buffer); buffer.append('\001'); for (int i = 0; i < groups.size(); i++) { final FieldMap groupFields = groups.get(i); groupFields.calculateString(buffer, preFields, postFields); } } } for (final Entry<Integer, List<Group>> entry : groups.entrySet()) { final Integer groupCountTag = entry.getKey(); if (!isOrderedField(groupCountTag.intValue(), fieldOrder)) { final List<Group> groups = entry.getValue(); int groupCount = groups.size(); if (groupCount > 0) { final IntField countField = new IntField(groupCountTag.intValue(), groupCount); countField.toString(buffer); buffer.append('\001'); for (int i = 0; i < groupCount; i++) { final FieldMap groupFields = groups.get(i); groupFields.calculateString(buffer, preFields, postFields); } } } } if (postFields != null) { for (final int postField : postFields) { final Field<?> field = getField(postField, null); field.toString(buffer); buffer.append('\001'); } } } private boolean isOrderedField(int field, int[] afieldOrder) { if (afieldOrder != null) { for (final int element : afieldOrder) { if (field == element) { return true; } } } return false; } int calculateLength() { int result = 0; int length = 0; for (final Field<?> field : fields.values()) { if (field.getField() == BeginString.FIELD || field.getField() == BodyLength.FIELD || field.getField() == CheckSum.FIELD || isGroupField(field.getField())) { continue; } length = field.getLength(); result += length; } final Iterator<Map.Entry<Integer, List<Group>>> iterator = groups.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<Integer, List<Group>> entry = iterator.next(); final List<Group> groupList = entry.getValue(); if (!groupList.isEmpty()) { final IntField groupField = new IntField((entry.getKey()).intValue()); groupField.setValue(groupList.size()); length = groupField.getLength(); result += length; for (int i = 0; i < groupList.size(); i++) { final Group group = groupList.get(i); length = group.calculateLength(); result += length; } } } return result; } int calculateTotal() { int result = 0; for (final Field<?> field2 : fields.values()) { final Field<?> field = field2; if (field.getField() == CheckSum.FIELD || isGroupField(field.getField())) { continue; } result += field.getTotal(); } final Iterator<Map.Entry<Integer, List<Group>>> iterator = groups.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<Integer, List<Group>> entry = iterator.next(); final List<Group> groupList = entry.getValue(); if (!groupList.isEmpty()) { final IntField groupField = new IntField((entry.getKey()).intValue()); groupField.setValue(groupList.size()); result += groupField.getTotal(); for (int i = 0; i < groupList.size(); i++) { final Group group = groupList.get(i); result += group.calculateTotal(); } } } return result; } /** * Returns the number of groups associated with the specified count tag. * @param tag the count tag number * @return the number of times the group repeats */ public int getGroupCount(int tag) { return getGroups(tag).size(); } public Iterator<Integer> groupKeyIterator() { return groups.keySet().iterator(); } Map<Integer, List<Group>> getGroups() { return groups; } public void addGroup(Group group) { final int countTag = group.getFieldTag(); final List<Group> currentGroups = getGroups(countTag); currentGroups.add(new Group(group)); setGroupCount(countTag, currentGroups.size()); } public void addGroupRef(Group group) { int countTag = group.getFieldTag(); List<Group> currentGroups = getGroups(countTag); currentGroups.add(group); setGroupCount(countTag, currentGroups.size()); } protected void setGroupCount(int countTag, int groupSize) { try { StringField count; if (groupSize == 1) { count = new StringField(countTag, "1"); setField(countTag, count); } else { count = getField(countTag); } count.setValue(Integer.toString(groupSize)); } catch (final FieldNotFound e) { // Shouldn't happen throw new RuntimeError(e); } } public List<Group> getGroups(int field) { List<Group> groupList = groups.get(field); if (groupList == null) { groupList = new ArrayList<Group>(); groups.put(field, groupList); } return groupList; } public Group getGroup(int num, Group group) throws FieldNotFound { final List<Group> groupList = getGroups(group.getFieldTag()); if (num > groupList.size()) { throw new FieldNotFound(group.getFieldTag() + ", index=" + num); } final Group grp = groupList.get(num - 1); group.setFields(grp); group.setGroups(grp); return group; } public Group getGroup(int num, int groupTag) throws FieldNotFound { List<Group> groupList = getGroups(groupTag); if (num > groupList.size()) { throw new FieldNotFound(groupTag + ", index=" + num); } final Group grp = (Group) groupList.get(num - 1); return grp; } public void replaceGroup(int num, Group group) { final int offset = num - 1; final List<Group> groupList = getGroups(group.getFieldTag()); if (offset < 0 || offset >= groupList.size()) { return; } groupList.set(offset, new Group(group)); } public void removeGroup(int field) { getGroups(field).clear(); removeField(field); } public void removeGroup(int num, int field) { final List<Group> groupList = getGroups(field); if (num <= groupList.size()) { groupList.remove(num - 1); } if (groupList.size() > 0) { setGroupCount(field, groupList.size()); } } public void removeGroup(int num, Group group) { removeGroup(num, group.getFieldTag()); } public void removeGroup(Group group) { removeGroup(group.getFieldTag()); } public boolean hasGroup(int field) { return groups.containsKey(field); } public boolean hasGroup(int num, int field) { return hasGroup(field) && num <= getGroups(field).size(); } public boolean hasGroup(int num, Group group) { return hasGroup(num, group.getFieldTag()); } public boolean hasGroup(Group group) { return hasGroup(group.getFieldTag()); } }