View Javadoc
1   /*
2    * To change this template, choose Tools | Templates
3    * and open the template in the editor.
4    */
5   package net.sf.atmodem4j.core.parser;
6   
7   /*
8    * #%L
9    * ATModem4J Core
10   * %%
11   * Copyright (C) 2009 - 2014 atmodem4j
12   * %%
13   * atmodem4j - Drivers for the AT modem - http://atmodem4j.sourceforge.net/
14   * Copyright (C) 2009-2014, atmodem4j.sf.net, and individual contributors as indicated
15   * by the @authors tag. See the copyright.txt in the distribution for a
16   * full listing of individual contributors.
17   * 
18   * This is free software; you can redistribute it and/or modify it
19   * under the terms of the GNU General Public License as
20   * published by the Free Software Foundation; either version 3 of
21   * the License, or (at your option) any later version.
22   * 
23   * This software is distributed in the hope that it will be useful,
24   * but WITHOUT ANY WARRANTY; without even the implied warranty of
25   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26   * Lesser General Public License for more details.
27   * 
28   * You should have received a copy of the GNU Lesser General Public
29   * License along with this software; if not, write to the Free
30   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
32   * #L%
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   * @author aploese
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}; //separator between data and resultcode and endmarker for resultcode
122     private char[] tokenCrCrLf = new char[]{cr, cr, lf}; //Echo before data and or resultcode after
123     private char[] tokenPrompt = new char[]{'>'}; //Prompt for GSM AT+CMGS=
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         //RingBuffer
159         private final int[] buffer = new int[2048];
160         //ReadPointer
161         private int readPos = 0;
162         //WritePointer
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         //TODO Buffer overrun?
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                 //wrap around
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                         //TODO close???
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      * @return the is
256      */
257     public InputStream getInputStream() {
258         return is;
259     }
260 
261     /**
262      * @param is the is to set
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      * @param lineEnd the lineEnd to set
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     //TODO remove only RING<CR><LF> from buffer
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      * @return the state
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                     //TODO bug rxtx close inputstream
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          System.out.print('"');
431          System.out.print(sb.toString());
432          System.out.println('"');
433          System.out.println();
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                         // Fall trough last is data....
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 //TODO pass thru and detect space +++ space
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 }