Changed package heirachy. Merged Controller and StartScreenController.

#refactor
This commit is contained in:
Calum
2017-07-21 09:22:55 +12:00
parent 9ecaa7c3b3
commit 881f7f8e30
62 changed files with 399 additions and 569 deletions
@@ -0,0 +1,7 @@
package seng302.model.stream;
import seng302.model.stream.packets.StreamPacket;
public interface PacketBufferDelegate {
boolean addToBuffer(StreamPacket streamPacket);
}
@@ -0,0 +1,647 @@
package seng302.model.stream;
import java.io.IOException;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.Yacht;
import seng302.model.mark.Mark;
import seng302.model.stream.packets.BoatPositionPacket;
import seng302.model.stream.packets.StreamPacket;
/**
* The purpose of this class is to take in the stream of divided packets so they can be read
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
* that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17.
*/
public class StreamParser{
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
private static boolean newRaceXmlReceived = false;
private static boolean raceStarted = false;
private static XMLParser xmlObject;
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0;
private static Double windSpeed = 0d;
private static Long currentTimeLong;
private static String currentTimeString;
//CONVERSION CONSTANTS
private static final Double MS_TO_KNOTS = 1.94384;
/**
* Used to initialise the thread name and stream parser object so a thread can be executed
*/
public StreamParser() {
}
/**
* Looks at the type of the packet then sends it to the appropriate parser to extract the
* specific data associated with that packet type
*
* @param packet the packet to be looked at and processed
*/
public static void parsePacket(StreamPacket packet) {
try {
switch (packet.getType()) {
case HEARTBEAT:
extractHeartBeat(packet);
break;
case RACE_STATUS:
extractRaceStatus(packet);
break;
case DISPLAY_TEXT_MESSAGE:
extractDisplayMessage(packet);
break;
case XML_MESSAGE:
newRaceXmlReceived = true;
extractXmlMessage(packet);
break;
case RACE_START_STATUS:
extractRaceStartStatus(packet);
break;
case YACHT_EVENT_CODE:
extractYachtEventCode(packet);
break;
case YACHT_ACTION_CODE:
extractYachtActionCode(packet);
break;
case CHATTER_TEXT:
extractChatterText(packet);
break;
case BOAT_LOCATION:
extractBoatLocation(packet);
break;
case MARK_ROUNDING:
extractMarkRounding(packet);
break;
case COURSE_WIND:
extractCourseWind(packet);
break;
case AVG_WIND:
extractAvgWind(packet);
break;
case BOAT_ACTION:
extractBoatAction(packet);
break;
}
} catch (NullPointerException e) {
System.out.println("Error parsing packet");
e.printStackTrace();
}
}
/**
* Extracts the seq num used in the heartbeat packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractHeartBeat(StreamPacket packet) {
long heartbeat = bytesToLong(packet.getPayload());
}
private static String getTimeZoneString() {
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
StringBuilder utcOffset = new StringBuilder();
utcOffset.append("GMT");
if (offset > 0) {
utcOffset.append("+");
utcOffset.append(offset);
} else if (offset < 0) {
utcOffset.append("-");
utcOffset.append(offset);
}
return utcOffset.toString();
}
/**
* Extracts the useful race status data from race status type packets. This method will also
* print to the console the current state of the race (if it has started/finished or is about to
* start), along side this it'll also display the amount of time since the race has started or
* time till it starts
*
* @param packet Packet parsed in to use the payload
*/
private static void extractRaceStatus(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
int raceStatus = payload[11];
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
currentTimeLong = currentTime;
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (xmlObject.getRegattaXML() != null) {
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
currentTimeString = format.format((new Date(currentTime)).getTime());
}
long timeTillStart =
((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
if (timeTillStart > 0) {
timeSinceStart = timeTillStart;
} else {
if (raceStatus == 4 || raceStatus == 8) {
raceFinished = true;
raceStarted = false;
} else if (!raceStarted) {
raceStarted = true;
raceFinished = false;
}
timeSinceStart = timeTillStart;
}
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
int noBoats = payload[22];
int raceType = payload[23];
for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID);
boat.setBoatStatus((int) payload[28 + (i * 20)]);
setBoatLegPosition(boat, (int) payload[29 + (i * 20)]);
boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
boat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID;
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
// boatStatuses.add(boatStatus);
}
// if (isRaceStarted()) {
// int pos = 1;
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition(String.valueOf(pos));
// pos++;
// }
// } else {
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition("-");
// }
// }
}
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
Integer placing = 1;
if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
for (Yacht boat : boats.values()) {
if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
placing += 1;
}
}
updatingBoat.setPosition(placing.toString());
updatingBoat.setLegNumber(leg);
boatsPos.putIfAbsent(placing, updatingBoat);
boatsPos.replace(placing, updatingBoat);
} else if(updatingBoat.getLegNumber() == null){
updatingBoat.setPosition("1");
updatingBoat.setLegNumber(leg);
}
}
/**
* Used to extract the messages passed through with the display message packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractDisplayMessage(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int numOfLines = payload[3];
int totalLen = 0;
for (int i = 0; i < numOfLines; i++) {
int lineNum = payload[4 + totalLen];
int textLength = payload[5 + totalLen];
byte[] messageTextBytes = Arrays
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
String messageText = new String(messageTextBytes);
totalLen += 2 + textLength;
}
}
/**
* Used to read in the xml data. Will call the specific methods to create the course and boats
*
* @param packet Packet parsed in to use the payload
*/
private static void extractXmlMessage(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageType = payload[9];
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
xmlObject.constructXML(doc, messageType);
if (messageType == 7) { //7 is the boat XML
boats = xmlObject.getBoatXML().getCompetingBoats();
}
if (messageType == 6) { //6 is race info xml
newRaceXmlReceived = true;
}
}
/**
* Extracts the race start status from the packet, currently is unused within the app but
* is here for potential future use
*
* @param packet Packet parsed in to use the payload
*/
private static void extractRaceStartStatus(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19));
int notificationType = payload[19];
}
/**
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
* currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtEventCode(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
int eventId = payload[21];
}
/**
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
* info, currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtActionCode(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int eventId = payload[17];
}
/**
* Strips the message from the chatter text type packets, currently the message is unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractChatterText(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length));
}
/**
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are
* all used All the other extra data is still being read and translated however is unused.
*
* @param packet Packet parsed in to use the payload
*/
private static void extractBoatLocation(StreamPacket packet) {
byte[] payload = packet.getPayload();
int deviceType = (int) payload[15];
long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15));
long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20));
long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24));
//Converts the double to a usable lat/lon
double lat = ((180d * (double) rawLat) / Math.pow(2, 31));
double lon = ((180d * (double) rawLon) / Math.pow(2, 31));
long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
if (deviceType == 1){
Yacht boat = boats.get((int) boatId);
boat.setVelocity(groundSpeed);
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!boatLocations.containsKey(boatId)) {
boatLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
boatLocations.get(boatId).put(boatPacket);
} else if (deviceType == 3) {
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!markLocations.containsKey(boatId)) {
markLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
markLocations.get(boatId).put(markPacket);
}
}
/**
* This packet type is received when a mark or gate is rounded by a boat
*
* @param packet The packet containing the payload
*/
private static void extractMarkRounding(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int boatStatus = payload[17];
int roundingSide = payload[18];
int markType = payload[19];
int markId = payload[20];
// assign mark rounding time to boat
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == markId) {
boats.get((int)subjectId).setLastMarkRounded(mark);
}
}
Long[] array = new Long[]{subjectId, timeStamp, (long) markId};
}
/**
* This packet type contains periodic data on the state of the wind
*
* @param packet The packet containing the payload
*/
private static void extractCourseWind(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int selectedWindId = payload[1];
int loopCount = payload[2];
List<String> windInfo = new ArrayList<>();
for (int i = 0; i < loopCount; i++) {
String wind = "WindId: " + payload[3 + (20 * i)];
wind +=
"\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i)));
wind += "\nRaceId: " + bytesToLong(
Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i)));
wind += "\nWindDirection: " + bytesToLong(
Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i)));
wind += "\nWindSpeed: " + bytesToLong(
Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i)));
wind += "\nBestUpWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i)));
wind += "\nBestDownWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i)));
wind += "\nFlags: " + String
.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF))
.replace(' ', '0');
windInfo.add(wind);
}
}
/**
* This packet conatins the average wind to ground speed
*
* @param packet The packet containing the paylaod
*/
private static void extractAvgWind(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11));
long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13));
long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15));
long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17));
long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19));
long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21));
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
}
private static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
*
* @return a positive long if there is less than 7 bytes -1 otherwise
*/
private static long bytesToLong(byte[] bytes) {
long partialLong = 0;
int index = 0;
for (byte b : bytes) {
if (index > 6) {
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
/**
* returns false if race not started, true otherwise
*
* @return race started status
*/
public static boolean isRaceStarted() {
return raceStarted;
}
/**
* returns false if stream not connected, true otherwise
*
* @return stream started status
*/
public static boolean isStreamStatus() {
return streamStatus;
}
/**
* returns race timer
*
* @return race timer in long
*/
public static long getTimeSinceStart() {
return timeSinceStart;
}
/**
* return false if race not finished, true otherwise
*
* @return race finished status
*/
public static boolean isRaceFinished() {
return raceFinished;
}
/**
* return a map of boats with sourceID and the boat
*
* @return map of boats
*/
public static Map<Integer, Yacht> getBoats() {
return boats;
}
/**
* returns the latest updated object from xml parser
*
* @return the latest xml object
*/
public static XMLParser getXmlObject() {
return xmlObject;
}
/**
* returns the wind direction in degrees
*
* @return a double wind direction value
*/
public static double getWindDirection() {
return windDirection;
}
/**
* Returns the wind speed in knots
* @return A double indicating the wind speed in knots
*/
public static Double getWindSpeed() {
return windSpeed;
}
/**
* returns stream time in formatted string format
*
* @return String of stream time
*/
public static String getCurrentTimeString() {
return currentTimeString;
}
/**
* used in boat position since tree map can sort position efficiently.
*
* @return a map of time to finish and boat.
*/
public static Map<Integer, Yacht> getBoatsPos() {
return boatsPos;
}
/**
* returns current time in stream in long
*
* @return a long value of current time
*/
public static Long getCurrentTimeLong() {
return currentTimeLong;
}
/**
* Used to check if a new un-processed xml has been found, if so will return true before
* toggling off so that the next check will return false.
*
* @return the status of if new xml has been received
*/
public static boolean isNewRaceXmlReceived() {
if (newRaceXmlReceived) {
newRaceXmlReceived = false;
return true;
} else {
return false;
}
}
}
@@ -0,0 +1,149 @@
package seng302.model.stream;
import seng302.model.stream.packets.StreamPacket;
import java.io.*;
import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
public class StreamReceiver extends Thread {
private InputStream inputStream;
private OutputStream outputStream;
private Socket host;
private ByteArrayOutputStream crcBuffer;
private Thread t;
private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
private static boolean moreBytes;
public StreamReceiver(String hostAddress, int hostPort, String threadName) {
this.threadName = threadName;
this.setDaemon(true);
try {
host = new Socket(hostAddress, hostPort);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void run(){
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
@Override
public int compare(StreamPacket s1, StreamPacket s2) {
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
}
});
packetBuffer = pq;
connect();
}
public void start () {
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
this.host=host;
this.packetBuffer = packetBuffer;
}
public void connect(){
// int sync1;
// int sync2;
// moreBytes = true;
// while(moreBytes) {
// try {
// crcBuffer = new ByteArrayOutputStream();
// sync1 = readByte();
// sync2 = readByte();
// //checking if it is the start of the packet
// if(sync1 == 0x47 && sync2 == 0x83) {
// int type = readByte();
// //No. of milliseconds since Jan 1st 1970
// long timeStamp = bytesToLong(getBytes(6));
// skipBytes(4);
// long payloadLength = bytesToLong(getBytes(2));
// byte[] payload = getBytes((int) payloadLength);
// Checksum checksum = new CRC32();
// checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
// long computedCrc = checksum.getValue();
// long packetCrc = bytesToLong(getBytes(4));
// if (computedCrc == packetCrc) {
// packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
// } else {
// System.err.println("Packet has been dropped");
// }
// }
// } catch (Exception e) {
// moreBytes = false;
// }
// }
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = inputStream.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++){
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception{
for (int i=0; i < n; i++){
readByte();
}
}
/**
* takes an array of up to 7 bytes in little endian format and
* returns a positive long constructed from the input bytes
*
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
private long bytesToLong(byte[] bytes){
long partialLong = 0;
int index = 0;
for (byte b: bytes){
if (index > 6){
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
public static void main(String[] args) {
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
sr.start();
}
public static void noMoreBytes(){
moreBytes = false;
}
}
@@ -0,0 +1,610 @@
package seng302.model.stream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.model.Yacht;
import seng302.model.mark.GateMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkType;
import seng302.model.mark.SingleMark;
/**
* Class to create an XML object from the XML Packet Messages.
*
* Example usage:
*
* Document doc; // some xml document
* Integer xmlMessageType; // an Integer of value 5, 6, 7
*
* xmlP = new XMLParser(doc, xmlMessageType);
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
*/
public class XMLParser {
private Document xmlDoc;
private RaceXMLObject raceXML;
private RegattaXMLObject regattaXML;
private BoatXMLObject boatXML;
public XMLParser() {
}
/**
* Constructor for XMLParser
*
* @param doc Document to create XML object.
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
*/
public void constructXML(Document doc, Integer messageType) {
this.xmlDoc = doc;
switch (messageType) {
case 5:
regattaXML = new RegattaXMLObject(this.xmlDoc);
break;
case 6:
raceXML = new RaceXMLObject(this.xmlDoc);
break;
case 7:
boatXML = new BoatXMLObject(this.xmlDoc);
break;
}
}
public RaceXMLObject getRaceXML() {
return raceXML;
}
public RegattaXMLObject getRegattaXML() {
return regattaXML;
}
public BoatXMLObject getBoatXML() {
return boatXML;
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an String.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static String getElementString(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return tagList.item(0).getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as a Double.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The String representation of the text content of an attribute in the given node, else
* returns null.
*/
private static String getNodeAttributeString(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return attrItem.getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Integer representation of the text content of an attribute in the given node,
* else returns null.
*/
private static Integer getNodeAttributeInt(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Integer.parseInt(attrItem.getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Double representation of the text content of an attribute in the given node, else
* returns null.
*/
private static Double getNodeAttributeDouble(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Double.parseDouble(attrItem.getTextContent());
} else {
return null;
}
}
public class RegattaXMLObject {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
/**
* Constructor for a RegattaXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
RegattaXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.regattaID = getElementInt(docEle, "RegattaID");
this.regattaName = getElementString(docEle, "RegattaName");
this.courseName = getElementString(docEle, "CourseName");
this.centralLat = getElementDouble(docEle, "CentralLatitude");
this.centralLng = getElementDouble(docEle, "CentralLongitude");
this.utcOffset = getElementInt(docEle, "UtcOffset");
}
public Integer getRegattaID() {
return regattaID;
}
public String getRegattaName() {
return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
}
public class RaceXMLObject {
// Race Info
private Integer raceID;
private String raceType;
private String creationTimeDate; // XML Creation Time
//Race Start Details
private String raceStartTime;
private Boolean postponeStatus;
//Non atomic race attributes
private ArrayList<Participant> participants;
private ArrayList<Mark> allMarks;
private ArrayList<Mark> nonDuplicateMarks;
private ArrayList<Corner> compoundMarkSequence;
private ArrayList<Limit> courseLimit;
// ensures there's no duplicate marks.
private List<Long> seenSourceIDs = new ArrayList<Long>();
/**
* Constructor for a RaceXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
RaceXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
//Atomic and Semi-Atomic Elements
this.raceID = getElementInt(docEle, "RaceID");
this.raceType = getElementString(docEle, "RaceType");
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
this.raceStartTime = getNodeAttributeString(raceStart, "Start");
this.postponeStatus = Boolean
.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
//Participants
participants = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
Node pNode = pList.item(i);
String entry;
if (pNode.getNodeName().equals("Yacht")) {
Integer sourceID = getNodeAttributeInt(pNode, "SourceID");
if (pNode.getAttributes().getLength() == 2) {
entry = getNodeAttributeString(pNode, "Entry");
} else {
entry = null;
}
Participant pa = new Participant(sourceID, entry);
participants.add(pa);
}
}
//Course
allMarks = new ArrayList<>();
nonDuplicateMarks = new ArrayList<>();
createCompoundMarks(docEle);
//Course Mark Sequence
compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0)
.getChildNodes();
for (int i = 0; i < cornerList.getLength(); i++) {
Node cornerNode = cornerList.item(i);
if (cornerNode.getNodeName().equals("Corner")) {
Corner corner = new Corner(cornerNode);
compoundMarkSequence.add(corner);
}
}
//Course Limits
courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
Limit limit = new Limit(limitNode);
courseLimit.add(limit);
}
}
}
private void createCompoundMarks(Element docEle) {
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
createAndAddMark(cMarkNode);
}
}
}
private void createAndAddMark(Node compoundMark) {
Boolean markSeen = false;
List<SingleMark> marksList = new ArrayList<>();
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes();
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer sourceID = getNodeAttributeInt(markNode, "SourceID");
String markName = getNodeAttributeString(markNode, "Name");
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
marksList.add(mark);
}
}
for (SingleMark mark : marksList) {
if (seenSourceIDs.contains(mark.getId())) {
markSeen = true;
} else {
seenSourceIDs.add(mark.getId());
}
}
if (marksList.size() == 1) {
if (!markSeen) {
nonDuplicateMarks.add(marksList.get(0));
}
allMarks.add(marksList.get(0));
} else if (marksList.size() == 2) {
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
marksList.get(1), marksList.get(0).getLatitude(),
marksList.get(0).getLongitude(), compoundMarkID);
if(!markSeen) {
nonDuplicateMarks.add(thisGateMark);
}
allMarks.add(thisGateMark);
}
}
public Integer getRaceID() {
return raceID;
}
public String getRaceType() {
return raceType;
}
public String getCreationTimeDate() {
return creationTimeDate;
}
public String getRaceStartTime() {
return raceStartTime;
}
public Boolean getPostponeStatus() {
return postponeStatus;
}
public ArrayList<Participant> getParticipants() {
return participants;
}
/**
* @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS)
*/
public List<Mark> getAllCompoundMarks() {
return allMarks;
}
/**
* @return Returns Marks from the race XML without any duplicates
*/
public List<Mark> getNonDupCompoundMarks() {
return nonDuplicateMarks;
}
public ArrayList<Corner> getCompoundMarkSequence() {
return compoundMarkSequence;
}
public ArrayList<Limit> getCourseLimit() {
return courseLimit;
}
public class Participant {
Integer sourceID;
String entry;
Participant(Integer sourceID, String entry) {
this.sourceID = sourceID;
this.entry = entry;
}
public Integer getsourceID() {
return sourceID;
}
public String getEntry() {
return entry;
}
}
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
Corner(Node cornerNode) {
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
}
public Integer getSeqID() {
return seqID;
}
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
}
public class Limit {
private Integer seqID;
private Double lat;
private Double lng;
Limit(Node limitNode) {
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
this.lat = getNodeAttributeDouble(limitNode, "Lat");
this.lng = getNodeAttributeDouble(limitNode, "Lon");
}
public Integer getSeqID() {
return seqID;
}
public Double getLat() {
return lat;
}
public Double getLng() {
return lng;
}
}
}
public class BoatXMLObject {
private String lastModified;
private Integer version;
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
private String boatType;
private Double boatLength;
private Double hullLength;
private Double markZoneSize;
private Double courseZoneSize;
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
//Boats
ArrayList<Yacht> boats;
//Competing boats
Map<Integer, Yacht> competingBoats = new HashMap<>();
/**
* Constructor for a BoatXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
BoatXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.lastModified = getElementString(docEle, "Modified");
this.version = getElementInt(docEle, "Version");
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
Node zoneLimitsList = settingsList.item(7);
this.zoneLimits = new ArrayList<>();
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
String tag = String.format("Limit%d", i + 1);
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
}
this.boats = new ArrayList<>();
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
for (int i = 0; i < boatsList.getLength(); i++) {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat);
Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
getNodeAttributeInt(currentBoat, "SourceID"),
getNodeAttributeString(currentBoat, "HullNum"),
getNodeAttributeString(currentBoat, "ShortName"),
getNodeAttributeString(currentBoat, "BoatName"),
getNodeAttributeString(currentBoat, "Country"));
this.boats.add(boat);
if (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat);
}
}
}
}
public String getLastModified() {
return lastModified;
}
public Integer getVersion() {
return version;
}
public String getBoatType() {
return boatType;
}
public Double getBoatLength() {
return boatLength;
}
public Double getHullLength() {
return hullLength;
}
public Double getMarkZoneSize() {
return markZoneSize;
}
public Double getCourseZoneSize() {
return courseZoneSize;
}
public ArrayList<Double> getZoneLimits() {
return zoneLimits;
}
public ArrayList<Yacht> getBoats() {
return boats;
}
public Map<Integer, Yacht> getCompetingBoats() {
return competingBoats;
}
}
}
@@ -0,0 +1,39 @@
package seng302.model.stream.packets;
public class BoatPositionPacket {
private long boatId;
private long timeValid;
private double lat;
private double lon;
private double heading;
private double groundSpeed;
public BoatPositionPacket(long boatId, long timeValid, double lat, double lon, double heading, double groundSpeed) {
this.boatId = boatId;
this.timeValid = timeValid;
this.lat = lat;
this.lon = lon;
this.heading = heading;
this.groundSpeed = groundSpeed;
}
public long getTimeValid() {
return timeValid;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getHeading() {
return heading;
}
public double getGroundSpeed() {
return groundSpeed;
}
}
@@ -0,0 +1,56 @@
package seng302.model.stream.packets;
import java.lang.reflect.Type;
/**
* Created by Kusal on 4/24/2017.
*/
public enum PacketType {
HEARTBEAT,
RACE_STATUS,
DISPLAY_TEXT_MESSAGE,
XML_MESSAGE,
RACE_START_STATUS,
YACHT_EVENT_CODE,
YACHT_ACTION_CODE,
CHATTER_TEXT,
BOAT_LOCATION,
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
BOAT_ACTION,
OTHER;
public static PacketType assignPacketType(int packetType){
switch(packetType){
case 1:
return HEARTBEAT;
case 12:
return RACE_STATUS;
case 20:
return DISPLAY_TEXT_MESSAGE;
case 26:
return XML_MESSAGE;
case 27:
return RACE_START_STATUS;
case 29:
return YACHT_EVENT_CODE;
case 31:
return YACHT_ACTION_CODE;
case 36:
return CHATTER_TEXT;
case 37:
return BOAT_LOCATION;
case 38:
return MARK_ROUNDING;
case 44:
return COURSE_WIND;
case 47:
return AVG_WIND;
case 100:
return BOAT_ACTION;
default:
}
return OTHER;
}
}
@@ -0,0 +1,37 @@
package seng302.model.stream.packets;
/**
* Created by kre39 on 23/04/17.
*/
public class StreamPacket {
//Change int to an ENUM for the type
private PacketType type;
private long messageLength;
private long timeStamp;
private byte[] payload;
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type);
this.messageLength = messageLength;
this.timeStamp = timeStamp;
this.payload = payload;
}
public PacketType getType() {
return type;
}
public long getMessageLength() {
return messageLength;
}
public byte[] getPayload() {
return payload;
}
public long getTimeStamp() {
return timeStamp;
}
}