package seng302.Data; import org.w3c.dom.Document; import org.w3c.dom.Element; import seng302.DataInput.RaceDataSource; import seng302.Exceptions.InvalidRaceDataException; import seng302.Model.Boat; import seng302.Model.CompoundMarker; import seng302.Model.GPSCoordinate; 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.StringWriter; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * Created by esa46 on 21/04/17. */ public class RaceData { private RaceDataSource dataSource; private Document doc; private Element rootElement; private ZonedDateTime creationTimeDate; public RaceData(RaceDataSource dataSource) { this.dataSource = dataSource; creationTimeDate = ZonedDateTime.now(); } /** * Creates an AC35 officially formatted xml description of a race. * * @return String containing xml-formatted race description */ public String createXML() { try { //create base xml document DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); doc = docBuilder.newDocument(); //create root element (In this case, "Race") rootElement = doc.createElement("Race"); doc.appendChild(rootElement); appendChildElements(); // write the content into xml file TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); DOMSource source = new DOMSource(doc); //Serialize document. StringWriter stringWriter = new StringWriter(); StreamResult result = new StreamResult(stringWriter); transformer.transform(source, result); return stringWriter.toString(); } catch (ParserConfigurationException pce) { throw new InvalidRaceDataException(); } catch (TransformerException tfe) { throw new InvalidRaceDataException(); } } /** * Creates all necessary child elements and appends them to the xml doc */ private void appendChildElements() { appendRaceId(); appendRaceType(); appendCreationTimeDate(); appendRaceStartTime(); appendParticipants(); appendCourse(); appendCourseLimit(); } /** * Creates and appends race id element */ private void appendRaceId() { Element raceIdElement = doc.createElement("RaceID"); raceIdElement.appendChild(doc.createTextNode(Integer.toString(dataSource.getRaceId()))); rootElement.appendChild(raceIdElement); } /** * Creates and appends race type element */ private void appendRaceType() { Element raceTypeElement = doc.createElement("RaceType"); raceTypeElement.appendChild(doc.createTextNode(dataSource.getRaceType())); rootElement.appendChild(raceTypeElement); } /** * Creates and appends creation time date element */ private void appendCreationTimeDate() { Element creationTimeElement = doc.createElement("CreationTimeDate"); creationTimeElement.appendChild(doc.createTextNode(toTruncatedString(creationTimeDate))); rootElement.appendChild(creationTimeElement); } /** * Creates and appends race start time element, which is 3 minutes after the race was created by default */ private void appendRaceStartTime() { Element startTimeElement = doc.createElement("RaceStartTime"); startTimeElement.setAttribute("Time", (toTruncatedString(creationTimeDate.plusMinutes(0)))); startTimeElement.setAttribute("Postpone", "false"); rootElement.appendChild(startTimeElement); } /** * Creates and appends participants element */ private void appendParticipants() { Element participantsElement = doc.createElement("Participants"); for (Boat boat : dataSource.getBoats()) { Element yachtElement = doc.createElement("Yacht"); yachtElement.setAttribute("SourceID", boat.getSourceID() + ""); participantsElement.appendChild(yachtElement); } rootElement.appendChild(participantsElement); } /** * Creates and appends course elements */ private void appendCourse() { Element courseElement = doc.createElement("Course"); Element compoundMarkSeqElement = doc.createElement("CompoundMarkSequence"); int i = 1; for (CompoundMarker compoundMarker : dataSource.getCompoundMarkers()) { courseElement.appendChild(createCompoundMarker(compoundMarker, i)); compoundMarkSeqElement.appendChild(createCornerElement(i)); i++; } rootElement.appendChild(compoundMarkSeqElement); rootElement.appendChild(courseElement); } /** * Creates a mark element for insertion in a coumpound mark element * * @param marker GPS coordinates of the mark * @return Element mark element */ private Element createMark(GPSCoordinate marker) { Element mark = doc.createElement("Mark"); mark.setAttribute("TargetLat", marker.getLatitude() + ""); mark.setAttribute("TargetLng", marker.getLongitude() + ""); return mark; } /** * Creates a compound compoundMarker holding one or two marks,and a sequence number * * @param compoundMarker compoundMarker * @param i sequence number * @return Element compound mark element */ private Element createCompoundMarker(CompoundMarker compoundMarker, int i) { Element compoundMarkElement = doc.createElement("CompoundMark"); compoundMarkElement.setAttribute("CompoundMarkID", i + ""); compoundMarkElement.setAttribute("Name", compoundMarker.getName()); compoundMarkElement.appendChild(createMark(compoundMarker.getMark1())); if (!(compoundMarker.getMark1().equals(compoundMarker.getMark2()))) { compoundMarkElement.appendChild(createMark(compoundMarker.getMark2())); } return compoundMarkElement; } /** * Creates a corner element * * @param i sequence number * @return Element corner element */ private Element createCornerElement(int i) { Element cornerElement = doc.createElement("Corner"); cornerElement.setAttribute("SeqID", i + ""); cornerElement.setAttribute("CompoundMarkID", i + ""); return cornerElement; } /** * Creates and appends course limits element (boundaries) */ private void appendCourseLimit() { int j = 1; Element courseLimitElement = doc.createElement("CourseLimit"); for (GPSCoordinate coordinate : dataSource.getBoundary()) { Element limitElement = doc.createElement("Limit"); limitElement.setAttribute("SeqID", j + ""); limitElement.setAttribute("Lat", coordinate.getLatitude() + ""); limitElement.setAttribute("Lon", coordinate.getLongitude() + ""); courseLimitElement.appendChild(limitElement); j++; } rootElement.appendChild(courseLimitElement); } /** * Format time data and return it. * @param time time data. * @return formatted time data. */ private String toTruncatedString(ZonedDateTime time) { DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); String text = dateFormat.format(time); return text; } }