-
Notifications
You must be signed in to change notification settings - Fork 637
/
Copy pathSimpleMessage.java
270 lines (239 loc) · 8.03 KB
/
SimpleMessage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
package quickfix;
import quickfix.field.BodyLength;
import quickfix.field.CheckSum;
import quickfix.field.MsgType;
import quickfix.field.SessionRejectReason;
import quickfix.field.converter.BooleanConverter;
import quickfix.field.converter.IntConverter;
import quickfix.field.converter.UtcTimestampConverter;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* SimpleMessage is designed to allow sending complex messages (e.g. repeating groups) of an arbitrary format *without* parsing the structure.
* Use cases include messages stored and then later sent.
*/
public class SimpleMessage implements IMessage {
private static final List<Integer> STRICT_ORDERING = Arrays.asList(new Integer[]{8, 9, 35});
private static final String SOH = String.valueOf('\001');
private static final String BODY_LENGTH_FIELD = SOH + String.valueOf(BodyLength.FIELD) + '=';
private static final String CHECKSUM_FIELD = SOH + String.valueOf(CheckSum.FIELD) + '=';
private final String messageData;
public static class TagPair {
public final int tag;
public String value;
public TagPair(int tag, String value) {
this.tag = tag;
this.value = value;
}
public String asString() {
return tag+"="+value+SOH;
}
}
private final List<TagPair> fields;
public SimpleMessage(String message) {
messageData = message;
fields = Arrays.stream(message.split("\u0001")).map(p -> {
String[] pairData = p.split("=", 2);
return new TagPair(Integer.parseInt(pairData[0]), pairData[1]);
}).collect(Collectors.toList());
}
public List<TagPair> getFields() {
return fields;
}
@Override
public String toString() {
setHeaderString(BodyLength.FIELD, "100");
setString(10, "000");
StringBuilder messageString = buildMessageString();
setBodyLength(messageString);
setChecksum(messageString);
return messageString.toString();
}
private static void setBodyLength(StringBuilder stringBuilder) {
int bodyLengthIndex = stringBuilder.indexOf(BODY_LENGTH_FIELD, 0);
int sohIndex = stringBuilder.indexOf(SOH, bodyLengthIndex + 1);
int checkSumIndex = stringBuilder.lastIndexOf(CHECKSUM_FIELD);
int length = checkSumIndex - sohIndex;
bodyLengthIndex += BODY_LENGTH_FIELD.length();
stringBuilder.replace(bodyLengthIndex, bodyLengthIndex + 3, NumbersCache.get(length));
}
private static void setChecksum(StringBuilder stringBuilder) {
int checkSumIndex = stringBuilder.lastIndexOf(CHECKSUM_FIELD);
int checkSum = 0;
for(int i = checkSumIndex; i-- != 0;)
checkSum += stringBuilder.charAt(i);
String checkSumValue = NumbersCache.get((checkSum + 1) & 0xFF); // better than sum % 256 since it avoids overflow issues
checkSumIndex += CHECKSUM_FIELD.length();
stringBuilder.replace(checkSumIndex + (3 - checkSumValue.length()), checkSumIndex + 3, checkSumValue);
}
private StringBuilder buildMessageString() {
StringBuilder message = new StringBuilder();
//Print strict order tags
for (Integer integer : STRICT_ORDERING) {
TagPair tagPair = getField(integer);
if (tagPair != null) {
message.append(tagPair.asString());
}
}
//Print unclaimed tags
for (TagPair tagPair : fields) {
if (tagPair.tag != 10 && !STRICT_ORDERING.contains(tagPair.tag)) {
message.append(tagPair.asString());
}
}
//Print footer tag
TagPair footer = getField(10);
if (footer != null) {
message.append(footer.asString());
}
return message;
}
private TagPair getField(int tag) {
for (TagPair field : fields) {
if (field.tag == tag) {
return field;
}
}
return null;
}
@Override
public String toRawString() {
return messageData;
}
@Override
public boolean isAdmin() {
if (isSetHeaderField(MsgType.FIELD)) {
try {
final String msgType = getHeaderString(MsgType.FIELD);
return MessageUtils.isAdminMessage(msgType);
} catch (final FieldNotFound e) {
// shouldn't happen
}
}
return false;
}
@Override
public String getHeaderString(int tag) throws FieldNotFound {
for (TagPair field : fields) {
if (field.tag == tag) {
return field.value;
}
}
throw new FieldNotFound(tag);
}
@Override
public int getHeaderInt(int tag) throws FieldNotFound {
for (TagPair field : fields) {
if (field.tag == tag) {
return Integer.parseInt(field.value);
}
}
throw new FieldNotFound(tag);
}
@Override
public void setHeaderString(int tag, String value) {
for (TagPair field : fields) {
if (field.tag == tag) {
field.value = value;
return;
}
}
fields.add(new TagPair(tag, value));
}
@Override
public void setString(int tag, String value) {
for (TagPair field : fields) {
if (field.tag == tag) {
field.value = value;
return;
}
}
fields.add(new TagPair(tag, value));
}
@Override
public void setHeaderInt(int tag, int value) {
for (TagPair field : fields) {
if (field.tag == tag) {
field.value = Integer.toString(value);
return;
}
}
fields.add(new TagPair(tag, Integer.toString(value)));
}
@Override
public void setInt(int tag, int value) {
for (TagPair field : fields) {
if (field.tag == tag) {
field.value = Integer.toString(value);
return;
}
}
fields.add(new TagPair(tag, Integer.toString(value)));
}
@Override
public void setHeaderUtcTimeStamp(int tag, LocalDateTime dateTime, UtcTimestampPrecision precision) {
for (TagPair field : fields) {
if (field.tag == tag) {
field.value = UtcTimestampConverter.convert(dateTime, precision);
return;
}
}
}
@Override
public boolean isSetHeaderField(int tag) {
for (TagPair field : fields) {
if (field.tag == tag) {
return true;
}
}
return false;
}
@Override
public boolean isSetField(int tag) {
for (TagPair field : fields) {
if (field.tag == tag) {
return true;
}
}
return false;
}
@Override
public boolean getBoolean(int tag) throws FieldNotFound {
try {
return BooleanConverter.convert(getString(tag));
} catch (FieldConvertError e) {
throw newIncorrectDataException(e, tag);
}
}
@Override
public int getInt(int tag) throws FieldNotFound {
try {
return IntConverter.convert(getString(tag));
} catch (FieldConvertError e) {
throw newIncorrectDataException(e, tag);
}
}
@Override
public String getString(int tag) throws FieldNotFound {
for (TagPair field : fields) {
if (field.tag == tag) {
return field.value;
}
}
throw new FieldNotFound(tag);
}
@Override
public void removeHeaderField(int field) {
fields.removeIf(f -> f.tag == field);
}
@Override
public FieldException getException() {
return null;
}
private FieldException newIncorrectDataException(FieldConvertError e, int tag) {
return new FieldException(SessionRejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE,
e.getMessage(), tag);
}
}