# Conflicts: # visualiser/src/main/java/seng302/Model/ResizableRaceMap.java #story[1087]main
commit
956ad1f59b
@ -1,3 +0,0 @@
|
|||||||
<component name="CopyrightManager">
|
|
||||||
<settings default="" />
|
|
||||||
</component>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="KotlinCommonCompilerArguments">
|
|
||||||
<option name="languageVersion" value="1.1" />
|
|
||||||
<option name="apiVersion" value="1.1" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import seng302.DataInput.PolarParser;
|
|
||||||
import seng302.DataInput.XMLReader;
|
|
||||||
import seng302.Model.Event;
|
|
||||||
import seng302.Model.Polars;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class App extends Application {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entry point for running the programme
|
|
||||||
*
|
|
||||||
* @param args for starting the programme
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
|
||||||
launch(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Stage primaryStage) {
|
|
||||||
try {
|
|
||||||
Polars boatPolars = PolarParser.parse("polars/acc_polars.csv");
|
|
||||||
|
|
||||||
String regattaXML = readFile("mockXML/regattaTest.xml", StandardCharsets.UTF_8);
|
|
||||||
String raceXML = readFile("mockXML/raceTest.xml", StandardCharsets.UTF_8);
|
|
||||||
String boatXML = readFile("mockXML/boatTest.xml", StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
Event raceEvent = new Event(raceXML, regattaXML, boatXML, boatPolars);
|
|
||||||
raceEvent.start();
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
//Catch all exceptions, print, and exit.
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the Initial Race XML files that are necessary to run the mock.
|
|
||||||
* @param path path of the XML
|
|
||||||
* @param encoding encoding of the xml
|
|
||||||
* @return
|
|
||||||
* @throws IOException No file etc
|
|
||||||
* @throws ParserConfigurationException Issue with the XML formatting
|
|
||||||
* @throws SAXException Issue with XML formatting
|
|
||||||
* @throws TransformerException Issue with the XML format
|
|
||||||
*/
|
|
||||||
private String readFile(String path, Charset encoding) throws IOException, ParserConfigurationException, SAXException, TransformerException {
|
|
||||||
|
|
||||||
InputSource fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(path));
|
|
||||||
|
|
||||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
|
||||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
|
||||||
Document doc = dBuilder.parse(fXmlFile);
|
|
||||||
doc.getDocumentElement().normalize();
|
|
||||||
|
|
||||||
return XMLReader.getContents(doc);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package seng302.DataInput;
|
|
||||||
|
|
||||||
import seng302.Model.Boat;
|
|
||||||
import seng302.Model.Mark;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boats Data
|
|
||||||
*/
|
|
||||||
public interface BoatDataSource {
|
|
||||||
Map<Integer, Boat> getBoats();
|
|
||||||
Map<Integer, Mark> getMarkerBoats();
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package seng302.DataInput;
|
|
||||||
|
|
||||||
import seng302.Model.Boat;
|
|
||||||
import seng302.Model.CompoundMark;
|
|
||||||
import seng302.Model.GPSCoordinate;
|
|
||||||
import seng302.Model.Leg;
|
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data Class for a Race
|
|
||||||
*/
|
|
||||||
public interface RaceDataSource {
|
|
||||||
List<Boat> getBoats();
|
|
||||||
|
|
||||||
List<Leg> getLegs();
|
|
||||||
|
|
||||||
List<GPSCoordinate> getBoundary();
|
|
||||||
|
|
||||||
List<CompoundMark> getCompoundMarks();
|
|
||||||
|
|
||||||
int getRaceId();
|
|
||||||
|
|
||||||
String getRaceType();
|
|
||||||
|
|
||||||
ZonedDateTime getZonedDateTime();
|
|
||||||
|
|
||||||
GPSCoordinate getMapTopLeft();
|
|
||||||
|
|
||||||
GPSCoordinate getMapBottomRight();
|
|
||||||
}
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
package seng302.DataInput;
|
|
||||||
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.NamedNodeMap;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import seng302.Exceptions.StreamedCourseXMLException;
|
|
||||||
import seng302.Model.*;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XML Reader that reads in the race data required for this race
|
|
||||||
*/
|
|
||||||
public class RaceXMLReader extends XMLReader implements RaceDataSource {
|
|
||||||
private static final double COORDINATEPADDING = 0.000;
|
|
||||||
private GPSCoordinate mapTopLeft, mapBottomRight;
|
|
||||||
private final List<GPSCoordinate> boundary = new ArrayList<>();
|
|
||||||
private final Map<Integer,Element> compoundMarkMap = new HashMap<>();
|
|
||||||
private final Map<Integer, Boat> participants = new HashMap<>();
|
|
||||||
private final List<Leg> legs = new ArrayList<>();
|
|
||||||
private final List<CompoundMark> compoundMarks = new ArrayList<>();
|
|
||||||
private ZonedDateTime creationTimeDate;
|
|
||||||
private ZonedDateTime raceStartTime;
|
|
||||||
private int raceID;
|
|
||||||
private String raceType;
|
|
||||||
private boolean postpone;
|
|
||||||
|
|
||||||
private Map<Integer, Boat> boats;
|
|
||||||
private Map<Integer, Mark> marks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for Streamed Race XML
|
|
||||||
* @param filePath path of the file
|
|
||||||
* @param boatData data for boats in race
|
|
||||||
* @throws IOException error
|
|
||||||
* @throws SAXException error
|
|
||||||
* @throws ParserConfigurationException error
|
|
||||||
* @throws ParseException error
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
public RaceXMLReader(String filePath, BoatDataSource boatData) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
|
|
||||||
this(filePath, boatData, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for Streamed Race XML
|
|
||||||
* @param filePath file path to read
|
|
||||||
* @param boatData data of the boats in race
|
|
||||||
* @param read whether or not to read and store the files straight away.
|
|
||||||
* @throws IOException error
|
|
||||||
* @throws SAXException error
|
|
||||||
* @throws ParserConfigurationException error
|
|
||||||
* @throws ParseException error
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
public RaceXMLReader(String filePath, BoatDataSource boatData, boolean read) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
|
|
||||||
super(filePath);
|
|
||||||
this.boats = boatData.getBoats();
|
|
||||||
this.marks = boatData.getMarkerBoats();
|
|
||||||
if (read) {
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
private void read() throws StreamedCourseXMLException {
|
|
||||||
readRace();
|
|
||||||
readParticipants();
|
|
||||||
readCourse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads a race
|
|
||||||
*/
|
|
||||||
private void readRace() {
|
|
||||||
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
|
||||||
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
|
|
||||||
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
|
|
||||||
|
|
||||||
if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
|
||||||
|
|
||||||
|
|
||||||
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
|
|
||||||
raceType = getTextValueOfNode(settings, "RaceType");
|
|
||||||
|
|
||||||
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
|
|
||||||
|
|
||||||
if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
|
|
||||||
else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
|
|
||||||
|
|
||||||
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads in the participants for htis race
|
|
||||||
*/
|
|
||||||
private void readParticipants() {
|
|
||||||
Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0);
|
|
||||||
nParticipants.getChildNodes().getLength();
|
|
||||||
for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) {
|
|
||||||
int sourceID;
|
|
||||||
Node yacht = nParticipants.getChildNodes().item(i);
|
|
||||||
if (yacht.getNodeName().equals("Yacht")) {
|
|
||||||
if (exists(yacht, "SourceID")) {
|
|
||||||
sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
|
|
||||||
participants.put(sourceID, boats.get(sourceID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads a course
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
private void readCourse() throws StreamedCourseXMLException {
|
|
||||||
readCompoundMarks();
|
|
||||||
readCompoundMarkSequence();
|
|
||||||
readCourseLimit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
|
|
||||||
* @see CompoundMark
|
|
||||||
*/
|
|
||||||
private void readCompoundMarks() throws StreamedCourseXMLException {
|
|
||||||
Element nCourse = (Element) doc.getElementsByTagName("Course").item(0);
|
|
||||||
for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) {
|
|
||||||
Node compoundMark = nCourse.getChildNodes().item(i);
|
|
||||||
if(compoundMark.getNodeName().equals("CompoundMark")) {
|
|
||||||
int compoundMarkID = getCompoundMarkID((Element) compoundMark);
|
|
||||||
compoundMarkMap.put(compoundMarkID, (Element)compoundMark);
|
|
||||||
compoundMarks.add(getCompoundMark(compoundMarkID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a CompoundMark from the CompoundMark element with given ID.
|
|
||||||
* @param compoundMarkID index of required CompoundMark element
|
|
||||||
* @return generated CompoundMark
|
|
||||||
* @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks
|
|
||||||
* @see CompoundMark
|
|
||||||
*/
|
|
||||||
private CompoundMark getCompoundMark(int compoundMarkID) throws StreamedCourseXMLException {
|
|
||||||
Element compoundMark = compoundMarkMap.get(compoundMarkID);
|
|
||||||
NodeList nMarks = compoundMark.getElementsByTagName("Mark");
|
|
||||||
CompoundMark marker;
|
|
||||||
|
|
||||||
switch(nMarks.getLength()) {
|
|
||||||
case 1: marker = new CompoundMark(getMark((Element)nMarks.item(0)));
|
|
||||||
break;
|
|
||||||
case 2: marker = new CompoundMark(getMark((Element)nMarks.item(0)), getMark((Element)nMarks.item(1))); break;
|
|
||||||
default: throw new StreamedCourseXMLException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a mark from an Element
|
|
||||||
* @param mark Element the mark is suppose to be part of
|
|
||||||
* @return a Mark that existed in the element
|
|
||||||
*/
|
|
||||||
private Mark getMark(Element mark) {
|
|
||||||
int sourceID = Integer.parseInt(mark.getAttribute("SourceID"));
|
|
||||||
return marks.get(sourceID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads "compoundMarkID" attribute of CompoundMark or Corner element
|
|
||||||
* @param element with "compoundMarkID" attribute
|
|
||||||
* @return value of "compoundMarkID" attribute
|
|
||||||
*/
|
|
||||||
private int getCompoundMarkID(Element element) {
|
|
||||||
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID
|
|
||||||
* @param compoundMarkID unique ID for CompoundMark element
|
|
||||||
* @return value of "name" attribute
|
|
||||||
*/
|
|
||||||
private String getCompoundMarkName(int compoundMarkID) {
|
|
||||||
return compoundMarkMap.get(compoundMarkID).getAttribute("Name");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
|
|
||||||
* @throws StreamedCourseXMLException if compoundMarks cannot be resolved from CompoundMark
|
|
||||||
*/
|
|
||||||
private void readCompoundMarkSequence() throws StreamedCourseXMLException {
|
|
||||||
Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
|
|
||||||
NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner");
|
|
||||||
Element markXML = (Element)nCorners.item(0);
|
|
||||||
CompoundMark lastCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
|
|
||||||
String legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
|
||||||
for(int i = 1; i < nCorners.getLength(); i++) {
|
|
||||||
markXML = (Element)nCorners.item(i);
|
|
||||||
CompoundMark currentCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
|
|
||||||
legs.add(new Leg(legName, lastCompoundMark, currentCompoundMark, i-1));
|
|
||||||
lastCompoundMark = currentCompoundMark;
|
|
||||||
legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the boundary limits of the course
|
|
||||||
*/
|
|
||||||
private void readCourseLimit() {
|
|
||||||
Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
|
|
||||||
for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) {
|
|
||||||
Node limit = nCourseLimit.getChildNodes().item(i);
|
|
||||||
if (limit.getNodeName().equals("Limit")) {
|
|
||||||
double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent());
|
|
||||||
double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent());
|
|
||||||
boundary.add(new GPSCoordinate(lat, lon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
|
||||||
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
|
||||||
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
|
||||||
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
|
||||||
|
|
||||||
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
|
|
||||||
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GPSCoordinate> getBoundary() {
|
|
||||||
return boundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapTopLeft() {
|
|
||||||
return mapTopLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapBottomRight() {
|
|
||||||
return mapBottomRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Leg> getLegs() {
|
|
||||||
return legs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CompoundMark> getCompoundMarks() { return compoundMarks; }
|
|
||||||
|
|
||||||
public Double getPadding() {
|
|
||||||
return COORDINATEPADDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZonedDateTime getCreationTimeDate() {
|
|
||||||
return creationTimeDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZonedDateTime getZonedDateTime() {
|
|
||||||
return raceStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRaceId() {
|
|
||||||
return raceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRaceType() {
|
|
||||||
return raceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPostpone() {
|
|
||||||
return postpone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Boat> getBoats() {
|
|
||||||
return new ArrayList<>(participants.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
package seng302.DataInput;
|
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.transform.*;
|
|
||||||
import javax.xml.transform.dom.DOMSource;
|
|
||||||
import javax.xml.transform.stream.StreamResult;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base Reader for XML Files
|
|
||||||
*/
|
|
||||||
public abstract class XMLReader {
|
|
||||||
|
|
||||||
protected Document doc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read in XML file
|
|
||||||
* @param filePath filepath for XML file
|
|
||||||
* @throws ParserConfigurationException If a document builder cannot be created.
|
|
||||||
* @throws IOException If any IO errors occur while parsing the XML file.
|
|
||||||
* @throws SAXException If any parse error occurs while parsing.
|
|
||||||
*/
|
|
||||||
public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException {
|
|
||||||
|
|
||||||
InputSource fXmlFile;
|
|
||||||
if (filePath.contains("<")) {
|
|
||||||
fXmlFile = new InputSource();
|
|
||||||
fXmlFile.setCharacterStream(new StringReader(filePath));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
|
||||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
|
||||||
doc = dBuilder.parse(fXmlFile);
|
|
||||||
doc.getDocumentElement().normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternate constructor
|
|
||||||
* @param xmlFile File to be read
|
|
||||||
* @param isWholeFile boolean value whether entire file is being passed
|
|
||||||
*/
|
|
||||||
public XMLReader(String xmlFile, Boolean isWholeFile) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return Document data of the read-in XML
|
|
||||||
* @return XML document
|
|
||||||
*/
|
|
||||||
public Document getDocument() {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get content of a tag in an element
|
|
||||||
* @param n Element to read tags from
|
|
||||||
* @param tagName Name of the tag
|
|
||||||
* @return Content of the tag
|
|
||||||
*/
|
|
||||||
public String getTextValueOfNode(Element n, String tagName) {
|
|
||||||
return n.getElementsByTagName(tagName).item(0).getTextContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get attributes for an element
|
|
||||||
* @param n Element to read attributes from
|
|
||||||
* @param attr Attributes of element
|
|
||||||
* @return Attributes of element
|
|
||||||
*/
|
|
||||||
public String getAttribute(Element n, String attr) {
|
|
||||||
return n.getAttribute(attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean exists(Node node, String attribute) {
|
|
||||||
return node.getAttributes().getNamedItem(attribute) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the contents of the XML FILe.
|
|
||||||
* @param document holds all xml information
|
|
||||||
* @return String representation of document
|
|
||||||
* @throws TransformerException when document is malformed, and cannot be turned into a string
|
|
||||||
*/
|
|
||||||
public static String getContents(Document document) throws TransformerException {
|
|
||||||
DOMSource source = new DOMSource(document);
|
|
||||||
|
|
||||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
|
||||||
Transformer transformer = transformerFactory.newTransformer();
|
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
|
||||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
|
||||||
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
StreamResult result = new StreamResult(stringWriter);
|
|
||||||
transformer.transform(source, result);
|
|
||||||
|
|
||||||
return stringWriter.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package seng302.Exceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception thrown when we cannot generate Boats.xml and send an XML message.
|
|
||||||
*/
|
|
||||||
public class InvalidBoatDataException extends RuntimeException {
|
|
||||||
|
|
||||||
public InvalidBoatDataException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidBoatDataException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package seng302.Exceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when we cannot generate Race.xml data, and send an XML message.
|
|
||||||
*/
|
|
||||||
public class InvalidRaceDataException extends RuntimeException {
|
|
||||||
public InvalidRaceDataException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidRaceDataException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package seng302.Exceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cbt24 on 25/04/17.
|
|
||||||
*/
|
|
||||||
public class StreamedCourseXMLException extends Throwable {
|
|
||||||
}
|
|
||||||
@ -1,251 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
|
|
||||||
import seng302.Networking.BinaryMessageEncoder;
|
|
||||||
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
|
|
||||||
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
|
|
||||||
import seng302.Networking.Messages.BoatLocation;
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
import seng302.Networking.Messages.RaceStatus;
|
|
||||||
import seng302.Networking.Messages.XMLMessage;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.*;
|
|
||||||
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TCP server to send race information to connected clients.
|
|
||||||
*/
|
|
||||||
public class MockOutput implements Runnable
|
|
||||||
{
|
|
||||||
///Timestamp of the last sent heartbeat message.
|
|
||||||
private long lastHeartbeatTime;
|
|
||||||
|
|
||||||
///Period for the heartbeat - that is, how often we send it.
|
|
||||||
private double heartbeatPeriod = 5.0;
|
|
||||||
|
|
||||||
///Port to expose server on.
|
|
||||||
private int serverPort = 4942;
|
|
||||||
///Socket used to listen for clients on.
|
|
||||||
private ServerSocket serverSocket;
|
|
||||||
///Socket used to communicate with a client.
|
|
||||||
private Socket mockSocket;
|
|
||||||
///Output stream which wraps around mockSocket outstream.
|
|
||||||
private DataOutputStream outToVisualiser;
|
|
||||||
|
|
||||||
///A queue that contains items that are waiting to be sent.
|
|
||||||
private ArrayBlockingQueue<byte[]> messagesToSendQueue = new ArrayBlockingQueue<>(99999999);
|
|
||||||
|
|
||||||
///Sequence numbers used in messages.
|
|
||||||
private short messageNumber = 1;
|
|
||||||
private short xmlSequenceNumber = 1;
|
|
||||||
private int heartbeatSequenceNum = 1;
|
|
||||||
private int boatLocationSequenceNumber = 1;
|
|
||||||
private int raceStatusSequenceNumber = 1;
|
|
||||||
|
|
||||||
///Strings containing XML data as strings.
|
|
||||||
private String raceXml;
|
|
||||||
private String regattaXml;
|
|
||||||
private String boatsXml;
|
|
||||||
|
|
||||||
private boolean stop = false; //whether or not hte thread keeps running
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ctor.
|
|
||||||
* @throws IOException if server socket cannot be opened.
|
|
||||||
*/
|
|
||||||
public MockOutput() throws IOException {
|
|
||||||
lastHeartbeatTime = System.currentTimeMillis();
|
|
||||||
serverSocket = new ServerSocket(serverPort);
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Calculates the time since last heartbeat message, in seconds.
|
|
||||||
* @return Time since last heartbeat message, in seconds.
|
|
||||||
*/
|
|
||||||
private double timeSinceHeartbeat() {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
return (now - lastHeartbeatTime) / 1000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//returns the heartbeat message
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the heartbeat value
|
|
||||||
* @return message for heartbeat data
|
|
||||||
*/
|
|
||||||
private byte[] heartbeat(){
|
|
||||||
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum);
|
|
||||||
heartbeatSequenceNum++;
|
|
||||||
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage);
|
|
||||||
messageNumber++;
|
|
||||||
return binaryMessageEncoder.getFullMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to give the mockOutput an xml string to be made into a message and sent
|
|
||||||
* @param xmlString the xml string to send
|
|
||||||
* @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat)
|
|
||||||
*/
|
|
||||||
public synchronized void parseXMLString(String xmlString, int messageType){
|
|
||||||
XMLMessageEncoder encoder = new XMLMessageEncoder(messageNumber, System.currentTimeMillis(), messageType, xmlSequenceNumber,(short) xmlString.length(), xmlString);
|
|
||||||
//iterates the sequence numbers
|
|
||||||
xmlSequenceNumber++;
|
|
||||||
byte[] encodedXML = encoder.encode();
|
|
||||||
|
|
||||||
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.XMLMESSAGE, System.currentTimeMillis(), messageNumber, (short)encodedXML.length, encodedXML);
|
|
||||||
//iterates the message number
|
|
||||||
messageNumber++;
|
|
||||||
|
|
||||||
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to give the mocOutput information about boat location to be made into a message and sent
|
|
||||||
* @param sourceID id of the boat
|
|
||||||
* @param lat latitude of boat
|
|
||||||
* @param lon longitude of boat
|
|
||||||
* @param heading heading of boat
|
|
||||||
* @param speed speed of boat
|
|
||||||
* @param time historical time of race
|
|
||||||
*/
|
|
||||||
public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){
|
|
||||||
|
|
||||||
BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time);
|
|
||||||
//iterates the sequence number
|
|
||||||
boatLocationSequenceNumber++;
|
|
||||||
|
|
||||||
//encodeds the messages
|
|
||||||
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
|
|
||||||
|
|
||||||
//encodeds the full message with header
|
|
||||||
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length,
|
|
||||||
encodedBoatLoc);
|
|
||||||
|
|
||||||
//iterates the message number
|
|
||||||
messageNumber++;
|
|
||||||
|
|
||||||
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the race status data and add it to the buffer to be sent
|
|
||||||
* @param raceStatus race status to parses
|
|
||||||
*/
|
|
||||||
public synchronized void parseRaceStatus(RaceStatus raceStatus){
|
|
||||||
|
|
||||||
//iterates the sequence number
|
|
||||||
raceStatusSequenceNumber++;
|
|
||||||
|
|
||||||
//encodeds the messages
|
|
||||||
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
|
|
||||||
|
|
||||||
//encodeds the full message with header
|
|
||||||
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.RACESTATUS, System.currentTimeMillis(), messageNumber, (short)encodedRaceStatus.length,
|
|
||||||
encodedRaceStatus);
|
|
||||||
|
|
||||||
//iterates the message number
|
|
||||||
messageNumber++;
|
|
||||||
|
|
||||||
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a message to the buffer to be sent
|
|
||||||
* @param messagesToSendBuffer message to add to the buffer
|
|
||||||
*/
|
|
||||||
private synchronized void addMessageToBufferToSend(byte[] messagesToSendBuffer) {
|
|
||||||
this.messagesToSendQueue.add(messagesToSendBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sending loop of the Server
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!stop){
|
|
||||||
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
|
|
||||||
mockSocket = serverSocket.accept();
|
|
||||||
|
|
||||||
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
|
|
||||||
|
|
||||||
if (boatsXml == null || regattaXml == null || raceXml == null){
|
|
||||||
try {
|
|
||||||
Thread.sleep(500);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
parseXMLString(raceXml, XMLMessage.XMLTypeRace);
|
|
||||||
parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta);
|
|
||||||
parseXMLString(boatsXml, XMLMessage.XMLTypeBoat);
|
|
||||||
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
try {
|
|
||||||
//Sends a heartbeat every so often.
|
|
||||||
if (timeSinceHeartbeat() >= heartbeatPeriod) {
|
|
||||||
outToVisualiser.write(heartbeat());
|
|
||||||
lastHeartbeatTime = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Checks the buffer to see if there is anything to send.
|
|
||||||
while (messagesToSendQueue.size() > 0) {
|
|
||||||
//Grabs message from head of queue.
|
|
||||||
byte[] binaryMessage = messagesToSendQueue.remove();
|
|
||||||
|
|
||||||
//sends the message to the visualiser
|
|
||||||
outToVisualiser.write(binaryMessage);
|
|
||||||
|
|
||||||
}
|
|
||||||
}catch(SocketException e){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop(){
|
|
||||||
stop = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Race XML to send
|
|
||||||
* @param raceXml XML to send to the CLient
|
|
||||||
*/
|
|
||||||
public void setRaceXml(String raceXml) {
|
|
||||||
this.raceXml = raceXml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Regatta XMl to send
|
|
||||||
* @param regattaXml XML to send to CLient
|
|
||||||
*/
|
|
||||||
public void setRegattaXml(String regattaXml) {
|
|
||||||
this.regattaXml = regattaXml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Boats XML to send
|
|
||||||
* @param boatsXml XMl to send to the CLient
|
|
||||||
*/
|
|
||||||
public void setBoatsXml(String boatsXml) {
|
|
||||||
this.boatsXml = boatsXml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String argv[]) throws Exception
|
|
||||||
{
|
|
||||||
MockOutput client = new MockOutput();
|
|
||||||
client.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
package seng302.Model;
|
|
||||||
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import seng302.DataInput.*;
|
|
||||||
import seng302.Exceptions.StreamedCourseXMLException;
|
|
||||||
import seng302.MockOutput;
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import java.io.*;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Race Event, this holds all of the race's information as well as handling the connection to its clients.
|
|
||||||
*/
|
|
||||||
public class Event {
|
|
||||||
|
|
||||||
String raceXML;
|
|
||||||
String regattaXML;
|
|
||||||
String boatXML;
|
|
||||||
Polars boatPolars;
|
|
||||||
MockOutput mockOutput;
|
|
||||||
|
|
||||||
public Event(String raceXML, String regattaXML, String boatXML, Polars boatPolars) {
|
|
||||||
|
|
||||||
this.raceXML = getRaceXMLAtCurrentTime(raceXML);
|
|
||||||
this.boatXML = boatXML;
|
|
||||||
this.regattaXML = regattaXML;
|
|
||||||
this.boatPolars = boatPolars;
|
|
||||||
try {
|
|
||||||
mockOutput = new MockOutput();
|
|
||||||
new Thread(mockOutput).start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the initial race data and then begins race simulation
|
|
||||||
*/
|
|
||||||
public void start() {
|
|
||||||
try {
|
|
||||||
sendXMLs();
|
|
||||||
Race newRace = new Race(new RaceXMLReader(this.raceXML, new BoatXMLReader(boatXML, this.boatPolars)), mockOutput);
|
|
||||||
new Thread((newRace)).start();
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends out each xml string, via the mock output
|
|
||||||
*/
|
|
||||||
private void sendXMLs() {
|
|
||||||
|
|
||||||
mockOutput.setRegattaXml(regattaXML);
|
|
||||||
mockOutput.parseXMLString(regattaXML, MessageType.XMLMESSAGE.getValue());
|
|
||||||
|
|
||||||
mockOutput.setRaceXml(raceXML);
|
|
||||||
mockOutput.parseXMLString(raceXML, MessageType.XMLMESSAGE.getValue());
|
|
||||||
|
|
||||||
mockOutput.setBoatsXml(boatXML);
|
|
||||||
mockOutput.parseXMLString(boatXML, MessageType.XMLMESSAGE.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the xml description of the race to show the race was created now, and starts in 3 minutes
|
|
||||||
* @param raceXML
|
|
||||||
* @return String containing edited xml
|
|
||||||
*/
|
|
||||||
private String getRaceXMLAtCurrentTime(String raceXML) {
|
|
||||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
|
||||||
ZonedDateTime creationTime = ZonedDateTime.now();
|
|
||||||
return raceXML.replace("CREATION_TIME", dateFormat.format(creationTime))
|
|
||||||
.replace("START_TIME", dateFormat.format(creationTime.plusMinutes(3)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package seng302.DataInput;
|
|
||||||
|
|
||||||
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
import seng302.Exceptions.InvalidPolarFileException;
|
|
||||||
import seng302.Model.Polars;
|
|
||||||
|
|
||||||
import static org.testng.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by f123 on 10-May-17.
|
|
||||||
*/
|
|
||||||
public class PolarParserTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
/*
|
|
||||||
Tests if we can parse a polar data file (stored in a string), and create a polar table.
|
|
||||||
*/
|
|
||||||
public void testParse() throws Exception {
|
|
||||||
|
|
||||||
try {
|
|
||||||
//Parse data file.
|
|
||||||
Polars polars = PolarParser.parse("polars/acc_polars.csv");
|
|
||||||
|
|
||||||
//If the parse function didn't through, it worked.
|
|
||||||
assertTrue(true);
|
|
||||||
}
|
|
||||||
catch (InvalidPolarFileException e) {
|
|
||||||
assertTrue(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
package seng302.Model;
|
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by esa46 on 22/03/17.
|
|
||||||
*/
|
|
||||||
public class BoatTest {
|
|
||||||
|
|
||||||
|
|
||||||
private GPSCoordinate ORIGIN_COORDS;
|
|
||||||
private Boat TEST_BOAT;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
ORIGIN_COORDS = new GPSCoordinate(0, 0);
|
|
||||||
TEST_BOAT = new Boat(1, "Test", "tt", new Polars());
|
|
||||||
TEST_BOAT.setCurrentPosition(ORIGIN_COORDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueNorthAzimuthReturns0() {
|
|
||||||
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueSouthAzimuthReturns180() {
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueEastAzimuthReturns90() {
|
|
||||||
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueWestAzimuthReturnsNegative90() {
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -90, 1e-8);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueNorthHeadingReturns0() {
|
|
||||||
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 0, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueEastHeadingReturns90() {
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 90, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueSouthHeadingReturns180() {
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 180, 1e-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void calculateDueWestHeadingReturns270() {
|
|
||||||
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
|
|
||||||
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50)));
|
|
||||||
Leg start = new Leg("Start", startMarker, endMarker, 0);
|
|
||||||
TEST_BOAT.setCurrentLeg(start);
|
|
||||||
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 270, 1e-8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,315 +0,0 @@
|
|||||||
package seng302.Model;
|
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import seng302.DataInput.BoatDataSource;
|
|
||||||
import seng302.DataInput.BoatXMLReader;
|
|
||||||
import seng302.DataInput.RaceDataSource;
|
|
||||||
import seng302.DataInput.RaceXMLReader;
|
|
||||||
import seng302.Exceptions.StreamedCourseXMLException;
|
|
||||||
import seng302.MockOutput;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by esa46 on 15/03/17.
|
|
||||||
*/
|
|
||||||
public class RaceTest{
|
|
||||||
|
|
||||||
|
|
||||||
public static final CompoundMark ORIGIN = new CompoundMark(new Mark(1, "test origin 1", new GPSCoordinate(0, 0)));
|
|
||||||
public static final CompoundMark THREE_NM_FROM_ORIGIN = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)));
|
|
||||||
public static final CompoundMark FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)));
|
|
||||||
public static ArrayList<Leg> TEST_LEGS = new ArrayList<>();
|
|
||||||
public static final int START_LEG_DISTANCE = 3;
|
|
||||||
public static final int MIDDLE_LEG_DISTANCE = 12;
|
|
||||||
public static final Leg START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0);
|
|
||||||
public static final Leg MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1);
|
|
||||||
public static final Leg FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
TEST_LEGS.add(START_LEG);
|
|
||||||
TEST_LEGS.add(MIDDLE_LEG);
|
|
||||||
TEST_LEGS.add(FINISH_LEG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void countdownTimerSendsBoatLocations() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
testRace.countdownTimer.handle(1);
|
|
||||||
verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong());
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void countdownTimerSendsRaceStatusMessages() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
testRace.countdownTimer.handle(1);
|
|
||||||
verify(mockOutput, atLeast(1)).parseRaceStatus(any());
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void checkPositionFinishedUpdatesNumberFinishedBoats() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(FINISH_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(1);
|
|
||||||
testRace.checkPosition(testBoat, 1);
|
|
||||||
|
|
||||||
assertEquals(testRace.getNumberOfActiveBoats(), 0);
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void checkPositionSetFinishedBoatVelocityTo0() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(FINISH_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(1);
|
|
||||||
testRace.checkPosition(testBoat, 1);
|
|
||||||
|
|
||||||
assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8);
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void checkPositionSetsFinishTime() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(FINISH_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(1);
|
|
||||||
testRace.checkPosition(testBoat, 1);
|
|
||||||
|
|
||||||
assertEquals(testBoat.getTimeFinished(), 1, 1e-8);
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(START_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE);
|
|
||||||
testRace.checkPosition(testBoat, 1);
|
|
||||||
|
|
||||||
assertEquals(testRace.getNumberOfActiveBoats(), 1);
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void distanceTravelledBeforeUpdatingLegIsRetained() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
|
|
||||||
Race testRace = new Race(dataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(START_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
|
|
||||||
testRace.checkPosition(testBoat, 0);
|
|
||||||
|
|
||||||
assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7);
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void doNotFinishAnswersYesIf100PercentChance() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
|
|
||||||
testRace.setDnfChance(100);
|
|
||||||
assertTrue(testRace.doNotFinish());
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void doNotFinishAnswersNoIf0PercentChance() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
testRace.setDnfChance(0);
|
|
||||||
assertFalse(testRace.doNotFinish());
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void boatsAreSetToDNF() {
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
testRace.setDnfChance(100);
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(START_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
|
|
||||||
testRace.checkPosition(testBoat, 1);
|
|
||||||
assertEquals(testBoat.getCurrentLeg().getName(), "DNF");
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void updatePositionIgnoresFinishedBoats() {
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(FINISH_LEG);
|
|
||||||
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
|
|
||||||
testRace.updatePosition(testBoat, 1, 1);
|
|
||||||
assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate());
|
|
||||||
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void updatePositionChangesBoatPosition() {
|
|
||||||
try {
|
|
||||||
MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
|
|
||||||
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
testRace.initialiseBoats();
|
|
||||||
Boat testBoat = testRace.getBoats().get(0);
|
|
||||||
testBoat.setCurrentLeg(START_LEG);
|
|
||||||
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1);
|
|
||||||
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
|
|
||||||
testRace.updatePosition(testBoat, 100, 100);
|
|
||||||
assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate());
|
|
||||||
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void windDirectionCorrectValues(){
|
|
||||||
// try {
|
|
||||||
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
|
|
||||||
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
|
|
||||||
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
|
|
||||||
// Race testRace = new Race(raceDataSource, mockOutput);
|
|
||||||
// testRace.setChangeWind(1);
|
|
||||||
// testRace.setWindDir(65535);
|
|
||||||
// testRace.changeWindDir();
|
|
||||||
// assertEquals(100, testRace.getWind());
|
|
||||||
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
package seng302.Networking.MessageEncoders;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.intToBytes;
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.longToBytes;
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.shortToBytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a XML file into a message of AC35 format
|
|
||||||
*/
|
|
||||||
public class XMLMessageEncoder {
|
|
||||||
private byte[] messageVersionNumber;
|
|
||||||
private short ackNumber;
|
|
||||||
private long timeStamp;
|
|
||||||
private byte[] xmlMsgSubType;
|
|
||||||
private short sequenceNumber;
|
|
||||||
private short xmlMsgLength;
|
|
||||||
private String xmlMessage;
|
|
||||||
|
|
||||||
public XMLMessageEncoder(short ackNumber, long timeStamp, int xmlMsgSubType, short sequenceNumber, short xmlMsgLength, String xmlMessage) {
|
|
||||||
this.messageVersionNumber = intToBytes(1, 1);
|
|
||||||
this.ackNumber = ackNumber;
|
|
||||||
this.timeStamp = timeStamp;
|
|
||||||
this.xmlMsgSubType = intToBytes(xmlMsgSubType, 1);
|
|
||||||
this.sequenceNumber = sequenceNumber;
|
|
||||||
this.xmlMsgLength = xmlMsgLength;
|
|
||||||
this.xmlMessage = xmlMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encode() {
|
|
||||||
byte[] messageBytes = xmlMessage.getBytes();
|
|
||||||
if (messageBytes.length > this.xmlMsgLength) {
|
|
||||||
//System.err.println("Xml message is to big");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
|
|
||||||
|
|
||||||
//ackNumber converted to bytes
|
|
||||||
byte[] ackNumberBytes = shortToBytes(ackNumber, 2);
|
|
||||||
|
|
||||||
//sequenceNumber converted to bytes
|
|
||||||
byte[] sequenceNumberBytes = shortToBytes(sequenceNumber, 2);
|
|
||||||
|
|
||||||
//xmlMsgLength converted to bytes
|
|
||||||
byte[] xmlMsgLengthBytes = shortToBytes(xmlMsgLength, 2);
|
|
||||||
|
|
||||||
|
|
||||||
tempOutputByteBuffer.put(messageVersionNumber);
|
|
||||||
tempOutputByteBuffer.put(ackNumberBytes);
|
|
||||||
tempOutputByteBuffer.put(longToBytes(timeStamp, 6));
|
|
||||||
tempOutputByteBuffer.put(xmlMsgSubType);
|
|
||||||
tempOutputByteBuffer.put(sequenceNumberBytes);
|
|
||||||
tempOutputByteBuffer.put(xmlMsgLengthBytes);
|
|
||||||
tempOutputByteBuffer.put(messageBytes);
|
|
||||||
|
|
||||||
return tempOutputByteBuffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
package seng302.Networking.Messages;
|
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by fwy13 on 25/04/17.
|
|
||||||
*/
|
|
||||||
public class XMLMessage extends AC35Data {
|
|
||||||
|
|
||||||
private int ackNumber;
|
|
||||||
private long timeStamp;
|
|
||||||
private int xmlMsgSubType;
|
|
||||||
private int sequenceNumber;
|
|
||||||
private int xmlMsgLength;
|
|
||||||
private InputStream xmlMessage;
|
|
||||||
|
|
||||||
public static int XMLTypeRegatta = 5;
|
|
||||||
public static int XMLTypeRace = 6;
|
|
||||||
public static int XMLTypeBoat = 7;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for an XML Message
|
|
||||||
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
|
|
||||||
* @param timeStamp Time received
|
|
||||||
* @param xmlMsgSubType Type of XML message
|
|
||||||
* @param sequenceNumber Order that it has arrived in
|
|
||||||
* @param xmlMsgLength Length of the xml message
|
|
||||||
* @param xmlMessage XML message
|
|
||||||
*/
|
|
||||||
public XMLMessage(int ackNumber, long timeStamp, int xmlMsgSubType, int sequenceNumber, int xmlMsgLength, InputStream xmlMessage){
|
|
||||||
super(MessageType.XMLMESSAGE);
|
|
||||||
this.ackNumber = ackNumber;
|
|
||||||
this.timeStamp = timeStamp;
|
|
||||||
this.xmlMsgSubType = xmlMsgSubType;
|
|
||||||
this.sequenceNumber = sequenceNumber;
|
|
||||||
this.xmlMsgLength = xmlMsgLength;
|
|
||||||
this.xmlMessage = xmlMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the XML Message
|
|
||||||
* @return the XML message as an input stream
|
|
||||||
*/
|
|
||||||
public InputStream getXmlMessage() {
|
|
||||||
return xmlMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type of message
|
|
||||||
* @return Gets the type of message the XML message is
|
|
||||||
*/
|
|
||||||
public int getXmlMsgSubType() {
|
|
||||||
return xmlMsgSubType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by hba56 on 20/04/17.
|
|
||||||
*/
|
|
||||||
public class XMLMessageDecoderTest {
|
|
||||||
@Test
|
|
||||||
public void getByteArrayTest(){
|
|
||||||
try{
|
|
||||||
StringBuilder xmlString;
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
|
||||||
this.getClass().getResourceAsStream(("../../../raceXML/Regatta.xml"))));
|
|
||||||
String line;
|
|
||||||
xmlString = new StringBuilder();
|
|
||||||
while((line=br.readLine())!= null){
|
|
||||||
xmlString.append(line.trim());
|
|
||||||
}
|
|
||||||
long time = System.currentTimeMillis();
|
|
||||||
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, time, (byte)7, (short)1, (short)xmlString.length(), xmlString.toString());
|
|
||||||
|
|
||||||
byte[] encodedXML = testEncoder.encode();
|
|
||||||
|
|
||||||
XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML);
|
|
||||||
decoderXML.decode();
|
|
||||||
|
|
||||||
|
|
||||||
Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber());
|
|
||||||
Assert.assertEquals((short)1, decoderXML.getAckNumber());
|
|
||||||
Assert.assertEquals(time, decoderXML.getTimeStamp());
|
|
||||||
Assert.assertEquals((byte)7, decoderXML.getXmlMsgSubType());
|
|
||||||
Assert.assertEquals((short)1, decoderXML.getSequenceNumber());
|
|
||||||
Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength());
|
|
||||||
|
|
||||||
}catch (IOException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
package seng302.Networking;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by hba56 on 19/04/17.
|
|
||||||
*/
|
|
||||||
public class XMLMessageEncoderTest {
|
|
||||||
@Test
|
|
||||||
public void getByteArrayTest(){
|
|
||||||
StringBuilder xmlString;
|
|
||||||
try{
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
|
||||||
this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml"))));
|
|
||||||
String line;
|
|
||||||
xmlString = new StringBuilder();
|
|
||||||
while((line=br.readLine())!= null){
|
|
||||||
xmlString.append(line.trim());
|
|
||||||
}
|
|
||||||
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)xmlString.length(), xmlString.toString());
|
|
||||||
|
|
||||||
byte[] encodedXML = testEncoder.encode();
|
|
||||||
|
|
||||||
//1 + 2 + 6 + 1 + 2 + 2 + 374
|
|
||||||
Assert.assertEquals(388, encodedXML.length);
|
|
||||||
}catch (IOException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getByteArrayNullTest(){
|
|
||||||
StringBuilder xmlString;
|
|
||||||
try{
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
|
||||||
this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml"))));
|
|
||||||
String line;
|
|
||||||
xmlString = new StringBuilder();
|
|
||||||
while((line=br.readLine())!= null){
|
|
||||||
xmlString.append(line.trim());
|
|
||||||
}
|
|
||||||
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)1, xmlString.toString());
|
|
||||||
|
|
||||||
byte[] encodedXML = testEncoder.encode();
|
|
||||||
|
|
||||||
Assert.assertEquals(null, encodedXML);
|
|
||||||
}catch (IOException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>seng302</groupId>
|
||||||
|
<artifactId>team-7</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>racevisionGame</name>
|
||||||
|
<artifactId>racevisionGame</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-all</artifactId>
|
||||||
|
<version>1.9.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testng</groupId>
|
||||||
|
<artifactId>testng</artifactId>
|
||||||
|
<version>6.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.jetbrains/annotations -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains</groupId>
|
||||||
|
<artifactId>annotations</artifactId>
|
||||||
|
<version>15.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.geotools</groupId>
|
||||||
|
<artifactId>gt-referencing</artifactId>
|
||||||
|
<version>9.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>maven2-repository.dev.java.net</id>
|
||||||
|
<name>Java.net repository</name>
|
||||||
|
<url>http://download.java.net/maven/2</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
|
||||||
|
<repository>
|
||||||
|
<id>osgeo</id>
|
||||||
|
<name>Open Source Geospatial Foundation Repository</name>
|
||||||
|
<url>http://download.osgeo.org/webdav/geotools/</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>mock</id>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.4.3</version>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<manifestEntries>
|
||||||
|
<Main-Class>mock.app.App</Main-Class>
|
||||||
|
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
|
||||||
|
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
|
||||||
|
</manifestEntries>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>visualiser</id>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.4.3</version>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<manifestEntries>
|
||||||
|
<Main-Class>visualiser.app.App</Main-Class>
|
||||||
|
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
|
||||||
|
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
|
||||||
|
</manifestEntries>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
</profiles>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<reporting>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jxr-plugin</artifactId>
|
||||||
|
<version>2.5</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
|
<version>3.6</version>
|
||||||
|
<configuration>
|
||||||
|
<linkXRef>true</linkXRef>
|
||||||
|
<targetJdk>${maven.compiler.target}</targetJdk>
|
||||||
|
<rulesets>
|
||||||
|
<ruleset>/rulesets/java/basic.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/imports.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/codesize.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/design.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/empty.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/junit.xml</ruleset>
|
||||||
|
<ruleset>/rulesets/java/unusedcode.xml</ruleset>
|
||||||
|
</rulesets>
|
||||||
|
<includeXmlInSite>true</includeXmlInSite>
|
||||||
|
<sourceEncoding>utf-8</sourceEncoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>2.10.3</version>
|
||||||
|
<configuration>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||||
|
<version>2.19.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
|
<version>2.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</reporting>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package mock.app;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import mock.dataInput.PolarParser;
|
||||||
|
import mock.model.Polars;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import shared.dataInput.XMLReader;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class App extends Application {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for running the programme
|
||||||
|
*
|
||||||
|
* @param args for starting the programme
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) {
|
||||||
|
try {
|
||||||
|
Polars boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
|
||||||
|
|
||||||
|
String regattaXML = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8);
|
||||||
|
String raceXML = XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8);
|
||||||
|
String boatXML = XMLReader.readXMLFileToString("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Event raceEvent = new Event(raceXML, regattaXML, boatXML, XMLFileType.Contents, boatPolars);
|
||||||
|
raceEvent.start();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
//Catch all exceptions, print, and exit.
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,124 @@
|
|||||||
|
package mock.app;
|
||||||
|
|
||||||
|
import mock.model.MockRace;
|
||||||
|
import mock.model.Polars;
|
||||||
|
import network.Messages.LatestMessages;
|
||||||
|
import shared.dataInput.*;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
import shared.exceptions.InvalidBoatDataException;
|
||||||
|
import shared.exceptions.InvalidRaceDataException;
|
||||||
|
import shared.exceptions.InvalidRegattaDataException;
|
||||||
|
import shared.exceptions.XMLReaderException;
|
||||||
|
import shared.model.Constants;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Race Event, this holds all of the race's information as well as handling the connection to its clients.
|
||||||
|
*/
|
||||||
|
public class Event {
|
||||||
|
|
||||||
|
private String raceXML;
|
||||||
|
private String regattaXML;
|
||||||
|
private String boatXML;
|
||||||
|
|
||||||
|
private XMLFileType xmlFileType;
|
||||||
|
|
||||||
|
private Polars boatPolars;
|
||||||
|
|
||||||
|
private MockOutput mockOutput;
|
||||||
|
private LatestMessages latestMessages;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an event, using various XML files.
|
||||||
|
* @param raceXML The race.xml file.
|
||||||
|
* @param regattaXML The regatta.xml file.
|
||||||
|
* @param boatXML The boat.xml file.
|
||||||
|
* @param type How to read the file - e.g., load as resource.
|
||||||
|
* @param boatPolars polars that the boat uses
|
||||||
|
*/
|
||||||
|
public Event(String raceXML, String regattaXML, String boatXML, XMLFileType type, Polars boatPolars) {
|
||||||
|
|
||||||
|
this.raceXML = getRaceXMLAtCurrentTime(raceXML);
|
||||||
|
this.boatXML = boatXML;
|
||||||
|
this.regattaXML = regattaXML;
|
||||||
|
|
||||||
|
this.xmlFileType = type;
|
||||||
|
|
||||||
|
this.boatPolars = boatPolars;
|
||||||
|
|
||||||
|
this.latestMessages = new LatestMessages();
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.mockOutput = new MockOutput(this.latestMessages);
|
||||||
|
new Thread(mockOutput).start();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the initial race data and then begins race simulation.
|
||||||
|
* @throws InvalidRaceDataException Thrown if the race xml file cannot be parsed.
|
||||||
|
* @throws XMLReaderException Thrown if any of the xml files cannot be parsed.
|
||||||
|
* @throws InvalidBoatDataException Thrown if the boat xml file cannot be parsed.
|
||||||
|
* @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed.
|
||||||
|
*/
|
||||||
|
public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException {
|
||||||
|
|
||||||
|
sendXMLs();
|
||||||
|
|
||||||
|
//Parse the XML files into data sources.
|
||||||
|
RaceDataSource raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType);
|
||||||
|
BoatDataSource boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType);
|
||||||
|
RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType);
|
||||||
|
|
||||||
|
//Create and start race.
|
||||||
|
MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale);
|
||||||
|
|
||||||
|
new Thread(newRace).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends out each xml string, via the mock output
|
||||||
|
*/
|
||||||
|
private void sendXMLs() {
|
||||||
|
|
||||||
|
mockOutput.setRegattaXml(regattaXML);
|
||||||
|
|
||||||
|
mockOutput.setRaceXml(raceXML);
|
||||||
|
|
||||||
|
mockOutput.setBoatsXml(boatXML);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
|
||||||
|
* @param raceXML The race.xml contents.
|
||||||
|
* @return String containing edited xml
|
||||||
|
*/
|
||||||
|
private String getRaceXMLAtCurrentTime(String raceXML) {
|
||||||
|
|
||||||
|
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||||
|
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
|
||||||
|
long secondsToAdd = millisecondsToAdd / 1000;
|
||||||
|
//Scale the time using our time scalar.
|
||||||
|
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
|
||||||
|
|
||||||
|
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||||
|
ZonedDateTime creationTime = ZonedDateTime.now();
|
||||||
|
raceXML = raceXML.replace("CREATION_TIME", dateFormat.format(creationTime));
|
||||||
|
|
||||||
|
raceXML = raceXML.replace("START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
|
||||||
|
|
||||||
|
return raceXML;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,431 @@
|
|||||||
|
package mock.app;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import network.BinaryMessageEncoder;
|
||||||
|
import network.MessageEncoders.RaceVisionByteEncoder;
|
||||||
|
import network.Messages.*;
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
import network.Messages.Enums.XMLMessageType;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP server to send race information to connected clients.
|
||||||
|
*/
|
||||||
|
public class MockOutput implements Runnable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Timestamp of the last sent heartbeat message.
|
||||||
|
*/
|
||||||
|
private long lastHeartbeatTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Period for the heartbeat - that is, how often we send it.
|
||||||
|
*/
|
||||||
|
private double heartbeatPeriod = 5.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port to expose server on.
|
||||||
|
*/
|
||||||
|
private int serverPort = 4942;
|
||||||
|
/**
|
||||||
|
* Socket used to listen for clients on.
|
||||||
|
*/
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
/**
|
||||||
|
* Socket used to communicate with a client.
|
||||||
|
*/
|
||||||
|
private Socket mockSocket;
|
||||||
|
/**
|
||||||
|
* Output stream which wraps around mockSocket outstream.
|
||||||
|
*/
|
||||||
|
private DataOutputStream outToVisualiser;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing the set of latest messages to send.
|
||||||
|
* Every server frame, MockOutput reads messages from this, and send them.
|
||||||
|
*/
|
||||||
|
private LatestMessages latestMessages;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ack numbers used in messages.
|
||||||
|
*/
|
||||||
|
private int ackNumber = 1;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number for race xml messages.
|
||||||
|
*/
|
||||||
|
private short raceXMLSequenceNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number for boat xml messages.
|
||||||
|
*/
|
||||||
|
private short boatXMLSequenceNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number for regatta xml messages.
|
||||||
|
*/
|
||||||
|
private short regattaXMLSequenceNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number for heartbeat messages.
|
||||||
|
*/
|
||||||
|
private int heartbeatSequenceNum = 1;
|
||||||
|
|
||||||
|
|
||||||
|
private boolean stop = false; //whether or not hte thread keeps running
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
* @param latestMessages The collection of messages to send to connected clients.
|
||||||
|
* @throws IOException if server socket cannot be opened.
|
||||||
|
*/
|
||||||
|
public MockOutput(LatestMessages latestMessages) throws IOException {
|
||||||
|
|
||||||
|
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
this.serverSocket = new ServerSocket(serverPort);
|
||||||
|
|
||||||
|
this.latestMessages = latestMessages;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the ackNumber value, and returns it.
|
||||||
|
* @return Incremented ackNumber.
|
||||||
|
*/
|
||||||
|
private int getNextAckNumber(){
|
||||||
|
this.ackNumber++;
|
||||||
|
|
||||||
|
return this.ackNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the time since last heartbeat message, in seconds.
|
||||||
|
* @return Time since last heartbeat message, in seconds.
|
||||||
|
*/
|
||||||
|
private double timeSinceHeartbeat() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
return (now - lastHeartbeatTime) / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next heartbeat message and returns it. Increments the heartbeat sequence number.
|
||||||
|
* @return The next heartbeat message.
|
||||||
|
*/
|
||||||
|
private Heartbeat createHeartbeatMessage() {
|
||||||
|
|
||||||
|
//Create the heartbeat message.
|
||||||
|
Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum);
|
||||||
|
heartbeatSequenceNum++;
|
||||||
|
|
||||||
|
return heartbeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a heartbeat message into a packet to be sent, and returns the byte array.
|
||||||
|
* @param heartbeat The heartbeat message to serialize.
|
||||||
|
* @return Byte array containing the next heartbeat message.
|
||||||
|
*/
|
||||||
|
private byte[] parseHeartbeat(Heartbeat heartbeat) {
|
||||||
|
|
||||||
|
//Serializes the heartbeat message.
|
||||||
|
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat);
|
||||||
|
|
||||||
|
//Places the serialized message in a packet.
|
||||||
|
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
|
||||||
|
MessageType.HEARTBEAT,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
getNextAckNumber(),
|
||||||
|
(short) heartbeatMessage.length,
|
||||||
|
heartbeatMessage );
|
||||||
|
|
||||||
|
return binaryMessageEncoder.getFullMessage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an XMLMessage of a specified subtype using the xml contents string.
|
||||||
|
* @param xmlString The contents of the xml file.
|
||||||
|
* @param messageType The subtype of xml message (race, regatta, boat).
|
||||||
|
* @return The created XMLMessage object.
|
||||||
|
*/
|
||||||
|
private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) {
|
||||||
|
|
||||||
|
//Get the correct sequence number to use, and increment it.
|
||||||
|
short sequenceNumber = 0;
|
||||||
|
if (messageType == XMLMessageType.RACE) {
|
||||||
|
sequenceNumber = this.raceXMLSequenceNumber;
|
||||||
|
this.raceXMLSequenceNumber++;
|
||||||
|
|
||||||
|
} else if (messageType == XMLMessageType.BOAT) {
|
||||||
|
sequenceNumber = this.boatXMLSequenceNumber;
|
||||||
|
this.boatXMLSequenceNumber++;
|
||||||
|
|
||||||
|
} else if (messageType == XMLMessageType.REGATTA) {
|
||||||
|
sequenceNumber = this.regattaXMLSequenceNumber;
|
||||||
|
this.regattaXMLSequenceNumber++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the message.
|
||||||
|
XMLMessage message = new XMLMessage(
|
||||||
|
XMLMessage.currentVersionNumber,
|
||||||
|
getNextAckNumber(),
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
messageType,
|
||||||
|
sequenceNumber,
|
||||||
|
xmlString );
|
||||||
|
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes/serialises a XMLMessage message, and returns it.
|
||||||
|
* @param xmlMessage The XMLMessage message to serialise.
|
||||||
|
* @return The XMLMessage message in a serialised form.
|
||||||
|
*/
|
||||||
|
private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) {
|
||||||
|
|
||||||
|
//Serialize the xml message.
|
||||||
|
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage);
|
||||||
|
|
||||||
|
//Place the message in a packet.
|
||||||
|
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
|
||||||
|
MessageType.XMLMESSAGE,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
xmlMessage.getAckNumber(), //We use the ack number from the xml message.
|
||||||
|
(short) encodedXML.length,
|
||||||
|
encodedXML );
|
||||||
|
|
||||||
|
|
||||||
|
return binaryMessageEncoder.getFullMessage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes/serialises a BoatLocation message, and returns it.
|
||||||
|
* @param boatLocation The BoatLocation message to serialise.
|
||||||
|
* @return The BoatLocation message in a serialised form.
|
||||||
|
*/
|
||||||
|
private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){
|
||||||
|
|
||||||
|
|
||||||
|
//Encodes the message.
|
||||||
|
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
|
||||||
|
|
||||||
|
//Encodes the full message with header.
|
||||||
|
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
|
||||||
|
MessageType.BOATLOCATION,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
getNextAckNumber(),
|
||||||
|
(short) encodedBoatLoc.length,
|
||||||
|
encodedBoatLoc );
|
||||||
|
|
||||||
|
|
||||||
|
return binaryMessageEncoder.getFullMessage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes/serialises a RaceStatus message, and returns it.
|
||||||
|
* @param raceStatus The RaceStatus message to serialise.
|
||||||
|
* @return The RaceStatus message in a serialised form.
|
||||||
|
*/
|
||||||
|
private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){
|
||||||
|
|
||||||
|
//Encodes the messages.
|
||||||
|
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
|
||||||
|
|
||||||
|
//Encodes the full message with header.
|
||||||
|
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
|
||||||
|
MessageType.RACESTATUS,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
getNextAckNumber(),
|
||||||
|
(short) encodedRaceStatus.length,
|
||||||
|
encodedRaceStatus );
|
||||||
|
|
||||||
|
|
||||||
|
return binaryMessageEncoder.getFullMessage();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sending loop of the Server
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!stop){
|
||||||
|
//Wait for a client to connect.
|
||||||
|
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
|
||||||
|
mockSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
|
||||||
|
|
||||||
|
//Wait until all of the xml files have been set.
|
||||||
|
if (!this.latestMessages.hasAllXMLMessages()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
long previousFrameTime = System.currentTimeMillis();
|
||||||
|
boolean sentXMLs = false;
|
||||||
|
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
long currentFrameTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
//This is the time elapsed, in milliseconds, since the last server "frame".
|
||||||
|
long framePeriod = currentFrameTime - previousFrameTime;
|
||||||
|
|
||||||
|
//We only attempt to send packets every X milliseconds.
|
||||||
|
long minimumFramePeriod = 16;
|
||||||
|
if (framePeriod >= minimumFramePeriod) {
|
||||||
|
|
||||||
|
//Sends a heartbeat every so often.
|
||||||
|
if (timeSinceHeartbeat() >= heartbeatPeriod) {
|
||||||
|
outToVisualiser.write(parseHeartbeat(createHeartbeatMessage()));
|
||||||
|
lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send XML messages.
|
||||||
|
if (!sentXMLs) {
|
||||||
|
//Serialise them.
|
||||||
|
byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage());
|
||||||
|
byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage());
|
||||||
|
byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage());
|
||||||
|
|
||||||
|
//Send them.
|
||||||
|
outToVisualiser.write(raceXMLBlob);
|
||||||
|
outToVisualiser.write(regattaXMLBlob);
|
||||||
|
outToVisualiser.write(boatsXMLBlob);
|
||||||
|
sentXMLs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sends the RaceStatus message.
|
||||||
|
if (this.latestMessages.getRaceStatus() != null) {
|
||||||
|
byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus());
|
||||||
|
|
||||||
|
this.outToVisualiser.write(raceStatusBlob);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send all of the BoatLocation messages.
|
||||||
|
for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) {
|
||||||
|
|
||||||
|
//Get the message.
|
||||||
|
BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID);
|
||||||
|
if (boatLocation != null) {
|
||||||
|
|
||||||
|
//Encode.
|
||||||
|
byte[] boatLocationBlob = this.parseBoatLocation(boatLocation);
|
||||||
|
|
||||||
|
//Write it.
|
||||||
|
this.outToVisualiser.write(boatLocationBlob);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousFrameTime = currentFrameTime;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Wait until the frame period will be large enough.
|
||||||
|
long timeToWait = minimumFramePeriod - framePeriod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(timeToWait);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//If we get interrupted, exit the function.
|
||||||
|
e.printStackTrace();
|
||||||
|
//Re-set the interrupt flag.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SocketException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(){
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Race XML to send.
|
||||||
|
* @param raceXml XML to send to the Client.
|
||||||
|
*/
|
||||||
|
public void setRaceXml(String raceXml) {
|
||||||
|
//Create the message.
|
||||||
|
XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE);
|
||||||
|
|
||||||
|
//Place it in LatestMessages.
|
||||||
|
this.latestMessages.setRaceXMLMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Regatta XMl to send.
|
||||||
|
* @param regattaXml XML to send to Client.
|
||||||
|
*/
|
||||||
|
public void setRegattaXml(String regattaXml) {
|
||||||
|
//Create the message.
|
||||||
|
XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA);
|
||||||
|
|
||||||
|
//Place it in LatestMessages.
|
||||||
|
this.latestMessages.setRegattaXMLMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Boats XML to send.
|
||||||
|
* @param boatsXml XMl to send to the Client.
|
||||||
|
*/
|
||||||
|
public void setBoatsXml(String boatsXml) {
|
||||||
|
//Create the message.
|
||||||
|
XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT);
|
||||||
|
|
||||||
|
//Place it in LatestMessages.
|
||||||
|
this.latestMessages.setBoatXMLMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String argv[]) throws Exception
|
||||||
|
{
|
||||||
|
MockOutput client = new MockOutput(new LatestMessages());
|
||||||
|
client.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
package seng302.DataInput;
|
package mock.dataInput;
|
||||||
|
|
||||||
import seng302.Exceptions.InvalidPolarFileException;
|
|
||||||
import seng302.Model.Bearing;
|
|
||||||
import seng302.Model.Polars;
|
import mock.exceptions.InvalidPolarFileException;
|
||||||
|
import mock.model.Polars;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Exceptions;
|
package mock.exceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when we cannot parse a polar data file.
|
* An exception thrown when we cannot parse a polar data file.
|
||||||
@ -0,0 +1,206 @@
|
|||||||
|
package mock.model;
|
||||||
|
|
||||||
|
|
||||||
|
import shared.model.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Boat on the mock side of a race.
|
||||||
|
* This adds mock specific functionality to a boat.
|
||||||
|
*/
|
||||||
|
public class MockBoat extends Boat {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This stores a boat's polars table.
|
||||||
|
* Can be used to calculate VMG.
|
||||||
|
*/
|
||||||
|
private Polars polars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds.
|
||||||
|
*/
|
||||||
|
private long timeSinceTackChange = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
|
||||||
|
*
|
||||||
|
* @param sourceID The id of the boat
|
||||||
|
* @param name The name of the Boat.
|
||||||
|
* @param country The abbreviation or country code for the boat.
|
||||||
|
* @param polars The polars table to use for this boat.
|
||||||
|
*/
|
||||||
|
public MockBoat(int sourceID, String name, String country, Polars polars) {
|
||||||
|
super(sourceID, name, country);
|
||||||
|
|
||||||
|
this.polars = polars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a mock boat object from a given boat and polars table.
|
||||||
|
*
|
||||||
|
* @param boat The boat to convert into a MockBoat.
|
||||||
|
* @param polars The polars table to use for this boat.
|
||||||
|
*/
|
||||||
|
public MockBoat(Boat boat, Polars polars) {
|
||||||
|
super(boat.getSourceID(), boat.getName(), boat.getCountry());
|
||||||
|
|
||||||
|
this.polars = polars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the bearing of the boat to its next marker.
|
||||||
|
* @return The bearing to the next marker.
|
||||||
|
*/
|
||||||
|
public Bearing calculateBearingToNextMarker() {
|
||||||
|
|
||||||
|
//Get the start and end points.
|
||||||
|
GPSCoordinate currentPosition = this.getCurrentPosition();
|
||||||
|
GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();
|
||||||
|
|
||||||
|
//Calculate bearing.
|
||||||
|
Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition);
|
||||||
|
|
||||||
|
return bearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the distance between the boat and its target marker in nautical miles.
|
||||||
|
* @return The distance (in nautical miles) between the boat and its target marker.
|
||||||
|
*/
|
||||||
|
public double calculateDistanceToNextMarker() {
|
||||||
|
|
||||||
|
//Get start and end markers.
|
||||||
|
GPSCoordinate startPosition = this.getCurrentPosition();
|
||||||
|
|
||||||
|
//When boats finish, their "current leg" doesn't have an end marker.
|
||||||
|
if (this.getCurrentLeg().getEndCompoundMark() == null) {
|
||||||
|
return 0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate distance.
|
||||||
|
double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker);
|
||||||
|
|
||||||
|
return distanceNauticalMiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the polars table for this boat.
|
||||||
|
* @return The polars table for this boat.
|
||||||
|
*/
|
||||||
|
public Polars getPolars() {
|
||||||
|
return polars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the polars table for this boat.
|
||||||
|
* @param polars The new polars table for this boat.
|
||||||
|
*/
|
||||||
|
public void setPolars(Polars polars) {
|
||||||
|
this.polars = polars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time since the boat changed its tack, in milliseconds.
|
||||||
|
* @return Time since the boat changed its tack, in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getTimeSinceTackChange() {
|
||||||
|
return timeSinceTackChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time since the boat changed it's tack, in milliseconds.
|
||||||
|
* @param timeSinceTackChange Time since the boat changed its tack, in milliseconds.
|
||||||
|
*/
|
||||||
|
public void setTimeSinceTackChange(long timeSinceTackChange) {
|
||||||
|
this.timeSinceTackChange = timeSinceTackChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat meters forward in the direction that it is facing
|
||||||
|
* @param meters The number of meters to move forward.
|
||||||
|
* @param milliseconds The number of milliseconds to advance the boat's timers by.
|
||||||
|
*/
|
||||||
|
public void moveForwards(double meters, long milliseconds) {
|
||||||
|
|
||||||
|
|
||||||
|
//Update the boat's time since last tack.
|
||||||
|
this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds);
|
||||||
|
|
||||||
|
//Update the time into the current leg.
|
||||||
|
this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds);
|
||||||
|
|
||||||
|
//Update the distance into the current leg.
|
||||||
|
this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters);
|
||||||
|
|
||||||
|
//Updates the current position of the boat.
|
||||||
|
GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing()));
|
||||||
|
this.setCurrentPosition(newPosition);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the boats speed and bearing to those in the given VMG.
|
||||||
|
* @param newVMG The new VMG to use for the boat - contains speed and bearing.
|
||||||
|
*/
|
||||||
|
public void setVMG(VMG newVMG) {
|
||||||
|
this.setBearing(newVMG.getBearing());
|
||||||
|
this.setCurrentSpeed(newVMG.getSpeed());
|
||||||
|
this.setTimeSinceTackChange(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of nautical miles the boat will travel in a given time slice.
|
||||||
|
* E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles.
|
||||||
|
* @param timeSlice The timeslice to use.
|
||||||
|
* @return The distance travelled, in nautical miles, over the given timeslice.
|
||||||
|
*/
|
||||||
|
public double calculateNauticalMilesTravelled(long timeSlice) {
|
||||||
|
|
||||||
|
//The proportion of one hour the current timeslice is.
|
||||||
|
//This will be a low fractional number, so we need to go from long -> double.
|
||||||
|
double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds;
|
||||||
|
|
||||||
|
//Calculates the distance travelled, in nautical miles, in the current timeslice.
|
||||||
|
//distanceTravelledNM = speed (nm p hr) * time taken to update loop
|
||||||
|
double distanceTravelledNM = this.getCurrentSpeed() * hourProportion;
|
||||||
|
|
||||||
|
return distanceTravelledNM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of meters the boat will travel in a given time slice.
|
||||||
|
* E.g., in 53 milliseconds a boat may travel 0.02 meters.
|
||||||
|
* @param timeSlice The timeslice to use.
|
||||||
|
* @return The distance travelled, in meters, over the given timeslice.
|
||||||
|
*/
|
||||||
|
public double calculateMetersTravelled(long timeSlice) {
|
||||||
|
|
||||||
|
//Calculate the distance travelled, in nautical miles.
|
||||||
|
double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice);
|
||||||
|
|
||||||
|
//Convert to meters.
|
||||||
|
double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion;
|
||||||
|
|
||||||
|
return distanceTravelledMeters;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,8 +1,12 @@
|
|||||||
package seng302.Model;
|
package mock.model;
|
||||||
|
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates an entire polar table. Has a function to calculate VMG.
|
* Encapsulates an entire polar table. Has a function to calculate VMG.
|
||||||
@ -1,4 +1,6 @@
|
|||||||
package seng302.Model;
|
package mock.model;
|
||||||
|
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component.
|
* This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component.
|
||||||
@ -1,14 +1,13 @@
|
|||||||
package seng302.Networking;
|
package network;
|
||||||
|
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.intToBytes;
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.longToBytes;
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.shortToBytes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can be used to encode/convert a byte array message body, plus header data into a byte array containing the entire message, ready to send.
|
* This class can be used to encode/convert a byte array message body, plus header data into a byte array containing the entire message, ready to send.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.Exceptions;
|
package network.Exceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception which is thrown when a message is read, but it is invalid in some way (CRC is wrong, sync bytes, etc...).
|
* Exception which is thrown when a message is read, but it is invalid in some way (CRC is wrong, sync bytes, etc...).
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
import seng302.Networking.Messages.AverageWind;
|
|
||||||
import seng302.Networking.Utils.ByteConverter;
|
import network.Messages.AverageWind;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -1,10 +1,14 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
import seng302.Networking.Messages.BoatLocation;
|
|
||||||
|
import network.Messages.BoatLocation;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.*;
|
import static network.Utils.ByteConverter.bytesToInt;
|
||||||
|
import static network.Utils.ByteConverter.bytesToLong;
|
||||||
|
import static network.Utils.ByteConverter.bytesToShort;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 21/04/17.
|
* Created by hba56 on 21/04/17.
|
||||||
@ -1,11 +1,14 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
import seng302.Networking.Messages.CourseWind;
|
|
||||||
|
import network.Messages.CourseWind;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.*;
|
import static network.Utils.ByteConverter.bytesToInt;
|
||||||
|
import static network.Utils.ByteConverter.bytesToLong;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 23/04/17.
|
* Created by hba56 on 23/04/17.
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
import seng302.Networking.Utils.ByteConverter;
|
|
||||||
import seng302.Networking.Messages.MarkRounding;
|
import network.Messages.MarkRounding;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package seng302.Networking.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static seng302.Networking.Utils.ByteConverter.*;
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 21/04/17.
|
* Created by hba56 on 21/04/17.
|
||||||
@ -1,14 +1,16 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for all message types.
|
* The base class for all message types.
|
||||||
*/
|
*/
|
||||||
public abstract class AC35Data {
|
public abstract class AC35Data {
|
||||||
|
|
||||||
///Message type from the header.
|
/**
|
||||||
|
* Message type from the header.
|
||||||
|
*/
|
||||||
private MessageType type;
|
private MessageType type;
|
||||||
|
|
||||||
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 25/04/17.
|
* Created by fwy13 on 25/04/17.
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Utils.ByteConverter;
|
|
||||||
import seng302.Networking.Messages.Enums.BoatStatusEnum;
|
import network.Messages.Enums.BoatStatusEnum;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 23/04/17.
|
* Created by hba56 on 23/04/17.
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 21/04/17.
|
* Created by fwy13 on 21/04/17.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.Messages.Enums;
|
package network.Messages.Enums;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.Messages.Enums;
|
package network.Messages.Enums;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various types of XML messages that can be sent.
|
||||||
|
*/
|
||||||
|
public enum XMLMessageType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regatta.xml message.
|
||||||
|
*/
|
||||||
|
REGATTA(5),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A race.xml message.
|
||||||
|
*/
|
||||||
|
RACE(6),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boats.xml message.
|
||||||
|
*/
|
||||||
|
BOAT(7),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for unrecognised byte values.
|
||||||
|
*/
|
||||||
|
NOT_A_MESSAGE_TYPE(0);
|
||||||
|
|
||||||
|
|
||||||
|
///Primitive value of the enum.
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a XMLMessageType enum from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private XMLMessageType(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Stores a mapping between Byte values and XMLMessageType values.
|
||||||
|
private static final Map<Byte, XMLMessageType> byteToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToTypeMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (XMLMessageType type : XMLMessageType.values()) {
|
||||||
|
byteToTypeMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param messageTypeByte Byte value to convert to a XMLMessageType value.
|
||||||
|
* @return The XMLMessageType value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static XMLMessageType fromByte(byte messageTypeByte) {
|
||||||
|
//Gets the corresponding XMLMessageType from the map.
|
||||||
|
XMLMessageType type = byteToTypeMap.get(messageTypeByte);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOTAMESSAGE XMLMessageType.
|
||||||
|
return XMLMessageType.NOT_A_MESSAGE_TYPE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Otherwise, return the XMLMessageType.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,13 +1,16 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Heartbeat message.
|
* Represents a Heartbeat message.
|
||||||
*/
|
*/
|
||||||
public class Heartbeat extends AC35Data {
|
public class Heartbeat extends AC35Data {
|
||||||
|
|
||||||
///Sequence number of the heartbeat.
|
/**
|
||||||
|
* Sequence number of the heartbeat.
|
||||||
|
*/
|
||||||
private long sequenceNumber;
|
private long sequenceNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
import network.Messages.Enums.XMLMessageType;
|
||||||
|
import shared.dataInput.RaceDataSource;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Observable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...).
|
||||||
|
* Currently, LatestMessage only notifies observers of change when a new XMLMessage is received.
|
||||||
|
*/
|
||||||
|
public class LatestMessages extends Observable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest RaceStatus message.
|
||||||
|
*/
|
||||||
|
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<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of the last MarkRounding message received, for each boat.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last AverageWind message received.
|
||||||
|
*/
|
||||||
|
private AverageWind averageWind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last CourseWinds message received.
|
||||||
|
*/
|
||||||
|
private CourseWinds courseWinds;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest race data XML message.
|
||||||
|
*/
|
||||||
|
private XMLMessage raceXMLMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest boat data XML message.
|
||||||
|
*/
|
||||||
|
private XMLMessage boatXMLMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest regatta data XML message.
|
||||||
|
*/
|
||||||
|
private XMLMessage regattaXMLMessage;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*/
|
||||||
|
public LatestMessages() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest RaceStatus message received.
|
||||||
|
* @return The latest RaceStatus message received.
|
||||||
|
*/
|
||||||
|
public RaceStatus getRaceStatus() {
|
||||||
|
return raceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest RaceStatus message received.
|
||||||
|
* @param raceStatus The new RaceStatus message to store.
|
||||||
|
*/
|
||||||
|
public void setRaceStatus(RaceStatus raceStatus) {
|
||||||
|
this.raceStatus = raceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest BoatStatus message received for a given boat.
|
||||||
|
* @param sourceID Source ID of the boat.
|
||||||
|
* @return The latest BoatStatus message for the specified boat.
|
||||||
|
*/
|
||||||
|
public BoatStatus getBoatStatus(int sourceID) {
|
||||||
|
return boatStatusMap.get(sourceID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a BoatStatus message for a given boat.
|
||||||
|
* @param boatStatus The BoatStatus message to set.
|
||||||
|
*/
|
||||||
|
public void setBoatStatus(BoatStatus boatStatus) {
|
||||||
|
boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest BoatLocation message received for a given boat.
|
||||||
|
* @param sourceID Source ID of the boat.
|
||||||
|
* @return The latest BoatLocation message for the specified boat.
|
||||||
|
*/
|
||||||
|
public BoatLocation getBoatLocation(int sourceID) {
|
||||||
|
return boatLocationMap.get(sourceID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a BoatLocation message for a given boat.
|
||||||
|
* @param boatLocation The BoatLocation message to set.
|
||||||
|
*/
|
||||||
|
public void setBoatLocation(BoatLocation boatLocation) {
|
||||||
|
//TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one.
|
||||||
|
boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest MarkRounding message received for a given boat.
|
||||||
|
* @param sourceID Source ID of the boat.
|
||||||
|
* @return The latest MarkRounding message for the specified boat.
|
||||||
|
*/
|
||||||
|
public MarkRounding getMarkRounding(int sourceID) {
|
||||||
|
return markRoundingMap.get(sourceID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a MarkRounding message for a given boat.
|
||||||
|
* @param markRounding The MarkRounding message to set.
|
||||||
|
*/
|
||||||
|
public void setMarkRounding(MarkRounding markRounding) {
|
||||||
|
//TODO should compare the sequence number of the new markRounding with the existing boatLocation for this boat (if it exists), and use the newer one.
|
||||||
|
markRoundingMap.put(markRounding.getSourceID(), markRounding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest AverageWind message received.
|
||||||
|
* @return The latest AverageWind message received.
|
||||||
|
*/
|
||||||
|
public AverageWind getAverageWind() {
|
||||||
|
return averageWind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest AverageWind message received.
|
||||||
|
* @param averageWind The new AverageWind message to store.
|
||||||
|
*/
|
||||||
|
public void setAverageWind(AverageWind averageWind) {
|
||||||
|
this.averageWind = averageWind;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest CourseWinds message received.
|
||||||
|
* @return The latest CourseWinds message received.
|
||||||
|
*/
|
||||||
|
public CourseWinds getCourseWinds() {
|
||||||
|
return courseWinds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest CourseWinds message received.
|
||||||
|
* @param courseWinds The new CourseWinds message to store.
|
||||||
|
*/
|
||||||
|
public void setCourseWinds(CourseWinds courseWinds) {
|
||||||
|
this.courseWinds = courseWinds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map of boat sourceIDs to BoatLocation messages.
|
||||||
|
* @return Map between boat sourceID and BoatLocation.
|
||||||
|
*/
|
||||||
|
public Map<Integer, BoatLocation> getBoatLocationMap() {
|
||||||
|
return boatLocationMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map of boat sourceIDs to BoatStatus messages.
|
||||||
|
* @return Map between boat sourceID and BoatStatus.
|
||||||
|
*/
|
||||||
|
public Map<Integer, BoatStatus> getBoatStatusMap() {
|
||||||
|
return boatStatusMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map of boat sourceIDs to MarkRounding messages.
|
||||||
|
* @return Map between boat sourceID and MarkRounding.
|
||||||
|
*/
|
||||||
|
public Map<Integer, MarkRounding> getMarkRoundingMap() {
|
||||||
|
return markRoundingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest race xml message.
|
||||||
|
* @return The latest race xml message.
|
||||||
|
*/
|
||||||
|
public XMLMessage getRaceXMLMessage() {
|
||||||
|
return raceXMLMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest race xml message to a specified race XML message.
|
||||||
|
* @param raceXMLMessage The new race XML message to use.
|
||||||
|
*/
|
||||||
|
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
|
||||||
|
this.raceXMLMessage = raceXMLMessage;
|
||||||
|
|
||||||
|
this.setChanged();
|
||||||
|
this.notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest boat xml message.
|
||||||
|
* @return The latest boat xml message.
|
||||||
|
*/
|
||||||
|
public XMLMessage getBoatXMLMessage() {
|
||||||
|
return boatXMLMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest boat xml message to a specified boat XML message.
|
||||||
|
* @param boatXMLMessage The new boat XML message to use.
|
||||||
|
*/
|
||||||
|
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
|
||||||
|
this.boatXMLMessage = boatXMLMessage;
|
||||||
|
|
||||||
|
this.setChanged();
|
||||||
|
this.notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest regatta xml message.
|
||||||
|
* @return The latest regatta xml message.
|
||||||
|
*/
|
||||||
|
public XMLMessage getRegattaXMLMessage() {
|
||||||
|
return regattaXMLMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest regatta xml message to a specified regatta XML message.
|
||||||
|
* @param regattaXMLMessage The new regatta XML message to use.
|
||||||
|
*/
|
||||||
|
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
|
||||||
|
this.regattaXMLMessage = regattaXMLMessage;
|
||||||
|
|
||||||
|
this.setChanged();
|
||||||
|
this.notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the type of xml message, and places it in this LatestMessages object.
|
||||||
|
* @param xmlMessage The new xml message to use.
|
||||||
|
*/
|
||||||
|
public void setXMLMessage(XMLMessage xmlMessage) {
|
||||||
|
|
||||||
|
if (xmlMessage.getXmlMsgSubType() == XMLMessageType.RACE) {
|
||||||
|
this.setRaceXMLMessage(xmlMessage);
|
||||||
|
|
||||||
|
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.REGATTA) {
|
||||||
|
this.setRegattaXMLMessage(xmlMessage);
|
||||||
|
|
||||||
|
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.BOAT) {
|
||||||
|
this.setBoatXMLMessage(xmlMessage);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not there is an xml message for each message type.
|
||||||
|
* @return True if race, boat, and regatta have an xml message, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasAllXMLMessages() {
|
||||||
|
|
||||||
|
if ((this.regattaXMLMessage == null) || (this.boatXMLMessage == null) || (this.raceXMLMessage == null)) {
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 25/04/17.
|
* Created by fwy13 on 25/04/17.
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 19/04/17.
|
* Created by fwy13 on 19/04/17.
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package seng302.Networking.Messages;
|
package network.Messages;
|
||||||
|
|
||||||
import seng302.Networking.Messages.Enums.MessageType;
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 25/04/17.
|
* Created by fwy13 on 25/04/17.
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
import network.Messages.Enums.XMLMessageType;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fwy13 on 25/04/17.
|
||||||
|
*/
|
||||||
|
public class XMLMessage extends AC35Data {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version number for xml messages is 1.
|
||||||
|
*/
|
||||||
|
public static byte currentVersionNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version number of the message.
|
||||||
|
*/
|
||||||
|
private byte versionNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ack number of the message.
|
||||||
|
*/
|
||||||
|
private int ackNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the message.
|
||||||
|
* Milliseconds since unix epoch.
|
||||||
|
*/
|
||||||
|
private long timeStamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subtype of the xml message (e.g., race xml message).
|
||||||
|
*/
|
||||||
|
private XMLMessageType xmlMsgSubType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sequence number of this specific xml subtype.
|
||||||
|
* Increments whenever the xml contents for a specific xml subtype changes.
|
||||||
|
*/
|
||||||
|
private int sequenceNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of the xml message.
|
||||||
|
* Number of bytes.
|
||||||
|
*/
|
||||||
|
private int xmlMsgLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contents of the xml message.
|
||||||
|
*/
|
||||||
|
private String xmlMessage;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for an XML Message
|
||||||
|
* @param versionNumber The version number of the xml message.
|
||||||
|
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
|
||||||
|
* @param timeStamp Time received
|
||||||
|
* @param xmlMsgSubType Type of XML message
|
||||||
|
* @param sequenceNumber Order that it has arrived in
|
||||||
|
* @param xmlMessage XML message
|
||||||
|
*/
|
||||||
|
public XMLMessage(byte versionNumber, int ackNumber, long timeStamp, XMLMessageType xmlMsgSubType, int sequenceNumber, String xmlMessage) {
|
||||||
|
super(MessageType.XMLMESSAGE);
|
||||||
|
this.versionNumber = versionNumber;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
this.xmlMsgSubType = xmlMsgSubType;
|
||||||
|
this.sequenceNumber = sequenceNumber;
|
||||||
|
this.xmlMsgLength = xmlMessage.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
this.xmlMessage = xmlMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the XML Message.
|
||||||
|
* @return the XML message as string.
|
||||||
|
*/
|
||||||
|
public String getXmlMessage() {
|
||||||
|
return xmlMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of message
|
||||||
|
* @return Gets the type of message the XML message is
|
||||||
|
*/
|
||||||
|
public XMLMessageType getXmlMsgSubType() {
|
||||||
|
return xmlMsgSubType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the version number of this xml message.
|
||||||
|
* @return The version number of this xml message.
|
||||||
|
*/
|
||||||
|
public byte getVersionNumber() {
|
||||||
|
return versionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ack number of this xml message.
|
||||||
|
* @return The ack number of this xml message.
|
||||||
|
*/
|
||||||
|
public int getAckNumber() {
|
||||||
|
return ackNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the timestamp of this xml message.
|
||||||
|
* @return The timestamp of this xml message.
|
||||||
|
*/
|
||||||
|
public long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sequence number of this xml message. This is specific to each message subtype.
|
||||||
|
* @return The sequence number of this xml message.
|
||||||
|
*/
|
||||||
|
public int getSequenceNumber() {
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length, in number of bytes, of the xml message.
|
||||||
|
* @return The length, in bytes, of the xml message.
|
||||||
|
*/
|
||||||
|
public int getXmlMsgLength() {
|
||||||
|
return xmlMsgLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package seng302.Networking.PacketDump;
|
package network.PacketDump;
|
||||||
|
|
||||||
import seng302.Networking.BinaryMessageDecoder;
|
|
||||||
import seng302.Networking.Exceptions.InvalidMessageException;
|
import network.BinaryMessageDecoder;
|
||||||
import seng302.Networking.Messages.AC35Data;
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.PacketDump;
|
package network.PacketDump;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 25/04/17.
|
* Created by fwy13 on 25/04/17.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.Utils;
|
package network.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 28/04/17.
|
* Created by fwy13 on 28/04/17.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Networking.Utils;
|
package network.Utils;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
|
||||||
|
import shared.model.Boat;
|
||||||
|
import shared.model.Mark;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides information about the boats and marker boats in a race.
|
||||||
|
*/
|
||||||
|
public interface BoatDataSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map between source ID and boat for all boats in the race.
|
||||||
|
* @return Map between source ID and boat.
|
||||||
|
*/
|
||||||
|
Map<Integer, Boat> getBoats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map between source ID and mark for all marks in the race.
|
||||||
|
* @return Map between source ID and mark.
|
||||||
|
*/
|
||||||
|
Map<Integer, Mark> getMarkerBoats();
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
|
import shared.model.Boat;
|
||||||
|
import shared.model.CompoundMark;
|
||||||
|
import shared.model.GPSCoordinate;
|
||||||
|
import shared.model.Leg;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that holds relevant data for a race. <br>
|
||||||
|
* Information includes: {@link shared.model.Boat Boat}s,
|
||||||
|
* {@link shared.model.Leg Leg}s, {@link shared.model.CompoundMark CompoundMark}s and
|
||||||
|
* the {@link shared.model.GPSCoordinate GPSCoordinate}s.
|
||||||
|
*/
|
||||||
|
public interface RaceDataSource {
|
||||||
|
/**
|
||||||
|
* Returns the list of sourceIDs for boats competing in the race.
|
||||||
|
* @return SourceIDs for boats competing in the race.
|
||||||
|
*/
|
||||||
|
List<Integer> getParticipants();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of legs in the race.
|
||||||
|
* @return The list of legs in the race.
|
||||||
|
*/
|
||||||
|
List<Leg> getLegs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of coordinates representing the boundary of the race.
|
||||||
|
* @return The boundary of the race.
|
||||||
|
*/
|
||||||
|
List<GPSCoordinate> getBoundary();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of CompoundMarks in the race.
|
||||||
|
* @return The sequence of compounds marks in the race.
|
||||||
|
*/
|
||||||
|
List<CompoundMark> getCompoundMarks();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the race.
|
||||||
|
* @return The ID of the race.
|
||||||
|
*/
|
||||||
|
int getRaceId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of race.
|
||||||
|
* @return The type of race.
|
||||||
|
*/
|
||||||
|
RaceTypeEnum getRaceType();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start time/date of the race.
|
||||||
|
* @return The race's start time.
|
||||||
|
*/
|
||||||
|
ZonedDateTime getStartDateTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the creation time/date of the race xml file.
|
||||||
|
* @return The race xml file's creation time.
|
||||||
|
*/
|
||||||
|
ZonedDateTime getCreationDateTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the race has been postponed.
|
||||||
|
* @return True if the race has been postponed, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean getPostponed();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GPS coordinate of the top left of the race map area.
|
||||||
|
* @return Top left GPS coordinate.
|
||||||
|
*/
|
||||||
|
GPSCoordinate getMapTopLeft();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GPS coordinate of the bottom right of the race map area.
|
||||||
|
* @return Bottom right GPS coordinate.
|
||||||
|
*/
|
||||||
|
GPSCoordinate getMapBottomRight();
|
||||||
|
}
|
||||||
@ -0,0 +1,463 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
import shared.exceptions.InvalidRaceDataException;
|
||||||
|
import shared.exceptions.XMLReaderException;
|
||||||
|
import shared.model.*;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xml Reader class for Race XML used for the race.
|
||||||
|
*/
|
||||||
|
public class RaceXMLReader extends XMLReader implements RaceDataSource {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GPS coordinate of the top left of the race boundary.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate mapTopLeft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GPS coordinate of the bottom right of the race boundary.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate mapBottomRight;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of GPS coordinates that make up the boundary of the race.
|
||||||
|
*/
|
||||||
|
private final List<GPSCoordinate> boundary = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map between compoundMarkID and a CompoundMark for all CompoundMarks in a race.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, CompoundMark> compoundMarkMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of boat sourceIDs participating in the race.
|
||||||
|
*/
|
||||||
|
private final List<Integer> participants = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of legs in the race.
|
||||||
|
*/
|
||||||
|
private final List<Leg> legs = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time that the race.xml file was created.
|
||||||
|
*/
|
||||||
|
private ZonedDateTime creationTimeDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time that the race should start at, if it hasn't been postponed.
|
||||||
|
*/
|
||||||
|
private ZonedDateTime raceStartTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the race has been postponed.
|
||||||
|
*/
|
||||||
|
private boolean postpone;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID number of the race.
|
||||||
|
*/
|
||||||
|
private int raceID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the race.
|
||||||
|
*/
|
||||||
|
private RaceTypeEnum raceType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for Streamed Race XML
|
||||||
|
* @param file The file to read.
|
||||||
|
* @param type How to read the file - e.g., load as resource.
|
||||||
|
* @throws XMLReaderException Thrown if an XML reader cannot be constructed for the given file.
|
||||||
|
* @throws InvalidRaceDataException Thrown if the XML file is invalid in some way.
|
||||||
|
*/
|
||||||
|
public RaceXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidRaceDataException {
|
||||||
|
|
||||||
|
super(file, type);
|
||||||
|
|
||||||
|
|
||||||
|
//Attempt to read race xml file.
|
||||||
|
try {
|
||||||
|
read();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidRaceDataException("An error occurred while reading the race xml file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the contents of the race xml file.
|
||||||
|
* @throws InvalidRaceDataException Thrown if we cannot parse the document properly.
|
||||||
|
*/
|
||||||
|
private void read() throws InvalidRaceDataException {
|
||||||
|
readRace();
|
||||||
|
readParticipants();
|
||||||
|
readCourse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads race related data from the race xml file.
|
||||||
|
*/
|
||||||
|
private void readRace() {
|
||||||
|
|
||||||
|
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||||
|
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
|
||||||
|
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
|
||||||
|
|
||||||
|
if (raceTimeTag.getNamedItem("Time") != null) {
|
||||||
|
dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Race ID.
|
||||||
|
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
|
||||||
|
|
||||||
|
//Race type.
|
||||||
|
String raceTypeString = getTextValueOfNode(settings, "RaceType");
|
||||||
|
raceType = RaceTypeEnum.fromString(raceTypeString);
|
||||||
|
|
||||||
|
//XML creation time.
|
||||||
|
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
|
||||||
|
|
||||||
|
//Race start time.
|
||||||
|
if (raceTimeTag.getNamedItem("Time") != null) {
|
||||||
|
raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Postpone status.
|
||||||
|
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads in the participants for this race.
|
||||||
|
*/
|
||||||
|
private void readParticipants() {
|
||||||
|
|
||||||
|
//Gets the "<Participants>..</Participants>" element.
|
||||||
|
Element participants = (Element) doc.getElementsByTagName("Participants").item(0);
|
||||||
|
|
||||||
|
//Gets the number of participants.
|
||||||
|
int numberOfParticipants = participants.getChildNodes().getLength();
|
||||||
|
|
||||||
|
//For each participant, read its sourceID.
|
||||||
|
for (int i = 0; i < numberOfParticipants; i++) {
|
||||||
|
|
||||||
|
//Get the participating yacht.
|
||||||
|
Node yacht = participants.getChildNodes().item(i);
|
||||||
|
|
||||||
|
if (yacht.getNodeName().equals("Yacht")) {
|
||||||
|
if (exists(yacht, "SourceID")) {
|
||||||
|
|
||||||
|
//If the node is a valid yacht with a sourceID, add it to participant list.
|
||||||
|
int sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
|
||||||
|
this.participants.add(sourceID);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads course data from the xml file.
|
||||||
|
* @throws InvalidRaceDataException Thrown if we cannot parse the document properly.
|
||||||
|
*/
|
||||||
|
private void readCourse() throws InvalidRaceDataException {
|
||||||
|
readCompoundMarks();
|
||||||
|
readCompoundMarkSequence();
|
||||||
|
readCourseLimits();
|
||||||
|
readMapTopLeft();
|
||||||
|
readMapBottomRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
|
||||||
|
* @throws InvalidRaceDataException thrown if we cannot create a compound mark from the document.
|
||||||
|
* @see CompoundMark
|
||||||
|
*/
|
||||||
|
private void readCompoundMarks() throws InvalidRaceDataException {
|
||||||
|
|
||||||
|
//Gets the "<Course>...</..>" element.
|
||||||
|
Element course = (Element) doc.getElementsByTagName("Course").item(0);
|
||||||
|
|
||||||
|
//Get the list of CompoundMark elements.
|
||||||
|
NodeList compoundMarkList = course.getElementsByTagName("CompoundMark");
|
||||||
|
|
||||||
|
//Number of compound marks in the course.
|
||||||
|
int numberOfCompoundMarks = compoundMarkList.getLength();
|
||||||
|
|
||||||
|
//For each CompoundMark element, create a CompoundMark object.
|
||||||
|
for(int i = 0; i < numberOfCompoundMarks; i++) {
|
||||||
|
|
||||||
|
//Get the CompoundMark element.
|
||||||
|
Element compoundMarkElement = (Element) compoundMarkList.item(i);
|
||||||
|
|
||||||
|
|
||||||
|
//Convert to CompoundMark object.
|
||||||
|
CompoundMark compoundMark = createCompoundMark(compoundMarkElement);
|
||||||
|
|
||||||
|
compoundMarkMap.put(compoundMark.getId(), compoundMark);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a CompoundMark from a given CompondMark element.
|
||||||
|
* @param compoundMarkElement The CompoundMark element to turn into a CompoundMark object.
|
||||||
|
* @return The corresponding CompoundMark object.
|
||||||
|
* @throws InvalidRaceDataException If the element cannot be converted into a CompoundMark.
|
||||||
|
*/
|
||||||
|
private CompoundMark createCompoundMark(Element compoundMarkElement) throws InvalidRaceDataException {
|
||||||
|
|
||||||
|
//CompoundMark ID.
|
||||||
|
int compoundMarkID = getCompoundMarkID(compoundMarkElement);
|
||||||
|
|
||||||
|
//CompoundMark name.
|
||||||
|
String compoundMarkName = getCompoundMarkName(compoundMarkElement);
|
||||||
|
|
||||||
|
|
||||||
|
//Get the list of marks within the compound mark.
|
||||||
|
NodeList marks = compoundMarkElement.getElementsByTagName("Mark");
|
||||||
|
CompoundMark compoundMark;
|
||||||
|
|
||||||
|
switch(marks.getLength()) {
|
||||||
|
case 1: {
|
||||||
|
//Create the Mark sub-object.
|
||||||
|
Mark mark1 = createMark((Element) marks.item(0));
|
||||||
|
|
||||||
|
//Create compound mark.
|
||||||
|
compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} case 2: {
|
||||||
|
//Create the Mark sub-objects.
|
||||||
|
Mark mark1 = createMark((Element) marks.item(0));
|
||||||
|
Mark mark2 = createMark((Element) marks.item(1));
|
||||||
|
|
||||||
|
//Create compound mark.
|
||||||
|
compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1, mark2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} default: {
|
||||||
|
throw new InvalidRaceDataException("Cannot create CompoundMark from " + compoundMarkElement.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundMark;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a mark from an Element.
|
||||||
|
* @param mark The {@link Element} describing the {@link Mark}.
|
||||||
|
* @return The {@link Mark}.
|
||||||
|
*/
|
||||||
|
private Mark createMark(Element mark) {
|
||||||
|
|
||||||
|
//Source ID.
|
||||||
|
int sourceID = Integer.parseInt(mark.getAttribute("SourceID"));
|
||||||
|
|
||||||
|
//Name.
|
||||||
|
String name = mark.getAttribute("Name");
|
||||||
|
|
||||||
|
//Latitude.
|
||||||
|
double latitude = Double.parseDouble(mark.getAttribute("TargetLat"));
|
||||||
|
|
||||||
|
//Longitude.
|
||||||
|
double longitude = Double.parseDouble(mark.getAttribute("TargetLng"));
|
||||||
|
|
||||||
|
//Create mark.
|
||||||
|
return new Mark(sourceID, name, new GPSCoordinate(latitude, longitude));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads "compoundMarkID" attribute of CompoundMark or Corner element.
|
||||||
|
* @param element with "compoundMarkID" attribute.
|
||||||
|
* @return value of "compoundMarkID" attribute.
|
||||||
|
*/
|
||||||
|
private int getCompoundMarkID(Element element) {
|
||||||
|
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads "Name" attribute of a CompoundMark element.
|
||||||
|
* @param element The CompoundMark element with a "Name" attribute.
|
||||||
|
* @return value of "name" attribute.
|
||||||
|
*/
|
||||||
|
private String getCompoundMarkName(Element element) {
|
||||||
|
return element.getAttribute("Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
|
||||||
|
*/
|
||||||
|
private void readCompoundMarkSequence() {
|
||||||
|
|
||||||
|
//The "<CompoundMarkSequence>...</...>" element. This contains a sequence of Corner elements.
|
||||||
|
Element compoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
|
||||||
|
|
||||||
|
//Gets the list of Corner elements.
|
||||||
|
NodeList corners = compoundMarkSequence.getElementsByTagName("Corner");
|
||||||
|
|
||||||
|
//Gets the first corner.
|
||||||
|
Element cornerElement = (Element) corners.item(0);
|
||||||
|
|
||||||
|
//Gets the ID number of this corner element.
|
||||||
|
int cornerID = getCompoundMarkID(cornerElement);
|
||||||
|
|
||||||
|
//Gets the CompoundMark associated with this corner.
|
||||||
|
CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID);
|
||||||
|
|
||||||
|
//The name of the leg is the name of the first compoundMark in the leg.
|
||||||
|
String legName = lastCompoundMark.getName();
|
||||||
|
|
||||||
|
//For each following corner, create a leg between cornerN and cornerN+1.
|
||||||
|
for(int i = 1; i < corners.getLength(); i++) {
|
||||||
|
|
||||||
|
//Gets the next corner element.
|
||||||
|
cornerElement = (Element) corners.item(i);
|
||||||
|
|
||||||
|
//Gets the ID number of this corner element.
|
||||||
|
cornerID = getCompoundMarkID(cornerElement);
|
||||||
|
|
||||||
|
//Gets the CompoundMark associated with this corner.
|
||||||
|
CompoundMark currentCompoundMark = this.compoundMarkMap.get(cornerID);
|
||||||
|
|
||||||
|
//Create a leg from these two adjacent compound marks.
|
||||||
|
Leg leg = new Leg(legName, lastCompoundMark, currentCompoundMark, i - 1);
|
||||||
|
legs.add(leg);
|
||||||
|
|
||||||
|
//Prepare for next iteration.
|
||||||
|
lastCompoundMark = currentCompoundMark;
|
||||||
|
legName = lastCompoundMark.getName();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the boundary limits of the course.
|
||||||
|
*/
|
||||||
|
private void readCourseLimits() {
|
||||||
|
|
||||||
|
//The "<CourseLimit>...</...>" element. This contains a sequence of Limit elements.
|
||||||
|
Element courseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
|
||||||
|
|
||||||
|
//Get the list of Limit elements.
|
||||||
|
NodeList limitList = courseLimit.getElementsByTagName("Limit");
|
||||||
|
|
||||||
|
//For each limit element...
|
||||||
|
for(int i = 0; i < limitList.getLength(); i++) {
|
||||||
|
|
||||||
|
//Get the Limit element.
|
||||||
|
Element limit = (Element) limitList.item(i);
|
||||||
|
|
||||||
|
//Convert to GPSCoordinate.
|
||||||
|
double latitude = Double.parseDouble(limit.getAttribute("Lat"));
|
||||||
|
double longitude = Double.parseDouble(limit.getAttribute("Lon"));
|
||||||
|
boundary.add(new GPSCoordinate(latitude, longitude));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the gps coordinate of the top left of the map, using the course limits.
|
||||||
|
*/
|
||||||
|
private void readMapTopLeft(){
|
||||||
|
|
||||||
|
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude();
|
||||||
|
|
||||||
|
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude();
|
||||||
|
|
||||||
|
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the gps coordinate of the bottom right of the map, using the course limits.
|
||||||
|
*/
|
||||||
|
private void readMapBottomRight(){
|
||||||
|
|
||||||
|
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude();
|
||||||
|
|
||||||
|
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude();
|
||||||
|
|
||||||
|
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public List<GPSCoordinate> getBoundary() {
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapTopLeft() {
|
||||||
|
return mapTopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapBottomRight() {
|
||||||
|
return mapBottomRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Leg> getLegs() {
|
||||||
|
return legs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CompoundMark> getCompoundMarks() {
|
||||||
|
return new ArrayList<>(compoundMarkMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationDateTime() {
|
||||||
|
return creationTimeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getStartDateTime() {
|
||||||
|
return raceStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRaceId() {
|
||||||
|
return raceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaceTypeEnum getRaceType() {
|
||||||
|
return raceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPostponed() {
|
||||||
|
return postpone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getParticipants() {
|
||||||
|
return participants;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides information about a race regatta.
|
||||||
|
*/
|
||||||
|
public interface RegattaDataSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the regatta.
|
||||||
|
* @return The ID of the regatta.
|
||||||
|
*/
|
||||||
|
int getRegattaID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the regatta.
|
||||||
|
* @return The name of the regatta.
|
||||||
|
*/
|
||||||
|
String getRegattaName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the race this regatta relates to.
|
||||||
|
* @return The ID of the race that this regatta relates to.
|
||||||
|
*/
|
||||||
|
int getRaceID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the course.
|
||||||
|
* @return the name of the course
|
||||||
|
*/
|
||||||
|
String getCourseName();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latitude of the centre of the course.
|
||||||
|
* @return The latitude of the centre of the course.
|
||||||
|
*/
|
||||||
|
double getCentralLatitude();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the longitude of the centre of the course.
|
||||||
|
* @return The longitude of the centre of the course.
|
||||||
|
*/
|
||||||
|
double getCentralLongitude();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the altitude of the centre of the course.
|
||||||
|
* @return The altitude of the centre of the course.
|
||||||
|
*/
|
||||||
|
double getCentralAltitude();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the UTC offset of the course's location.
|
||||||
|
* @return The UTC offset of the course.
|
||||||
|
*/
|
||||||
|
float getUtcOffset();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the magnetic variation of the course's location.
|
||||||
|
* @return The magnetic variation of the course.
|
||||||
|
*/
|
||||||
|
float getMagneticVariation();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,189 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
import shared.exceptions.XMLReaderException;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.OutputKeys;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
|
import javax.xml.transform.dom.DOMSource;
|
||||||
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Reader for XML Files
|
||||||
|
*/
|
||||||
|
public abstract class XMLReader {
|
||||||
|
|
||||||
|
protected Document doc;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an XML file.
|
||||||
|
* @param file The file to read.
|
||||||
|
* @param type How to read the file - e.g., load as resource.
|
||||||
|
* @throws XMLReaderException Throw if the file cannot be parsed.
|
||||||
|
*/
|
||||||
|
public XMLReader(String file, XMLFileType type) throws XMLReaderException {
|
||||||
|
|
||||||
|
|
||||||
|
InputStream xmlInputStream = null;
|
||||||
|
|
||||||
|
//Create an input stream. Method depends on type parameter.
|
||||||
|
|
||||||
|
if (type == XMLFileType.Contents) {
|
||||||
|
//Wrap file contents in input stream.
|
||||||
|
xmlInputStream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
} else if (type == XMLFileType.ResourcePath) {
|
||||||
|
xmlInputStream = XMLReader.class.getClassLoader().getResourceAsStream(file);
|
||||||
|
|
||||||
|
} else if (type == XMLFileType.FilePath) {
|
||||||
|
try {
|
||||||
|
xmlInputStream = new FileInputStream(file);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new XMLReaderException("Could not open file " + file, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.doc = parseInputStream(xmlInputStream);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an XML file from an input stream.
|
||||||
|
* @param xmlInputStream The input stream to parse.
|
||||||
|
* @throws XMLReaderException Thrown if the input stream cannot be parsed.
|
||||||
|
*/
|
||||||
|
public XMLReader(InputStream xmlInputStream) throws XMLReaderException {
|
||||||
|
|
||||||
|
this.doc = parseInputStream(xmlInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an input stream into a document.
|
||||||
|
* @param inputStream The xml input stream to parse.
|
||||||
|
* @return The parsed document.
|
||||||
|
* @throws XMLReaderException Thrown when a document builder cannot be constructed, or the stream cannot be parsed.
|
||||||
|
*/
|
||||||
|
private static Document parseInputStream(InputStream inputStream) throws XMLReaderException {
|
||||||
|
|
||||||
|
//Create document builder.
|
||||||
|
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
|
||||||
|
DocumentBuilder dBuilder = null;
|
||||||
|
try {
|
||||||
|
dBuilder = dbFactory.newDocumentBuilder();
|
||||||
|
} catch (ParserConfigurationException e) {
|
||||||
|
throw new XMLReaderException("Could not create a DocumentBuilder.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse document.
|
||||||
|
Document document = null;
|
||||||
|
try {
|
||||||
|
document = dBuilder.parse(inputStream);
|
||||||
|
} catch (SAXException | IOException e) {
|
||||||
|
throw new XMLReaderException("Could not parse the xml input stream.", e);
|
||||||
|
}
|
||||||
|
document.getDocumentElement().normalize();
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return Document data of the read-in XML
|
||||||
|
* @return XML document
|
||||||
|
*/
|
||||||
|
public Document getDocument() {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content of a tag in an element
|
||||||
|
* @param n Element to read tags from
|
||||||
|
* @param tagName Name of the tag
|
||||||
|
* @return Content of the tag
|
||||||
|
*/
|
||||||
|
public String getTextValueOfNode(Element n, String tagName) {
|
||||||
|
return n.getElementsByTagName(tagName).item(0).getTextContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get attributes for an element
|
||||||
|
* @param n Element to read attributes from
|
||||||
|
* @param attr Attributes of element
|
||||||
|
* @return Attributes of element
|
||||||
|
*/
|
||||||
|
public String getAttribute(Element n, String attr) {
|
||||||
|
return n.getAttribute(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean exists(Node node, String attribute) {
|
||||||
|
return node.getAttributes().getNamedItem(attribute) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the contents of the XML FILe.
|
||||||
|
* @param document holds all xml information
|
||||||
|
* @return String representation of document
|
||||||
|
* @throws TransformerException when document is malformed, and cannot be turned into a string
|
||||||
|
*/
|
||||||
|
public static String getContents(Document document) throws TransformerException {
|
||||||
|
DOMSource source = new DOMSource(document);
|
||||||
|
|
||||||
|
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||||
|
Transformer transformer = transformerFactory.newTransformer();
|
||||||
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||||
|
|
||||||
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
StreamResult result = new StreamResult(stringWriter);
|
||||||
|
transformer.transform(source, result);
|
||||||
|
|
||||||
|
return stringWriter.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the an XML file, as a resource, into a string.
|
||||||
|
* @param path path of the XML
|
||||||
|
* @param encoding encoding of the xml
|
||||||
|
* @return A string containing the contents of the specified file.
|
||||||
|
* @throws TransformerException Issue with the XML format
|
||||||
|
* @throws XMLReaderException Thrown if file cannot be read for some reason.
|
||||||
|
*/
|
||||||
|
public static String readXMLFileToString(String path, Charset encoding) throws TransformerException, XMLReaderException {
|
||||||
|
|
||||||
|
InputStream fileStream = XMLReader.class.getClassLoader().getResourceAsStream(path);
|
||||||
|
|
||||||
|
//Resource can't be found.
|
||||||
|
if (fileStream == null) {
|
||||||
|
throw new XMLReaderException("Could not open resource: " + path, new IOException());
|
||||||
|
}
|
||||||
|
|
||||||
|
Document doc = XMLReader.parseInputStream(fileStream);
|
||||||
|
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
|
||||||
|
return XMLReader.getContents(doc);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package shared.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the various ways in which we want to open/read an xml file - e.g., a string may contain the file contents, or the file path.
|
||||||
|
*/
|
||||||
|
public enum XMLFileType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This means that a provided string contains the contents of an XML file.
|
||||||
|
*/
|
||||||
|
Contents,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This means that a provided string contains the path to an XML file.
|
||||||
|
*/
|
||||||
|
FilePath,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This means that a provided string contains the path, to be loaded as a resource, of an XML file.
|
||||||
|
*/
|
||||||
|
ResourcePath;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we cannot generate Boats.xml and send an XML message, or we cannot parse a Boats.xml file.
|
||||||
|
*/
|
||||||
|
public class InvalidBoatDataException extends Exception {
|
||||||
|
|
||||||
|
public InvalidBoatDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidBoatDataException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when we cannot generate Race.xml data, and send an XML message, or we cannot parse a Race.xml file.
|
||||||
|
*/
|
||||||
|
public class InvalidRaceDataException extends Exception {
|
||||||
|
|
||||||
|
public InvalidRaceDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidRaceDataException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we cannot generate Regatta.xml and send an XML message, or we cannot parse a Regatta.xml file.
|
||||||
|
*/
|
||||||
|
public class InvalidRegattaDataException extends Exception {
|
||||||
|
|
||||||
|
public InvalidRegattaDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidRegattaDataException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when an XMLReader cannot be constructed for some reason.
|
||||||
|
*/
|
||||||
|
public class XMLReaderException extends Exception {
|
||||||
|
|
||||||
|
public XMLReaderException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XMLReaderException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Model;
|
package shared.model;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -0,0 +1,346 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
|
import network.Messages.LatestMessages;
|
||||||
|
import shared.dataInput.BoatDataSource;
|
||||||
|
import shared.dataInput.RaceDataSource;
|
||||||
|
import shared.dataInput.RegattaDataSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a yacht race.
|
||||||
|
* This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}.
|
||||||
|
* Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}.
|
||||||
|
*/
|
||||||
|
public abstract class Race implements Runnable {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source of race related data.
|
||||||
|
*/
|
||||||
|
protected RaceDataSource raceDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source of boat related data.
|
||||||
|
*/
|
||||||
|
protected BoatDataSource boatDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source of regatta related data.
|
||||||
|
*/
|
||||||
|
protected RegattaDataSource regattaDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collection of latest race messages.
|
||||||
|
* Can be either read from or written to.
|
||||||
|
*/
|
||||||
|
protected LatestMessages latestMessages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sequence number of the latest BoatLocation message sent or received.
|
||||||
|
*/
|
||||||
|
protected int boatLocationSequenceNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sequence number of the latest RaceStatus message sent or received.
|
||||||
|
*/
|
||||||
|
protected int raceStatusSequenceNumber = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of compound marks in the race.
|
||||||
|
*/
|
||||||
|
protected List<CompoundMark> compoundMarks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of legs in the race.
|
||||||
|
*/
|
||||||
|
protected List<Leg> legs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of coordinates describing the boundary of the course.
|
||||||
|
*/
|
||||||
|
protected List<GPSCoordinate> boundary;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The clock which tracks the race's start time, current time, and elapsed duration.
|
||||||
|
*/
|
||||||
|
protected RaceClock raceClock;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race ID of the course.
|
||||||
|
*/
|
||||||
|
protected int raceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the regatta.
|
||||||
|
*/
|
||||||
|
protected String regattaName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of the race.
|
||||||
|
*/
|
||||||
|
protected RaceStatusEnum raceStatusEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of race this is.
|
||||||
|
*/
|
||||||
|
protected RaceTypeEnum raceType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current wind direction bearing.
|
||||||
|
*/
|
||||||
|
protected Bearing windDirection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wind speed (knots).
|
||||||
|
* Convert this to millimeters per second before passing to RaceStatus.
|
||||||
|
*/
|
||||||
|
protected double windSpeed;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of frames per second.
|
||||||
|
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset.
|
||||||
|
*/
|
||||||
|
private int currentFps = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of frames per second we generated over the last 1 second period.
|
||||||
|
*/
|
||||||
|
private IntegerProperty lastFps = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
|
||||||
|
*/
|
||||||
|
private long lastFpsResetTime;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource.
|
||||||
|
* @param boatDataSource Data source for boat related data (yachts and marker boats).
|
||||||
|
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
|
||||||
|
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
|
||||||
|
* @param latestMessages The collection of latest messages, which can be written to, or read from.
|
||||||
|
*/
|
||||||
|
public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages) {
|
||||||
|
|
||||||
|
//Keep a reference to data sources.
|
||||||
|
this.raceDataSource = raceDataSource;
|
||||||
|
this.boatDataSource = boatDataSource;
|
||||||
|
this.regattaDataSource = regattaDataSource;
|
||||||
|
|
||||||
|
this.latestMessages = latestMessages;
|
||||||
|
|
||||||
|
|
||||||
|
//Marks.
|
||||||
|
this.compoundMarks = raceDataSource.getCompoundMarks();
|
||||||
|
|
||||||
|
//Boundaries.
|
||||||
|
this.boundary = raceDataSource.getBoundary();
|
||||||
|
|
||||||
|
|
||||||
|
//Legs.
|
||||||
|
this.useLegsList(raceDataSource.getLegs());
|
||||||
|
|
||||||
|
|
||||||
|
//Race ID.
|
||||||
|
this.raceId = raceDataSource.getRaceId();
|
||||||
|
|
||||||
|
//Regatta name.
|
||||||
|
this.regattaName = regattaDataSource.getRegattaName();
|
||||||
|
|
||||||
|
//Race clock.
|
||||||
|
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
|
||||||
|
|
||||||
|
//Race status.
|
||||||
|
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
|
||||||
|
//Race type.
|
||||||
|
this.raceType = raceDataSource.getRaceType();
|
||||||
|
|
||||||
|
//Wind speed.
|
||||||
|
this.windSpeed = 0;
|
||||||
|
//Wind direction.
|
||||||
|
this.windDirection = Bearing.fromDegrees(0);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the boats in the race.
|
||||||
|
* This sets their starting positions and current legs.
|
||||||
|
*/
|
||||||
|
protected abstract void initialiseBoats();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the race to use a new list of legs, and adds a dummy "Finish" leg at the end.
|
||||||
|
* @param legs The new list of legs to use.
|
||||||
|
*/
|
||||||
|
protected void useLegsList(List<Leg> legs) {
|
||||||
|
//We add a "dummy" leg at the end of the race.
|
||||||
|
this.legs = legs;
|
||||||
|
this.legs.add(new Leg("Finish", this.legs.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not a specific leg is the last leg in the race.
|
||||||
|
* @param leg The leg to check.
|
||||||
|
* @return Returns true if it is the last, false otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isLastLeg(Leg leg) {
|
||||||
|
|
||||||
|
//Get the last leg.
|
||||||
|
Leg lastLeg = this.legs.get(this.legs.size() - 1);
|
||||||
|
|
||||||
|
//Check its ID.
|
||||||
|
int lastLegID = lastLeg.getLegNumber();
|
||||||
|
|
||||||
|
//Get the specified leg's ID.
|
||||||
|
int legID = leg.getLegNumber();
|
||||||
|
|
||||||
|
|
||||||
|
//Check if they are the same.
|
||||||
|
return legID == lastLegID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current race status.
|
||||||
|
* @return The current race status.
|
||||||
|
*/
|
||||||
|
public RaceStatusEnum getRaceStatusEnum() {
|
||||||
|
return raceStatusEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current race status.
|
||||||
|
* @param raceStatusEnum The new status of the race.
|
||||||
|
*/
|
||||||
|
protected void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
|
||||||
|
this.raceStatusEnum = raceStatusEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of race this is.
|
||||||
|
* @return The type of race this is.
|
||||||
|
*/
|
||||||
|
public RaceTypeEnum getRaceType() {
|
||||||
|
return raceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the regatta.
|
||||||
|
* @return The name of the regatta.
|
||||||
|
*/
|
||||||
|
public String getRegattaName() {
|
||||||
|
return regattaName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wind bearing.
|
||||||
|
* @return The wind bearing.
|
||||||
|
*/
|
||||||
|
public Bearing getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wind speed.
|
||||||
|
* Measured in knots.
|
||||||
|
* @return The wind speed.
|
||||||
|
*/
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RaceClock for this race.
|
||||||
|
* This is used to track the start time, current time, and elapsed duration of the race.
|
||||||
|
* @return The RaceClock for the race.
|
||||||
|
*/
|
||||||
|
public RaceClock getRaceClock() {
|
||||||
|
return raceClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RaceDataSource used for the race.
|
||||||
|
* @return The RaceDataSource used for the race.
|
||||||
|
*/
|
||||||
|
public RaceDataSource getRaceDataSource() {
|
||||||
|
return raceDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of legs in the race.
|
||||||
|
* @return The number of legs in the race.
|
||||||
|
*/
|
||||||
|
public int getLegCount() {
|
||||||
|
//We minus one, as we have added an extra "dummy" leg.
|
||||||
|
return legs.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race boundary.
|
||||||
|
* @return The race boundary.
|
||||||
|
*/
|
||||||
|
public List<GPSCoordinate> getBoundary() {
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of frames generated per second.
|
||||||
|
* @return Frames per second.
|
||||||
|
*/
|
||||||
|
public int getFps() {
|
||||||
|
return lastFps.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fps property.
|
||||||
|
* @return The fps property.
|
||||||
|
*/
|
||||||
|
public IntegerProperty fpsProperty() {
|
||||||
|
return lastFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
|
||||||
|
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
|
||||||
|
*/
|
||||||
|
protected void incrementFps(long timePeriod) {
|
||||||
|
//Increment.
|
||||||
|
this.currentFps++;
|
||||||
|
|
||||||
|
//Add period to timer.
|
||||||
|
this.lastFpsResetTime += timePeriod;
|
||||||
|
|
||||||
|
//If we have reached 1 second period, snapshot the framerate and reset.
|
||||||
|
if (this.lastFpsResetTime > 1000) {
|
||||||
|
this.lastFps.set(this.currentFps);
|
||||||
|
|
||||||
|
this.currentFps = 0;
|
||||||
|
this.lastFpsResetTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,305 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import visualiser.model.ResizableRaceCanvas;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to implement a clock which keeps track of and
|
||||||
|
* displays times relevant to a race. This is displayed on the
|
||||||
|
* {@link ResizableRaceCanvas} via the
|
||||||
|
* {@link visualiser.Controllers.RaceController} and the
|
||||||
|
* {@link visualiser.Controllers.StartController}.
|
||||||
|
*/
|
||||||
|
public class RaceClock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time zone of the race.
|
||||||
|
*/
|
||||||
|
private final ZoneId zoneId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start time of the race.
|
||||||
|
*/
|
||||||
|
private ZonedDateTime startingTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current time of the race.
|
||||||
|
*/
|
||||||
|
private final StringProperty startingTimeProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current time of the race.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ZonedDateTime currentTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current time of the race.
|
||||||
|
*/
|
||||||
|
private final StringProperty currentTimeProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time until the race starts, or elapsed time in the race after it has started.
|
||||||
|
*/
|
||||||
|
private StringProperty durationProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
|
|
||||||
|
//Format strings.
|
||||||
|
/**
|
||||||
|
* Format string used for starting time.
|
||||||
|
*/
|
||||||
|
private String startingTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format string used for current time.
|
||||||
|
*/
|
||||||
|
private String currentTimeFormat = "'Current time:' HH:mm dd/MM/YYYY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format string used for duration before it has started.
|
||||||
|
*/
|
||||||
|
private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d";
|
||||||
|
/**
|
||||||
|
* Format string used for duration once the race has started.
|
||||||
|
*/
|
||||||
|
private String durationAfterStartFormat = "Time: %02d:%02d:%02d";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a RaceClock using a specified starting ZonedDateTime.
|
||||||
|
* @param startingTime The ZonedDateTime that the race starts at.
|
||||||
|
*/
|
||||||
|
public RaceClock(ZonedDateTime startingTime) {
|
||||||
|
this.zoneId = startingTime.getZone();
|
||||||
|
|
||||||
|
//Set start time.
|
||||||
|
setStartingTime(startingTime);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
|
||||||
|
* @param time UTC time.
|
||||||
|
*/
|
||||||
|
public void setUTCTime(long time) {
|
||||||
|
Date utcTime = new Date(time);
|
||||||
|
setCurrentTime(utcTime.toInstant().atZone(this.zoneId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ZonedDateTime corresponding to local time zone and given UTC time.
|
||||||
|
* @param time time in mills
|
||||||
|
* @return local date time
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getLocalTime(long time) {
|
||||||
|
Date utcTime = new Date(time);
|
||||||
|
return utcTime.toInstant().atZone(this.zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the starting time of the race.
|
||||||
|
* @return The starting time of the race.
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getStartingTime() {
|
||||||
|
return startingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race start time, expressed as the number of milliseconds since the unix epoch.
|
||||||
|
* @return Start time expressed as milliseconds since unix epoch.
|
||||||
|
*/
|
||||||
|
public long getStartingTimeMilli() {
|
||||||
|
return startingTime.toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the starting time of the race.
|
||||||
|
* @param startingTime The starting time of the race.
|
||||||
|
*/
|
||||||
|
public void setStartingTime(ZonedDateTime startingTime) {
|
||||||
|
this.startingTime = startingTime;
|
||||||
|
|
||||||
|
//Convert time into string.
|
||||||
|
String startingTimeString = DateTimeFormatter.ofPattern(this.startingTimeFormat).format(startingTime);
|
||||||
|
|
||||||
|
//Use it.
|
||||||
|
setStartingTimeString(startingTimeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the starting time of the race, as a string.
|
||||||
|
* @return The starting time of the race, as a string.
|
||||||
|
*/
|
||||||
|
public String getStartingTimeString() {
|
||||||
|
return startingTimeProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the starting time string of the race.
|
||||||
|
* This should only be called by {@link #setStartingTime(ZonedDateTime)}.
|
||||||
|
* @param startingTime The new value for the starting time string.
|
||||||
|
*/
|
||||||
|
private void setStartingTimeString(String startingTime) {
|
||||||
|
this.startingTimeProperty.setValue(startingTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the starting time property.
|
||||||
|
* @return The starting time property.
|
||||||
|
*/
|
||||||
|
public StringProperty startingTimeProperty() {
|
||||||
|
return startingTimeProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race duration, in milliseconds.
|
||||||
|
* A negative value means that the race has not started.
|
||||||
|
* @return Race duration in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getDurationMilli() {
|
||||||
|
return getCurrentTimeMilli() - getStartingTimeMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race duration, as a string.
|
||||||
|
* @return Duration as a string.
|
||||||
|
*/
|
||||||
|
public String getDurationString() {
|
||||||
|
return durationProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration time string of the race.
|
||||||
|
* @param duration The new value for the duration time string.
|
||||||
|
*/
|
||||||
|
private void setDurationString(String duration) {
|
||||||
|
this.durationProperty.setValue(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration property.
|
||||||
|
* @return The duration property.
|
||||||
|
*/
|
||||||
|
public StringProperty durationProperty() {
|
||||||
|
return durationProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current time of the race.
|
||||||
|
* @return The current time of the race.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public ZonedDateTime getCurrentTime() {
|
||||||
|
return currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the race current time, expressed as the number of milliseconds since the unix epoch.
|
||||||
|
* @return Current time expressed as milliseconds since unix epoch.
|
||||||
|
*/
|
||||||
|
public long getCurrentTimeMilli() {
|
||||||
|
return currentTime.toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current time of the race.
|
||||||
|
* @param currentTime The current time of the race.
|
||||||
|
*/
|
||||||
|
private void setCurrentTime(ZonedDateTime currentTime) {
|
||||||
|
this.currentTime = currentTime;
|
||||||
|
|
||||||
|
//Convert time into string.
|
||||||
|
String currentTimeString = DateTimeFormatter.ofPattern(this.currentTimeFormat).format(currentTime);
|
||||||
|
|
||||||
|
//Use it.
|
||||||
|
setCurrentTimeString(currentTimeString);
|
||||||
|
|
||||||
|
|
||||||
|
//Update the duration string.
|
||||||
|
updateDurationString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the duration string based on the start time and current time.
|
||||||
|
* This requires {@link #currentTime} to be non-null.
|
||||||
|
*/
|
||||||
|
private void updateDurationString() {
|
||||||
|
//Calculates the duration in seconds.
|
||||||
|
long seconds = Duration.between(startingTime.toLocalDateTime(), currentTime.toLocalDateTime()).getSeconds();
|
||||||
|
|
||||||
|
//Check if the race has already started or not. This determines the format string used.
|
||||||
|
String formatString;
|
||||||
|
if (seconds < 0) {
|
||||||
|
//Race hasn't started.
|
||||||
|
formatString = this.durationBeforeStartFormat;
|
||||||
|
//The seconds value is negative, so we make it positive.
|
||||||
|
seconds = seconds * -1;
|
||||||
|
} else {
|
||||||
|
//Race has started.
|
||||||
|
formatString = this.durationAfterStartFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Format the seconds value.
|
||||||
|
//Hours : minutes : seconds.
|
||||||
|
String formattedDuration = String.format(formatString, seconds / 3600, (seconds % 3600) / 60, seconds % 60);
|
||||||
|
|
||||||
|
//Use it.
|
||||||
|
setDurationString(formattedDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current time of the race, as a string.
|
||||||
|
* @return The current time of the race, as a string.
|
||||||
|
*/
|
||||||
|
public String getCurrentTimeString() {
|
||||||
|
return currentTimeProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current time string of the race.
|
||||||
|
* @param currentTime The new value for the current time string.
|
||||||
|
*/
|
||||||
|
private void setCurrentTimeString(String currentTime) {
|
||||||
|
this.currentTimeProperty.setValue(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current time property.
|
||||||
|
* @return The current time property.
|
||||||
|
*/
|
||||||
|
public StringProperty currentTimeProperty() {
|
||||||
|
return currentTimeProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time zone of the race, as a string.
|
||||||
|
* @return The race time zone.
|
||||||
|
*/
|
||||||
|
public String getTimeZone() {
|
||||||
|
return zoneId.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package seng302.Controllers;
|
package visualiser.Controllers;
|
||||||
|
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
|
||||||
@ -0,0 +1,383 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.chart.LineChart;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
|
import shared.model.Leg;
|
||||||
|
import visualiser.app.VisualiserInput;
|
||||||
|
import visualiser.model.*;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used to display a running race.
|
||||||
|
*/
|
||||||
|
public class RaceController extends Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object used to read packets from the connected server.
|
||||||
|
*/
|
||||||
|
private VisualiserInput visualiserInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race object which describes the currently occurring race.
|
||||||
|
*/
|
||||||
|
private VisualiserRace visualiserRace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats.
|
||||||
|
*/
|
||||||
|
private ObservableList<VisualiserBoat> tableBoatList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canvas that draws the race.
|
||||||
|
*/
|
||||||
|
private ResizableRaceCanvas raceCanvas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sparkline graph.
|
||||||
|
*/
|
||||||
|
private Sparkline sparkline;
|
||||||
|
|
||||||
|
|
||||||
|
@FXML private GridPane canvasBase;
|
||||||
|
@FXML private Pane arrow;
|
||||||
|
@FXML private SplitPane race;
|
||||||
|
@FXML private StackPane arrowPane;
|
||||||
|
@FXML private Label timer;
|
||||||
|
@FXML private Label FPS;
|
||||||
|
@FXML private Label timeZone;
|
||||||
|
@FXML private CheckBox showFPS;
|
||||||
|
@FXML private TableView<VisualiserBoat> boatInfoTable;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
|
||||||
|
@FXML private LineChart<Number, Number> sparklineChart;
|
||||||
|
@FXML private AnchorPane annotationPane;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*/
|
||||||
|
public RaceController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the various UI components to listen to the {@link #visualiserRace}.
|
||||||
|
*/
|
||||||
|
private void initialiseRace() {
|
||||||
|
//Fps display.
|
||||||
|
initialiseFps(this.visualiserRace);
|
||||||
|
|
||||||
|
//Need to add the included arrow pane to the arrowPane container.
|
||||||
|
initialiseArrow();
|
||||||
|
|
||||||
|
//Information table.
|
||||||
|
initialiseInfoTable(this.visualiserRace);
|
||||||
|
|
||||||
|
//Sparkline.
|
||||||
|
initialiseSparkline(this.visualiserRace);
|
||||||
|
|
||||||
|
//Race canvas.
|
||||||
|
initialiseRaceCanvas(this.visualiserRace);
|
||||||
|
|
||||||
|
//Race timezone label.
|
||||||
|
initialiseRaceTimezoneLabel(this.visualiserRace);
|
||||||
|
|
||||||
|
//Race clock.
|
||||||
|
initialiseRaceClock(this.visualiserRace);
|
||||||
|
|
||||||
|
|
||||||
|
//Start the race animation timer.
|
||||||
|
raceTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property.
|
||||||
|
* @param visualiserRace The race to connect the fps label to.
|
||||||
|
*/
|
||||||
|
private void initialiseFps(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
//On/off toggle.
|
||||||
|
initialiseFpsToggle();
|
||||||
|
|
||||||
|
//Label value.
|
||||||
|
initialiseFpsLabel(visualiserRace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises a listener for the fps toggle.
|
||||||
|
*/
|
||||||
|
private void initialiseFpsToggle() {
|
||||||
|
|
||||||
|
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
|
||||||
|
if (showFPS.isSelected()) {
|
||||||
|
FPS.setVisible(true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
FPS.setVisible(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the fps label to update when the race fps changes.
|
||||||
|
* @param visualiserRace The race to monitor the frame rate of.
|
||||||
|
*/
|
||||||
|
private void initialiseFpsLabel(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the information table view to listen to a given race.
|
||||||
|
* @param race Race to listen to.
|
||||||
|
*/
|
||||||
|
public void initialiseInfoTable(VisualiserRace race) {
|
||||||
|
|
||||||
|
//Copy list of boats.
|
||||||
|
this.tableBoatList = FXCollections.observableArrayList(race.getBoats());
|
||||||
|
|
||||||
|
|
||||||
|
//Set up table.
|
||||||
|
boatInfoTable.setItems(this.tableBoatList);
|
||||||
|
|
||||||
|
|
||||||
|
//Set up each column.
|
||||||
|
|
||||||
|
//Name.
|
||||||
|
boatTeamColumn.setCellValueFactory(
|
||||||
|
cellData -> cellData.getValue().nameProperty() );
|
||||||
|
|
||||||
|
//Speed.
|
||||||
|
boatSpeedColumn.setCellValueFactory(
|
||||||
|
cellData -> cellData.getValue().currentSpeedProperty() );
|
||||||
|
|
||||||
|
//Kind of ugly, but allows for formatting an observed speed.
|
||||||
|
boatSpeedColumn.setCellFactory(
|
||||||
|
//Callback object.
|
||||||
|
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
|
||||||
|
|
||||||
|
//Callback function.
|
||||||
|
@Override
|
||||||
|
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
|
||||||
|
//We return a table cell that populates itself with a Number, and formats it.
|
||||||
|
return new TableCell<VisualiserBoat, Number>(){
|
||||||
|
|
||||||
|
//Function to update the cell text.
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Number item, boolean empty) {
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
|
setText(String.format("%.2fkn", item.doubleValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
//Last mark.
|
||||||
|
boatMarkColumn.setCellValueFactory(
|
||||||
|
cellData -> cellData.getValue().legProperty() );
|
||||||
|
|
||||||
|
//Kind of ugly, but allows for turning an observed Leg into a string.
|
||||||
|
boatMarkColumn.setCellFactory(
|
||||||
|
//Callback object.
|
||||||
|
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
|
||||||
|
|
||||||
|
//Callback function.
|
||||||
|
@Override
|
||||||
|
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
|
||||||
|
//We return a table cell that populates itself with a Leg's name.
|
||||||
|
return new TableCell<VisualiserBoat, Leg>(){
|
||||||
|
|
||||||
|
//Function to update the cell text.
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Leg item, boolean empty) {
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
|
setText(item.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
//Current place within race.
|
||||||
|
boatPlacingColumn.setCellValueFactory(
|
||||||
|
cellData -> cellData.getValue().positionProperty() );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}.
|
||||||
|
* @param race The race to listen to.
|
||||||
|
*/
|
||||||
|
private void initialiseSparkline(VisualiserRace race) {
|
||||||
|
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
|
||||||
|
this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
|
||||||
|
* @param race Race to read data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceCanvas(VisualiserRace race) {
|
||||||
|
|
||||||
|
//Create canvas.
|
||||||
|
raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0));
|
||||||
|
|
||||||
|
//Set properties.
|
||||||
|
raceCanvas.setMouseTransparent(true);
|
||||||
|
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
|
||||||
|
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
|
||||||
|
|
||||||
|
//Draw it and show it.
|
||||||
|
raceCanvas.draw();
|
||||||
|
raceCanvas.setVisible(true);
|
||||||
|
|
||||||
|
//Add to scene.
|
||||||
|
canvasBase.getChildren().add(0, raceCanvas);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intialises the race time zone label with the race's time zone.
|
||||||
|
* @param race The race to get time zone from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceTimezoneLabel(VisualiserRace race) {
|
||||||
|
timeZone.setText(race.getRaceClock().getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race clock to listen to the specified race.
|
||||||
|
* @param race The race to listen to.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceClock(VisualiserRace race) {
|
||||||
|
|
||||||
|
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
|
||||||
|
race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
timer.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a specified race.
|
||||||
|
* @param visualiserInput Object used to read packets from server.
|
||||||
|
* @param visualiserRace Object modelling the race.
|
||||||
|
*/
|
||||||
|
public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
this.visualiserInput = visualiserInput;
|
||||||
|
this.visualiserRace = visualiserRace;
|
||||||
|
|
||||||
|
initialiseRace();
|
||||||
|
|
||||||
|
//Display this controller.
|
||||||
|
race.setVisible(true);
|
||||||
|
|
||||||
|
|
||||||
|
// set up annotation displays
|
||||||
|
new Annotations(annotationPane, raceCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transition from the race view to the finish view.
|
||||||
|
* @param boats boats there are in the race.
|
||||||
|
*/
|
||||||
|
public void finishRace(ObservableList<VisualiserBoat> boats){
|
||||||
|
race.setVisible(false);
|
||||||
|
parent.enterFinish(boats);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml).
|
||||||
|
*/
|
||||||
|
private void initialiseArrow() {
|
||||||
|
arrowPane.getChildren().add(arrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer which monitors the race.
|
||||||
|
*/
|
||||||
|
private void raceTimer() {
|
||||||
|
new AnimationTimer() {
|
||||||
|
@Override
|
||||||
|
public void handle(long arg0) {
|
||||||
|
|
||||||
|
//Get the current race status.
|
||||||
|
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
|
||||||
|
|
||||||
|
|
||||||
|
//If the race has finished, go to finish view.
|
||||||
|
if (raceStatus == RaceStatusEnum.FINISHED) {
|
||||||
|
//Stop this timer.
|
||||||
|
stop();
|
||||||
|
|
||||||
|
//Hide this, and display the finish controller.
|
||||||
|
finishRace(visualiserRace.getBoats());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Otherwise, render the canvas.
|
||||||
|
raceCanvas.drawRace();
|
||||||
|
|
||||||
|
|
||||||
|
//Sort the tableview. Doesn't automatically work for all columns.
|
||||||
|
boatInfoTable.sort();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,321 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
|
import network.Messages.LatestMessages;
|
||||||
|
import shared.dataInput.*;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
import shared.exceptions.InvalidBoatDataException;
|
||||||
|
import shared.exceptions.InvalidRaceDataException;
|
||||||
|
import shared.exceptions.InvalidRegattaDataException;
|
||||||
|
import shared.exceptions.XMLReaderException;
|
||||||
|
import visualiser.app.VisualiserInput;
|
||||||
|
import visualiser.model.VisualiserBoat;
|
||||||
|
import visualiser.model.VisualiserRace;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to for waiting for the race to start.
|
||||||
|
*/
|
||||||
|
public class StartController extends Controller implements Observer {
|
||||||
|
|
||||||
|
@FXML private GridPane start;
|
||||||
|
@FXML private AnchorPane startWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the race/regatta.
|
||||||
|
*/
|
||||||
|
@FXML private Label raceTitleLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the race starts at.
|
||||||
|
*/
|
||||||
|
@FXML private Label raceStartLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current time at the race location.
|
||||||
|
*/
|
||||||
|
@FXML private Label timeZoneTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time until the race starts.
|
||||||
|
*/
|
||||||
|
@FXML private Label timer;
|
||||||
|
|
||||||
|
@FXML private TableView<VisualiserBoat> boatNameTable;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
|
||||||
|
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of the race.
|
||||||
|
*/
|
||||||
|
@FXML private Label raceStatusLabel;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object used to read packets from the connected server.
|
||||||
|
*/
|
||||||
|
private VisualiserInput visualiserInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race object which describes the currently occurring race.
|
||||||
|
*/
|
||||||
|
private VisualiserRace visualiserRace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
|
||||||
|
*/
|
||||||
|
List<Color> colors = new ArrayList<>(Arrays.asList(
|
||||||
|
Color.BLUEVIOLET,
|
||||||
|
Color.BLACK,
|
||||||
|
Color.RED,
|
||||||
|
Color.ORANGE,
|
||||||
|
Color.DARKOLIVEGREEN,
|
||||||
|
Color.LIMEGREEN,
|
||||||
|
Color.PURPLE,
|
||||||
|
Color.DARKGRAY,
|
||||||
|
Color.YELLOW
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*/
|
||||||
|
public StartController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the race.
|
||||||
|
* Called once we have received all XML files from the server.
|
||||||
|
* @param latestMessages The set of latest race messages to use for race.
|
||||||
|
* @throws XMLReaderException Thrown if XML file cannot be parsed.
|
||||||
|
* @throws InvalidRaceDataException Thrown if XML file cannot be parsed.
|
||||||
|
* @throws InvalidBoatDataException Thrown if XML file cannot be parsed.
|
||||||
|
* @throws InvalidRegattaDataException Thrown if XML file cannot be parsed.
|
||||||
|
*/
|
||||||
|
private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException {
|
||||||
|
|
||||||
|
//Create data sources from latest messages for the race.
|
||||||
|
RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage(), XMLFileType.Contents);
|
||||||
|
BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage(), XMLFileType.Contents);
|
||||||
|
RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage(), XMLFileType.Contents);
|
||||||
|
|
||||||
|
//Create race.
|
||||||
|
this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors);
|
||||||
|
new Thread(this.visualiserRace).start();
|
||||||
|
|
||||||
|
|
||||||
|
//Initialise the boat table.
|
||||||
|
initialiseBoatTable(this.visualiserRace);
|
||||||
|
|
||||||
|
//Initialise the race name.
|
||||||
|
initialiseRaceName(this.visualiserRace);
|
||||||
|
|
||||||
|
//Initialises the race clock.
|
||||||
|
initialiseRaceClock(this.visualiserRace);
|
||||||
|
|
||||||
|
//Starts the race countdown timer.
|
||||||
|
countdownTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public AnchorPane startWrapper(){
|
||||||
|
return startWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the boat table that is to be shown on the pane.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseBoatTable(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
//Get the boats.
|
||||||
|
ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();
|
||||||
|
|
||||||
|
//Populate table.
|
||||||
|
boatNameTable.setItems(boats);
|
||||||
|
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
|
||||||
|
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race name which is shown on the pane.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceName(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
raceTitleLabel.setText(visualiserRace.getRegattaName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceClock(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
//Start time.
|
||||||
|
initialiseRaceClockStartTime(visualiserRace);
|
||||||
|
|
||||||
|
//Current time.
|
||||||
|
initialiseRaceClockCurrentTime(visualiserRace);
|
||||||
|
|
||||||
|
//Remaining time.
|
||||||
|
initialiseRaceClockDuration(visualiserRace);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race current time label.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString());
|
||||||
|
|
||||||
|
visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
raceStartLabel.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race current time label.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
timeZoneTime.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the race duration label.
|
||||||
|
* @param visualiserRace The race to get data from.
|
||||||
|
*/
|
||||||
|
private void initialiseRaceClockDuration(VisualiserRace visualiserRace) {
|
||||||
|
|
||||||
|
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
timer.setText(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countdown timer until race starts.
|
||||||
|
*/
|
||||||
|
private void countdownTimer() {
|
||||||
|
new AnimationTimer() {
|
||||||
|
@Override
|
||||||
|
public void handle(long arg0) {
|
||||||
|
|
||||||
|
//TODO instead of having an AnimationTimer checking the race status, we could provide a Property<RaceStatusEnum>, and connect a listener to that.
|
||||||
|
//Get the current race status.
|
||||||
|
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
|
||||||
|
|
||||||
|
//Display it.
|
||||||
|
raceStatusLabel.setText("Race Status: " + raceStatus.name());
|
||||||
|
|
||||||
|
//If the race has reached the preparatory phase, or has started...
|
||||||
|
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
|
||||||
|
//Stop this timer.
|
||||||
|
stop();
|
||||||
|
|
||||||
|
//Hide this, and display the race controller.
|
||||||
|
startWrapper.setVisible(false);
|
||||||
|
start.setVisible(false);
|
||||||
|
|
||||||
|
parent.beginRace(visualiserInput, visualiserRace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to handle changes in objects we observe.
|
||||||
|
* We observe LatestMessages.
|
||||||
|
* @param o The observed object.
|
||||||
|
* @param arg The {@link Observable#notifyObservers(Object)} parameter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
|
||||||
|
//Check that we actually have LatestMessages.
|
||||||
|
if (o instanceof LatestMessages) {
|
||||||
|
LatestMessages latestMessages = (LatestMessages) o;
|
||||||
|
|
||||||
|
//If we've received all of the xml files, start the race. Only start it if it hasn't already been created.
|
||||||
|
if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) {
|
||||||
|
|
||||||
|
//Need to handle it in the javafx thread.
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
|
this.startRace(latestMessages);
|
||||||
|
|
||||||
|
} catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) {
|
||||||
|
//We currently don't handle this in meaningful way, as it should never occur.
|
||||||
|
//If we reach this point it means that malformed XML files were sent.
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show starting information for a race given a socket.
|
||||||
|
* @param socket network source of information
|
||||||
|
*/
|
||||||
|
public void enterLobby(Socket socket) {
|
||||||
|
startWrapper.setVisible(true);
|
||||||
|
try {
|
||||||
|
//Begin reading packets from the socket/server.
|
||||||
|
this.visualiserInput = new VisualiserInput(socket);
|
||||||
|
//Store a reference to latestMessages so that we can observe it.
|
||||||
|
LatestMessages latestMessages = this.visualiserInput.getLatestMessages();
|
||||||
|
latestMessages.addObserver(this);
|
||||||
|
new Thread(this.visualiserInput).start();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,377 @@
|
|||||||
|
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;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private long lastHeartbeatTime = -1;
|
||||||
|
/**
|
||||||
|
* Sequence number of the last heartbeat.
|
||||||
|
*/
|
||||||
|
private long lastHeartbeatSequenceNum = -1;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The socket that we have connected to.
|
||||||
|
*/
|
||||||
|
private Socket connectionSocket;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @throws IOException If there is something wrong with the socket's input stream.
|
||||||
|
*/
|
||||||
|
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.latestMessages = new LatestMessages();
|
||||||
|
|
||||||
|
|
||||||
|
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LatestMessages object, which can be queried for any received race related messages.
|
||||||
|
* @return The LatestMessages object.
|
||||||
|
*/
|
||||||
|
public LatestMessages getLatestMessages() {
|
||||||
|
return latestMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the time since last heartbeat, in milliseconds.
|
||||||
|
* @return Time since last heartbeat, in milliseconds..
|
||||||
|
*/
|
||||||
|
private double timeSinceHeartbeat() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
return (now - lastHeartbeatTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead.
|
||||||
|
* @return Encoded binary message bytes.
|
||||||
|
* @throws IOException Thrown when an error occurs while reading from the socket.
|
||||||
|
*/
|
||||||
|
private byte[] getNextMessageBytes() throws IOException {
|
||||||
|
inStream.mark(0);
|
||||||
|
short CRCLength = 4;
|
||||||
|
short headerLength = 15;
|
||||||
|
|
||||||
|
//Read the header of the next message.
|
||||||
|
byte[] headerBytes = new byte[headerLength];
|
||||||
|
inStream.readFully(headerBytes);
|
||||||
|
|
||||||
|
//Read the message body length.
|
||||||
|
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
|
||||||
|
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
|
||||||
|
|
||||||
|
//Read the message body.
|
||||||
|
byte[] messageBodyBytes = new byte[messageBodyLength];
|
||||||
|
inStream.readFully(messageBodyBytes);
|
||||||
|
|
||||||
|
//Read the message CRC.
|
||||||
|
byte[] messageCRCBytes = new byte[CRCLength];
|
||||||
|
inStream.readFully(messageCRCBytes);
|
||||||
|
|
||||||
|
//Put the head + body + crc into one large array.
|
||||||
|
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
|
||||||
|
messageBytes.put(headerBytes);
|
||||||
|
messageBytes.put(messageBodyBytes);
|
||||||
|
messageBytes.put(messageCRCBytes);
|
||||||
|
|
||||||
|
return messageBytes.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the next message object from the socket.
|
||||||
|
* @return The message object. Use instanceof for concrete type.
|
||||||
|
* @throws IOException Thrown when an error occurs while reading from the socket.
|
||||||
|
* @throws InvalidMessageException Thrown when the message is invalid in some way.
|
||||||
|
*/
|
||||||
|
private AC35Data getNextMessage() throws IOException, InvalidMessageException
|
||||||
|
{
|
||||||
|
//Get the next message from the socket as a block of bytes.
|
||||||
|
byte[] messageBytes = this.getNextMessageBytes();
|
||||||
|
|
||||||
|
//Decode the binary message into an appropriate message object.
|
||||||
|
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
|
||||||
|
|
||||||
|
return decoder.decode();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main loop which reads messages from the socket, and exposes them.
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
boolean receiverLoop = true;
|
||||||
|
//receiver loop that gets the input
|
||||||
|
while (receiverLoop) {
|
||||||
|
|
||||||
|
//If no heartbeat has been received in more the heartbeat period
|
||||||
|
//then the connection will need to be restarted.
|
||||||
|
//System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE
|
||||||
|
long heartBeatPeriod = 10 * 1000;
|
||||||
|
if (timeSinceHeartbeat() > heartBeatPeriod) {
|
||||||
|
System.out.println("Connection has stopped, trying to reconnect.");
|
||||||
|
|
||||||
|
//Attempt to reconnect the socket.
|
||||||
|
try {//This attempt doesn't really work. Under what circumstances would
|
||||||
|
this.connectionSocket = new Socket(this.connectionSocket.getInetAddress(), this.connectionSocket.getPort());
|
||||||
|
//this.connectionSocket.connect(this.connectionSocket.getRemoteSocketAddress());
|
||||||
|
//Reset the heartbeat timer.
|
||||||
|
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
System.err.println("Unable to reconnect.");
|
||||||
|
|
||||||
|
//Wait 500ms. Ugly hack, should refactor.
|
||||||
|
long waitPeriod = 500;
|
||||||
|
long waitTimeStart = System.currentTimeMillis() + waitPeriod;
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() < waitTimeStart){
|
||||||
|
//Nothing. Busyloop.
|
||||||
|
}
|
||||||
|
|
||||||
|
//Swallow the exception.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reads the next message.
|
||||||
|
AC35Data message;
|
||||||
|
try {
|
||||||
|
message = this.getNextMessage();
|
||||||
|
}
|
||||||
|
catch (InvalidMessageException | IOException e) {
|
||||||
|
//Prints exception to stderr, and iterate loop (that is, read the next message).
|
||||||
|
System.err.println("Unable to read message: " + e.getMessage());
|
||||||
|
try {
|
||||||
|
inStream.reset();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
//Continue to the next loop iteration/message.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Checks which message is being received and does what is needed for that message.
|
||||||
|
switch (message.getType()) {
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//DisplayTextMessage.
|
||||||
|
case DISPLAYTEXTMESSAGE: {
|
||||||
|
//System.out.println("Display Text Message");
|
||||||
|
//No decoder for this.
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//XMLMessage.
|
||||||
|
case XMLMESSAGE: {
|
||||||
|
XMLMessage xmlMessage = (XMLMessage) message;
|
||||||
|
|
||||||
|
//System.out.println("XML Message!");
|
||||||
|
|
||||||
|
this.latestMessages.setXMLMessage(xmlMessage);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//RaceStartStatus.
|
||||||
|
case RACESTARTSTATUS: {
|
||||||
|
|
||||||
|
//System.out.println("Race Start Status Message");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//YachtEventCode.
|
||||||
|
case YACHTEVENTCODE: {
|
||||||
|
//YachtEventCode yachtEventCode = (YachtEventCode) message;
|
||||||
|
|
||||||
|
//System.out.println("Yacht Event Code!");
|
||||||
|
//No decoder for this.
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//YachtActionCode.
|
||||||
|
case YACHTACTIONCODE: {
|
||||||
|
//YachtActionCode yachtActionCode = (YachtActionCode) message;
|
||||||
|
|
||||||
|
//System.out.println("Yacht Action Code!");
|
||||||
|
// No decoder for this.
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ChatterText.
|
||||||
|
case CHATTERTEXT: {
|
||||||
|
//ChatterText chatterText = (ChatterText) message;
|
||||||
|
|
||||||
|
//System.out.println("Chatter Text Message!");
|
||||||
|
//No decoder for this.
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//MarkRounding.
|
||||||
|
case MARKROUNDING: {
|
||||||
|
MarkRounding markRounding = (MarkRounding) 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//CourseWinds.
|
||||||
|
case COURSEWIND: {
|
||||||
|
|
||||||
|
//System.out.println("Course Wind Message!");
|
||||||
|
CourseWinds courseWinds = (CourseWinds) message;
|
||||||
|
|
||||||
|
this.latestMessages.setCourseWinds(courseWinds);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//AverageWind.
|
||||||
|
case AVGWIND: {
|
||||||
|
|
||||||
|
//System.out.println("Average Wind Message!");
|
||||||
|
AverageWind averageWind = (AverageWind) message;
|
||||||
|
|
||||||
|
this.latestMessages.setAverageWind(averageWind);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Unrecognised message.
|
||||||
|
default: {
|
||||||
|
System.out.println("Broken Message!");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue