Skip to content

Commit dcc0ce0

Browse files
committed
Extracted PatternMatcher utility class from decoder
1 parent 99fd131 commit dcc0ce0

File tree

3 files changed

+152
-179
lines changed

3 files changed

+152
-179
lines changed

quickfixj-core/src/main/java/quickfix/mina/message/FIXMessageDecoder.java

Lines changed: 13 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@
4646
* message string is then passed to MINA IO handlers for further processing.
4747
*/
4848
public class FIXMessageDecoder implements MessageDecoder {
49+
4950
private static final char SOH = '\001';
5051
private static final String FIELD_DELIMITER = String.valueOf(SOH);
5152

5253
private final Logger log = LoggerFactory.getLogger(getClass());
5354

54-
private final byte[] HEADER_PATTERN;
55-
private final byte[] CHECKSUM_PATTERN;
56-
private final byte[] LOGON_PATTERN;
55+
private final PatternMatcher HEADER_PATTERN;
56+
private final PatternMatcher CHECKSUM_PATTERN;
57+
private final PatternMatcher LOGON_PATTERN;
5758

5859
// Parsing states
5960
private static final int SEEKING_HEADER = 1;
@@ -86,14 +87,14 @@ public FIXMessageDecoder(String charset) throws UnsupportedEncodingException {
8687

8788
public FIXMessageDecoder(String charset, String delimiter) throws UnsupportedEncodingException {
8889
charsetEncoding = CharsetSupport.validate(charset);
89-
HEADER_PATTERN = getBytes("8=FIXt.?.?" + delimiter + "9=");
90-
CHECKSUM_PATTERN = getBytes("10=???" + delimiter);
91-
LOGON_PATTERN = getBytes(delimiter + "35=A" + delimiter);
90+
HEADER_PATTERN = new PatternMatcher("8=FIXt.?.?" + delimiter + "9=");
91+
CHECKSUM_PATTERN = new PatternMatcher("10=???" + delimiter);
92+
LOGON_PATTERN = new PatternMatcher(delimiter + "35=A" + delimiter);
9293
resetState();
9394
}
9495

9596
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
96-
boolean hasHeader = indexOf(in, in.position(), HEADER_PATTERN) != -1L;
97+
boolean hasHeader = HEADER_PATTERN.find(in, in.position()) != -1L;
9798
return hasHeader ? MessageDecoderResult.OK :
9899
(in.remaining() > MAX_UNDECODED_DATA_LENGTH ? MessageDecoderResult.NOT_OK : MessageDecoderResult.NEED_DATA);
99100
}
@@ -129,7 +130,7 @@ private boolean parseMessage(IoBuffer in, ProtocolDecoderOutput out)
129130
while (in.hasRemaining() && !messageFound) {
130131
if (state == SEEKING_HEADER) {
131132

132-
long headerPos = indexOf(in, position, HEADER_PATTERN);
133+
long headerPos = HEADER_PATTERN.find(in, position);
133134
if (headerPos == -1L) {
134135
break;
135136
}
@@ -183,7 +184,7 @@ private boolean parseMessage(IoBuffer in, ProtocolDecoderOutput out)
183184
}
184185

185186
if (state == PARSING_CHECKSUM) {
186-
if (matches(in, position, CHECKSUM_PATTERN) > 0) {
187+
if (CHECKSUM_PATTERN.match(in, position) > 0) {
187188
// we are trying to parse the checksum but should
188189
// check if the CHECKSUM_PATTERN is preceded by SOH
189190
// or if the pattern just occurs inside of another field
@@ -195,9 +196,9 @@ private boolean parseMessage(IoBuffer in, ProtocolDecoderOutput out)
195196
if (log.isDebugEnabled()) {
196197
log.debug("found checksum: " + getBufferDebugInfo(in));
197198
}
198-
position += CHECKSUM_PATTERN.length;
199+
position += CHECKSUM_PATTERN.getMinLength();
199200
} else {
200-
if (position + CHECKSUM_PATTERN.length <= in.limit()) {
201+
if (position + CHECKSUM_PATTERN.getMinLength() <= in.limit()) {
201202
// FEATURE allow configurable recovery position
202203
// int recoveryPosition = in.position() + 1;
203204
// Following recovery position is compatible with QuickFIX C++
@@ -250,16 +251,6 @@ private boolean hasRemaining(IoBuffer in) {
250251
return position < in.limit();
251252
}
252253

253-
private static int minPatternLength(byte[] pattern) {
254-
int len = 0;
255-
for (byte b : pattern) {
256-
if (b < 'a' || b > 'z') { // if not optional character (lowercase)
257-
len++;
258-
}
259-
}
260-
return len;
261-
}
262-
263254
private String getMessageString(IoBuffer buffer) throws UnsupportedEncodingException {
264255
byte[] data = new byte[position - buffer.position()];
265256
buffer.get(data);
@@ -288,68 +279,7 @@ private void handleError(IoBuffer buffer, int recoveryPosition, String text,
288279
}
289280

290281
private boolean isLogon(IoBuffer buffer) {
291-
return indexOf(buffer, buffer.position(), LOGON_PATTERN) != -1L;
292-
}
293-
294-
/**
295-
* Searches for the given pattern within a buffer,
296-
* starting at the given buffer position.
297-
*
298-
* @param buffer the buffer to search within
299-
* @param position the buffer position to start searching at
300-
* @param pattern the pattern to search for
301-
* @return a long value whose lower 32 bits contain the index of the
302-
* found pattern, and upper 32 bits contain the found pattern length;
303-
* if the pattern is not found at all, returns -1L
304-
*/
305-
private static long indexOf(IoBuffer buffer, int position, byte[] pattern) {
306-
int length;
307-
byte first = pattern[0];
308-
for (int limit = buffer.limit() - minPatternLength(pattern) + 1; position < limit; position++) {
309-
if (buffer.get(position) == first && (length = matches(buffer, position, pattern)) > 0) {
310-
return (long)length << 32 | position;
311-
}
312-
}
313-
return -1L;
314-
}
315-
316-
/**
317-
* Checks if the buffer at the given offset matches the given pattern.
318-
* The character '?' is a one byte wildcard, and lowercase letters are optional.
319-
*
320-
* @param buffer the buffer to check
321-
* @param bufferOffset the buffer offset at which to check
322-
* @param pattern the pattern to try matching
323-
* @return the length of the matched pattern, or -1 if there is no match
324-
*/
325-
private static int matches(IoBuffer buffer, int bufferOffset, byte[] pattern) {
326-
if (bufferOffset + minPatternLength(pattern) > buffer.limit()) {
327-
return -1;
328-
}
329-
final int initOffset = bufferOffset;
330-
int patternOffset = 0;
331-
for (int bufferLimit = buffer.limit(); patternOffset < pattern.length
332-
&& bufferOffset < bufferLimit; patternOffset++, bufferOffset++) {
333-
byte b = pattern[patternOffset];
334-
// check exact character match or wildcard match
335-
if (buffer.get(bufferOffset) == b || b == '?')
336-
continue;
337-
// check optional character match
338-
if (b >= 'a' && b <= 'z') { // lowercase is optional
339-
// at this point we know it's not an exact match, so we only need to check the
340-
// uppercase character. If there's a match we go on as usual, and if not we
341-
// ignore the optional character by rewinding the buffer offset
342-
if (b - 'a' + 'A' != buffer.get(bufferOffset)) // no uppercase match
343-
bufferOffset--;
344-
continue;
345-
}
346-
return -1; // no match
347-
}
348-
if (patternOffset != pattern.length) {
349-
// when minPatternLength(pattern) != pattern.length we might run out of buffer before we run out of pattern
350-
return -1;
351-
}
352-
return bufferOffset - initOffset;
282+
return LOGON_PATTERN.find(buffer, buffer.position()) != -1L;
353283
}
354284

355285
public void finishDecode(IoSession arg0, ProtocolDecoderOutput arg1) throws Exception {
@@ -419,11 +349,4 @@ public void flush(IoFilter.NextFilter nextFilter, IoSession ioSession) {
419349
fileIn.close();
420350
}
421351

422-
private static byte[] getBytes(String s) {
423-
try {
424-
return s.getBytes(CharsetSupport.getDefaultCharset());
425-
} catch (UnsupportedEncodingException e) {
426-
throw new RuntimeException(e);
427-
}
428-
}
429352
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package quickfix.mina.message;
2+
3+
import org.apache.mina.core.buffer.IoBuffer;
4+
import org.quickfixj.CharsetSupport;
5+
6+
import java.io.UnsupportedEncodingException;
7+
8+
/**
9+
* Finds a byte pattern within a buffer.
10+
* <p>
11+
* Matching is performed on bytes rather than characters, but we
12+
* consider them interchangeable with ASCII characters for simplicity.
13+
* <p>
14+
* The question mark character ('?') is treated as a one-byte wildcard.
15+
* Lowercase letters are considered optional (and matched case-insensitively).
16+
* Uppercase letters and all other values are matched as literals.
17+
* <p>
18+
* This class is immutable and thus can be used concurrently from multiple threads.
19+
*/
20+
class PatternMatcher {
21+
22+
private final byte[] pattern;
23+
private final int minLength;
24+
25+
/**
26+
* Constructs a PatternMatcher which matches the given pattern.
27+
*
28+
* @param pattern a pattern (see {@link PatternMatcher} for details)
29+
*/
30+
PatternMatcher(String pattern) {
31+
this.pattern = getBytes(pattern);
32+
this.minLength = calculateMinLength();
33+
}
34+
35+
/**
36+
* Returns the minimum number of bytes that the pattern can match.
37+
* If the pattern has no optional characters, this is simply the
38+
* pattern length.
39+
*
40+
* @return the minimum number of bytes that the pattern can match
41+
*/
42+
public int getMinLength() {
43+
return minLength;
44+
}
45+
46+
private static byte[] getBytes(String s) {
47+
try {
48+
return s.getBytes(CharsetSupport.getDefaultCharset());
49+
} catch (UnsupportedEncodingException e) {
50+
throw new RuntimeException(e);
51+
}
52+
}
53+
54+
/**
55+
* Calculates the minimum number of bytes that the pattern can match.
56+
*
57+
* @return the minimum number of bytes that the pattern can match
58+
*/
59+
private int calculateMinLength() {
60+
int len = 0;
61+
for (byte b : pattern) {
62+
if (b < 'a' || b > 'z') { // if not optional character (lowercase)
63+
len++;
64+
}
65+
}
66+
return len;
67+
}
68+
69+
/**
70+
* Searches for the given pattern within a buffer,
71+
* starting at the given buffer offset.
72+
*
73+
* @param buffer the buffer to search within
74+
* @param offset the buffer offset to start searching at
75+
* @return a long value whose lower 32 bits contain the index of the
76+
* found pattern, and upper 32 bits contain the found pattern length;
77+
* if the pattern is not found at all, returns -1L
78+
*/
79+
public long find(IoBuffer buffer, int offset) {
80+
int length;
81+
byte first = pattern[0];
82+
for (int limit = buffer.limit() - minLength + 1; offset < limit; offset++) {
83+
if (buffer.get(offset) == first && (length = match(buffer, offset)) > 0) {
84+
return (long)length << 32 | offset;
85+
}
86+
}
87+
return -1L;
88+
}
89+
90+
/**
91+
* Checks if the buffer contents at the given offset matches the pattern.
92+
*
93+
* @param buffer the buffer to check
94+
* @param offset the buffer offset at which to check
95+
* @return the length of the matched pattern, or -1 if there is no match
96+
*/
97+
public int match(IoBuffer buffer, int offset) {
98+
if (offset + minLength > buffer.limit()) {
99+
return -1;
100+
}
101+
final int startOffset = offset;
102+
int patternOffset = 0;
103+
for (int bufferLimit = buffer.limit(); patternOffset < pattern.length
104+
&& offset < bufferLimit; patternOffset++, offset++) {
105+
byte b = pattern[patternOffset];
106+
// check exact character match or wildcard match
107+
if (buffer.get(offset) == b || b == '?')
108+
continue;
109+
// check optional character match
110+
if (b >= 'a' && b <= 'z') { // lowercase is optional
111+
// at this point we know it's not an exact match, so we only need to check the
112+
// uppercase character. If there's a match we go on as usual, and if not we
113+
// ignore the optional character by rewinding the buffer offset
114+
if (b - 'a' + 'A' != buffer.get(offset)) // no uppercase match
115+
offset--;
116+
continue;
117+
}
118+
return -1; // no match
119+
}
120+
if (patternOffset != pattern.length) {
121+
// when minPatternLength(pattern) != pattern.length we might run out of buffer before we run out of pattern
122+
return -1;
123+
}
124+
return offset - startOffset;
125+
}
126+
}

0 commit comments

Comments
 (0)