LatestMessages can hold MarkRounding messages. It also holds XMLMessage for each message type.

MockOutput now uses LatestMessages for xml messages.
Moved xml message encoding into RaceVisionByteEncoder. Removed XMLMessageEncoder.
Moved XML message sub types into an enumeration (XMLMessageType).
XMLMessage can now be queried for its attributes (like timestamp, length, etc...).

VisualiserInput now uses LatestMessages.
VisualiserInput now (as it did previously) uses switch statements for checking packet type, instead of if statements.
VisualiserRace now uses LatestMessages instead of VisualiserInput.
This commit is contained in:
fjc40
2017-07-07 02:05:21 +12:00
parent 8e18ad62ca
commit f057ad58b7
20 changed files with 767 additions and 418 deletions
@@ -1,7 +1,16 @@
package visualiser.app;
import javafx.application.Platform;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.*;
import org.xml.sax.SAXException;
import shared.dataInput.BoatXMLReader;
import shared.dataInput.RaceXMLReader;
import shared.dataInput.RegattaXMLReader;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.DataInputStream;
@@ -12,78 +21,65 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static network.Utils.ByteConverter.bytesToShort;
/**
* TCP client which receives packets/messages from a race data source
* (e.g., mock source, official source), and exposes them to any observers.
*/
public class VisualiserInput implements Runnable {
///Timestamp of the last heartbeat.
/**
* Timestamp of the last heartbeat.
*/
private long lastHeartbeatTime = -1;
///Sequence number of the last heartbeat.
/**
* Sequence number of the last heartbeat.
*/
private long lastHeartbeatSequenceNum = -1;
///The socket that we have connected to.
/**
* The socket that we have connected to.
*/
private Socket connectionSocket;
///The last RaceStatus message received.
private RaceStatus raceStatus;
///A map of the last BoatStatus message received, for each boat.
private final Map<Integer, BoatStatus> boatStatusMap = new HashMap<>();
///A map of the last BoatLocation message received, for each boat.
private final Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
///The last AverageWind message received.
private AverageWind averageWind;
///The last CourseWinds message received.
private CourseWinds courseWinds;
///A map of the last MarkRounding message received, for each boat.
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
///InputStream (from the socket).
/**
* InputStream (from the socket).
*/
private DataInputStream inStream;
/**
* An object containing the set of latest messages to write to.
* Every server frame, VisualiserInput reads messages from its inputStream, and write them to this.
*/
private LatestMessages latestMessages;
/**
* Ctor.
* @param socket Socket from which we will receive race data.
* @param course TODO comment?
* @throws IOException If there is something wrong with the socket's input stream.
*/
public VisualiserInput(Socket socket, StreamedCourse course) throws IOException {
public VisualiserInput(Socket socket) throws IOException {
this.connectionSocket = socket;
//We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled.
this.inStream = new DataInputStream(connectionSocket.getInputStream());
this.course = course;
this.latestMessages = new LatestMessages();
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Provides StreamedCourse container for fixed course data.
* @return Course for current VisualiserInput instance.
* @see seng302.Mock.StreamedCourse
*/
public StreamedCourse getCourse() {
return course;
}
/**
* Returns the last boat location message associated with the given boat source ID.
* @param sourceID Unique global identifier for the boat.
* @return The most recent location message.
*/
public BoatLocation getBoatLocationMessage(int sourceID) {
return boatLocationMap.get(sourceID);
}
public BoatStatus getBoatStatusMessage(int sourceID) {
return boatStatusMap.get(sourceID);
}
/**
* Calculates the time since last heartbeat, in milliseconds.
@@ -94,62 +90,7 @@ public class VisualiserInput implements Runnable {
return (now - lastHeartbeatTime);
}
/**
* Returns the boat locations map. Maps from Integer (Boat ID) to BoatLocation.
* @return Map of boat locations.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Gets the status of the race.
* @return The status of the race.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Returns the boat statuses map. Maps from Integer (Boat ID) to BoatStatus.
* @return Map of boat statuses.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the average wind of the race.
* @return Average wind in the race.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Returns winds in the course.
* @return Winds that are in the course.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Returns the mark roundings map. Maps from Integer (Boat ID) to MarkRounding.
* @return Map of mark roundings.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Sets the wind direction for the current course.
* @param direction The new wind direction for the course.
*/
private void setCourseWindDirection(double direction) {
this.course.setWindDirection(direction);
}
/**
* Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead.
@@ -258,166 +199,153 @@ public class VisualiserInput implements Runnable {
}
//Continue to the next loop iteration/message.
continue;
}/*
//Add it to message queue.
this.messagesReceivedQueue.add(message);*/
}
//Checks which message is being received and does what is needed for that message.
//Heartbeat.
if (message instanceof Heartbeat) {
Heartbeat heartbeat = (Heartbeat) message;
switch (message.getType()) {
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
}
//RaceStatus.
else if (message instanceof RaceStatus) {
RaceStatus raceStatus = (RaceStatus) message;
//Heartbeat.
case HEARTBEAT: {
Heartbeat heartbeat = (Heartbeat) message;
//System.out.println("Race Status Message");
this.raceStatus = raceStatus;
for (BoatStatus boatStatus: this.raceStatus.getBoatStatuses()) {
this.boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
}
setCourseWindDirection(raceStatus.getScaledWindDirection());
}
//DisplayTextMessage.
/*else if (message instanceof DisplayTextMessage) {
//System.out.println("Display Text Message");
//No decoder for this.
}*/
//XMLMessage.
else if (message instanceof XMLMessage) {
XMLMessage xmlMessage = (XMLMessage) message;
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
}
//System.out.println("XML Message!");
//RaceStatus.
case RACESTATUS: {
RaceStatus raceStatus = (RaceStatus) message;
Platform.runLater(()-> {
if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRegatta) {
//System.out.println("Setting Regatta");
try {
course.setRegattaXMLReader(new RegattaXMLReader(xmlMessage.getXmlMessage()));
//System.out.println("Race Status Message");
this.latestMessages.setRaceStatus(raceStatus);
}
//TODO REFACTOR should put all of these exceptions behind a RegattaXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating RegattaXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) {
this.latestMessages.setBoatStatus(boatStatus);
}
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRace) {
//System.out.println("Setting Course");
try {
course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a StreamedCourseXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException | StreamedCourseXMLException e) {
System.err.println("Error creating StreamedCourseXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
}
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeBoat) {
//System.out.println("Setting Boats");
try {
course.setBoatXMLReader(new BoatXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a BoatXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating BoatXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
//DisplayTextMessage.
case DISPLAYTEXTMESSAGE: {
//System.out.println("Display Text Message");
//No decoder for this.
}
}
});
}
//RaceStartStatus.
else if (message instanceof RaceStartStatus) {
//XMLMessage.
case XMLMESSAGE: {
XMLMessage xmlMessage = (XMLMessage) message;
//System.out.println("Race Start Status Message");
}
//YachtEventCode.
/*else if (message instanceof YachtEventCode) {
YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("XML Message!");
this.latestMessages.setXMLMessage(xmlMessage);
}
//RaceStartStatus.
case RACESTARTSTATUS: {
//System.out.println("Race Start Status Message");
}
//YachtEventCode.
case YACHTEVENTCODE: {
//YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("Yacht Event Code!");
//No decoder for this.
}*/
//YachtActionCode.
/*else if (message instanceof YachtActionCode) {
YachtActionCode yachtActionCode = (YachtActionCode) message;
}
//System.out.println("Yacht Action Code!");
//No decoder for this.
//YachtActionCode.
case YACHTACTIONCODE: {
//YachtActionCode yachtActionCode = (YachtActionCode) message;
}*/
//ChatterText.
/*else if (message instanceof ChatterText) {
ChatterText chatterText = (ChatterText) message;
//System.out.println("Yacht Action Code!");
// No decoder for this.
//System.out.println("Chatter Text Message!");
//No decoder for this.
}
}*/
//BoatLocation.
else if (message instanceof BoatLocation) {
BoatLocation boatLocation = (BoatLocation) message;
//ChatterText.
case CHATTERTEXT: {
//ChatterText chatterText = (ChatterText) message;
//System.out.println("Boat Location!");
if (this.boatLocationMap.containsKey(boatLocation.getSourceID())) {
//If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (boatLocation.getTime() > this.boatLocationMap.get(boatLocation.getSourceID()).getTime()){
//If it is, replace the old message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}
//MarkRounding.
else if (message instanceof MarkRounding) {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Chatter Text Message!");
//No decoder for this.
//System.out.println("Mark Rounding Message!");
}
if (this.markRoundingMap.containsKey(markRounding.getSourceID())) {
//If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (markRounding.getTime() > this.markRoundingMap.get(markRounding.getSourceID()).getTime()){
//If it is, replace the old message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
//BoatLocation.
case BOATLOCATION: {
BoatLocation boatLocation = (BoatLocation) message;
}
//CourseWinds.
else if (message instanceof CourseWinds) {
//System.out.println("Boat Location!");
//System.out.println("Course Wind Message!");
this.courseWinds = (CourseWinds) message;
BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID());
if (existingBoatLocation != null) {
//If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (boatLocation.getTime() > existingBoatLocation.getTime()) {
//If it is, replace the old message.
this.latestMessages.setBoatLocation(boatLocation);
}
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.latestMessages.setBoatLocation(boatLocation);
}
}
}
//AverageWind.
else if (message instanceof AverageWind) {
//MarkRounding.
case MARKROUNDING: {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Average Wind Message!");
this.averageWind = (AverageWind) message;
//System.out.println("Mark Rounding Message!");
}
//Unrecognised message.
else {
System.out.println("Broken Message!");
}
MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID());
if (existingMarkRounding != null) {
//If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (markRounding.getTime() > existingMarkRounding.getTime()) {
//If it is, replace the old message.
this.latestMessages.setMarkRounding(markRounding);
}
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.latestMessages.setMarkRounding(markRounding);
}
}
//CourseWinds.
case COURSEWIND: {
//System.out.println("Course Wind Message!");
CourseWinds courseWinds = (CourseWinds) message;
this.latestMessages.setCourseWinds(courseWinds);
}
//AverageWind.
case AVGWIND: {
//System.out.println("Average Wind Message!");
AverageWind averageWind = (AverageWind) message;
this.latestMessages.setAverageWind(averageWind);
}
//Unrecognised message.
default: {
System.out.println("Broken Message!");
}
}
}
}