View Javadoc
1   package au.gov.amsa.util.nmea;
2   
3   import java.util.LinkedHashMap;
4   import java.util.List;
5   import java.util.Map.Entry;
6   import java.util.Set;
7   
8   import au.gov.amsa.ais.AisParseException;
9   
10  import com.google.common.collect.Sets;
11  
12  public final class NmeaUtil {
13  
14      private static final char BACKSLASH = '\\';
15  
16      private NmeaUtil() {
17          // private constructor to prevent instantiation
18      }
19  
20      static void forTestCoverageOnly() {
21          new NmeaUtil();
22      }
23  
24      private static final Set<Integer> invalidFieldCharacters = Sets.newHashSet(33, 36, 42, 44, 92,
25              94, 126, 127);
26  
27      private static final Set<Character> validCharacterSymbols = createValidCharacterSymbols();
28  
29      private static Set<Character> createValidCharacterSymbols() {
30          String s = "AaBCcDdEFfGgHhIJKkLlMmNnPQRrSsTtUuVWxyZ";
31          Set<Character> set = Sets.newHashSet();
32          for (char ch : s.toCharArray())
33              set.add(ch);
34          return set;
35      }
36  
37      static boolean isValidFieldCharacter(char ch) {
38          return ch <= 127 && ch >= 32 && !invalidFieldCharacters.contains((int) ch);
39      }
40  
41      static boolean isValidCharacterSymbol(char ch) {
42          return validCharacterSymbols.contains(ch);
43      }
44  
45      /**
46       * Returns true if and only if the sentence's checksum matches the
47       * calculated checksum.
48       * 
49       * @param sentence
50       * @return
51       */
52      public static boolean isValid(String sentence) {
53          // Compare the characters after the asterisk to the calculation
54  
55          try {
56              return sentence.substring(sentence.lastIndexOf("*") + 1)
57                      .equalsIgnoreCase(getChecksum(sentence));
58          } catch (AisParseException e) {
59              return false;
60          }
61      }
62  
63      public static String getChecksum(String sentence) {
64          return getChecksum(sentence, true);
65      }
66  
67      public static String getChecksum(String sentence, boolean ignoreLeadingDollarOrExclamation) {
68  
69          int startIndex;
70          // Start after tag block
71          if (sentence.startsWith("\\")) {
72              startIndex = sentence.indexOf('\\', 1) + 1;
73              if (startIndex == 0)
74                  throw new AisParseException("no closing \\ for tag block");
75          } else
76              startIndex = 0;
77          // Loop through all chars to get a checksum
78          int checksum = 0;
79          for (int i = startIndex; i < sentence.length(); i++) {
80              char ch = sentence.charAt(i);
81              if (ignoreLeadingDollarOrExclamation && (ch == '$' || ch == '!')) {
82                  // Ignore the dollar sign
83              } else if (ch == '*') {
84                  // Stop processing before the asterisk
85                  break;
86              } else {
87                  // Is this the first value for the checksum?
88                  if (checksum == 0) {
89                      // Yes. Set the checksum to the value
90                      checksum = ch;
91                  } else {
92                      // No. XOR the checksum with this character's value
93                      checksum = checksum ^ ch;
94                  }
95              }
96          }
97          // Return the checksum formatted as a two-character hexadecimal
98          String s = Integer.toHexString(checksum % 256);
99          if (s.length() == 1)
100             s = "0" + s;
101         return s.toUpperCase();
102     }
103 
104     private static NmeaMessageParser nmeaParser = new NmeaMessageParser();
105 
106     public static NmeaMessage parseNmea(String line) {
107         return nmeaParser.parse(line);
108     }
109     
110     public static String insertKeyValueInTagBlock(String line, String name, String value) {
111         line = line.trim();
112         if (line.startsWith("\\")) {
113             // insert time into tag block, and adjust the
114             // hash for the tag block
115             int i = line.indexOf(BACKSLASH, 1);
116             if (i == -1) {
117                 //return line unchanged
118                 return line;
119             }
120             if (i < 4) {
121                 // tag block not long enough to have a checksum");
122                 return line;
123             }
124             String content = line.substring(1, i - 3);
125             StringBuilder s = new StringBuilder(content);
126             s.append(",");
127             s.append(name);
128             s.append(":");
129             s.append(value);
130             String checksum = NmeaUtil.getChecksum(s.toString(), false);
131             s.append('*');
132             s.append(checksum);
133             s.append(line.substring(i));
134             s.insert(0, BACKSLASH);
135             return s.toString();
136         } else {
137             return line;
138         }
139     }
140 
141     public static String supplementWithTime(String line, long arrivalTime) {
142         line = line.trim();
143         final String amendedLine;
144         NmeaMessage m = parseNmea(line);
145         Long t = m.getUnixTimeMillis();
146         Long a = m.getArrivalTimeMillis();
147         if (t == null) {
148             // use arrival time if not present
149             t = arrivalTime;
150 
151             // if has tag block
152             if (line.startsWith("\\")) {
153                 // insert time into tag block, and adjust the
154                 // hash for the tag block
155                 int i = line.indexOf(BACKSLASH, 1);
156                 if (i == -1)
157                     throw new RuntimeException(
158                             "line starts with \\ but does not have closing tag block delimiter \\");
159                 if (i < 4)
160                     throw new RuntimeException("tag block not long enough to have a checksum");
161                 String content = line.substring(1, i - 3);
162                 StringBuilder s = new StringBuilder(content);
163                 s.append(",");
164                 appendTimes(arrivalTime, t, s);
165                 String checksum = NmeaUtil.getChecksum(s.toString(), false);
166                 s.append('*');
167                 s.append(checksum);
168                 s.append(line.substring(i));
169                 s.insert(0, BACKSLASH);
170                 amendedLine = s.toString();
171             } else {
172                 StringBuilder s = new StringBuilder();
173                 appendTimes(t, arrivalTime, s);
174                 String checksum = NmeaUtil.getChecksum(s.toString(), false);
175                 s.append("*");
176                 s.append(checksum);
177                 s.append(BACKSLASH);
178                 s.append(line);
179                 s.insert(0, BACKSLASH);
180                 amendedLine = s.toString();
181             }
182         } else if (a == null) {
183             // must have a proper tag block because t != null
184 
185             // insert time into tag block, and adjust the
186             // hash for the tag block
187             int i = line.indexOf(BACKSLASH, 1);
188             String content = line.substring(1, i - 3);
189             StringBuilder s = new StringBuilder(content);
190             s.append(",a:");
191             s.append(arrivalTime);
192             String checksum = NmeaUtil.getChecksum(s.toString(), false);
193             s.append('*');
194             s.append(checksum);
195             s.append(line.substring(i));
196             s.insert(0, BACKSLASH);
197             amendedLine = s.toString();
198         } else {
199             amendedLine = line;
200         }
201         return amendedLine;
202 
203     }
204 
205     private static void appendTimes(long arrivalTime, Long t, StringBuilder s) {
206         s.append("c:");
207         s.append(t / 1000);
208         s.append(",a:");
209         s.append(arrivalTime);
210     }
211 
212     public static Talker getTalker(String s) {
213         if (s == null)
214             return null;
215         else {
216             try {
217                 return Talker.valueOf(s);
218             } catch (RuntimeException e) {
219                 return Talker.UNKNOWN;
220             }
221         }
222     }
223 
224     public static String createTagBlock(LinkedHashMap<String, String> tags) {
225         if (tags == null || tags.size() == 0)
226             return "";
227         StringBuilder s = new StringBuilder(128);
228         s.append("\\");
229         int startChecksum = s.length();
230         boolean first = true;
231         for (Entry<String, String> entry : tags.entrySet()) {
232             if (!first)
233                 s.append(",");
234             s.append(entry.getKey());
235             s.append(":");
236             s.append(entry.getValue());
237             first = false;
238         }
239         String checksum = NmeaUtil.getChecksum(s.substring(startChecksum));
240         s.append("*");
241         s.append(checksum);
242         s.append("\\");
243         return s.toString();
244     }
245 
246     public static String createNmeaLine(LinkedHashMap<String, String> tags, List<String> items) {
247         StringBuilder s = new StringBuilder(40);
248         s.append(createTagBlock(tags));
249         int startForChecksum = s.length();
250         boolean first = true;
251         for (String item : items) {
252             if (!first)
253                 s.append(",");
254             s.append(item);
255             first = false;
256         }
257         String checksum = NmeaUtil.getChecksum(s.substring(startForChecksum));
258         s.append("*");
259         s.append(checksum);
260 
261         return s.toString();
262     }
263 
264 }