All Downloads are FREE. Search and download functionalities are using the official Maven repository.

quickfix.FieldMap Maven / Gradle / Ivy

There is a newer version: 2.3.1
Show newest version
/*******************************************************************************
 * 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 [email protected] if any conditions of this licensing
 * are not clear to you.
 ******************************************************************************/

package quickfix;

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;
import org.quickfixj.CharsetSupport;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Map.Entry;

/**
 * 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> fields;

    private final TreeMap> groups = new TreeMap<>();

    /**
     * Constructs a FieldMap with the given field order.
     * The given array must not be modified.
     *
     * @param fieldOrder the field order, or null if there is none
     */
    protected FieldMap(int[] fieldOrder) {
        this.fieldOrder = fieldOrder;
        fields = new TreeMap<>(fieldOrder != null ? new FieldOrderComparator() : null);
    }

    protected FieldMap() {
        this(null);
    }

    /**
     * Returns the order of fields in the group.
     * The returned array must not be modified.
     *
     * @return the field order
     */
    public int[] getFieldOrder() {
        return fieldOrder;
    }

    public void clear() {
        fields.clear();
        groups.clear();
    }

    public void reset() {
        fields.clear();
        for(List groupList : groups.values()) {
            for(Group group : groupList)
                group.reset();
        }
        groups.clear();
    }

    public boolean isEmpty() {
        return fields.isEmpty();
    }

    protected static int indexOf(int field, int[] fieldOrder) {
        if (fieldOrder != null) {
            for (int i = 0; i < fieldOrder.length; i++) {
                if (field == fieldOrder[i]) {
                    return i;
                }
            }
        }
        return -1;
    }

    private static boolean isOrderedField(int field, int[] fieldOrder) {
        return indexOf(field, fieldOrder) > -1;
    }

    private class FieldOrderComparator implements Comparator, Serializable {
        static final long serialVersionUID = 3416006398018829270L;

        private int rank(int field, int[] fieldOrder) {
            int index = indexOf(field, fieldOrder);
            return index > -1 ? index : Integer.MAX_VALUE; // unspecified fields are last
        }

        public int compare(Integer tag1, Integer tag2) {
            int rank1 = rank(tag1, getFieldOrder());
            int rank2 = rank(tag2, getFieldOrder());

            return rank1 != Integer.MAX_VALUE || rank2 != Integer.MAX_VALUE
                    ? rank1 - rank2 // order by rank if it is specified for either tag
                    : tag1 - tag2; // order by tag if both tags have unspecified ordering
        }
    }

    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 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) {
        setField(new StringField(field, BooleanConverter.convert(value)));
    }

    public void setChar(int field, char value) {
        setField(new StringField(field, CharConverter.convert(value)));
    }

    public void setInt(int field, int value) {
        setField(new StringField(field, IntConverter.convert(value)));
    }

    public void setDouble(int field, double value) {
        setDouble(field, value, 0);
    }

    public void setDouble(int field, double value, int padding) {
        setField(new StringField(field, DoubleConverter.convert(value, padding)));
    }

    public void setDecimal(int field, BigDecimal value) {
        setField(new StringField(field, DecimalConverter.convert(value)));
    }

    public void setDecimal(int field, BigDecimal value, int padding) {
        setField(new StringField(field, DecimalConverter.convert(value, padding)));
    }

    public void setUtcTimeStamp(int field, LocalDateTime value) {
        setUtcTimeStamp(field, value, false);
    }

    public void setUtcTimeStamp(int field, LocalDateTime value, boolean includeMilliseconds) {
        setField(new StringField(field, UtcTimestampConverter.convert(value, includeMilliseconds ? UtcTimestampPrecision.MILLIS : UtcTimestampPrecision.SECONDS)));
    }

    public void setUtcTimeStamp(int field, LocalDateTime value, UtcTimestampPrecision precision) {
        setField(new StringField(field, UtcTimestampConverter.convert(value, precision)));
    }

    public void setUtcTimeOnly(int field, LocalTime value) {
        setUtcTimeOnly(field, value, false);
    }

    public void setUtcTimeOnly(int field, LocalTime value, boolean includeMilliseconds) {
        setField(new StringField(field, UtcTimeOnlyConverter.convert(value, includeMilliseconds ? UtcTimestampPrecision.MILLIS : UtcTimestampPrecision.SECONDS)));
    }

    public void setUtcTimeOnly(int field, LocalTime value, UtcTimestampPrecision precision) {
        setField(new StringField(field, UtcTimeOnlyConverter.convert(value, precision)));
    }

    public void setUtcDateOnly(int field, LocalDate value) {
        setField(new StringField(field, UtcDateOnlyConverter.convert(value)));
    }

    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 String getString(int field) throws FieldNotFound {
        return getField(field).getObject();
    }

    public Optional getOptionalString(int field) {
        final StringField f = (StringField) fields.get(field);
        if (f == null) {
            return Optional.empty();
        } else {
            return Optional.of(f.getValue());
        }
    }

    public boolean getBoolean(int field) throws FieldNotFound {
        try {
            return BooleanConverter.convert(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public char getChar(int field) throws FieldNotFound {
        try {
            return CharConverter.convert(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public int getInt(int field) throws FieldNotFound {
        try {
            return IntConverter.convert(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public double getDouble(int field) throws FieldNotFound {
        try {
            return DoubleConverter.convert(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public BigDecimal getDecimal(int field) throws FieldNotFound {
        return getDecimalFromString(field, getString(field));
    }

    public Optional getOptionalDecimal(int field) {
        return getOptionalString(field).map(s -> getDecimalFromString(field, s));
    }

    private BigDecimal getDecimalFromString(int field, String s) {
        try {
            return DecimalConverter.convert(s);
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public LocalDateTime getUtcTimeStamp(int field) throws FieldNotFound {
        try {
            return UtcTimestampConverter.convertToLocalDateTime(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public LocalTime getUtcTimeOnly(int field) throws FieldNotFound {
        try {
            return UtcTimeOnlyConverter.convertToLocalTime(getString(field));
        } catch (final FieldConvertError e) {
            throw newIncorrectDataException(e, field);
        }
    }

    public LocalDate getUtcDateOnly(int field) throws FieldNotFound {
        try {
            return UtcDateOnlyConverter.convertToLocalDate(getString(field));
        } 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.getPrecision());
    }

    public void setField(UtcTimeOnlyField field) {
        setUtcTimeOnly(field.getField(), field.getValue(), field.getPrecision());
    }

    public void setField(UtcDateOnlyField field) {
        setUtcDateOnly(field.getField(), field.getValue());
    }

    public void setField(BytesField field) {
        setBytes(field.getField(), field.getObject());
    }

    static > F updateValue(F field, T value) {
        field.setObject(value);
        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) {
            return (BytesField) returnField;
        } else {
            throw new FieldException(SessionRejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE,
                    field.getField());
        }
    }

    public StringField getField(StringField field) throws FieldNotFound {
        return updateValue(field, getString(field.getField()));
    }

    public BooleanField getField(BooleanField field) throws FieldNotFound {
        return updateValue(field, getBoolean(field.getField()));
    }

    public CharField getField(CharField field) throws FieldNotFound {
        return updateValue(field, getChar(field.getField()));
    }

    public IntField getField(IntField field) throws FieldNotFound {
        return updateValue(field, getInt(field.getField()));
    }

    public DoubleField getField(DoubleField field) throws FieldNotFound {
        return updateValue(field, getDouble(field.getField()));
    }

    public DecimalField getField(DecimalField field) throws FieldNotFound {
        return updateValue(field, getDecimal(field.getField()));
    }

    public UtcTimeStampField getField(UtcTimeStampField field) throws FieldNotFound {
        return updateValue(field, getUtcTimeStamp(field.getField()));
    }

    public UtcTimeOnlyField getField(UtcTimeOnlyField field) throws FieldNotFound {
        return updateValue(field, getUtcTimeOnly(field.getField()));
    }

    public UtcDateOnlyField getField(UtcDateOnlyField field) throws FieldNotFound {
        return updateValue(field, getUtcDateOnly(field.getField()));
    }

    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> iterator() {
        return fields.values().iterator();
    }

    protected void initializeFrom(FieldMap source) {
        fields.clear();
        fields.putAll(source.fields);
        for (Entry> entry : source.groups.entrySet()) {
            final List clones = new ArrayList<>();
            for (final Group group : entry.getValue()) {
                final Group clone = new Group(group.getFieldTag(),
                        group.delim(), group.getFieldOrder());
                clone.initializeFrom(group);
                clones.add(clone);
            }
            groups.put(entry.getKey(), clones);
        }
    }

    private boolean isGroupField(int field) {
        return groups.containsKey(field);
    }

    private static void appendField(StringBuilder buffer, Field field) {
        if (field != null) {
            field.toString(buffer);
            buffer.append('\001');
        }
    }

    protected void calculateString(StringBuilder buffer, int[] preFields, int[] postFields) {
        if (preFields != null) {
            for (int preField : preFields) {
                appendField(buffer, getField(preField, null));
            }
        }

        for (final Field field : fields.values()) {
            final int tag = field.getField();
            if (!isOrderedField(tag, preFields) && !isOrderedField(tag, postFields)
                    && !isGroupField(tag)) {
                appendField(buffer, field);
            } else if (isGroupField(tag) && isOrderedField(tag, fieldOrder)
                    && getGroupCount(tag) > 0) {
                appendField(buffer, field);
                List groups = getGroups(tag);
                for (int i = 0; i < groups.size(); i++) {
                    groups.get(i).calculateString(buffer, preFields, postFields);
                }
            }
        }

        for (final Entry> entry : groups.entrySet()) {
            final Integer groupCountTag = entry.getKey();
            if (!isOrderedField(groupCountTag, fieldOrder)) {
                final List groups = entry.getValue();
                int groupCount = groups.size();
                if (groupCount > 0) {
                    buffer.append(NumbersCache.get(groupCountTag)).append('=');
                    buffer.append(NumbersCache.get(groupCount)).append('\001');
                    for (int i = 0; i < groups.size(); i++) {
                        groups.get(i).calculateString(buffer, preFields, postFields);
                    }
                }
            }
        }

        if (postFields != null) {
            for (int postField : postFields) {
                appendField(buffer, getField(postField, null));
            }
        }
    }

    private static final boolean IS_STRING_EQUIVALENT = CharsetSupport.isStringEquivalent(CharsetSupport.getCharsetInstance());

    int calculateLength() {
        int result = 0;
        for (final Field field : fields.values()) {
            int tag = field.getField();
            if (tag != BeginString.FIELD && tag != BodyLength.FIELD
                    && tag != CheckSum.FIELD && !isGroupField(tag)) {
                result += field.getLength();
            }
        }

        for (Entry> entry : groups.entrySet()) {
            final List groupList = entry.getValue();
            if (!groupList.isEmpty()) {
                if(IS_STRING_EQUIVALENT) {
                    result += getStringLength(entry.getKey()) + getStringLength(groupList.size()) + 2;
                } else {
                    result += MessageUtils.length(CharsetSupport.getCharsetInstance(), NumbersCache.get(entry.getKey()));
                    result += MessageUtils.length(CharsetSupport.getCharsetInstance(), NumbersCache.get(groupList.size()));
                    result += 2;
                }
                for (int i = 0; i < groupList.size(); i++) {
                    result += groupList.get(i).calculateLength();
                }
            }
        }
        return result;
    }

    private static int getStringLength(int num) {
        if(num == 0)
            return 1;
        return (int)(num > 0 ? Math.log10(num) + 1 : Math.log10(-num) + 2);
    }

    int calculateChecksum() {
        int result = 0;
        for (final Field field : fields.values()) {
            if (field.getField() != CheckSum.FIELD && !isGroupField(field.getField())) {
                result += field.getChecksum();
            }
        }

        for (Entry> entry : groups.entrySet()) {
            final List groupList = entry.getValue();
            if (!groupList.isEmpty()) {
                if(IS_STRING_EQUIVALENT) {
                    String value = NumbersCache.get(entry.getKey());
                    for (int i = value.length(); i-- != 0;)
                        result += value.charAt(i);
                    value = NumbersCache.get(groupList.size());
                    for (int i = value.length(); i-- != 0;)
                        result += value.charAt(i);
                    result += '=' + 1;
                } else {
                    final IntField groupField = new IntField(entry.getKey());
                    groupField.setValue(groupList.size());
                    result += groupField.getChecksum();
                }
                for (int i = 0; i < groupList.size(); i++) {
                    result += groupList.get(i).calculateChecksum();
                }
            }
        }

        return result & 0xFF;
    }

    /**
     * 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 groupKeyIterator() {
        return groups.keySet().iterator();
    }

    Map> getGroups() {
        return groups;
    }

    public void addGroup(Group group) {
        addGroupRef(new Group(group));
    }

    public void addGroupRef(Group group) {
        int countTag = group.getFieldTag();
        List 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 getGroups(int field) {
        return groups.computeIfAbsent(field, k -> new ArrayList<>());
    }

    public Group getGroup(int num, Group group) throws FieldNotFound {
        final List 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 groupList = getGroups(groupTag);
        if (num > groupList.size()) {
            throw new FieldNotFound(groupTag + ", index=" + num);
        }
        return groupList.get(num - 1);
    }

    public void replaceGroup(int num, Group group) {
        final int offset = num - 1;
        final List 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 groupList = getGroups(field);
        if (num <= groupList.size()) {
            groupList.remove(num - 1);
        }
        if (!groupList.isEmpty()) {
            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());
    }

    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy