package au.gov.amsa.util.nmea;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import au.gov.amsa.ais.AisParseException;
import com.google.common.collect.Sets;
public final class NmeaUtil {
private static final char BACKSLASH = '\\';
private NmeaUtil() {
// private constructor to prevent instantiation
}
static void forTestCoverageOnly() {
new NmeaUtil();
}
private static final Set<Integer> invalidFieldCharacters = Sets.newHashSet(33, 36, 42, 44, 92,
94, 126, 127);
private static final Set<Character> validCharacterSymbols = createValidCharacterSymbols();
private static Set<Character> createValidCharacterSymbols() {
String s = "AaBCcDdEFfGgHhIJKkLlMmNnPQRrSsTtUuVWxyZ";
Set<Character> set = Sets.newHashSet();
for (char ch : s.toCharArray())
set.add(ch);
return set;
}
static boolean isValidFieldCharacter(char ch) {
return ch <= 127 && ch >= 32 && !invalidFieldCharacters.contains((int) ch);
}
static boolean isValidCharacterSymbol(char ch) {
return validCharacterSymbols.contains(ch);
}
/**
* Returns true if and only if the sentence's checksum matches the
* calculated checksum.
*
* @param sentence
* @return
*/
public static boolean isValid(String sentence) {
// Compare the characters after the asterisk to the calculation
try {
return sentence.substring(sentence.lastIndexOf("*") + 1)
.equalsIgnoreCase(getChecksum(sentence));
} catch (AisParseException e) {
return false;
}
}
public static String getChecksum(String sentence) {
return getChecksum(sentence, true);
}
public static String getChecksum(String sentence, boolean ignoreLeadingDollarOrExclamation) {
int startIndex;
// Start after tag block
if (sentence.startsWith("\\")) {
startIndex = sentence.indexOf('\\', 1) + 1;
if (startIndex == 0)
throw new AisParseException("no closing \\ for tag block");
} else
startIndex = 0;
// Loop through all chars to get a checksum
int checksum = 0;
for (int i = startIndex; i < sentence.length(); i++) {
char ch = sentence.charAt(i);
if (ignoreLeadingDollarOrExclamation && (ch == '$' || ch == '!')) {
// Ignore the dollar sign
} else if (ch == '*') {
// Stop processing before the asterisk
break;
} else {
// Is this the first value for the checksum?
if (checksum == 0) {
// Yes. Set the checksum to the value
checksum = ch;
} else {
// No. XOR the checksum with this character's value
checksum = checksum ^ ch;
}
}
}
// Return the checksum formatted as a two-character hexadecimal
String s = Integer.toHexString(checksum % 256);
if (s.length() == 1)
s = "0" + s;
return s.toUpperCase();
}
private static NmeaMessageParser nmeaParser = new NmeaMessageParser();
public static NmeaMessage parseNmea(String line) {
return nmeaParser.parse(line);
}
public static String supplementWithTime(String line, long arrivalTime) {
line = line.trim();
final String amendedLine;
NmeaMessage m = parseNmea(line);
Long t = m.getUnixTimeMillis();
Long a = m.getArrivalTimeMillis();
if (t == null) {
// use arrival time if not present
t = arrivalTime;
// if has tag block
if (line.startsWith("\\")) {
// insert time into tag block, and adjust the
// hash for the tag block
int i = line.indexOf(BACKSLASH, 1);
if (i == -1)
throw new RuntimeException(
"line starts with \\ but does not have closing tag block delimiter \\");
if (i < 4)
throw new RuntimeException("tag block not long enough to have a checksum");
String content = line.substring(1, i - 3);
StringBuilder s = new StringBuilder(content);
s.append(",");
appendTimes(arrivalTime, t, s);
String checksum = NmeaUtil.getChecksum(s.toString(), false);
s.append('*');
s.append(checksum);
s.append(line.substring(i));
s.insert(0, BACKSLASH);
amendedLine = s.toString();
} else {
StringBuilder s = new StringBuilder();
appendTimes(t, arrivalTime, s);
String checksum = NmeaUtil.getChecksum(s.toString(), false);
s.append("*");
s.append(checksum);
s.append(BACKSLASH);
s.append(line);
s.insert(0, BACKSLASH);
amendedLine = s.toString();
}
} else if (a == null) {
// must have a proper tag block because t != null
// insert time into tag block, and adjust the
// hash for the tag block
int i = line.indexOf(BACKSLASH, 1);
String content = line.substring(1, i - 3);
StringBuilder s = new StringBuilder(content);
s.append(",a:");
s.append(arrivalTime);
String checksum = NmeaUtil.getChecksum(s.toString(), false);
s.append('*');
s.append(checksum);
s.append(line.substring(i));
s.insert(0, BACKSLASH);
amendedLine = s.toString();
} else {
amendedLine = line;
}
return amendedLine;
}
private static void appendTimes(long arrivalTime, Long t, StringBuilder s) {
s.append("c:");
s.append(t / 1000);
s.append(",a:");
s.append(arrivalTime);
}
public static Talker getTalker(String s) {
if (s == null)
return null;
else {
try {
return Talker.valueOf(s);
} catch (RuntimeException e) {
return Talker.UNKNOWN;
}
}
}
public static String createTagBlock(LinkedHashMap<String, String> tags) {
if (tags == null || tags.size() == 0)
return "";
StringBuilder s = new StringBuilder(128);
s.append("\\");
int startChecksum = s.length();
boolean first = true;
for (Entry<String, String> entry : tags.entrySet()) {
if (!first)
s.append(",");
s.append(entry.getKey());
s.append(":");
s.append(entry.getValue());
first = false;
}
String checksum = NmeaUtil.getChecksum(s.substring(startChecksum));
s.append("*");
s.append(checksum);
s.append("\\");
return s.toString();
}
public static String createNmeaLine(LinkedHashMap<String, String> tags, List<String> items) {
StringBuilder s = new StringBuilder(40);
s.append(createTagBlock(tags));
int startForChecksum = s.length();
boolean first = true;
for (String item : items) {
if (!first)
s.append(",");
s.append(item);
first = false;
}
String checksum = NmeaUtil.getChecksum(s.substring(startForChecksum));
s.append("*");
s.append(checksum);
return s.toString();
}
}