package seng302.Mock; 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.GPSCoordinate; import seng302.Model.Leg; import seng302.Model.Marker; import seng302.XMLReader; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; /** * XML Read for the Course that is being received */ public class StreamedCourseXMLReader extends XMLReader { private static final double COORDINATEPADDING = 0.000; private GPSCoordinate mapTopLeft, mapBottomRight; private final List boundary = new ArrayList<>(); private final Map compoundMarks = new HashMap<>(); private final Map participants = new HashMap<>(); private final List legs = new ArrayList<>(); private final List markers = new ArrayList<>(); private ZonedDateTime creationTimeDate; private ZonedDateTime raceStartTime; private int raceID; private String raceType; private boolean postpone; /** * Constructor for Streamed Race XML * @param filePath file path to read * @param read whether or not to read and store the files straight away. * @throws IOException error * @throws SAXException error * @throws ParserConfigurationException error * @throws StreamedCourseXMLException error */ public StreamedCourseXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException { super(filePath); if (read) { read(); } } /** * Constructor for Streamed Race XML * @param xmlString string to read * @throws IOException error * @throws SAXException error * @throws ParserConfigurationException error * @throws StreamedCourseXMLException error */ public StreamedCourseXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException { super(xmlString); 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 the participants of the 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, new StreamedBoat(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. * @throws StreamedCourseXMLException if a CompoundMark element contains an unhandled number of compoundMarks. * @see seng302.Model.Marker */ 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); compoundMarks.put(compoundMarkID, (Element)compoundMark); markers.add(getMarker(compoundMarkID)); } } } /** * Generates a Marker from the CompoundMark element with given ID. * @param compoundMarkID index of required CompoundMark element * @return generated Marker * @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks * @see seng302.Model.Marker */ private Marker getMarker(int compoundMarkID) throws StreamedCourseXMLException { Element compoundMark = compoundMarks.get(compoundMarkID); NodeList nMarks = compoundMark.getElementsByTagName("Mark"); Marker marker; switch(nMarks.getLength()) { case 1: marker = new Marker(getCoordinate((Element)nMarks.item(0)),getSourceId((Element)nMarks.item(0))); break; case 2: marker = new Marker(getCoordinate((Element)nMarks.item(0)), getCoordinate((Element)nMarks.item(1)), getSourceId((Element)nMarks.item(0)), getSourceId((Element)nMarks.item(1))); break; default: throw new StreamedCourseXMLException(); } return marker; } /** * Extracts the GPS Coordinates from a XMl Element * @param mark Element to Extract from * @return the GPS coordinate that it reflects */ private GPSCoordinate getCoordinate(Element mark) { double lat = Double.parseDouble(mark.getAttribute("TargetLat")); double lon = Double.parseDouble(mark.getAttribute("TargetLng")); return new GPSCoordinate(lat,lon); } /** * Extracts the SourceID from a XML ELement * @param mark Element to Extract from * @return the Source ID of the Extracted Element. */ private int getSourceId(Element mark) { String sourceId = mark.getAttribute("SourceID"); if (sourceId.isEmpty()){ return 0; } return Integer.parseInt(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 compoundMarks.get(compoundMarkID).getAttribute("Name"); } /** * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. * @throws StreamedCourseXMLException if markers 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); Marker lastMarker = getMarker(getCompoundMarkID(markXML)); String legName = getCompoundMarkName(getCompoundMarkID(markXML)); for(int i = 1; i < nCorners.getLength(); i++) { markXML = (Element)nCorners.item(i); Marker currentMarker = getMarker(getCompoundMarkID(markXML)); legs.add(new Leg(legName, lastMarker, currentMarker, i-1)); lastMarker = currentMarker; legName = getCompoundMarkName(getCompoundMarkID(markXML)); } } /** * Reads the course boundary limitations of the course recieved. */ 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 getBoundary() { return boundary; } public GPSCoordinate getMapTopLeft() { return mapTopLeft; } public GPSCoordinate getMapBottomRight() { return mapBottomRight; } public List getLegs() { return legs; } public List getMarkers() { return markers; } public Double getPadding() { return COORDINATEPADDING; } public ZonedDateTime getRaceStartTime() { return raceStartTime; } public int getRaceID() { return raceID; } public String getRaceType() { return raceType; } public boolean isPostpone() { return postpone; } public Map getParticipants() { return participants; } }