1
2
3
4
5 package net.sf.atmodem4j.core.parser;
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 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.Arrays;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
40 import net.sf.atmodem4j.core.Modem;
41
42
43
44
45
46 public class Parser {
47
48 private ParserState parserState = ParserState.COLLECT_ALL;
49
50 public String getEcho() {
51 return parsedEcho;
52 }
53
54 private char[] buildToken(String s, char... c) {
55 StringBuilder sb1 = new StringBuilder(s);
56 sb1.append(c);
57 return sb1.toString().toCharArray();
58 }
59
60 private char[] buildToken(char... c) {
61 return c;
62 }
63
64 private void matchedGarbage(int end) {
65 modem.garbageCollected(this, sb.substring(0, end));
66 sb.delete(0, end);
67 }
68
69 private int findLastToken(char[] token) {
70 if (sb.length() - token.length <= 0) {
71 return -1;
72 }
73 for (int i = sb.length() - token.length; i >= 0; i--) {
74 for (int j = token.length - 1; j >= 0; j--) {
75 if (sb.charAt(i + j) == token[j]) {
76 if (j == 0) {
77 return i;
78 }
79 } else {
80 break;
81 }
82 }
83 }
84 return -1;
85 }
86
87 private boolean lastIs(char[] chars) {
88 if (sb.length() < chars.length) {
89 return false;
90 }
91 boolean result = true;
92 int sbOffset = sb.length() - 1;
93 int charsOffset = chars.length - 1;
94 for (int i = 0; i < chars.length; i++) {
95 if (sb.charAt(sbOffset - i) != chars[charsOffset - i]) {
96 result = false;
97 break;
98 }
99 }
100 return result;
101 }
102
103 public void resetParser() {
104 clearBuffer();
105 parserState = ParserState.COLLECT_ALL;
106 parsedData = new String[0];
107 parsedEcho = null;
108 }
109
110 public enum ParserState {
111
112 COLLECT_ALL, COLLECT_CMD_ECHO, COLLECT_DATA_OR_RESPONSE;
113 }
114 private static final Logger log = Logger.getLogger("atmodem4j-core");
115 public static final String ONLINE_DATA_OMNLINE_COMMAND_INDICATOR = "+++";
116 private InputStream os;
117 private InputStream is;
118 private ParserThread parserThread;
119 private char cr = '\r';
120 private char lf = '\n';
121 private char[] tokenCrLf = new char[]{cr, lf};
122 private char[] tokenCrCrLf = new char[]{cr, cr, lf};
123 private char[] tokenPrompt = new char[]{'>'};
124 private char[] tokenAT = new char[]{'A', 'T'};
125 private char[] tokenOk;
126 private char[] tokenConnect;
127 private char[] tokenRing;
128 private char[] tokenNoCarrier;
129 private char[] tokenError;
130 private char[] tokenNoDialtone;
131 private char[] tokenBusy;
132 private char[] tokenNoAnswer;
133 private char[] tokenConnectWithText;
134 private ModemState state;
135 private final StringBuilder sb = new StringBuilder();
136 private int onlineEscapeIndex;
137 private String parsedEcho;
138 private String[] parsedData;
139
140 public char getCR() {
141 return cr;
142 }
143
144 public char getLF() {
145 return lf;
146 }
147
148 public boolean isOnlineDataMode() {
149 return ModemState.ONLINE_DATA_STATE.equals(state);
150 }
151
152 public void prepareOnlineHangup() {
153 state = ModemState.ONLINE_COMMAND_STATE;
154 }
155
156 class ConnectionInputStream extends InputStream {
157
158
159 private final int[] buffer = new int[2048];
160
161 private int readPos = 0;
162
163 private int writePos = 0;
164
165 private void clearBuffer() {
166 log.info("Clean Buffer start");
167 synchronized (buffer) {
168 readPos = 0;
169 writePos = 0;
170 }
171 log.info("Clean Buffer done");
172 }
173
174
175 private void putByte(int b) {
176 synchronized (buffer) {
177 if (writePos == buffer.length - 1 && readPos == 0) {
178 throw new RuntimeException("Buffer full no read");
179 } else {
180 if (writePos == readPos - 1) {
181 throw new RuntimeException("Buffer Overrun at: " + writePos);
182 }
183 }
184
185 buffer[writePos++] = b;
186
187 if (writePos == buffer.length) {
188 writePos = 0;
189 log.info("Buffer write wrap");
190 }
191
192 buffer.notifyAll();
193 }
194 }
195
196 @Override
197 public int read() throws IOException {
198 int result;
199 synchronized (buffer) {
200 if (writePos == readPos) {
201 try {
202 buffer.wait();
203 } catch (InterruptedException ex) {
204
205 return -1;
206 }
207 }
208 result = buffer[readPos++];
209 if (readPos == buffer.length) {
210 readPos = 0;
211 log.info("Buffer read wrap");
212 }
213 }
214 return result;
215 }
216
217 @Override
218 public void close() {
219 log.info("Buffer Close");
220 putByte(-1);
221 clearBuffer();
222 }
223
224 }
225 private Modem modem;
226 private final ConnectionInputStream connectionInputStream;
227
228 public Parser() {
229 this.connectionInputStream = new ConnectionInputStream();
230 state = ModemState.COMMAND_STATE;
231 setLineChars('\r', '\n');
232 }
233
234 public Parser(InputStream is, char cr, char lf,
235 boolean rxtxTimeoutEnabled) {
236 this();
237 setInputStream(is);
238 setLineChars(cr, lf);
239 start();
240 }
241
242 public void setModem(Modem modem) {
243 this.modem = modem;
244 }
245
246 public Modem getModem() {
247 return modem;
248 }
249
250 public synchronized void clearBuffer() {
251 sb.delete(0, sb.length());
252 }
253
254
255
256
257 public InputStream getInputStream() {
258 return is;
259 }
260
261
262
263
264 public void setInputStream(InputStream is) {
265 final InputStream oldIs = this.is;
266 this.is = is;
267 if (oldIs == null && is != null) {
268 start();
269 }
270 }
271
272 public void setOutputStream(InputStream os) {
273 this.os = os;
274 }
275
276
277
278
279 public void setLineChars(char cr, char lf) {
280 this.cr = cr;
281 this.lf = lf;
282 this.tokenCrLf = buildToken(cr, lf);
283 this.tokenCrCrLf = buildToken(cr, cr, lf);
284 this.tokenOk = buildToken(ResultCodeToken.OK, cr, lf);
285 this.tokenConnect = buildToken(ResultCodeToken.CONNECT, cr, lf);
286 this.tokenRing = buildToken(ResultCodeToken.RING, cr, lf);
287 this.tokenNoCarrier = buildToken(ResultCodeToken.NO_CARRIER, cr, lf);
288 this.tokenError = buildToken(ResultCodeToken.ERROR, cr, lf);
289 this.tokenNoDialtone = buildToken(ResultCodeToken.NO_DIALTONE, cr, lf);
290 this.tokenBusy = buildToken(ResultCodeToken.BUSY, cr, lf);
291 this.tokenNoAnswer = buildToken(ResultCodeToken.NO_ANSWER, cr, lf);
292 this.tokenConnectWithText = buildToken(ResultCodeToken.CONNECT);
293 }
294
295 public void start() {
296 if (parserThread != null) {
297 return;
298 }
299 parserThread = new ParserThread();
300 parserThread.setDaemon(true);
301 parserThread.start();
302 }
303
304 private void matchedBusy() {
305 if (modem != null) {
306 modem.parsedResultCode(this, new ResultCodeToken(
307 ResultCodeToken.BUSY, parsedData, parsedEcho));
308 }
309 resetParser();
310 }
311
312 private void matchedConnect() {
313 state = ModemState.ONLINE_DATA_STATE;
314 connectionInputStream.clearBuffer();
315 if (modem != null) {
316 modem.parsedResultCode(this, new ResultCodeToken(
317 ResultCodeToken.CONNECT, parsedData, parsedEcho));
318 }
319 resetParser();
320 }
321
322 private void matchedConnectWithText() {
323 state = ModemState.ONLINE_DATA_STATE;
324 connectionInputStream.clearBuffer();
325 if (modem != null) {
326 modem.parsedResultCode(this, new ResultCodeToken(
327 ResultCodeToken.CONNECT, parsedData, parsedEcho));
328 }
329 resetParser();
330 }
331
332 private void matchedError() {
333 if (modem != null) {
334 modem.parsedResultCode(this, new ResultCodeToken(
335 ResultCodeToken.ERROR, parsedData, parsedEcho));
336 }
337 resetParser();
338 }
339
340 private void matchedNoAnswer() {
341 if (modem != null) {
342 modem.parsedResultCode(this, new ResultCodeToken(
343 ResultCodeToken.NO_ANSWER, parsedData, parsedEcho));
344 }
345 resetParser();
346 }
347
348 private void matchedNoCarrier() {
349 if (modem != null) {
350 modem.parsedResultCode(this, new ResultCodeToken(
351 ResultCodeToken.NO_CARRIER, parsedData, parsedEcho));
352 }
353 resetParser();
354 }
355
356 private void matchedNoDialtone() {
357 if (modem != null) {
358 modem.parsedResultCode(this, new ResultCodeToken(
359 ResultCodeToken.NO_DIALTONE, parsedData, parsedEcho));
360 }
361 resetParser();
362 }
363
364 private void matchedOk() {
365 if (modem != null) {
366 modem.parsedResultCode(this, new ResultCodeToken(ResultCodeToken.OK,
367 parsedData, parsedEcho));
368 }
369 resetParser();
370 }
371
372
373 private void matchedRing() {
374 if (modem != null) {
375 modem.parsedRing(this);
376 }
377 sb.delete(sb.length() - tokenRing.length, sb.length() - 1);
378 }
379
380 private void matchedPrompt() {
381 sb.delete(0, 1);
382 if (modem != null) {
383 modem.parsedPrompt(this);
384 }
385 }
386
387
388
389
390 public ModemState getState() {
391 return state;
392 }
393
394 class ParserThread extends Thread {
395
396 @Override
397 public void run() {
398 log.fine("Execute ParserThread");
399 try {
400 while (true) {
401 int i = is.read();
402
403 if (i == -1) {
404 parseByte(i);
405 break;
406 } else {
407 if (log.isLoggable(Level.FINEST)) {
408 log.finest(String.format("Char received 0x%x\t%s", i,
409 (char) i));
410 }
411 parseByte((byte) i);
412 }
413 }
414 } catch (IOException ex) {
415 } finally {
416 parserThread = null;
417 log.fine("ParserThread finished");
418 }
419 }
420 }
421
422 private boolean matchResult(String token) {
423 return sb.lastIndexOf(token) != -1;
424 }
425
426 private synchronized void parseChar(char b) {
427 sb.append(b);
428
429
430
431
432
433
434
435 if (lastIs(tokenRing)) {
436 matchedRing();
437 }
438 switch (parserState) {
439 case COLLECT_ALL:
440 if (lastIs(tokenOk)) {
441 matchedOk();
442 } else if (lastIs(tokenBusy)) {
443 matchedBusy();
444 } else if (lastIs(tokenConnect)) {
445 matchedConnect();
446 } else if (lastIs(tokenError)) {
447 matchedError();
448 } else if (lastIs(tokenNoAnswer)) {
449 matchedNoAnswer();
450 } else if (lastIs(tokenNoCarrier)) {
451 matchedNoCarrier();
452 } else if (lastIs(tokenNoDialtone)) {
453 matchedNoDialtone();
454 } else if (lastIs(tokenCrLf)) {
455 if (findLastToken(tokenConnectWithText) >= 0) {
456 matchedConnectWithText();
457 }
458 } else if (lastIs(tokenAT)) {
459 parserState = ParserState.COLLECT_CMD_ECHO;
460 if (sb.length() > tokenAT.length) {
461 matchedGarbage(sb.length() - tokenAT.length);
462 }
463 break;
464 }
465 break;
466 case COLLECT_CMD_ECHO:
467 if (lastIs(tokenCrCrLf)) {
468 parsedEcho = sb.substring(0, sb.length()
469 - tokenCrCrLf.length);
470 parsedData = new String[0];
471 parserState = ParserState.COLLECT_DATA_OR_RESPONSE;
472 clearBuffer();
473 }
474 break;
475 case COLLECT_DATA_OR_RESPONSE:
476 if (parsedData.length == 0 && lastIs(tokenPrompt)) {
477 matchedPrompt();
478 } else if (lastIs(tokenOk)) {
479 matchedOk();
480 } else if (lastIs(tokenBusy)) {
481 matchedBusy();
482 } else if (lastIs(tokenConnect)) {
483 matchedConnect();
484 } else if (lastIs(tokenError)) {
485 matchedError();
486 } else if (lastIs(tokenNoAnswer)) {
487 matchedNoAnswer();
488 } else if (lastIs(tokenNoCarrier)) {
489 matchedNoCarrier();
490 } else if (lastIs(tokenNoDialtone)) {
491 matchedNoDialtone();
492 } else if (lastIs(tokenCrLf)) {
493 if (findLastToken(tokenConnectWithText) >= 0) {
494 matchedConnectWithText();
495 } else {
496
497 parsedData = Arrays.copyOf(parsedData,
498 parsedData.length + 1);
499 parsedData[parsedData.length - 1] = sb.substring(0, sb.
500 length() - tokenCrLf.length);
501 clearBuffer();
502 }
503 }
504 break;
505 default:
506 throw new RuntimeException("UNKNOWN PARSER STATE");
507 }
508
509 }
510
511 private void parseByte(int b) {
512 switch (state) {
513
514 case COMMAND_STATE:
515 if (b != -1) {
516 parseChar((char) b);
517 }
518 break;
519 case ONLINE_COMMAND_STATE:
520 if (b != -1) {
521 parseChar((char) b);
522 }
523 break;
524 case ONLINE_DATA_STATE:
525
526
527 if ((char) b == ONLINE_DATA_OMNLINE_COMMAND_INDICATOR.charAt(
528 onlineEscapeIndex)) {
529 onlineEscapeIndex++;
530 if (onlineEscapeIndex
531 == ONLINE_DATA_OMNLINE_COMMAND_INDICATOR.length()) {
532 state = ModemState.ONLINE_COMMAND_STATE;
533 onlineEscapeIndex = 0;
534 log.info("received \"+++\", goto online command mode");
535 }
536 } else {
537 onlineEscapeIndex = 0;
538 }
539
540 connectionInputStream.putByte(b);
541 break;
542 }
543 }
544
545 public InputStream getConnectionInputStream() {
546 return connectionInputStream;
547 }
548
549 public String getBuffer() {
550 return sb.toString();
551 }
552 }