@ -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.
private Socket connectionSocket ;
/ * *
* 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 < > ( ) ;
/ * *
* InputStream ( from the socket ) .
* /
private DataInputStream inStream ;
///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 ;
/ * *
* 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 ;
///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).
private DataInputStream inStream ;
/ * *
* 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 ;
//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 ;
//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 ;
//System.out.println("XML Message!");
Platform . runLater ( ( ) - > {
if ( xmlMessage . getXmlMsgSubType ( ) = = XMLMessage . XMLTypeRegatta ) {
//System.out.println("Setting Regatta");
try {
course . setRegattaXMLReader ( new RegattaXMLReader ( xmlMessage . getXmlMessage ( ) ) ) ;
}
//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.
}
} 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.
}
}
} ) ;
}
//RaceStartStatus.
else if ( message instanceof RaceStartStatus ) {
switch ( message . getType ( ) ) {
//System.out.println("Race Start Status Message");
}
//YachtEventCode.
/ * else if ( message instanceof YachtEventCode ) {
YachtEventCode yachtEventCode = ( YachtEventCode ) message ;
//Heartbeat.
case HEARTBEAT : {
Heartbeat heartbeat = ( Heartbeat ) 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);
}
}
//RaceStatus.
case RACESTATUS : {
RaceStatus raceStatus = ( RaceStatus ) message ;
//System.out.println("Race Status Message");
this . latestMessages . setRaceStatus ( raceStatus ) ;
for ( BoatStatus boatStatus : raceStatus . getBoatStatuses ( ) ) {
this . latestMessages . setBoatStatus ( boatStatus ) ;
}
}
//DisplayTextMessage.
case DISPLAYTEXTMESSAGE : {
//System.out.println("Display Text Message");
//No decoder for this.
}
//XMLMessage.
case XMLMESSAGE : {
XMLMessage xmlMessage = ( XMLMessage ) 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 ;
//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("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 ) ;
}
//ChatterText.
case CHATTERTEXT : {
//ChatterText chatterText = (ChatterText) message;
}
//CourseWinds.
else if ( message instanceof CourseWinds ) {
//System.out.println("Chatter Text Message!");
//No decoder for this.
//System.out.println("Course Wind Message!");
this . courseWinds = ( CourseWinds ) message ;
}
}
//AverageWind.
else if ( message instanceof AverageWind ) {
//BoatLocation.
case BOATLOCATION : {
BoatLocation boatLocation = ( BoatLocation ) message ;
//System.out.println("Boat Location!");
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 ) ;
}
}
//System.out.println("Average Wind Message!");
this . averageWind = ( AverageWind ) message ;
//MarkRounding.
case MARKROUNDING : {
MarkRounding markRounding = ( MarkRounding ) message ;
}
//Unrecognised message.
else {
System . out . println ( "Broken Message!" ) ;
}
//System.out.println("Mark Rounding 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!" ) ;
}
}
}
}