View Javadoc
1   package au.gov.amsa.util.nmea;
2   
3   import java.util.Arrays;
4   import java.util.LinkedHashMap;
5   
6   import org.apache.commons.lang3.StringUtils;
7   
8   import com.google.common.collect.Maps;
9   
10  /**
11   * Parses NMEA messages.
12   * 
13   */
14  public class NmeaMessageParser {
15  
16      private static final String CHECKSUM_DELIMITER = "*";
17      private static final String PARAMETER_DELIMITER = ",";
18      private static final String CODE_DELIMITER = ":";
19  
20      /**
21       * Return an {@link NmeaMessage} from the given NMEA line.
22       * 
23       * @param line
24       * @return
25       */
26      public NmeaMessage parse(String line) {
27          LinkedHashMap<String, String> tags = Maps.newLinkedHashMap();
28  
29          String remaining;
30          if (line.startsWith("\\")) {
31              int tagFinish = line.lastIndexOf('\\', line.length() - 1);
32              if (tagFinish == -1)
33                  throw new NmeaMessageParseException(
34                          "no matching \\ symbol to finish tag block: " + line);
35              if (tagFinish == 0)
36                  throw new NmeaMessageParseException("tag block is empty or not terminated");
37              tags = extractTags(line.substring(1, tagFinish));
38              remaining = line.substring(tagFinish + 1);
39          } else
40              remaining = line;
41  
42          String[] items;
43          String checksum;
44          if (remaining.length() > 0) {
45              if (!remaining.contains("*"))
46                  throw new NmeaMessageParseException("checksum delimiter * not found");
47              items = getNmeaItems(remaining);
48              // TODO validate message using checksum
49              checksum = line.substring(line.indexOf('*') + 1);
50          } else {
51              items = new String[] {};
52              // TODO decide what value to put here
53              checksum = "";
54          }
55  
56          return new NmeaMessage(tags, Arrays.asList(items), checksum);
57      }
58  
59      /**
60       * Returns the items from the comma delimited NMEA line. The last item
61       * always contains a * followed by a checksum. If * is not present
62       * {@link IndexOutOfBoundsException} will be thrown.
63       * 
64       * @param line
65       * @return
66       */
67      private static String[] getNmeaItems(String line) {
68          String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(line,
69                  PARAMETER_DELIMITER);
70          // remove the checksum from the end
71          String last = items[items.length - 1];
72          if (last.contains("*"))
73              items[items.length - 1] = last.substring(0, last.lastIndexOf('*'));
74          return items;
75      }
76  
77      /**
78       * Returns the tags from the tag block section of the message (NMEA v4.0).
79       * If there is no tag block then returns an empty map.
80       * 
81       * @param s
82       * @return
83       */
84      public static LinkedHashMap<String, String> extractTags(String s) {
85          LinkedHashMap<String, String> map = Maps.newLinkedHashMap();
86          int c = s.lastIndexOf(CHECKSUM_DELIMITER);
87          if (c == -1) {
88              return map;
89          }
90          s = s.substring(0, c);
91          String[] items = s.split(PARAMETER_DELIMITER);
92          for (String item : items) {
93              int i = item.indexOf(CODE_DELIMITER);
94              if (i == -1)
95                  throw new NmeaMessageParseException(
96                          "TAG BLOCK parameter is not is format 'a:b' :" + s);
97              map.put(item.substring(0, i), item.substring(i + 1));
98          }
99          return map;
100     }
101 }