From fcea323cfd9ecb805f15833f6a2385e1a745be47 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Fri, 28 Jul 2017 09:30:26 +1200 Subject: [PATCH 01/59] The racevisionGame pom still had "profiles", which aren't needed with a single jar build, and the built jar didn't have a mainifest/main class. --- racevisionGame/pom.xml | 117 ++++++++++++----------------------------- 1 file changed, 34 insertions(+), 83 deletions(-) diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml index f77e5ea3..b0f6d203 100644 --- a/racevisionGame/pom.xml +++ b/racevisionGame/pom.xml @@ -83,89 +83,40 @@ - - - - mock - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - - - visualiser.app.App - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - package - - shade - - - - - - - - - - - visualiser - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - - - visualiser.app.App - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - package - - shade - - - - - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + + + visualiser.app.App + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + package + + shade + + + + + + From 1385500e6822d387944b18f7347eea9df8a13641 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:10:15 +1200 Subject: [PATCH 02/59] Added JoinAcceptance and RequestToJoin enumerations. Issue #35 #story[1095] --- .../Messages/Enums/JoinAcceptanceEnum.java | 102 ++++++++++++++++++ .../Messages/Enums/RequestToJoinEnum.java | 97 +++++++++++++++++ .../java/network/Messages/JoinAcceptance.java | 4 + .../java/network/Messages/RequestToJoin.java | 13 +++ 4 files changed, 216 insertions(+) create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/JoinAcceptance.java create mode 100644 racevisionGame/src/main/java/network/Messages/RequestToJoin.java diff --git a/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java new file mode 100644 index 00000000..6939d8f3 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java @@ -0,0 +1,102 @@ +package network.Messages.Enums; + + +import java.util.HashMap; +import java.util.Map; + +/** + * This enum encapsulates the different ways in which a server may respond to a client {@link network.Messages.RequestToJoin} message. + */ +public enum JoinAcceptanceEnum { + + + /** + * Client is allowed to join. + */ + JOIN_SUCCESSFUL(1), + + /** + * The race is full - no more participants allowed. + */ + RACE_PARTICIPANTS_FULL(2), + + /** + * The race cannot allow any more ghost participants to join. + */ + GHOST_PARTICIPANTS_FULL(3), + + /** + * The server is completely full, cannot participate or spectate. + */ + SERVER_FULL(4), + + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_AN_ACCEPTANCE_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a JoinAcceptanceEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private JoinAcceptanceEnum(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 JoinAcceptanceEnum values. + */ + private static final Map byteToAcceptanceMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToAcceptanceMap. + */ + static { + for (JoinAcceptanceEnum type : JoinAcceptanceEnum.values()) { + JoinAcceptanceEnum.byteToAcceptanceMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param joinAcceptanceEnum Byte value to convert to a JoinAcceptanceEnum value. + * @return The RequestToJoinEnum value which corresponds to the given byte value. + */ + public static JoinAcceptanceEnum fromByte(byte joinAcceptanceEnum) { + //Gets the corresponding MessageType from the map. + JoinAcceptanceEnum type = JoinAcceptanceEnum.byteToAcceptanceMap.get(joinAcceptanceEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_AN_ACCEPTANCE_TYPE JoinAcceptanceEnum. + return JoinAcceptanceEnum.NOT_AN_ACCEPTANCE_TYPE; + } else { + //Otherwise, return the JoinAcceptanceEnum. + return type; + } + + } + + + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java new file mode 100644 index 00000000..2aab2925 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java @@ -0,0 +1,97 @@ +package network.Messages.Enums; + + +import java.util.HashMap; +import java.util.Map; + +/** + * This enum encapsulates the different ways in which a client may wish to connect to a server. + */ +public enum RequestToJoinEnum { + + + /** + * Client wants to spectate. + */ + SPECTATOR(0), + + /** + * Client wants to participate. + */ + PARTICIPANT(1), + + /** + * Client wants to particpate as a ghost. + */ + GHOST(5), + + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_REQUEST_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RequestToJoinEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RequestToJoinEnum(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 RequestToJoinEnum values. + */ + private static final Map byteToRequestMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToRequestMap. + */ + static { + for (RequestToJoinEnum type : RequestToJoinEnum.values()) { + RequestToJoinEnum.byteToRequestMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param requestToJoinEnum Byte value to convert to a RequestToJoinEnum value. + * @return The RequestToJoinEnum value which corresponds to the given byte value. + */ + public static RequestToJoinEnum fromByte(byte requestToJoinEnum) { + //Gets the corresponding MessageType from the map. + RequestToJoinEnum type = RequestToJoinEnum.byteToRequestMap.get(requestToJoinEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. + return RequestToJoinEnum.NOT_A_REQUEST_TYPE; + } else { + //Otherwise, return the RequestToJoinEnum. + return type; + } + + } + + + +} diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java new file mode 100644 index 00000000..911efa3f --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -0,0 +1,4 @@ +package network.Messages; + +public class JoinAcceptance { +} diff --git a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java new file mode 100644 index 00000000..cf3f0ccd --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java @@ -0,0 +1,13 @@ +package network.Messages; + + +/** + * This is the message a client sends to a server to request to join/view a race. + */ +public class RequestToJoin { + + + + + +} From f65ed79619b4d347690b049369a23a25bcfdd55d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:23:32 +1200 Subject: [PATCH 03/59] Implemented RequestToJoin and JoinAcceptance messages. Also added their message types to MessageType. issue #35 #story[1095] --- .../network/Messages/Enums/MessageType.java | 11 ++++ .../java/network/Messages/JoinAcceptance.java | 51 ++++++++++++++++++- .../java/network/Messages/RequestToJoin.java | 25 ++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 086673f5..4072f912 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -19,6 +19,17 @@ public enum MessageType { MARKROUNDING(38), COURSEWIND(44), AVGWIND(47), + + /** + * This is used for {@link network.Messages.RequestToJoin} messages. + */ + REQUEST_TO_JOIN(55), + + /** + * This is used for {@link network.Messages.JoinAcceptance} messages. + */ + JOIN_ACCEPTANCE(56), + BOATACTION(100), NOTAMESSAGE(0); diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java index 911efa3f..d2ff47e2 100644 --- a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -1,4 +1,53 @@ package network.Messages; -public class JoinAcceptance { +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.Enums.MessageType; + + +/** + * This is the message a server sends to a client to tell them their boat sourceID, and if they have actually managed to join the server. + */ +public class JoinAcceptance extends AC35Data { + + + /** + * The source ID of the boat assigned to the client. + * 0 indicates they haven't been assigned a boat. + */ + private int sourceID = 0; + + /** + * The type of acceptance response this is. + */ + private JoinAcceptanceEnum acceptanceType; + + + + + /** + * Constructs a JoinAcceptance message of a given acceptance type. + * @param acceptanceType The type of join acceptance this is. + */ + public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){ + super(MessageType.JOIN_ACCEPTANCE); + this.acceptanceType = acceptanceType; + this.sourceID = sourceID; + } + + + /** + * The type of acceptance response this is. + * @return The type of acceptance response. + */ + public JoinAcceptanceEnum getAcceptanceType() { + return acceptanceType; + } + + /** + * Returns the source ID of the boat assigned to the client. + * @return The source ID of the boat assigned to the client. + */ + public int getSourceID() { + return sourceID; + } } diff --git a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java index cf3f0ccd..291bb138 100644 --- a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java +++ b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java @@ -1,13 +1,36 @@ package network.Messages; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.RequestToJoinEnum; + /** * This is the message a client sends to a server to request to join/view a race. */ -public class RequestToJoin { +public class RequestToJoin extends AC35Data { + + /** + * The type of join request this is. + */ + private RequestToJoinEnum requestType; + /** + * Constructs a RequestToJoin message of a given request type. + * @param requestType The type of join request this is. + */ + public RequestToJoin(RequestToJoinEnum requestType){ + super(MessageType.REQUEST_TO_JOIN); + this.requestType = requestType; + } + /** + * The type of join request this is. + * @return The type of join request. + */ + public RequestToJoinEnum getRequestType() { + return requestType; + } } From ca2b8a88999e489e5edd16cc951689154c850ba7 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:43:58 +1200 Subject: [PATCH 04/59] Added missing javadoc. #story[1095] --- .../src/main/java/network/Messages/JoinAcceptance.java | 1 + 1 file changed, 1 insertion(+) diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java index d2ff47e2..378d3285 100644 --- a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -27,6 +27,7 @@ public class JoinAcceptance extends AC35Data { /** * Constructs a JoinAcceptance message of a given acceptance type. * @param acceptanceType The type of join acceptance this is. + * @param sourceID The sourceID to assign to the client. 0 indicates no sourceID. */ public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){ super(MessageType.JOIN_ACCEPTANCE); From 7ea5b31fa1999d3b91b219a36eb87d6a36bb513d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 20:42:25 +1200 Subject: [PATCH 05/59] RequestToJoinEnum contains an int instead of a byte. Added requestToJoin and joinAcceptance encoding functions to RaceVisionByteEncoder. Implemented JoinAcceptanceDecoder. Implemented RequestToJoinDecoder. Added tests for encoding/decoding RequestToJoin and JoinAcceptance messages. issue #35 #story[1095] --- .../JoinAcceptanceDecoder.java | 70 +++++++++++++ .../MessageDecoders/RequestToJoinDecoder.java | 62 ++++++++++++ .../RaceVisionByteEncoder.java | 38 +++++++ .../Messages/Enums/RequestToJoinEnum.java | 30 +++--- .../JoinAcceptanceDecoderTest.java | 99 +++++++++++++++++++ .../RequestToJoinDecoderTest.java | 82 +++++++++++++++ 6 files changed, 366 insertions(+), 15 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java new file mode 100644 index 00000000..95c3889e --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -0,0 +1,70 @@ +package network.MessageDecoders; + + +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.JoinAcceptance; +import network.Utils.ByteConverter; + +import java.util.Arrays; + +/** + * Decoder for {@link JoinAcceptance} messages. + */ +public class JoinAcceptanceDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private JoinAcceptance message; + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public JoinAcceptanceDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + //SourceID is first four bytes. + byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + + //Next byte is acceptance type. + byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + + + + //SourceID is an int. + int sourceID = ByteConverter.bytesToInt(sourceIdBytes); + + //Acceptance enum is a byte. + JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); + + + message = new JoinAcceptance(acceptanceType, sourceID); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public JoinAcceptance getMessage() { + return message; + } + + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java new file mode 100644 index 00000000..2d272f05 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -0,0 +1,62 @@ +package network.MessageDecoders; + + +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import network.Utils.ByteConverter; + +import java.util.Arrays; + +/** + * Decoder for {@link network.Messages.RequestToJoin} messages. + */ +public class RequestToJoinDecoder { + + /** + * The encoded message. + */ + private byte[] encodedRequest; + + /** + * The decoded message. + */ + private RequestToJoin message; + + /** + * Constructs a decoder to decode a given message. + * @param encodedRequest The message to decode. + */ + public RequestToJoinDecoder(byte[] encodedRequest) { + this.encodedRequest = encodedRequest; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + //Request type is first four bytes. + byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + + //Request type is an integral type. + int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); + RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); + + + message = new RequestToJoin(requestType); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RequestToJoin getMessage() { + return message; + } + + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 4c57cf0c..88dcb024 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -346,4 +346,42 @@ public class RaceVisionByteEncoder { return result; } + + /** + * Encodes a {@link RequestToJoin} message. + * @param requestToJoin Message to encode. + * @return Encoded message. + */ + public static byte[] requestToJoin(RequestToJoin requestToJoin) { + + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); + + requestToJoinBuffer.put(intToBytes(requestToJoin.getRequestType().getValue(), 4)); + + byte [] result = requestToJoinBuffer.array(); + + return result; + + } + + /** + * Encodes a {@link JoinAcceptance} message. + * @param joinAcceptance Message to encode. + * @return Encoded message. + */ + public static byte[] joinAcceptance(JoinAcceptance joinAcceptance) { + + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + + byte [] result = joinAcceptanceBuffer.array(); + + return result; + + } } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java index 2aab2925..36bb5955 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java @@ -35,22 +35,22 @@ public enum RequestToJoinEnum { /** * Primitive value of the enum. */ - private byte value; + private int value; /** - * Ctor. Creates a RequestToJoinEnum from a given primitive integer value, cast to a byte. - * @param value Integer, which is cast to byte, to construct from. + * Ctor. Creates a RequestToJoinEnum from a given int value. + * @param value Integer to construct from. */ private RequestToJoinEnum(int value) { - this.value = (byte) value; + this.value = value; } /** * Returns the primitive value of the enum. * @return Primitive value of the enum. */ - public byte getValue() { + public int getValue() { return value; } @@ -58,32 +58,32 @@ public enum RequestToJoinEnum { /** - * Stores a mapping between Byte values and RequestToJoinEnum values. + * Stores a mapping between Integer values and RequestToJoinEnum values. */ - private static final Map byteToRequestMap = new HashMap<>(); + private static final Map intToRequestMap = new HashMap<>(); /* - Static initialization block. Initializes the byteToRequestMap. + Static initialization block. Initializes the intToRequestMap. */ static { for (RequestToJoinEnum type : RequestToJoinEnum.values()) { - RequestToJoinEnum.byteToRequestMap.put(type.value, type); + RequestToJoinEnum.intToRequestMap.put(type.value, type); } } /** - * Returns the enumeration value which corresponds to a given byte value. - * @param requestToJoinEnum Byte value to convert to a RequestToJoinEnum value. - * @return The RequestToJoinEnum value which corresponds to the given byte value. + * Returns the enumeration value which corresponds to a given int value. + * @param requestToJoinEnum int value to convert to a RequestToJoinEnum value. + * @return The RequestToJoinEnum value which corresponds to the given int value. */ - public static RequestToJoinEnum fromByte(byte requestToJoinEnum) { + public static RequestToJoinEnum fromInt(int requestToJoinEnum) { //Gets the corresponding MessageType from the map. - RequestToJoinEnum type = RequestToJoinEnum.byteToRequestMap.get(requestToJoinEnum); + RequestToJoinEnum type = RequestToJoinEnum.intToRequestMap.get(requestToJoinEnum); if (type == null) { - //If the byte value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. + //If the int value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. return RequestToJoinEnum.NOT_A_REQUEST_TYPE; } else { //Otherwise, return the RequestToJoinEnum. diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java new file mode 100644 index 00000000..cca27fb2 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -0,0 +1,99 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.JoinAcceptance; +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * Test for the {@link network.Messages.JoinAcceptance} encoder and decoder + */ +public class JoinAcceptanceDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.joinAcceptance(message); + + //Decode. + JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); + JoinAcceptance decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a specific acceptance type message can be encoded and decoded correctly. + * @param type The type of acceptance response. + * @param sourceID The source ID to assign. + */ + private void responseTypeTest(JoinAcceptanceEnum type, int sourceID) throws Exception { + + //Prepare message. + JoinAcceptance beforeMessage = new JoinAcceptance(type, sourceID); + + + //Encode/decode it. + JoinAcceptance afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getAcceptanceType().getValue(), afterMessage.getAcceptanceType().getValue()); + assertEquals(beforeMessage.getSourceID(), afterMessage.getSourceID()); + + } + + + /** + * Tests if a join success message, with a source ID, can be encoded and decoded correctly. + */ + @Test + public void joinSuccessSourceIDTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 12345); + } + + /** + * Tests if a join success message, with no source ID, can be encoded and decoded correctly. + */ + @Test + public void joinSuccessNoSourceIDTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 0); + } + + /** + * Tests if a participants full message can be encoded and decoded correctly. + */ + @Test + public void participantFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, 0); + } + + /** + * Tests if a ghosts full message can be encoded and decoded correctly. + */ + @Test + public void ghostFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.GHOST_PARTICIPANTS_FULL, 0); + } + + /** + * Tests if a server full message can be encoded and decoded correctly. + */ + @Test + public void serverFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.SERVER_FULL, 0); + } + + + +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java new file mode 100644 index 00000000..5ea9f5ee --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -0,0 +1,82 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * Test for the RequestToJoin encoder and decoder + */ +public class RequestToJoinDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private RequestToJoin encodeDecodeMessage(RequestToJoin message) { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.requestToJoin(message); + + //Decode. + RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); + RequestToJoin decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a specific request type message can be encoded and decoded correctly. + * @param type The type of join request. + */ + private void requestTypeTest(RequestToJoinEnum type) throws Exception { + + //Prepare message. + RequestToJoin beforeMessage = new RequestToJoin(type); + + + //Encode/decode it. + RequestToJoin afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getRequestType().getValue(), afterMessage.getRequestType().getValue()); + + } + + + /** + * Tests if a spectator request message can be encoded and decoded correctly. + */ + @Test + public void spectatorTest() throws Exception { + requestTypeTest(RequestToJoinEnum.SPECTATOR); + } + + /** + * Tests if a participant request message can be encoded and decoded correctly. + */ + @Test + public void participantTest() throws Exception { + requestTypeTest(RequestToJoinEnum.PARTICIPANT); + } + + /** + * Tests if a ghost request message can be encoded and decoded correctly. + */ + @Test + public void ghostTest() throws Exception { + requestTypeTest(RequestToJoinEnum.GHOST); + } + + + +} From 16686678a76ed99e65517d29ea3af9cd57214e08 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 21:31:47 +1200 Subject: [PATCH 06/59] Added an InvalidMessageTypeException - thrown whenever we encounter a MessageType that isn't recognised or isn't supported. Added EncoderFactory. This creates specific MessageEncoders. Supports JoinAcceptance and RequestToJoin. Added MessageEncoder interface. Added JoinAcceptanceEncoder. Added encode(AC35Data) function to RaceVisionByteEncoder. Added RequestToJonEncoder. Updated RequestToJoin and JoinAcceptance decode/encode tests to use the above. issue #35 #36 #story[1095] --- .../InvalidMessageTypeException.java | 17 +++++++ .../MessageEncoders/EncoderFactory.java | 44 +++++++++++++++++++ .../JoinAcceptanceEncoder.java | 44 +++++++++++++++++++ .../MessageEncoders/MessageEncoder.java | 21 +++++++++ .../RaceVisionByteEncoder.java | 42 ++++++------------ .../MessageEncoders/RequestToJoinEncoder.java | 39 ++++++++++++++++ .../JoinAcceptanceDecoderTest.java | 5 ++- .../RequestToJoinDecoderTest.java | 5 ++- 8 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java diff --git a/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java new file mode 100644 index 00000000..02867eaa --- /dev/null +++ b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java @@ -0,0 +1,17 @@ +package network.Exceptions; + + +/** + * An exception thrown when we encounter a message type that isn't recognised. + */ +public class InvalidMessageTypeException extends Exception { + + + public InvalidMessageTypeException(String message) { + super(message); + } + + public InvalidMessageTypeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java new file mode 100644 index 00000000..620107fa --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -0,0 +1,44 @@ +package network.MessageEncoders; + + +import network.Exceptions.InvalidMessageTypeException; +import network.Messages.Enums.MessageType; + +/** + * Factory to create the appropriate encoder for a given message. + */ +public class EncoderFactory { + + + /** + * Private constructor. Currently doesn't need to be constructed. + */ + private EncoderFactory(){ + + } + + + /** + * Creates the correct type of encoder for a given message type. + * @param type Type of message you want an encoder for. + * @return The encoder. + * @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised. + */ + public static MessageEncoder create(MessageType type) throws InvalidMessageTypeException { + + + switch (type) { + + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); + + case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); + + + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); + } + + + + } + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java new file mode 100644 index 00000000..2b23c0dd --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java @@ -0,0 +1,44 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.JoinAcceptance; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link JoinAcceptance} message. + */ +public class JoinAcceptanceEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public JoinAcceptanceEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + JoinAcceptance joinAcceptance = (JoinAcceptance) message; + + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + + byte [] result = joinAcceptanceBuffer.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java new file mode 100644 index 00000000..d5ec4cd8 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java @@ -0,0 +1,21 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; + + +/** + * This is the interface that all message encoders must implement. + * It allows for {@link #encode(AC35Data)}ing messages. + */ +public interface MessageEncoder { + + + /** + * Encodes a given message. + * @param message The message to encode. + * @return Message in byte encoded form. + */ + public byte[] encode(AC35Data message); + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 88dcb024..78498457 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -1,6 +1,8 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; +import network.Exceptions.InvalidMessageTypeException; import network.Messages.*; import static network.Utils.ByteConverter.*; @@ -348,40 +350,24 @@ public class RaceVisionByteEncoder { /** - * Encodes a {@link RequestToJoin} message. - * @param requestToJoin Message to encode. + * Encodes a given message. + * @param message Message to encode. * @return Encoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ - public static byte[] requestToJoin(RequestToJoin requestToJoin) { + public static byte[] encode(AC35Data message) throws InvalidMessageException { - ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - - requestToJoinBuffer.put(intToBytes(requestToJoin.getRequestType().getValue(), 4)); - - byte [] result = requestToJoinBuffer.array(); + MessageEncoder encoder = null; + try { + encoder = EncoderFactory.create(message.getType()); + } catch (InvalidMessageTypeException e) { + throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e); + } - return result; + byte[] encodedMessage = encoder.encode(message); + return encodedMessage; } - /** - * Encodes a {@link JoinAcceptance} message. - * @param joinAcceptance Message to encode. - * @return Encoded message. - */ - public static byte[] joinAcceptance(JoinAcceptance joinAcceptance) { - //Message is 5 bytes. - ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); - - //Source ID is first four bytes. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); - //Acceptance type is next byte. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); - - byte [] result = joinAcceptanceBuffer.array(); - - return result; - - } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java new file mode 100644 index 00000000..b01e92de --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java @@ -0,0 +1,39 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.RequestToJoin; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +/** + * This encoder can encode a {@link network.Messages.RequestToJoin} message. + */ +public class RequestToJoinEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RequestToJoinEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RequestToJoin requestToJoin = (RequestToJoin) message; + + + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); + + requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); + + byte [] result = requestToJoinBuffer.array(); + + return result; + + } +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index cca27fb2..5cefe5ea 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; @@ -19,10 +20,10 @@ public class JoinAcceptanceDecoderTest { * @param message Message to encode/decode. * @return The decoded message. */ - private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) { + private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) throws InvalidMessageException { //Encode. - byte [] testEncodedMessage = RaceVisionByteEncoder.joinAcceptance(message); + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index 5ea9f5ee..ddfd11bd 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; @@ -20,10 +21,10 @@ public class RequestToJoinDecoderTest { * @param message Message to encode/decode. * @return The decoded message. */ - private RequestToJoin encodeDecodeMessage(RequestToJoin message) { + private RequestToJoin encodeDecodeMessage(RequestToJoin message) throws InvalidMessageException { //Encode. - byte [] testEncodedMessage = RaceVisionByteEncoder.requestToJoin(message); + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); From 31ce9fff94638209dd6a88b3e301ffdee7f7a20d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 21:53:54 +1200 Subject: [PATCH 07/59] Added BoatActonEncoder. Updated ControllerClient to use RaceVisionByteEncoder.encode(message). It also logs a warning if the encoding fails. Also removed pointless and out of place encode/decode of the action enum. Added a BoatActionDecoderTest which tests encoding/decoding a BoatAction Message. issue #35 #36 #story[1095] --- .../MessageEncoders/BoatActionEncoder.java | 40 +++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 6 - .../Messages/Enums/BoatActionEnum.java | 6 +- .../gameController/ControllerClient.java | 19 +-- .../BoatActionDecoderTest.java | 109 ++++++++++++++++++ 6 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java new file mode 100644 index 00000000..ba5ce901 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -0,0 +1,40 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatAction; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link BoatAction} message. + */ +public class BoatActionEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public BoatActionEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + BoatAction boatAction = (BoatAction) message; + + //Message is 1 byte. + ByteBuffer boatActionMessage = ByteBuffer.allocate(1); + + boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); + + byte [] result = boatActionMessage.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 620107fa..ebb7814a 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -33,6 +33,8 @@ public class EncoderFactory { case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); + case BOATACTION: return new BoatActionEncoder(); + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 78498457..e3269fba 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -341,12 +341,6 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] boatActionMessage(BoatAction boatAction){ - ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); - byte [] result = boatActionMessage.array(); - return result; - } /** diff --git a/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java index 84f6e0fd..372349f7 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java @@ -8,6 +8,10 @@ import java.util.Map; */ public enum BoatActionEnum { NOT_A_STATUS(-1), + + /** + * Autopilot = auto VMG. + */ AUTO_PILOT(1), SAILS_IN(2), SAILS_OUT(3), @@ -68,4 +72,4 @@ public enum BoatActionEnum { } } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index a0f1af75..6c49cd69 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -1,6 +1,7 @@ package visualiser.gameController; import network.BinaryMessageEncoder; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -12,6 +13,8 @@ import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Basic service for sending key presses to game server @@ -50,18 +53,20 @@ public class ControllerClient { BoatActionEnum protocolCode = key.getProtocolCode(); if(protocolCode != BoatActionEnum.NOT_A_STATUS) { - byte[] bytes = new byte[4]; - ByteBuffer.wrap(bytes).putInt(protocolCode.getValue()); - BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]); + BoatAction boatAction = new BoatAction(protocolCode); - BoatAction boatAction = new BoatAction(boatActionEnum); - - byte[] encodedBoatAction = RaceVisionByteEncoder.boatActionMessage(boatAction); + //Encode BoatAction. + byte[] encodedBoatAction = new byte[0]; + try { + encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e); + } BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, (short) encodedBoatAction.length, encodedBoatAction); - System.out.println("Sending out key: " + boatActionEnum); + System.out.println("Sending out key: " + protocolCode); outputStream.write(binaryMessage.getFullMessage()); } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java new file mode 100644 index 00000000..1a16f669 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -0,0 +1,109 @@ +package network.MessageDecoders; + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatAction; +import network.Messages.Enums.BoatActionEnum; +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +/** + * Test for the BoatAction encoder and decoder + */ +public class BoatActionDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private BoatAction encodeDecodeMessage(BoatAction message) throws InvalidMessageException { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); + + //Decode. + BoatActionDecoder testDecoder = new BoatActionDecoder(testEncodedMessage); + BoatActionEnum decodedBoatAction = testDecoder.getBoatAction(); + + BoatAction decodedMessage = new BoatAction(decodedBoatAction); + + return decodedMessage; + } + + + /** + * Tests if a specific boat action type message can be encoded and decoded correctly. + * @param type The type of boat action. + */ + private void boatActionTypeTest(BoatActionEnum type) throws Exception { + + //Prepare message. + BoatAction beforeMessage = new BoatAction(type); + + + //Encode/decode it. + BoatAction afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getBoatAction(), afterMessage.getBoatAction()); + + } + + + /** + * Tests if an autopilot message can be encoded and decoded correctly. + */ + @Test + public void autoPilotTest() throws Exception { + boatActionTypeTest(BoatActionEnum.AUTO_PILOT); + } + + /** + * Tests if a sails in message can be encoded and decoded correctly. + */ + @Test + public void sailsInTest() throws Exception { + boatActionTypeTest(BoatActionEnum.SAILS_IN); + } + + /** + * Tests if a sails out message can be encoded and decoded correctly. + */ + @Test + public void sailsOutTest() throws Exception { + boatActionTypeTest(BoatActionEnum.SAILS_OUT); + } + + /** + * Tests if a tack/gybe message can be encoded and decoded correctly. + */ + @Test + public void tackGybeTest() throws Exception { + boatActionTypeTest(BoatActionEnum.TACK_GYBE); + } + + /** + * Tests if an upwind message can be encoded and decoded correctly. + */ + @Test + public void upwindTest() throws Exception { + boatActionTypeTest(BoatActionEnum.UPWIND); + } + + /** + * Tests if a downwind message can be encoded and decoded correctly. + */ + @Test + public void downwindTest() throws Exception { + boatActionTypeTest(BoatActionEnum.DOWNWIND); + } + + +} From c3ed30019cab1b8826d8b8a52f04c0b5a2091b36 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 22:11:54 +1200 Subject: [PATCH 08/59] Added BoatLocationEncoder. Updated BoatLocationDecoder test to use new encoder. Updated MockOutput to use new encoder. It logs a warning if encoding fails. #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 24 ++++-- .../MessageEncoders/BoatLocationEncoder.java | 83 +++++++++++++++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 50 ----------- .../BoatLocationDecoderTest.java | 45 ++++++++-- 5 files changed, 140 insertions(+), 64 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index aff0947e..b773a06a 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -3,6 +3,7 @@ package mock.app; import network.BinaryMessageEncoder; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.*; import network.Messages.Enums.MessageType; @@ -13,6 +14,8 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; +import java.util.logging.Level; +import java.util.logging.Logger; /** * TCP server to send race information to connected clients. @@ -158,12 +161,13 @@ public class MockOutput implements Runnable * Encodes/serialises a BoatLocation message, and returns it. * @param boatLocation The BoatLocation message to serialise. * @return The BoatLocation message in a serialised form. + * @throws InvalidMessageException If the message cannot be encoded. */ - private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){ + private synchronized byte[] parseBoatLocation(BoatLocation boatLocation) throws InvalidMessageException { //Encodes the message. - byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); + byte[] encodedBoatLoc = RaceVisionByteEncoder.encode(boatLocation); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -277,11 +281,19 @@ public class MockOutput implements Runnable BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); if (boatLocation != null) { - //Encode. - byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); - //Write it. - this.outToVisualiser.write(boatLocationBlob); + try { + //Encode. + byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); + + //Write it. + this.outToVisualiser.write(boatLocationBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); + } + + } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java new file mode 100644 index 00000000..9ff3e197 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -0,0 +1,83 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatLocation; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link BoatLocation} message. + */ +public class BoatLocationEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public BoatLocationEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + BoatLocation boatLocation = (BoatLocation) message; + + + int messageVersionNumber = 0b1; + byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); + byte[] time = longToBytes(boatLocation.getTime(), 6); + byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); + byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); + byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); + byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); + byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); + byte[] heading = intToBytes(boatLocation.getHeading(), 2); + byte[] pitch = intToBytes(boatLocation.getPitch(), 2); + byte[] roll = intToBytes(boatLocation.getRoll(), 2); + byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); + byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); + byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); + byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); + byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); + byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); + byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); + byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); + byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); + byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); + byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); + + ByteBuffer result = ByteBuffer.allocate(56); + result.put(messageVersionBytes); + result.put(time); + result.put(sourceID); + result.put(seqNum); + result.put(deviceType); + result.put(latitude); + result.put(longitude); + result.put(altitude); + result.put(heading); + result.put(pitch); + result.put(roll); + result.put(boatSpeed); + result.put(cog); + result.put(sog); + result.put(apparentWindSpeed); + result.put(apparentWindAngle); + result.put(trueWindSpeed); + result.put(trueWindDirection); + result.put(trueWindAngle); + result.put(currentDrift); + result.put(currentSet); + result.put(rudderAngle); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index ebb7814a..e23541db 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -29,6 +29,8 @@ public class EncoderFactory { switch (type) { + case BOATLOCATION: return new BoatLocationEncoder(); + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index e3269fba..1bd0d1f9 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -220,56 +220,6 @@ public class RaceVisionByteEncoder { } - public static byte[] boatLocation(BoatLocation boatLocation){ - int messageVersionNumber = 0b1; - byte[] time = longToBytes(boatLocation.getTime(), 6); - byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); - byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); - byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); - byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); - byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(boatLocation.getHeading(), 2); - byte[] pitch = intToBytes(boatLocation.getPitch(), 2); - byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); - byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); - byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); - byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); - byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); - byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); - byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); - byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); - byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); - byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); - byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); - - ByteBuffer result = ByteBuffer.allocate(56); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(time); - result.put(sourceID); - result.put(seqNum); - result.put(deviceType); - result.put(latitude); - result.put(longitude); - result.put(altitude); - result.put(heading); - result.put(pitch); - result.put(roll); - result.put(boatSpeed); - result.put(cog); - result.put(sog); - result.put(apparentWindSpeed); - result.put(apparentWindAngle); - result.put(trueWindSpeed); - result.put(trueWindDirection); - result.put(trueWindAngle); - result.put(currentDrift); - result.put(currentSet); - result.put(rudderAngle); - return result.array(); - } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time, 6); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 2f269257..18893609 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -7,23 +7,52 @@ import org.junit.Test; /** - * Created by hba56 on 23/04/17. + * Test for the BoatLocation encoder and decoder */ public class BoatLocationDecoderTest { + + + /** + * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. + */ @Test - public void getByteArrayTest(){ + public void getByteArrayTest() throws Exception { + + //Create message. long time = System.currentTimeMillis(); - BoatLocation testMessage = new BoatLocation((byte) 1, time, 2, - 3, (byte) 1, 180, -180, 4, 5, - (short) 6, (short) 7, 8, 9, 10, 11, - (short) 12, 13, 14 , (short) 15, - 16, 17, (short) 18); - byte [] testEncodedMessage = RaceVisionByteEncoder.boatLocation(testMessage); + BoatLocation testMessage = new BoatLocation( + (byte) 1, + time, + 2, + 3, + (byte) 1, + 180, + -180, + 4, + 5, + (short) 6, + (short) 7, + 8, + 9, + 10, + 11, + (short) 12, + 13, + 14, + (short) 15, + 16, + 17, + (short) 18 ); + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); + //Decode. BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage); BoatLocation decodedTest = testDecoder.getMessage(); + //Check if valid. Assert.assertEquals(testMessage.getMessageVersionNumber(), decodedTest.getMessageVersionNumber()); Assert.assertEquals(testMessage.getTime(), decodedTest.getTime()); Assert.assertEquals(testMessage.getSequenceNumber(), decodedTest.getSequenceNumber()); From 8ef906472bd13d98d8a559c8cbfd89d6186871f2 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 23:27:28 +1200 Subject: [PATCH 09/59] Renamed Heartbeat to HeartBeat. Added HeartBeatDecoder. Added HeartBeatEncoder. BinaryMessageDecoder now uses HeartBeatDecoder. MockOutput now logs a warning if a heartBeat cannot be encoded. Added HeartBeatDecoderTest. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 26 ++++--- .../java/network/BinaryMessageDecoder.java | 5 +- .../MessageDecoders/HeartBeatDecoder.java | 53 +++++++++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../MessageEncoders/HeartBeatEncoder.java | 40 ++++++++++ .../RaceVisionByteEncoder.java | 13 ---- .../{Heartbeat.java => HeartBeat.java} | 4 +- .../java/visualiser/app/VisualiserInput.java | 21 +----- .../MessageDecoders/HeartBeatDecoderTest.java | 74 +++++++++++++++++++ 9 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java rename racevisionGame/src/main/java/network/Messages/{Heartbeat.java => HeartBeat.java} (87%) create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index b773a06a..23e7b738 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -7,12 +7,9 @@ import network.Exceptions.InvalidMessageException; 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; import java.util.logging.Level; import java.util.logging.Logger; @@ -101,24 +98,25 @@ public class MockOutput implements Runnable * Generates the next heartbeat message and returns it. Increments the heartbeat sequence number. * @return The next heartbeat message. */ - private Heartbeat createHeartbeatMessage() { + private HeartBeat createHeartbeatMessage() { //Create the heartbeat message. - Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum); + HeartBeat heartBeat = new HeartBeat(this.heartbeatSequenceNum); heartbeatSequenceNum++; - return heartbeat; + return heartBeat; } /** * Serializes a heartbeat message into a packet to be sent, and returns the byte array. - * @param heartbeat The heartbeat message to serialize. + * @param heartBeat The heartbeat message to serialize. * @return Byte array containing the next heartbeat message. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private byte[] parseHeartbeat(Heartbeat heartbeat) { + private byte[] parseHeartbeat(HeartBeat heartBeat) throws InvalidMessageException { //Serializes the heartbeat message. - byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat); + byte[] heartbeatMessage = RaceVisionByteEncoder.encode(heartBeat); //Places the serialized message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -213,7 +211,15 @@ public class MockOutput implements Runnable public void sendHeartBeat() throws IOException { //Sends a heartbeat every so often. if (timeSinceHeartbeat() >= heartbeatPeriod) { - outToVisualiser.write(parseHeartbeat(createHeartbeatMessage())); + + HeartBeat heartBeat = createHeartbeatMessage(); + + try { + outToVisualiser.write(parseHeartbeat(heartBeat)); + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode HeartBeat: " + heartBeat, e); + } + lastHeartbeatTime = System.currentTimeMillis(); } } diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index ecc6b2f3..69ecc1f0 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -133,9 +133,8 @@ public class BinaryMessageDecoder { switch(mType) { case HEARTBEAT: //System.out.println("Decoding HeartBeat Message!"); - //TODO maybe use HeartbeatDecoder.decode(message). - //TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);. - return new Heartbeat(bytesToLong(messageBody)); + HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(messageBody); + return heartBeatDecoder.getMessage(); case RACESTATUS: //System.out.println("Race Status Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java new file mode 100644 index 00000000..0984a9ea --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -0,0 +1,53 @@ +package network.MessageDecoders; + +import network.Messages.Enums.BoatActionEnum; +import network.Messages.HeartBeat; + +import static network.Utils.ByteConverter.bytesToLong; + +/** + * Decodes {@link network.Messages.HeartBeat} messages. + */ +public class HeartBeatDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private HeartBeat message; + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public HeartBeatDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + message = new HeartBeat(bytesToLong(encodedMessage)); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public HeartBeat getMessage() { + return message; + } + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index e23541db..8eff9cae 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -29,6 +29,8 @@ public class EncoderFactory { switch (type) { + case HEARTBEAT: return new HeartBeatEncoder(); + case BOATLOCATION: return new BoatLocationEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java new file mode 100644 index 00000000..934d1217 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -0,0 +1,40 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.HeartBeat; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link HeartBeat} message. + */ +public class HeartBeatEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public HeartBeatEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + HeartBeat heartbeat = (HeartBeat) message; + + //Message is 4 bytes. + ByteBuffer heartBeat = ByteBuffer.allocate(4); + heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); + + byte[] result = heartBeat.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 1bd0d1f9..51e232d7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -19,20 +19,7 @@ import java.util.List; */ public class RaceVisionByteEncoder { - /** - * Serializes a heartbeat message. - * @param heartbeat Heartbeat message. - * @return Serialized message. - */ - public static byte[] heartBeat(Heartbeat heartbeat) { - - ByteBuffer heartBeat = ByteBuffer.allocate(4); - heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); - byte[] result = heartBeat.array(); - - return result; - } /** * Serializes a RaceStatus message. diff --git a/racevisionGame/src/main/java/network/Messages/Heartbeat.java b/racevisionGame/src/main/java/network/Messages/HeartBeat.java similarity index 87% rename from racevisionGame/src/main/java/network/Messages/Heartbeat.java rename to racevisionGame/src/main/java/network/Messages/HeartBeat.java index fb1dd23f..35f378ac 100644 --- a/racevisionGame/src/main/java/network/Messages/Heartbeat.java +++ b/racevisionGame/src/main/java/network/Messages/HeartBeat.java @@ -6,7 +6,7 @@ import network.Messages.Enums.MessageType; /** * Represents a Heartbeat message. */ -public class Heartbeat extends AC35Data { +public class HeartBeat extends AC35Data { /** * Sequence number of the heartbeat. @@ -17,7 +17,7 @@ public class Heartbeat extends AC35Data { * Ctor. * @param sequenceNumber Sequence number of the heartbeat. */ - public Heartbeat(long sequenceNumber) { + public HeartBeat(long sequenceNumber) { super(MessageType.HEARTBEAT); this.sequenceNumber = sequenceNumber; } diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java index 0b8102b5..e77a2fef 100644 --- a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java +++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java @@ -1,26 +1,13 @@ 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 java.util.concurrent.ArrayBlockingQueue; import static network.Utils.ByteConverter.bytesToShort; @@ -213,12 +200,12 @@ public class VisualiserInput implements Runnable { //Heartbeat. case HEARTBEAT: { - Heartbeat heartbeat = (Heartbeat) message; + 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) { + if (heartBeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) { lastHeartbeatTime = System.currentTimeMillis(); - lastHeartbeatSequenceNum = heartbeat.getSequenceNumber(); + lastHeartbeatSequenceNum = heartBeat.getSequenceNumber(); //System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum); } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java new file mode 100644 index 00000000..ca1086e7 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -0,0 +1,74 @@ +package network.MessageDecoders; + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatAction; +import network.Messages.Enums.BoatActionEnum; +import network.Messages.HeartBeat; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +/** + * Test for the HeartBeat encoder and decoder + */ +public class HeartBeatDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private HeartBeat encodeDecodeMessage(HeartBeat message) throws InvalidMessageException { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); + + //Decode. + HeartBeatDecoder testDecoder = new HeartBeatDecoder(testEncodedMessage); + HeartBeat decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a heartbeat message with a given sequence number can be encoded and decoded correctly. + * @param sequenceNumber The sequenceNumber to use. + */ + private void heartBeatTest(long sequenceNumber) throws Exception { + + //Prepare message. + HeartBeat beforeMessage = new HeartBeat(sequenceNumber); + + + //Encode/decode it. + HeartBeat afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getSequenceNumber(), afterMessage.getSequenceNumber()); + + } + + + /** + * Tests if a heartbeat message with a sequence number of zero can be encoded and decoded correctly. + */ + @Test + public void heartBeatZeroTest() throws Exception { + heartBeatTest(0); + } + + /** + * Tests if a heartbeat message with a sequence number of 1234512 can be encoded and decoded correctly. + */ + @Test + public void heartBeatNonZeroTest() throws Exception { + heartBeatTest(1234512); + } + + +} From b486f99dbef9e18d2c5b4b89d24d52b6bf24313b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 01:01:05 +1200 Subject: [PATCH 10/59] Added XMLMessageEncoder. Refactored XMLMessageDecoder to be consistent with other decoders - only needs to expose the XMLMessage. Refactored BoatLocationDecoder to be consistent with other decoders - only needs to expose the BoatLocation. Updated XMLMessageDecoderTest to use new encoder/decoder. Also tests all three message types. Removed XMLMessageEncoderTest as it was redundant. Updated BinaryMessageDecoderTest.xmlMessageTest() to use updated XMLMessage encoder/decoder. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 31 ++- .../java/network/BinaryMessageDecoder.java | 3 +- .../MessageDecoders/BoatLocationDecoder.java | 242 +++++++++--------- .../MessageDecoders/XMLMessageDecoder.java | 107 ++++---- .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 37 --- .../MessageEncoders/XMLMessageEncoder.java | 62 +++++ .../network/BinaryMessageDecoderTest.java | 68 ++--- .../BoatLocationDecoderTest.java | 2 +- .../XMLMessageDecoderTest.java | 77 +++++- .../java/network/XMLMessageEncoderTest.java | 50 ---- .../test/resources/network/raceXML/Boats.xml | 119 +++++++++ .../test/resources/network/raceXML/Race.xml | 58 +++++ 13 files changed, 538 insertions(+), 320 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java delete mode 100644 racevisionGame/src/test/java/network/XMLMessageEncoderTest.java create mode 100644 racevisionGame/src/test/resources/network/raceXML/Boats.xml create mode 100644 racevisionGame/src/test/resources/network/raceXML/Race.xml diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 23e7b738..2119fa06 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -134,11 +134,12 @@ public class MockOutput implements Runnable * Encodes/serialises a XMLMessage message, and returns it. * @param xmlMessage The XMLMessage message to serialise. * @return The XMLMessage message in a serialised form. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) { + private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) throws InvalidMessageException { //Serialize the xml message. - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage); + byte[] encodedXML = RaceVisionByteEncoder.encode(xmlMessage); //Place the message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -262,15 +263,23 @@ public class MockOutput implements Runnable //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; + + try { + 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; + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); + continue; //Go to next iteration. + } + } //Sends the RaceStatus message. diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 69ecc1f0..34a5d88d 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -149,8 +149,7 @@ public class BinaryMessageDecoder { case XMLMESSAGE: //System.out.println("XML Message!"); XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody); - xmdecoder.decode(); - return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents()); + return xmdecoder.getMessage(); case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index db90a343..c0f4a652 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -11,132 +11,138 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 21/04/17. + * Decodes {@link BoatLocation} messages. */ public class BoatLocationDecoder { - private byte messageVersionNumber; - private byte[] time; - private byte[] sourceID; - private byte[] seqNum; - private byte deviceType; - private byte[] latitude; - private byte[] longitude; - private byte[] altitude; - private byte[] heading; - private byte[] pitch; - private byte[] roll; - private byte[] boatSpeed; - private byte[] cog; - private byte[] sog; - private byte[] apparentWindSpeed; - private byte[] apparentWindAngle; - private byte[] trueWindSpeed; - private byte[] trueWindDirection; - private byte[] trueWindAngle; - private byte[] currentDrift; - private byte[] currentSet; - private byte[] rudderAngle; + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ private BoatLocation message; - public BoatLocationDecoder(byte[] encodedBoatLocation) { - byte numMessageVersionNumber = 0; - long numTime = 0; - int numSourceID = 0; - int numSeqNum = 0; - byte numDeviceType = 0; - int numLatitude = 0; - int numLongitude = 0; - int numAltitude = 0; - int numHeading = 0; - short numPitch = 0; - short numRoll = 0; - int numBoatSpeed = 0; - int numCog = 0; - int numSog = 0; - int numApparentWindSpeed = 0; - short numApparentWindAngle = 0; - int numTrueWindSpeed = 0; - short numTrueWindDirection = 0; - short numTrueWindAngle = 0; - int numCurrentDrift = 0; - int numCurrentSet = 0; - short numRudderAngle = 0; - - try { - messageVersionNumber = encodedBoatLocation[0]; - numMessageVersionNumber = messageVersionNumber; - time = Arrays.copyOfRange(encodedBoatLocation, 1, 7); - numTime = bytesToLong(time); - sourceID = Arrays.copyOfRange(encodedBoatLocation, 7, 11); - numSourceID = bytesToInt(sourceID); - seqNum = Arrays.copyOfRange(encodedBoatLocation, 11, 15); - numSeqNum = bytesToInt(seqNum); - deviceType = encodedBoatLocation[15]; - numDeviceType = deviceType; - latitude = Arrays.copyOfRange(encodedBoatLocation, 16, 20); - numLatitude = bytesToInt(latitude); - longitude = Arrays.copyOfRange(encodedBoatLocation, 20, 24); - numLongitude = bytesToInt(longitude); - altitude = Arrays.copyOfRange(encodedBoatLocation, 24, 28); - numAltitude = bytesToInt(altitude); - heading = Arrays.copyOfRange(encodedBoatLocation, 28, 30); - numHeading = bytesToInt(heading); - pitch = Arrays.copyOfRange(encodedBoatLocation, 30, 32); - numPitch = bytesToShort(pitch); - roll = Arrays.copyOfRange(encodedBoatLocation, 32, 34); - numRoll = bytesToShort(roll); - boatSpeed = Arrays.copyOfRange(encodedBoatLocation, 34, 36); - numBoatSpeed = bytesToInt(boatSpeed); - cog = Arrays.copyOfRange(encodedBoatLocation, 36, 38); - numCog = bytesToInt(cog); - sog = Arrays.copyOfRange(encodedBoatLocation, 38, 40); - numSog = bytesToInt(sog); - apparentWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 40, 42); - numApparentWindSpeed = bytesToInt(apparentWindSpeed); - apparentWindAngle = Arrays.copyOfRange(encodedBoatLocation, 42, 44); - numApparentWindAngle = bytesToShort(apparentWindAngle); - trueWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 44, 46); - numTrueWindSpeed = bytesToInt(trueWindSpeed); - trueWindDirection = Arrays.copyOfRange(encodedBoatLocation, 46, 48); - numTrueWindDirection = bytesToShort(trueWindDirection); - trueWindAngle = Arrays.copyOfRange(encodedBoatLocation, 48, 50); - numTrueWindAngle = bytesToShort(trueWindAngle); - currentDrift = Arrays.copyOfRange(encodedBoatLocation, 50, 52); - numCurrentDrift = bytesToInt(currentDrift); - currentSet = Arrays.copyOfRange(encodedBoatLocation, 52, 54); - numCurrentSet = bytesToShort(currentSet); - rudderAngle = Arrays.copyOfRange(encodedBoatLocation, 54, 56); - numRudderAngle = bytesToShort(rudderAngle); - } catch(ArrayIndexOutOfBoundsException e){ - - } - - message = new BoatLocation(numMessageVersionNumber, numTime, - numSourceID, numSeqNum, numDeviceType, numLatitude, - numLongitude, numAltitude, numHeading, numPitch, - numRoll, numBoatSpeed, numCog, numSog, numApparentWindSpeed, - numApparentWindAngle, numTrueWindSpeed, numTrueWindDirection, - numTrueWindAngle, numCurrentDrift, numCurrentSet, numRudderAngle - );/* - message = new BoatLocation(messageVersionNumber, bytesToLong(time), - bytesToInt(sourceID), bytesToInt(seqNum), - deviceType, bytesToInt(latitude), - bytesToInt(longitude), bytesToInt(altitude), - bytesToInt(heading), bytesToShort(pitch), - bytesToShort(roll), bytesToInt(boatSpeed), - bytesToInt(cog), bytesToInt(sog), - bytesToInt(apparentWindSpeed), bytesToShort(apparentWindAngle), - bytesToInt(trueWindSpeed), bytesToShort(trueWindDirection), - bytesToShort(trueWindAngle), bytesToInt(currentDrift), - bytesToInt(currentSet), bytesToShort(rudderAngle) - );*/ - -// System.out.println(bytesToInt(sourceID)); -// System.out.println(bytesToInt(boatSpeed)); + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public BoatLocationDecoder(byte[] encodedMessage) { + + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + byte[] messageVersionNumber = Arrays.copyOfRange(encodedMessage, 0, 1); + byte numMessageVersionNumber = messageVersionNumber[0]; + + byte[] time = Arrays.copyOfRange(encodedMessage, 1, 7); + long numTime = bytesToLong(time); + + byte[] sourceID = Arrays.copyOfRange(encodedMessage, 7, 11); + int numSourceID = bytesToInt(sourceID); + + byte[] seqNum = Arrays.copyOfRange(encodedMessage, 11, 15); + int numSeqNum = bytesToInt(seqNum); + + byte[] deviceType = Arrays.copyOfRange(encodedMessage, 15, 16); + byte numDeviceType = deviceType[0]; + + byte[] latitude = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitude); + + byte[] longitude = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitude); + + byte[] altitude = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitude); + + byte[] heading = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(heading); + + byte[] pitch = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitch); + + byte[] roll = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(roll); + + byte[] boatSpeed = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeed); + + byte[] cog = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cog); + + byte[] sog = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sog); + + byte[] apparentWindSpeed = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeed); + + byte[] apparentWindAngle = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngle); + + byte[] trueWindSpeed = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeed); + + byte[] trueWindDirection = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirection); + + byte[] trueWindAngle = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngle); + + byte[] currentDrift = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDrift); + + byte[] currentSet = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSet); + + byte[] rudderAngle = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngle); + + + message = new BoatLocation( + numMessageVersionNumber, + numTime, + numSourceID, + numSeqNum, + numDeviceType, + numLatitude, + numLongitude, + numAltitude, + numHeading, + numPitch, + numRoll, + numBoatSpeed, + numCog, + numSog, + numApparentWindSpeed, + numApparentWindAngle, + numTrueWindSpeed, + numTrueWindDirection, + numTrueWindAngle, + numCurrentDrift, + numCurrentSet, + numRudderAngle ); + } + /** + * Returns the decoded message. + * @return The decoded message. + */ public BoatLocation getMessage() { return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 18ead92e..4fb985a1 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,10 +1,8 @@ package network.MessageDecoders; import network.Messages.Enums.XMLMessageType; +import network.Messages.XMLMessage; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import static network.Utils.ByteConverter.bytesToLong; @@ -12,73 +10,70 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 20/04/17. + * Decodes {@link network.Messages.XMLMessage} messages. */ public class XMLMessageDecoder { - private byte messageVersionNumber; - private short ackNumber; - private long timeStamp; - private byte xmlMsgSubType; - private short sequenceNumber; - private short xmlMsgLength; - private String xmlMessage; - - private byte[] bytes; - - public XMLMessageDecoder(byte[] bytes) { - this.bytes = bytes; - } - - public void decode(){ - byte[] ackNumberBytes = Arrays.copyOfRange(bytes, 1, 3); - byte[] timeStampBytes = Arrays.copyOfRange(bytes, 3, 9); - byte[] sequenceNumberBytes = Arrays.copyOfRange(bytes, 10, 12); - byte[] xmlMsgLengthBytes = Arrays.copyOfRange(bytes, 12, 14); - byte[] xmlMessagebytes = Arrays.copyOfRange(bytes, 14, bytes.length); - - this.xmlMsgSubType = bytes[9]; - this.messageVersionNumber = bytes[0]; - this.ackNumber = bytesToShort(ackNumberBytes); - this.timeStamp = bytesToLong(timeStampBytes); + /** + * The encoded message. + */ + private byte[] encodedMessage; - this.sequenceNumber = bytesToShort(sequenceNumberBytes); - this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes); - this.xmlMessage = new String(xmlMessagebytes).trim(); - } + /** + * The decoded message. + */ + private XMLMessage message; - public byte getMessageVersionNumber() { - return messageVersionNumber; - } - public short getAckNumber() { - return ackNumber; - } - public long getTimeStamp() { - return timeStamp; - } - - public XMLMessageType getXmlMsgSubType() { - return XMLMessageType.fromByte(xmlMsgSubType); - } + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public XMLMessageDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; - public short getSequenceNumber() { - return sequenceNumber; + decode(); } - public short getXmlMsgLength() { - return xmlMsgLength; + /** + * Decodes the contained message. + */ + private void decode() { + + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); + byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); + byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); + byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); + byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); + byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); + + + byte messageVersionNumber = messageVersionNumberBytes[0]; + short ackNumber = bytesToShort(ackNumberBytes); + long timeStamp = bytesToLong(timeStampBytes); + XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); + short sequenceNumber = bytesToShort(sequenceNumberBytes); + short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); + String xmlMessage = new String(xmlMessagebytes); + + + message = new XMLMessage( + messageVersionNumber, + ackNumber, + timeStamp, + xmlMsgSubType, + sequenceNumber, + xmlMessage ); } - /** - * Returns the contents of the XML message (e.g., the contents of a race.xml file). - * @return The contents of the XML message. + * Returns the decoded message. + * @return The decoded message. */ - public String getXmlMessageContents() { - return xmlMessage; + public XMLMessage getMessage() { + return message; } - } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 8eff9cae..52bff99a 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -31,6 +31,8 @@ public class EncoderFactory { case HEARTBEAT: return new HeartBeatEncoder(); + case XMLMESSAGE: return new XMLMessageEncoder(); + case BOATLOCATION: return new BoatLocationEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 51e232d7..b6b7c774 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -170,43 +170,6 @@ public class RaceVisionByteEncoder { } - /** - * Serializes an xml message into a byte array. - * @param xmlMessage The message to serialize. - * @return byte array contaning serialized message. - */ - public static byte[] xmlMessage(XMLMessage xmlMessage) { - - - byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - - ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); - - //ackNumber converted to bytes - byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); - - //Timestamp converted to bytes. - byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); - - //sequenceNumber converted to bytes - byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); - - //xmlMsgLength converted to bytes - byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); - - - tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); - tempOutputByteBuffer.put(ackNumberBytes); - tempOutputByteBuffer.put(timestampBytes); - tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); - tempOutputByteBuffer.put(sequenceNumberBytes); - tempOutputByteBuffer.put(xmlMsgLengthBytes); - tempOutputByteBuffer.put(messageBytes); - - return tempOutputByteBuffer.array(); - - } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time, 6); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java new file mode 100644 index 00000000..92a75ca7 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java @@ -0,0 +1,62 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.XMLMessage; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link XMLMessage} message. + */ +public class XMLMessageEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public XMLMessageEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + XMLMessage xmlMessage = (XMLMessage) message; + + + byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); + + //Message is 14 + xmlMessage.length bytes. + ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); + + //ackNumber converted to bytes + byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); + + //Timestamp converted to bytes. + byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); + + //sequenceNumber converted to bytes + byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + + //xmlMsgLength converted to bytes + byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); + + + tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); + tempOutputByteBuffer.put(ackNumberBytes); + tempOutputByteBuffer.put(timestampBytes); + tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); + tempOutputByteBuffer.put(sequenceNumberBytes); + tempOutputByteBuffer.put(xmlMsgLengthBytes); + tempOutputByteBuffer.put(messageBytes); + + return tempOutputByteBuffer.array(); + + } +} diff --git a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java index 7414d709..c7c843e5 100644 --- a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java @@ -2,6 +2,7 @@ package network; import network.Exceptions.InvalidMessageException; import network.MessageDecoders.XMLMessageDecoder; +import network.MessageDecoders.XMLMessageDecoderTest; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.AC35Data; import network.Messages.Enums.MessageType; @@ -21,29 +22,46 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.fail; /** - * Created by hba56 on 21/04/17. + * Tests the binary message decoder and encoder for a variety of messages. */ public class BinaryMessageDecoderTest { + + + /** + * Tests if an XMLMessage can be encoded and decoded correctly. + */ @Test - public void decodeTest(){ - try{ - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + public void xmlMessageTest() throws Exception { + + try { + + String filePath = "network/raceXML/Regatta.xml"; + XMLMessageType messageType = XMLMessageType.REGATTA; + + String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8); long time = System.currentTimeMillis(); - XMLMessage xmlMessagePre = new XMLMessage( + XMLMessage xmlMessage = new XMLMessage( (byte)1, 1, time, - XMLMessageType.REGATTA, + messageType, (short)1, - xmlString ); + xmlString ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(xmlMessage); + - byte[] encodedMessage = RaceVisionByteEncoder.xmlMessage(xmlMessagePre); + BinaryMessageEncoder encoder = new BinaryMessageEncoder( + xmlMessage.getType(), + time, + 1, + (short)encodedMessage.length, + encodedMessage ); - BinaryMessageEncoder testMessage = new BinaryMessageEncoder(MessageType.XMLMESSAGE, time, 1, (short)encodedMessage.length, encodedMessage); + BinaryMessageDecoder decoder = new BinaryMessageDecoder(encoder.getFullMessage()); - BinaryMessageDecoder decoder = new BinaryMessageDecoder(testMessage.getFullMessage()); AC35Data message = null; try { @@ -56,7 +74,7 @@ public class BinaryMessageDecoderTest { if (!(message instanceof XMLMessage)){ Assert.assertFalse(true); } - XMLMessage xmlMessage = (XMLMessage) message; + XMLMessage xmlMessageDecoded = (XMLMessage) message; //message length @@ -66,29 +84,17 @@ public class BinaryMessageDecoderTest { //source ID Assert.assertEquals((short) 1, decoder.getHeaderSourceID()); //message type - Assert.assertEquals(26, decoder.getHeaderMessageType()); - - XMLMessageDecoder decoderXML = new XMLMessageDecoder(decoder.getMessageBody()); - decoderXML.decode(); - - //tests from seng302.Networking.MessageDecoders.XMLMessageDecoderTest to make sure the file is still good - Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); - Assert.assertEquals((short)1, decoderXML.getAckNumber()); - Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); - Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); - Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); - -// Reader reader = decoderXML.getXmlMessageInputStream().getCharacterStream(); -// int c; -// String contents = ""; -// while((c = reader.read()) != -1) { -// contents += (char)c; -// } -// Assert.assertEquals(xmlString.toString(), contents); + Assert.assertEquals(MessageType.XMLMESSAGE.getValue(), decoder.getHeaderMessageType()); + + + XMLMessageDecoderTest.compareXMLMessages(xmlMessage, xmlMessageDecoded); + } catch (XMLReaderException | TransformerException e){ fail("couldn't read file" + e.getMessage()); } } + + //TODO add some tests for more messages types. + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 18893609..22831eaa 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -16,7 +16,7 @@ public class BoatLocationDecoderTest { * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. */ @Test - public void getByteArrayTest() throws Exception { + public void boatLocationEncodeDecodeTest() throws Exception { //Create message. long time = System.currentTimeMillis(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index b0d0eee3..fb0683a3 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -14,13 +15,21 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.fail; /** - * Created by hba56 on 20/04/17. + * Test for the XMLMessage encoder and decoder */ public class XMLMessageDecoderTest { - @Test - public void getByteArrayTest(){ - try{ - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + + + /** + * Creates an XML message of the given type, using the specified filePath, encodes it, decodes it, and checks that the result matches the starting message. + * @param filePath The file path for xml file. + * @param type The type of xml file. + * @throws InvalidMessageException Thrown if message cannot be encoded. + */ + private void xmlMessageTest(String filePath, XMLMessageType type) throws InvalidMessageException { + + try { + String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8); long time = System.currentTimeMillis(); @@ -28,27 +37,67 @@ public class XMLMessageDecoderTest { (byte)1, 1, time, - XMLMessageType.REGATTA, + type, (short)1, xmlString ); - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); + byte[] encodedXML = RaceVisionByteEncoder.encode(message); XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); - decoderXML.decode(); + XMLMessage decodedMessage = decoderXML.getMessage(); + + compareXMLMessages(message, decodedMessage); - Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); - Assert.assertEquals((short)1, decoderXML.getAckNumber()); - Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); - Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); - Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); } catch (XMLReaderException | TransformerException e){ fail("couldn't read file" + e.getMessage()); } + + } + + /** + * Compares two XML messages to check that they are the same. + * @param originalMessage The first message to test. + * @param decodedMessage The second message to test. + */ + public static void compareXMLMessages(XMLMessage originalMessage, XMLMessage decodedMessage) { + + Assert.assertEquals(originalMessage.getVersionNumber(), decodedMessage.getVersionNumber()); + Assert.assertEquals(originalMessage.getAckNumber(), decodedMessage.getAckNumber()); + Assert.assertEquals(originalMessage.getTimeStamp(), decodedMessage.getTimeStamp()); + Assert.assertEquals(originalMessage.getXmlMsgSubType(), decodedMessage.getXmlMsgSubType()); + Assert.assertEquals(originalMessage.getSequenceNumber(), decodedMessage.getSequenceNumber()); + Assert.assertEquals(originalMessage.getXmlMsgLength(), decodedMessage.getXmlMsgLength()); + Assert.assertEquals(originalMessage.getXmlMessage(), decodedMessage.getXmlMessage()); + + } + + /** + * Tests if a regatta.xml message can be encoded and decoded. + */ + @Test + public void regattaXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Regatta.xml", XMLMessageType.REGATTA); + } + + + /** + * Tests if a race.xml message can be encoded and decoded. + */ + @Test + public void raceXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Race.xml", XMLMessageType.RACE); + } + + + /** + * Tests if a boat.xml message can be encoded and decoded. + */ + @Test + public void boatXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Boats.xml", XMLMessageType.BOAT); } } diff --git a/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java b/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java deleted file mode 100644 index 44a99997..00000000 --- a/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package network; - -import network.MessageEncoders.RaceVisionByteEncoder; -import network.Messages.Enums.XMLMessageType; -import network.Messages.XMLMessage; -import org.junit.Assert; -import org.junit.Test; -import shared.dataInput.XMLReader; -import shared.exceptions.XMLReaderException; - -import javax.xml.transform.TransformerException; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.fail; - -/** - * Created by hba56 on 19/04/17. - */ -public class XMLMessageEncoderTest { - @Test - public void getByteArrayTest() { - try { - - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); - - - XMLMessage message = new XMLMessage( - (byte)1, - 1, - System.currentTimeMillis(), - XMLMessageType.REGATTA, - (short)1, - xmlString ); - - int xmlMessageLength = xmlString.getBytes().length; - - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); - - //1 + 2 + 6 + 1 + 2 + 2 + xml.byteLength - Assert.assertEquals(14 + xmlMessageLength, encodedXML.length); - - } catch (XMLReaderException | TransformerException e){ - fail("couldn't read file" + e.getMessage()); - } - } - -} diff --git a/racevisionGame/src/test/resources/network/raceXML/Boats.xml b/racevisionGame/src/test/resources/network/raceXML/Boats.xml new file mode 100644 index 00000000..ed4b6ded --- /dev/null +++ b/racevisionGame/src/test/resources/network/raceXML/Boats.xml @@ -0,0 +1,119 @@ + + + + 2017-04-19T15:49:40+1200 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/network/raceXML/Race.xml b/racevisionGame/src/test/resources/network/raceXML/Race.xml new file mode 100644 index 00000000..29478c60 --- /dev/null +++ b/racevisionGame/src/test/resources/network/raceXML/Race.xml @@ -0,0 +1,58 @@ + + + 17041901 + Fleet + 2017-04-19T15:30:00+1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e464ee298e3e56360085eacf8a09d78889992e58 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 01:08:47 +1200 Subject: [PATCH 11/59] javadoc fixes for some warnings. --- .../src/test/java/network/BinaryMessageDecoderTest.java | 1 + .../network/MessageDecoders/BoatActionDecoderTest.java | 8 ++++++++ .../network/MessageDecoders/BoatLocationDecoderTest.java | 1 + .../network/MessageDecoders/HeartBeatDecoderTest.java | 4 ++++ .../MessageDecoders/JoinAcceptanceDecoderTest.java | 7 +++++++ .../network/MessageDecoders/RequestToJoinDecoderTest.java | 5 +++++ .../network/MessageDecoders/XMLMessageDecoderTest.java | 3 +++ 7 files changed, 29 insertions(+) diff --git a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java index c7c843e5..55ac00be 100644 --- a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java @@ -29,6 +29,7 @@ public class BinaryMessageDecoderTest { /** * Tests if an XMLMessage can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void xmlMessageTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java index 1a16f669..d7e13b97 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -21,6 +21,7 @@ public class BoatActionDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private BoatAction encodeDecodeMessage(BoatAction message) throws InvalidMessageException { @@ -40,6 +41,7 @@ public class BoatActionDecoderTest { /** * Tests if a specific boat action type message can be encoded and decoded correctly. * @param type The type of boat action. + * @throws Exception if test fails. */ private void boatActionTypeTest(BoatActionEnum type) throws Exception { @@ -59,6 +61,7 @@ public class BoatActionDecoderTest { /** * Tests if an autopilot message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void autoPilotTest() throws Exception { @@ -67,6 +70,7 @@ public class BoatActionDecoderTest { /** * Tests if a sails in message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void sailsInTest() throws Exception { @@ -75,6 +79,7 @@ public class BoatActionDecoderTest { /** * Tests if a sails out message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void sailsOutTest() throws Exception { @@ -83,6 +88,7 @@ public class BoatActionDecoderTest { /** * Tests if a tack/gybe message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void tackGybeTest() throws Exception { @@ -91,6 +97,7 @@ public class BoatActionDecoderTest { /** * Tests if an upwind message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void upwindTest() throws Exception { @@ -99,6 +106,7 @@ public class BoatActionDecoderTest { /** * Tests if a downwind message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void downwindTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 22831eaa..05964aaf 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -14,6 +14,7 @@ public class BoatLocationDecoderTest { /** * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. */ @Test public void boatLocationEncodeDecodeTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java index ca1086e7..778a667a 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -20,6 +20,7 @@ public class HeartBeatDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException if the message cannot be encoded. */ private HeartBeat encodeDecodeMessage(HeartBeat message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a given sequence number can be encoded and decoded correctly. * @param sequenceNumber The sequenceNumber to use. + * @throws Exception if test fails. */ private void heartBeatTest(long sequenceNumber) throws Exception { @@ -56,6 +58,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a sequence number of zero can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void heartBeatZeroTest() throws Exception { @@ -64,6 +67,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a sequence number of 1234512 can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void heartBeatNonZeroTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index 5cefe5ea..cd31ad67 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -19,6 +19,7 @@ public class JoinAcceptanceDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class JoinAcceptanceDecoderTest { * Tests if a specific acceptance type message can be encoded and decoded correctly. * @param type The type of acceptance response. * @param sourceID The source ID to assign. + * @throws Exception if test fails. */ private void responseTypeTest(JoinAcceptanceEnum type, int sourceID) throws Exception { @@ -57,6 +59,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a join success message, with a source ID, can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void joinSuccessSourceIDTest() throws Exception { @@ -65,6 +68,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a join success message, with no source ID, can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void joinSuccessNoSourceIDTest() throws Exception { @@ -73,6 +77,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a participants full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void participantFullTest() throws Exception { @@ -81,6 +86,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a ghosts full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void ghostFullTest() throws Exception { @@ -89,6 +95,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a server full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void serverFullTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index ddfd11bd..bde4ae75 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -20,6 +20,7 @@ public class RequestToJoinDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private RequestToJoin encodeDecodeMessage(RequestToJoin message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a specific request type message can be encoded and decoded correctly. * @param type The type of join request. + * @throws Exception if test fails. */ private void requestTypeTest(RequestToJoinEnum type) throws Exception { @@ -56,6 +58,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a spectator request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void spectatorTest() throws Exception { @@ -64,6 +67,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a participant request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void participantTest() throws Exception { @@ -72,6 +76,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a ghost request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void ghostTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index fb0683a3..32d1a61e 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -77,6 +77,7 @@ public class XMLMessageDecoderTest { /** * Tests if a regatta.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void regattaXMLMessageTest() throws Exception { @@ -86,6 +87,7 @@ public class XMLMessageDecoderTest { /** * Tests if a race.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void raceXMLMessageTest() throws Exception { @@ -95,6 +97,7 @@ public class XMLMessageDecoderTest { /** * Tests if a boat.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void boatXMLMessageTest() throws Exception { From b1922fc3fc7be6fb6d208ff5d96d745d97ef43ab Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 13:38:27 +1200 Subject: [PATCH 12/59] Added RaceStatusEncoder. Refactored RaceStatusDecoder to be more consistent with other decoders - it now has a getMessage() function. Added BoatStatus encoder and decoder - the RaceStatus encoder and decoder uses this for BoatStatuses. The BoatStatus encoder doesn't implement the MessageEncoder interface as BoatStatus is not a proper message type (doesn't inherit from AC35Data). Added remaining cases to EncoderFactory, but commented them out. BoatStatus now uses BoatStatusEnum instead of a byte. Added some comments to RaceStatus, and it uses enums instead of bytes. MockOutput logs a warning if a RaceStatus cannot be encoded. Added a BoatStatusDecoderTest. Updated RaceStatusDecoder to use new encoders/decoders. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 15 +- .../src/main/java/mock/model/RaceServer.java | 5 +- .../java/network/BinaryMessageDecoder.java | 2 +- .../MessageDecoders/BoatStatusDecoder.java | 90 +++++++++ .../MessageDecoders/RaceStatusDecoder.java | 177 +++++++++--------- .../MessageEncoders/BoatStatusEncoder.java | 56 ++++++ .../MessageEncoders/EncoderFactory.java | 18 ++ .../MessageEncoders/HeartBeatEncoder.java | 1 - .../MessageEncoders/RaceStatusEncoder.java | 90 +++++++++ .../RaceVisionByteEncoder.java | 51 ----- .../java/network/Messages/BoatLocation.java | 18 +- .../java/network/Messages/BoatStatus.java | 8 +- .../network/Messages/Enums/MessageType.java | 8 +- .../java/network/Messages/RaceStatus.java | 137 +++++++++++--- .../java/visualiser/model/VisualiserRace.java | 4 +- .../BoatStatusDecoderTest.java | 90 +++++++++ .../RaceStatusDecoderTest.java | 103 +++++++--- 17 files changed, 659 insertions(+), 214 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 2119fa06..328c5e79 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -185,11 +185,12 @@ public class MockOutput implements Runnable * Encodes/serialises a RaceStatus message, and returns it. * @param raceStatus The RaceStatus message to serialise. * @return The RaceStatus message in a serialised form. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){ + private synchronized byte[] parseRaceStatus(RaceStatus raceStatus) throws InvalidMessageException { //Encodes the messages. - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatus); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -284,9 +285,15 @@ public class MockOutput implements Runnable //Sends the RaceStatus message. if (this.latestMessages.getRaceStatus() != null) { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - this.outToVisualiser.write(raceStatusBlob); + try { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + + this.outToVisualiser.write(raceStatusBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); + } } //Send all of the BoatLocation messages. diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index c7e3ab69..b68ada32 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -139,13 +139,14 @@ public class RaceServer { //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( + RaceStatus.currentMessageVersionNumber, System.currentTimeMillis(), race.getRaceId(), - race.getRaceStatusEnum().getValue(), + race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), windDirectionInt, windSpeedInt, - race.getRaceType().getValue(), + race.getRaceType(), boatStatuses); this.latestMessages.setRaceStatus(raceStatus); diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 34a5d88d..eb9c6997 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -139,7 +139,7 @@ public class BinaryMessageDecoder { case RACESTATUS: //System.out.println("Race Status Message"); RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody); - return new RaceStatus(rsdecoder.getTime(), rsdecoder.getRace(), rsdecoder.getRaceState(), rsdecoder.getStartTime(), rsdecoder.getRaceWindDir(), rsdecoder.getRaceWindSpeed(), rsdecoder.getRaceType(), rsdecoder.getBoats()); + return rsdecoder.getMessage(); case DISPLAYTEXTMESSAGE: //System.out.println("Display Text Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java new file mode 100644 index 00000000..dd482486 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + + +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static network.Utils.ByteConverter.*; + + +/** + * Decodes {@link BoatStatus} messages. + */ +public class BoatStatusDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private BoatStatus message; + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public BoatStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + int sourceID = bytesToInt(sourceIDBytes); + + byte[] boatStatusBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + BoatStatusEnum boatStatus = BoatStatusEnum.fromByte(boatStatusBytes[0]); + + byte[] legNumberBytes = Arrays.copyOfRange(encodedMessage, 5, 6); + byte legNumber = legNumberBytes[0]; + + byte[] numPenaltiesAwardedBytes = Arrays.copyOfRange(encodedMessage, 6, 7); + byte numPenaltiesAwarded = numPenaltiesAwardedBytes[0]; + + byte[] numPenaltiesServedBytes = Arrays.copyOfRange(encodedMessage, 7, 8); + byte numPenaltiesServed = numPenaltiesServedBytes[0]; + + byte[] estTimeAtNextMarkBytes = Arrays.copyOfRange(encodedMessage, 8, 14); + long estTimeAtNextMark = bytesToLong(estTimeAtNextMarkBytes); + + byte[] estTimeAtFinishBytes = Arrays.copyOfRange(encodedMessage, 14, 20); + long estTimeAtFinish = bytesToLong(estTimeAtFinishBytes); + + message = new BoatStatus( + sourceID, + boatStatus, + legNumber, + numPenaltiesAwarded, + numPenaltiesServed, + estTimeAtNextMark, + estTimeAtFinish ); + + + } + + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public BoatStatus getMessage() { + return message; + } +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index e4d147df..4b67daa5 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -2,9 +2,13 @@ package network.MessageDecoders; import network.Messages.BoatStatus; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import network.Messages.RaceStatus; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.bytesToLong; @@ -12,110 +16,101 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 21/04/17. + * Decodes {@link RaceStatus} messages. */ public class RaceStatusDecoder { - private byte versionNum; - private byte[] timeBytes; - private byte[] raceID; - private byte raceStatus; - private byte[] expectedStart; - private byte[] raceWind; - private byte[] windSpeed; - private byte numBoats; - private byte bytesRaceType; - private byte[] boatsBytes; - - private long time; - private int race; - private byte raceState; - private long startTime; - private int raceWindDir; - private short raceWindSpeed; - private int numberOfBoats; - private int raceType; - private ArrayList boats = new ArrayList<>(); - - - public RaceStatusDecoder(byte[] encodedRaceStatus){ - versionNum = encodedRaceStatus[0]; - timeBytes = Arrays.copyOfRange(encodedRaceStatus, 1, 7); - raceID = Arrays.copyOfRange(encodedRaceStatus, 7, 11); - raceStatus = encodedRaceStatus[11]; - expectedStart = Arrays.copyOfRange(encodedRaceStatus, 12, 18); - raceWind = Arrays.copyOfRange(encodedRaceStatus, 18, 20); - windSpeed = Arrays.copyOfRange(encodedRaceStatus, 20, 22); - numBoats = encodedRaceStatus[22]; - bytesRaceType = encodedRaceStatus[23]; - boatsBytes = Arrays.copyOfRange(encodedRaceStatus, 24, 25+20*this.numBoats); - - time = bytesToLong(timeBytes); - race = bytesToInt(raceID); - raceState = raceStatus; - startTime = bytesToLong(expectedStart); - raceWindDir = bytesToInt(raceWind); - raceWindSpeed = bytesToShort(windSpeed); - numberOfBoats = bytesToInt(numBoats); - - int boatLoopIndex = 0; - - for (int i=0; i < numberOfBoats; i++) { - byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20); - - byte[] sourceID = Arrays.copyOfRange(boatBytes, 0, 3); - byte boatStatus = boatBytes[4]; - byte legNumber = boatBytes[5]; - byte numPenaltiesAwarded = boatBytes[6]; - byte numPenaltiesServed = boatBytes[7]; - byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14); - byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20); - - BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus, - legNumber, numPenaltiesAwarded, numPenaltiesServed, - bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish)); - - boats.add(boat); - boatLoopIndex += 20; - } - } - public byte getVersionNum() { - return versionNum; - } + /** + * The encoded message. + */ + private byte[] encodedMessage; - public long getTime() { - return time; - } + /** + * The decoded message. + */ + private RaceStatus message; - public int getRace() { - return race; - } - public byte getRaceState() { - return raceState; - } - public long getStartTime() { - return startTime; - } + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public RaceStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; - public int getRaceWindDir() { - return raceWindDir; + decode(); } - public short getRaceWindSpeed() { - return raceWindSpeed; - } - public int getNumberOfBoats() { - return numberOfBoats; - } + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte versionNum = versionNumBytes[0]; + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceID = bytesToInt(raceIDBytes); + + byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); + RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); + + byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); + long expectedStart = bytesToLong(expectedStartBytes); - public int getRaceType() { - return raceType; + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); + int windDirection = bytesToInt(windDirectionBytes); + + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); + short windSpeed = bytesToShort(windSpeedBytes); + + byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); + int numberOfBoats = bytesToInt(numberOfBoatsBytes); + + byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); + RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); + + byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); + List boatStatuses = new ArrayList<>(); + + + + //Decode each BoatStatus. + for (int boatLoopIndex=0; boatLoopIndex < (numberOfBoats * 20); boatLoopIndex += 20) { + + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + 20); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusBytes); + + boatStatuses.add(boatStatusDecoder.getMessage()); + } + + + message = new RaceStatus( + versionNum, + time, + raceID, + raceStatus, + expectedStart, + windDirection, + windSpeed, + raceType, + boatStatuses ); } - public ArrayList getBoats() { - return boats; + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RaceStatus getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java new file mode 100644 index 00000000..3a40cbba --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java @@ -0,0 +1,56 @@ +package network.MessageEncoders; + + +import network.Messages.BoatStatus; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link BoatStatus} message. + */ +public class BoatStatusEncoder { + + + /** + * Constructor. + */ + public BoatStatusEncoder() { + } + + + /** + * Encodes a given BoatStatus message. + * @param message The message to encode. + * @return The encoded message. + */ + public byte[] encode(BoatStatus message) { + + //Downcast. + BoatStatus boatStatus = (BoatStatus) message; + + //BoatStatus is 20 bytes. + ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); + + byte[] sourceID = intToBytes(boatStatus.getSourceID()); + byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); + byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); + byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); + byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); + byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); + byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); + + boatStatusBuffer.put(sourceID); + boatStatusBuffer.put(boatStatusBytes); + boatStatusBuffer.put(legNum); + boatStatusBuffer.put(numPenalties); + boatStatusBuffer.put(numPenaltiesServed); + boatStatusBuffer.put(estNextMarkTime); + boatStatusBuffer.put(estFinishTime); + + return boatStatusBuffer.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 52bff99a..c026efd7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -31,10 +31,28 @@ public class EncoderFactory { case HEARTBEAT: return new HeartBeatEncoder(); + case RACESTATUS: return new RaceStatusEncoder(); + + //case DISPLAYTEXTMESSAGE: return new DisplayTextMessageEncoder();//TODO + case XMLMESSAGE: return new XMLMessageEncoder(); + //case RACESTARTSTATUS: return new RaceStartStatusEncoder();//TODO + + //case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO + + //case YACHTACTIONCODE: return new YachtActionCodeEncoder();//TODO + + //case CHATTERTEXT: return new ChatterTextEncoder();//TODO + case BOATLOCATION: return new BoatLocationEncoder(); + //case MARKROUNDING: return new MarkRoundingEncoder();//TODO + + //case COURSEWIND: return new CourseWindEncoder();//TODO + + //case AVGWIND: return new AverageWindEncoder();//TODO + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java index 934d1217..dcf64b61 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -6,7 +6,6 @@ import network.Messages.HeartBeat; import java.nio.ByteBuffer; -import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; /** diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java new file mode 100644 index 00000000..7a0be153 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -0,0 +1,90 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatStatus; +import network.Messages.RaceStatus; + +import java.nio.ByteBuffer; +import java.util.List; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link RaceStatus} message. + */ +public class RaceStatusEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RaceStatusEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RaceStatus raceStatus = (RaceStatus) message; + + + List boatStatuses = raceStatus.getBoatStatuses(); + + //24 byte header, plus 20 bytes per boat status. + ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); + + //Version Number 1 bytes. this changes with the pdf. (2) + byte versionNum = 0b10; + + //time (6 bytes) + byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); + + //race identifier in case multiple races are going at once. + byte[] raceID = intToBytes(raceStatus.getRaceID()); + + //race status 0 - 10 + byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); + + //number of milliseconds from Jan 1, 1970 for when the data is valid + byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); + + //North = 0x0000 East = 0x4000 South = 0x8000. + byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + + //mm/sec + byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + + + byte[] numBoats = intToBytes(boatStatuses.size(), 1); + + //1 match race, 2 fleet race + byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); + + + raceStatusMessage.put(versionNum); + raceStatusMessage.put(timeBytes); + raceStatusMessage.put(raceID); + raceStatusMessage.put(raceStatusByte); + raceStatusMessage.put(expectedStart); + raceStatusMessage.put(raceWind); + raceStatusMessage.put(windSpeed); + raceStatusMessage.put(numBoats); + raceStatusMessage.put(bytesRaceType); + + //Encode each BoatStatus. + for (BoatStatus boatStatus : boatStatuses) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + raceStatusMessage.put(boatStatusEncoded); + } + + return raceStatusMessage.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index b6b7c774..ebd67819 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -21,57 +21,6 @@ public class RaceVisionByteEncoder { - /** - * Serializes a RaceStatus message. - * @param raceStatus Message to serialize. - * @return Serialized (byte array) message, ready to be written to a socket. - */ - public static byte[] raceStatus(RaceStatus raceStatus){ - - List boatStatuses = raceStatus.getBoatStatuses(); - - ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size()); - //Version Number 1 bytes - byte versionNum = 0b10; //this changes with the pdf. (2) - byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6);//time (6 bytes) - byte[] raceID = ByteBuffer.allocate(4).put(intToBytes(raceStatus.getRaceID())).array();//race identifier incase multiple races are going at once. - byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus(), 1);//race status 0 - 10 - byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6);//number of milliseconds from Jan 1, 1970 for when the data is valid - byte[] raceWind = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindDirection(), 2)).array();//North = 0x0000 East = 0x4000 South = 0x8000. - byte[] windSpeed = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindSpeed(), 2)).array();//mm/sec - byte[] numBoats = intToBytes(boatStatuses.size(), 1); - byte[] bytesRaceType = intToBytes(raceStatus.getRaceType(), 1);//1 match race, 2 fleet race - - raceStatusMessage.put(versionNum); - raceStatusMessage.put(timeBytes); - raceStatusMessage.put(raceID); - raceStatusMessage.put(raceStatusByte); - raceStatusMessage.put(expectedStart); - raceStatusMessage.put(raceWind); - raceStatusMessage.put(windSpeed); - raceStatusMessage.put(numBoats); - raceStatusMessage.put(bytesRaceType); - - for (int i = 0; i < boatStatuses.size(); i++){ - byte[] sourceID = intToBytes(boatStatuses.get(i).getSourceID()); - byte[] boatStatus = intToBytes(boatStatuses.get(i).getBoatStatus(), 1); - byte[] legNum = intToBytes(boatStatuses.get(i).getLegNumber(), 1); - byte[] numPenalties = intToBytes(boatStatuses.get(i).getNumPenaltiesAwarded(), 1); - byte[] numPenaltiesServed = intToBytes(boatStatuses.get(i).getNumPenaltiesServed(), 1); - byte[] estNextMarkTime = longToBytes(boatStatuses.get(i).getEstTimeAtNextMark(), 6); - byte[] estFinishTime = longToBytes( boatStatuses.get(i).getEstTimeAtFinish(), 6); - - raceStatusMessage.put(sourceID); - raceStatusMessage.put(boatStatus); - raceStatusMessage.put(legNum); - raceStatusMessage.put(numPenalties); - raceStatusMessage.put(numPenaltiesServed); - raceStatusMessage.put(estNextMarkTime); - raceStatusMessage.put(estFinishTime); - } - - return raceStatusMessage.array(); - } public static byte[] displayTextMessage(RaceMessage[] message){ //ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 301584b4..1f2bfc49 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -29,8 +29,14 @@ public class BoatLocation extends AC35Data { public static final byte Helicopter = 12; public static final byte DataProcessingApplication = 13; - ///Version number of the message - is always 1. - private byte messageVersionNumber = 1; + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 1; + + ///Version number of the message. + private byte messageVersionNumber; ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. private long time; ///Source ID of the boat. @@ -91,12 +97,6 @@ public class BoatLocation extends AC35Data { private short rudderAngle; - /** - * Ctor. Default. - */ - public BoatLocation() { - super(MessageType.BOATLOCATION); - } /** * Ctor, with all parameters. @@ -154,7 +154,7 @@ public class BoatLocation extends AC35Data { public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { super(MessageType.BOATLOCATION); - this.messageVersionNumber = (byte) 1; + this.messageVersionNumber = BoatLocation.currentMessageVersionNumber; this.time = time; this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index 54996726..277ae9ca 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -10,14 +10,14 @@ import network.Utils.ByteConverter; public class BoatStatus { private int sourceID; - private byte boatStatus; + private BoatStatusEnum boatStatus; private byte legNumber; private byte numPenaltiesAwarded; private byte numPenaltiesServed; private long estTimeAtNextMark; private long estTimeAtFinish; - public BoatStatus(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { this.sourceID = sourceID; this.boatStatus = boatStatus; this.legNumber = legNumber; @@ -30,7 +30,7 @@ public class BoatStatus { public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { this.sourceID = sourceID; - this.boatStatus = boatStatusEnum.getValue(); + this.boatStatus = boatStatusEnum; this.legNumber = ByteConverter.intToBytes(legNum)[0]; this.numPenaltiesAwarded = 0; this.numPenaltiesServed = 0; @@ -43,7 +43,7 @@ public class BoatStatus { return sourceID; } - public byte getBoatStatus() { + public BoatStatusEnum getBoatStatus() { return boatStatus; } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 4072f912..15f70f40 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -33,11 +33,15 @@ public enum MessageType { BOATACTION(100), NOTAMESSAGE(0); - ///Primitive value of the enum. + + /** + * Primitive value of the enum. + */ private byte value; + /** - * Ctor. Creates a MessageType enum from a given primitive integer value, cast to a byte. + * Creates a MessageType enum from a given primitive integer value, cast to a byte. * @param value Integer, which is cast to byte, to construct from. */ private MessageType(int value) { diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 42bb8208..4d98700d 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -2,27 +2,89 @@ package network.Messages; import network.Messages.Enums.MessageType; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; import shared.model.Constants; import java.util.List; /** - * Created by fwy13 on 25/04/17. + * Represents the information in a RaceStatus message (AC streaming spec: 4.2). */ public class RaceStatus extends AC35Data { + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 2; + + + /** + * Version number of the message. + */ + private byte messageVersionNumber; + + /** + * Time the message was generated at. + * Milliseconds since unix epoch. + */ private long currentTime; + + /** + * ID number of the race. + */ private int raceID; - private byte raceStatus; + + /** + * The status of the race. + */ + private RaceStatusEnum raceStatus; + + /** + * The expected race start time. + * Milliseconds since unix epoch. + */ private long expectedStartTime; + + /** + * The wind direction of the course. + */ private int windDirection; + + /** + * The wind speed of the course. + */ private int windSpeed; - private int raceType; + + /** + * The type of race this is. + */ + private RaceTypeEnum raceType; + + /** + * A list of boat statuses. + * One for each boat. + */ private List boatStatuses; - public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ + + /** + * Constructs a RaceStatus message with the given parameters. + * @param messageVersionNumber The version number of the message. + * @param currentTime Time at which the message was generated. + * @param raceID The ID of the race. + * @param raceStatus The status of the race. + * @param expectedStartTime The expected start time of the race. + * @param windDirection The current wind direction in the race. + * @param windSpeed The current wind speed in the race. + * @param raceType The type of race this is. + * @param boatStatuses A list of BoatStatuses. One for each boat. + */ + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { super(MessageType.RACESTATUS); + this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; this.raceID = raceID; this.raceStatus = raceStatus; @@ -30,37 +92,58 @@ public class RaceStatus extends AC35Data { this.windDirection = windDirection; this.windSpeed = windSpeed; this.raceType = raceType; - this.boatStatuses = boatStatuses;//note this is not a copy so any alterations to the parent will affect this. + this.boatStatuses = boatStatuses; } + /** + * Returns the version number of this message. + * @return The version number of the message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } - ///Getters. - + /** + * Returns the current time at which this message was generated. Milliseconds since unix epoch. + * @return Time this message was generated at. + */ public long getCurrentTime() { return currentTime; } + /** + * Returns the RaceID. + * @return The raceID. + */ public int getRaceID() { return raceID; } /** - * - * @return race status number + * Returns the race status. + * @return The current race status. */ - public byte getRaceStatus() + public RaceStatusEnum getRaceStatus() { return raceStatus; } + /** + * Returns the expected start time for the race. Milliseconds since unix epoch. + * @return Expected start time for the race. + */ public long getExpectedStartTime() { return expectedStartTime; } + /** + * Returns the current direction of the wind in the race. + * @return Current wind direction. + */ public int getWindDirection() { return windDirection; @@ -75,60 +158,70 @@ public class RaceStatus extends AC35Data { return windSpeed; } - public int getRaceType() + /** + * Retrusn the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { return raceType; } + /** + * Returns the list of BoatStatuses. One for each boat. + * @return List of BoatStatuses. + */ public List getBoatStatuses() { return boatStatuses; } + public boolean isNotActive() { - return raceStatus == 0; + return raceStatus == RaceStatusEnum.NOT_ACTIVE; } public boolean isWarning() { - return raceStatus == 1; + return raceStatus == RaceStatusEnum.WARNING; } public boolean isPreparatory() { - return raceStatus == 2; + return raceStatus == RaceStatusEnum.PREPARATORY; } public boolean isStarted() { - return raceStatus == 3; + return raceStatus == RaceStatusEnum.STARTED; } public boolean isFinished() { - return raceStatus == 4; + return raceStatus == RaceStatusEnum.FINISHED; } public boolean isRetired() { - return raceStatus == 5; + return raceStatus == RaceStatusEnum.RETIRED; } public boolean isAbandoned() { - return raceStatus == 6; + return raceStatus == RaceStatusEnum.ABANDONED; } public boolean isPostponed() { - return raceStatus == 7; + return raceStatus == RaceStatusEnum.POSTPONED; } public boolean isTerminated() { - return raceStatus == 8; + return raceStatus == RaceStatusEnum.TERMINATED; } public boolean isStartTimeSet() { - return raceStatus != 9; + return raceStatus != RaceStatusEnum.RACE_START_TIME_NOT_SET; } public boolean isPrestart() { - return raceStatus == 10; + return raceStatus == RaceStatusEnum.PRESTART; } + public double getScaledWindDirection() { return AC35UnitConverter.convertHeading(windDirection); } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 2969fbb8..fdc2802d 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -215,7 +215,7 @@ public class VisualiserRace extends Race implements Runnable { //Boat status. - BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus()); + BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus(); //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { @@ -309,7 +309,7 @@ public class VisualiserRace extends Race implements Runnable { private void updateRaceStatus(RaceStatus raceStatus) { //Race status enum. - this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); + this.raceStatusEnum = raceStatus.getRaceStatus(); //Wind. this.setWind( diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java new file mode 100644 index 00000000..635e581c --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + +import network.MessageEncoders.BoatStatusEncoder; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Test for the BoatStatus encoder and decoder + */ +public class BoatStatusDecoderTest { + + + /** + * Creates a BoatStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void boatStatusEncodeDecodeTest() throws Exception { + + long time = System.currentTimeMillis(); + + //Create data to serialize. + int boatSourceID = 5; + BoatStatusEnum boatStatusEnum = BoatStatusEnum.RACING; + byte boatLegNumber = 5; + byte boatPenaltiesAwarded = 4; + byte boatPenaltiesServed = 2; + long boatTimeAtNextMark = time + (1000 * 3); + long boatTimeAtFinish = boatTimeAtNextMark + (1000 * 15); + + BoatStatus boatStatus = new BoatStatus( + boatSourceID, + boatStatusEnum, + boatLegNumber, + boatPenaltiesAwarded, + boatPenaltiesServed, + boatTimeAtNextMark, + boatTimeAtFinish ); + + + BoatStatus boatStatusDecoded = encodeDecodeBoatStatus(boatStatus); + + compareBoatStatusMessages(boatStatus, boatStatusDecoded); + + } + + /** + * Encodes and decodes a BoatStatus, and returns it. + * @param boatStatus The BoatStatus to encode and decode. + * @return The decoded BoatStatus. + */ + private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusEncoded); + + BoatStatus boatStatusDecoded = boatStatusDecoder.getMessage(); + + return boatStatusDecoded; + } + + + /** + * Compares two BoatStatus messages to check that they are equal. + * @param original The original BoatStatus message. + * @param decoded The decoded BoatStatus message. + */ + public static void compareBoatStatusMessages(BoatStatus original, BoatStatus decoded) { + + Assert.assertEquals(original.getSourceID(), decoded.getSourceID()); + Assert.assertEquals(original.getBoatStatus(), decoded.getBoatStatus()); + Assert.assertEquals(original.getLegNumber(), decoded.getLegNumber()); + Assert.assertEquals(original.getNumPenaltiesAwarded(), decoded.getNumPenaltiesAwarded()); + Assert.assertEquals(original.getNumPenaltiesServed(), decoded.getNumPenaltiesServed()); + Assert.assertEquals(original.getEstTimeAtNextMark(), decoded.getEstTimeAtNextMark()); + Assert.assertEquals(original.getEstTimeAtFinish(), decoded.getEstTimeAtFinish()); + + } + +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index a5600446..1adb69b4 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -1,27 +1,39 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** - * Created by hba56 on 23/04/17. + * Test for the RaceStatus encoder and decoder */ public class RaceStatusDecoderTest { + + + /** + * Creates a RaceStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ @Test - public void getByteArrayTest(){ + public void raceStatusEncodeDecodeTest() throws Exception { + long time = System.currentTimeMillis(); //Create data to serialize. int boat1SourceID = 5; int boat2SourceID = 8; - byte boat1Status = 2; - byte boat2Status = 2; + BoatStatusEnum boat1Status = BoatStatusEnum.RACING; + BoatStatusEnum boat2Status = BoatStatusEnum.RACING; byte boat1LegNumber = 5; byte boat2LegNumber = 3; byte boat1PenaltiesAwarded = 4; @@ -33,44 +45,85 @@ public class RaceStatusDecoderTest { long boat1TimeAtFinish = boat1TimeAtNextMark + (1000 * 15); long boat2TimeAtFinish = boat2TimeAtNextMark + (1000 * 7); - BoatStatus boatStatus1 = new BoatStatus(boat1SourceID, boat1Status, boat1LegNumber, boat1PenaltiesAwarded, boat1PenaltiesServed, boat1TimeAtNextMark, boat1TimeAtFinish); - BoatStatus boatStatus2 = new BoatStatus(boat2SourceID, boat2Status, boat2LegNumber, boat2PenaltiesAwarded, boat2PenaltiesServed, boat2TimeAtNextMark, boat2TimeAtFinish); + BoatStatus boatStatus1 = new BoatStatus( + boat1SourceID, + boat1Status, + boat1LegNumber, + boat1PenaltiesAwarded, + boat1PenaltiesServed, + boat1TimeAtNextMark, + boat1TimeAtFinish ); + + BoatStatus boatStatus2 = new BoatStatus( + boat2SourceID, + boat2Status, + boat2LegNumber, + boat2PenaltiesAwarded, + boat2PenaltiesServed, + boat2TimeAtNextMark, + boat2TimeAtFinish ); + int raceID = 585; - byte raceStatus = 3; + RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); int windDirection = 2341; int windSpeed = 10201; - int raceType = 1; + RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); boatStatuses.add(boatStatus2); - RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatuses); + RaceStatus raceStatusOriginal = new RaceStatus( + RaceStatus.currentMessageVersionNumber, + time, + raceID, + raceStatus, + raceStartTime, + windDirection, + windSpeed, + raceType, + boatStatuses ); - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatusObject); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal); RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus); - Assert.assertEquals(0b10, decoderTest.getVersionNum()); - Assert.assertEquals(time, decoderTest.getTime()); - Assert.assertEquals(raceID, decoderTest.getRace()); - Assert.assertEquals(raceStatus, decoderTest.getRaceState()); - Assert.assertEquals(raceStartTime, decoderTest.getStartTime()); - Assert.assertEquals(windDirection, decoderTest.getRaceWindDir()); - Assert.assertEquals(windSpeed, decoderTest.getRaceWindSpeed()); + RaceStatus decodedMessage = decoderTest.getMessage(); + + compareRaceStatusMessages(raceStatusOriginal, decodedMessage); + + } + + + /** + * Compares two RaceStatus messages to check that they are equal. + * @param original The original RaceStatus message. + * @param decoded The decoded RaceStatus message. + */ + public static void compareRaceStatusMessages(RaceStatus original, RaceStatus decoded) { + + //Compare RaceStatus body. + Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + Assert.assertEquals(original.getCurrentTime(), decoded.getCurrentTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); + Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); + Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + //Compare all BoatStatuses + Iterator originalIterator = original.getBoatStatuses().iterator(); + Iterator decodedIterator = decoded.getBoatStatuses().iterator(); - BoatStatus boat1 = decoderTest.getBoats().get(0); + while (originalIterator.hasNext() && decodedIterator.hasNext()) { - Assert.assertEquals(boat1SourceID, boat1.getSourceID()); - Assert.assertEquals(boat1Status, boat1.getBoatStatus()); - Assert.assertEquals(boat1LegNumber, boat1.getLegNumber()); - Assert.assertEquals(boat1PenaltiesAwarded, boat1.getNumPenaltiesAwarded()); - Assert.assertEquals(boat1PenaltiesServed, boat1.getNumPenaltiesServed()); - Assert.assertEquals(boat1TimeAtNextMark, boat1.getEstTimeAtNextMark()); - Assert.assertEquals(boat1TimeAtFinish, boat1.getEstTimeAtFinish()); + BoatStatusDecoderTest.compareBoatStatusMessages(originalIterator.next(), decodedIterator.next()); + + } } + + } From a0f98eadaa18186a9979e82311097b324d1925ae Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 13:57:09 +1200 Subject: [PATCH 13/59] Added some documentation to BoatStatus. issue #35 #36 #story[1095] --- .../java/network/Messages/BoatStatus.java | 98 ++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index 277ae9ca..b62c4469 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -5,22 +5,62 @@ import network.Messages.Enums.BoatStatusEnum; import network.Utils.ByteConverter; /** - * Created by hba56 on 23/04/17. + * Represents the information in a BoatStatus message, which is contained inside a RaceStatus message (AC streaming spec: 4.2). */ public class BoatStatus { + /** + * The sourceID of the boat. + */ private int sourceID; + + /** + * The status of the boat. + */ private BoatStatusEnum boatStatus; + + /** + * The leg number that the boat is on. + */ private byte legNumber; + + /** + * The number of penalties awarded to the boat. + */ private byte numPenaltiesAwarded; + + /** + * The number of penalties served by the boat. + */ private byte numPenaltiesServed; + + /** + * The time at which it is estimated the boat will reach the next mark. + * Milliseconds since unix epoch. + */ private long estTimeAtNextMark; + + /** + * The time at which it is estimated the boat will finish the race. + * Milliseconds since unix epoch. + */ private long estTimeAtFinish; - public BoatStatus(int sourceID, BoatStatusEnum boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { + + /** + * Constructs a BoatStatus message with the given parameters. + * @param sourceID The sourceID of the boat. + * @param boatStatus The status of the boat. + * @param legNumber The leg number the boat is on. + * @param numPenaltiesAwarded The number of penalties awarded to the boat. + * @param numPenaltiesServed The number of penalties served by the boat. + * @param estTimeAtNextMark The estimated time at which the boat will reach the next mark. + * @param estTimeAtFinish The estimated time at which the boat will finish the race. + */ + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { this.sourceID = sourceID; this.boatStatus = boatStatus; - this.legNumber = legNumber; + this.legNumber = ByteConverter.intToBytes(legNumber, 1)[0]; this.numPenaltiesAwarded = numPenaltiesAwarded; this.numPenaltiesServed = numPenaltiesServed; this.estTimeAtNextMark = estTimeAtNextMark; @@ -28,41 +68,77 @@ public class BoatStatus { } - public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { - this.sourceID = sourceID; - this.boatStatus = boatStatusEnum; - this.legNumber = ByteConverter.intToBytes(legNum)[0]; - this.numPenaltiesAwarded = 0; - this.numPenaltiesServed = 0; - this.estTimeAtFinish = 0; - this.estTimeAtNextMark = estTimeAtNextMark; + /** + * Constructs a BoatStatus message with the given parameters. Sets penalties to zero, and time at finish to zero. + * @param sourceID The sourceID of the boat. + * @param boatStatus The status of the boat. + * @param legNumber The leg number the boat is on. + * @param estTimeAtNextMark The estimated time at which the boat will reach the next mark. + */ + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, long estTimeAtNextMark) { + this( + sourceID, + boatStatus, + legNumber, + (byte) 0, + (byte) 0, + estTimeAtNextMark, + 0 ); } + /** + * Returns the sourceID of the boat. + * @return The sourceID of the boat. + */ public int getSourceID() { return sourceID; } + /** + * Returns the status of the boat. + * @return The status of the boat. + */ public BoatStatusEnum getBoatStatus() { return boatStatus; } + /** + * Returns the leg number of boat is on. + * @return The leg number of boat is on. + */ public byte getLegNumber() { return legNumber; } + /** + * Returns the number of penalties awarded to the boat. + * @return Number of penalties awarded to boat. + */ public byte getNumPenaltiesAwarded() { return numPenaltiesAwarded; } + /** + * Returns the number of penalties served by the boat. + * @return The number of penalties served by the boat. + */ public byte getNumPenaltiesServed() { return numPenaltiesServed; } + /** + * Returns he time at which it is estimated the boat will reach the next mark. Milliseconds since unix epoch. + * @return Time at which boat will reach next mark. + */ public long getEstTimeAtNextMark() { return estTimeAtNextMark; } + /** + * Returns he time at which it is estimated the boat will finish the race. Milliseconds since unix epoch. + * @return Time at which boat will finish the race. + */ public long getEstTimeAtFinish() { return estTimeAtFinish; } From ff262a62272050cee68d0897212078dddfcdcae9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 14:57:51 +1200 Subject: [PATCH 14/59] Added Knots <-> MMperSec conversions to AC35UnitConverter. Removed redundant/unused conversions. Documented all of the conversions, and renamed them to pack/unpackX, to match the API spec. Updated/added some tests in AC35UnitConverterTest. RaceStatus now contains a Bearing instead of a packed int bearing. RaceStatus now contains wind speed in knots, instead of MMperSec packed. This means that only RaceStatus decoder/encoder need to care about the bits-over-wire packed values. issue #35 #36 #story[1095] --- .../src/main/java/mock/model/RaceServer.java | 7 +- .../MessageDecoders/RaceStatusDecoder.java | 10 ++- .../MessageEncoders/RaceStatusEncoder.java | 9 +- .../java/network/Messages/BoatLocation.java | 16 ++-- .../java/network/Messages/RaceStatus.java | 32 +++---- .../java/network/Utils/AC35UnitConverter.java | 79 +++++++++++++---- .../java/visualiser/model/VisualiserRace.java | 4 +- .../RaceStatusDecoderTest.java | 11 +-- .../network/Utils/AC35UnitConverterTest.java | 84 +++++++++++++++---- 9 files changed, 173 insertions(+), 79 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index b68ada32..8629056d 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -133,9 +133,6 @@ public class RaceServer { } - //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. - int windDirectionInt = AC35UnitConverter.encodeHeading(race.getWindDirection().degrees()); - int windSpeedInt = (int) (race.getWindSpeed() * Constants.KnotsToMMPerSecond); //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( @@ -144,8 +141,8 @@ public class RaceServer { race.getRaceId(), race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), - windDirectionInt, - windSpeedInt, + race.getWindDirection(), + race.getWindSpeed(), race.getRaceType(), boatStatuses); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 4b67daa5..307c2815 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -5,6 +5,8 @@ import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Arrays; @@ -65,10 +67,12 @@ public class RaceStatusDecoder { long expectedStart = bytesToLong(expectedStartBytes); byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); - int windDirection = bytesToInt(windDirectionBytes); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); - short windSpeed = bytesToShort(windSpeedBytes); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); int numberOfBoats = bytesToInt(numberOfBoatsBytes); @@ -99,7 +103,7 @@ public class RaceStatusDecoder { raceStatus, expectedStart, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java index 7a0be153..fcb06d82 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -4,10 +4,13 @@ package network.MessageEncoders; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.nio.ByteBuffer; import java.util.List; +import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; @@ -52,10 +55,12 @@ public class RaceStatusEncoder implements MessageEncoder { byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); //North = 0x0000 East = 0x4000 South = 0x8000. - byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); + byte[] raceWind = intToBytes(windDirectionInt, 2); //mm/sec - byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); + byte[] windSpeed = intToBytes(windSpeedInt, 2); byte[] numBoats = intToBytes(boatStatuses.size(), 1); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 1f2bfc49..e870a802 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -5,8 +5,8 @@ import network.Messages.Enums.MessageType; import network.Utils.AC35UnitConverter; import shared.model.Constants; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; +import static network.Utils.AC35UnitConverter.unpackGPS; +import static network.Utils.AC35UnitConverter.packGPS; /** * Represents the information in a boat location message (AC streaming spec: 4.9). @@ -159,8 +159,8 @@ public class BoatLocation extends AC35Data { this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; this.deviceType = 1; - this.latitude = convertGPSToInt(lat); - this.longitude = convertGPSToInt(lon); + this.latitude = packGPS(lat); + this.longitude = packGPS(lon); this.altitude = 0; this.heading = convertHeadingDoubleToInt(heading); this.pitch = 0; @@ -340,11 +340,11 @@ public class BoatLocation extends AC35Data { } public double getLatitudeDouble(){ - return convertGPS(this.latitude); + return unpackGPS(this.latitude); } public double getLongitudeDouble(){ - return convertGPS(this.longitude); + return unpackGPS(this.longitude); } public void setLongitude(int longitude) { @@ -474,11 +474,11 @@ public class BoatLocation extends AC35Data { } public double getHeadingDegrees(){ - return AC35UnitConverter.convertHeading(getHeading()); + return AC35UnitConverter.unpackHeading(getHeading()); } public double getTrueWindAngleDegrees(){ - return AC35UnitConverter.convertTrueWindAngle(getTrueWindAngle()); + return AC35UnitConverter.unpackTrueWindAngle(getTrueWindAngle()); } @Override diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 4d98700d..574cfce4 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -5,6 +5,7 @@ import network.Messages.Enums.MessageType; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import shared.model.Constants; import java.util.List; @@ -51,12 +52,13 @@ public class RaceStatus extends AC35Data { /** * The wind direction of the course. */ - private int windDirection; + private Bearing windDirection; /** * The wind speed of the course. + * Knots. */ - private int windSpeed; + private double windSpeed; /** * The type of race this is. @@ -78,11 +80,12 @@ public class RaceStatus extends AC35Data { * @param raceStatus The status of the race. * @param expectedStartTime The expected start time of the race. * @param windDirection The current wind direction in the race. - * @param windSpeed The current wind speed in the race. + * @param windSpeed The current wind speed in the race, in knots. * @param raceType The type of race this is. * @param boatStatuses A list of BoatStatuses. One for each boat. */ - public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, Bearing windDirection, double windSpeed, RaceTypeEnum raceType, List boatStatuses) { + super(MessageType.RACESTATUS); this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; @@ -96,6 +99,7 @@ public class RaceStatus extends AC35Data { } + /** * Returns the version number of this message. * @return The version number of the message. @@ -144,16 +148,16 @@ public class RaceStatus extends AC35Data { * Returns the current direction of the wind in the race. * @return Current wind direction. */ - public int getWindDirection() + public Bearing getWindDirection() { return windDirection; } /** - * Returns the wind speed for this race status, in millimeters per second. - * @return Wind speed in millimeters per second. + * Returns the wind speed for this race status, in knots. + * @return Wind speed in knots. */ - public int getWindSpeed() + public double getWindSpeed() { return windSpeed; } @@ -221,16 +225,4 @@ public class RaceStatus extends AC35Data { return raceStatus == RaceStatusEnum.PRESTART; } - - public double getScaledWindDirection() { - return AC35UnitConverter.convertHeading(windDirection); - } - - /** - * Returns the wind speed for this race status, in knots. - * @return Wind speed in knots. - */ - public double getWindSpeedKnots() { - return (windSpeed / Constants.KnotsToMMPerSecond); - } } diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 73f6d0e9..4838e42c 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -1,43 +1,88 @@ package network.Utils; +import shared.model.Constants; + /** - * Created by fwy13 on 28/04/17. + * Contains various unit conversion for encoding/decoding messages. + * Our program uses the "unpacked" units, and the over-the-wire format uses "packed" units (e.g., degrees stored as ints). */ public class AC35UnitConverter { - public static double convertGPS(int value){ - //converts latitude or longitue to angle + + /** + * Converts a packed GPSCoordinate (latitude or longitude) into the unpacked unit. + * @param value Packed lat/long value. + * @return Unpacked lat/long angle, in degrees. + */ + public static double unpackGPS(int value) { return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648 } - public static int convertGPSToInt(double value){ - //converts latitude or longitue to angle + /** + * Converts a latitude or longitude angle into a packed unit. + * @param value The lat/long angle, in degrees, to convert. + * @return The packed value. + */ + public static int packGPS(double value) { return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648 } - public static double convertHeading(long value){ - return (double) value * 360.0/65536.0;//2^15 + + /** + * Unpacks a heading from an int to an angle in degrees (this is a bearing). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackHeading(int value) { + return (value * 360.0 / 65536.0);//2^15 } - public static double convertHeading(int value){ - return (double) value * 360.0/65536.0;//2^15 + /** + * Packs a heading (this is a bearing), in degrees, to a packed int value. + * @param value The heading in degrees. + * @return The packed value. + */ + public static int packHeading(double value) { + return (int) (value / 360.0 * 65536.0);//2^15 } - public static double convertHeading(double value){ - return value * 360.0/65536.0;//2^15 + /** + * Unpacks a true wind angle from a short to an angle in degrees (this is an azimuth). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackTrueWindAngle(short value) { + return (value * 180.0 / 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(int value){ - return (int) (value / 360.0 * 65536.0);//2^15 + /** + * Packs a true wind angle (this is an azimuth) from an angle in degrees to a packed short value. + * @param value The unpacked value in degrees. + * @return The packed value. + */ + public static short packTrueWindAngle(double value) { + return (short) (value / 180.0 * 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(double value){ - return (int) (value / 360.0 * 65536.0);//2^15 + + /** + * Unpacks a speed, in millimeters per second, to a double, in knots. + * @param millimetersPerSec Speed in millimeters per second. + * @return Speed in knots. + */ + public static double unpackMMperSecToKnots(int millimetersPerSec) { + return (millimetersPerSec / Constants.KnotsToMMPerSecond); } - public static double convertTrueWindAngle(long value){ - return (double) value * 180.0/32768.0;//-2^15 to 2^15 + /** + * Packs a speed, in knots, into an int, in millimeters per second. + * @param speedKnots Speed in knots. + * @return Speed in millimeters per second. + */ + public static int packKnotsToMMperSec(double speedKnots) { + return (int) (speedKnots * Constants.KnotsToMMPerSecond); } + } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index fdc2802d..faa47688 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -313,8 +313,8 @@ public class VisualiserRace extends Race implements Runnable { //Wind. this.setWind( - Bearing.fromDegrees(raceStatus.getScaledWindDirection()), - raceStatus.getWindSpeedKnots() ); + raceStatus.getWindDirection(), + raceStatus.getWindSpeed() ); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime()); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 1adb69b4..4f1e2721 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -9,6 +9,7 @@ import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Iterator; @@ -67,8 +68,8 @@ public class RaceStatusDecoderTest { int raceID = 585; RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); - int windDirection = 2341; - int windSpeed = 10201; + Bearing windDirection = Bearing.fromDegrees(185.34); + double windSpeedKnots = 14.52; RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); @@ -81,7 +82,7 @@ public class RaceStatusDecoderTest { raceStatus, raceStartTime, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); @@ -110,8 +111,8 @@ public class RaceStatusDecoderTest { Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); - Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); - Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed(), 0.01); //Compare all BoatStatuses Iterator originalIterator = original.getBoatStatuses().iterator(); diff --git a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java index 24fc2be2..17369098 100644 --- a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java +++ b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java @@ -2,42 +2,92 @@ package network.Utils; import org.junit.Test; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; -import static network.Utils.AC35UnitConverter.convertHeading; -import static network.Utils.AC35UnitConverter.convertTrueWindAngle; +import static network.Utils.AC35UnitConverter.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** - * Created by fwy13 on 4/05/17. + * Tests the pack/unpack and conversion functions in {@link AC35UnitConverter}. */ public class AC35UnitConverterTest { + + /** + * Tests if gps coordinates can be unpacked. + */ + @Test + public void testUnpackGPS(){ + assertTrue(unpackGPS(0) == 0); + assertTrue(unpackGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + + } + + + /** + * Tests if gps coodinates can be packed. + */ @Test - public void testConvertGPS(){ - assertTrue(convertGPS(0) == 0); - assertTrue(convertGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + public void testPackGPS(){ + assertTrue(packGPS(0) == 0); + assertTrue(packGPS(180) == (int)2147483648.0); } + /** + * Tests if headings/bearings can be unpacked. + */ + @Test + public void testUnpackHeading(){ + assertTrue(unpackHeading(0) == 0); + assertTrue(unpackHeading(65536) == 360.0); + } + + /** + * Tests if headings/bearings can be packed. + */ @Test - public void testConvertGPSToInt(){ - assertTrue(convertGPSToInt(0) == 0); - assertTrue(convertGPSToInt(180) == (int)2147483648.0); + public void testPackHeading(){ + assertTrue(packHeading(0) == 0); + assertTrue(packHeading(360) == 65536); + } + + /** + * Tests if true wind angles (azimuths) can be unpacked. + */ + @Test + public void testUnpackTrueWindAngle(){ + assertEquals(unpackTrueWindAngle((short)0), 0, 0.001); + assertEquals(unpackTrueWindAngle((short)32767), 180.0, 0.01); } + /** + * Tests if true wind angles (azimuths) can be packed. + */ @Test - public void testConvertHeading(){ - assertTrue(convertHeading(0) == 0); - assertTrue(convertHeading(65536) == 360.0); + public void testPackTrueWindAngle(){ + assertTrue(packTrueWindAngle(0) == (short)0); + assertTrue(packTrueWindAngle(180.0) == (short)32768); } + /** + * Tests if millimeters per second can be unpacked to knots. + */ @Test - public void testConvertTrueWindAngle(){ - assertTrue(convertTrueWindAngle(0) == 0); - assertTrue(convertTrueWindAngle(32768) == 180.0); + public void testUnpackMMperSecToKnots(){ + assertEquals(unpackMMperSecToKnots(0), 0d, 0.001); + assertEquals(unpackMMperSecToKnots(7331), 14.25, 0.01); } + + /** + * Tests if knots can be packed into millimeters per second. + */ + @Test + public void testPackKnotsToMMperSec(){ + assertEquals(packKnotsToMMperSec(0), 0, 1); + assertEquals(packKnotsToMMperSec(7.44), 3828, 1); + } + } From 1fbdd09d70b91988897e8d6658c7012646ad73a1 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 16:13:25 +1200 Subject: [PATCH 15/59] Refactored BoatLocation message, encoders, decoders. It now exposes datatypes that we actually use in the program (double knots, bearings, etc..), instead of the bits-on-the-wire packed units (like int mmPerSec). Also documented it, and updated test. issue #35 #36 #story[1095] --- .../src/main/java/mock/model/MockRace.java | 2 +- .../src/main/java/mock/model/RaceServer.java | 9 +- .../MessageDecoders/BoatLocationDecoder.java | 148 +++-- .../MessageEncoders/BoatLocationEncoder.java | 31 +- .../java/network/Messages/BoatLocation.java | 579 ++++++++---------- .../Enums/BoatLocationDeviceEnum.java | 107 ++++ .../java/network/Utils/AC35UnitConverter.java | 2 +- .../java/visualiser/model/VisualiserRace.java | 13 +- .../BoatLocationDecoderTest.java | 47 +- 9 files changed, 515 insertions(+), 423 deletions(-) create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 0ae5cfcc..9755099d 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -446,4 +446,4 @@ public class MockRace extends Race { public List getCompoundMarks() { return compoundMarks; } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index 8629056d..d776b693 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -2,9 +2,11 @@ package mock.model; import network.Messages.BoatLocation; import network.Messages.BoatStatus; +import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.LatestMessages; import network.Messages.RaceStatus; import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import shared.model.CompoundMark; import shared.model.Constants; import shared.model.Mark; @@ -47,7 +49,9 @@ public class RaceServer { mark.getPosition().getLatitude(), mark.getPosition().getLongitude(), this.boatLocationSequenceNumber, - 0, 0, + BoatLocationDeviceEnum.Mark, + Bearing.fromDegrees(0), + 0, race.getRaceClock().getCurrentTimeMilli()); //Iterates the sequence number. @@ -99,7 +103,8 @@ public class RaceServer { boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), this.boatLocationSequenceNumber, - boat.getBearing().degrees(), + BoatLocationDeviceEnum.RacingYacht, + boat.getBearing(), boat.getCurrentSpeed(), race.getRaceClock().getCurrentTimeMilli()); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index c0f4a652..46a4dccd 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -2,12 +2,14 @@ package network.MessageDecoders; import network.Messages.BoatLocation; +import network.Messages.Enums.BoatLocationDeviceEnum; +import shared.model.Azimuth; +import shared.model.Bearing; import java.util.Arrays; -import static network.Utils.ByteConverter.bytesToInt; -import static network.Utils.ByteConverter.bytesToLong; -import static network.Utils.ByteConverter.bytesToShort; +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; /** @@ -45,96 +47,110 @@ public class BoatLocationDecoder { */ private void decode() { - byte[] messageVersionNumber = Arrays.copyOfRange(encodedMessage, 0, 1); - byte numMessageVersionNumber = messageVersionNumber[0]; + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte messageVersionNumber = messageVersionNumberBytes[0]; - byte[] time = Arrays.copyOfRange(encodedMessage, 1, 7); - long numTime = bytesToLong(time); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] sourceID = Arrays.copyOfRange(encodedMessage, 7, 11); - int numSourceID = bytesToInt(sourceID); + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int sourceID = bytesToInt(sourceIDBytes); - byte[] seqNum = Arrays.copyOfRange(encodedMessage, 11, 15); - int numSeqNum = bytesToInt(seqNum); + byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); + int seqNum = bytesToInt(seqNumBytes); - byte[] deviceType = Arrays.copyOfRange(encodedMessage, 15, 16); - byte numDeviceType = deviceType[0]; + byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); + BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); - byte[] latitude = Arrays.copyOfRange(encodedMessage, 16, 20); - int numLatitude = bytesToInt(latitude); + byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitudeBytes); + double latitude = unpackGPS(numLatitude); - byte[] longitude = Arrays.copyOfRange(encodedMessage, 20, 24); - int numLongitude = bytesToInt(longitude); + byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitudeBytes); + double longitude = unpackGPS(numLongitude); - byte[] altitude = Arrays.copyOfRange(encodedMessage, 24, 28); - int numAltitude = bytesToInt(altitude); + byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitudeBytes); - byte[] heading = Arrays.copyOfRange(encodedMessage, 28, 30); - int numHeading = bytesToInt(heading); + byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(headingBytes); + Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); - byte[] pitch = Arrays.copyOfRange(encodedMessage, 30, 32); - short numPitch = bytesToShort(pitch); + byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitchBytes); - byte[] roll = Arrays.copyOfRange(encodedMessage, 32, 34); - short numRoll = bytesToShort(roll); + byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(rollBytes); - byte[] boatSpeed = Arrays.copyOfRange(encodedMessage, 34, 36); - int numBoatSpeed = bytesToInt(boatSpeed); + byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeedBytes); + double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); - byte[] cog = Arrays.copyOfRange(encodedMessage, 36, 38); - int numCog = bytesToInt(cog); + byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cogBytes); + Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); - byte[] sog = Arrays.copyOfRange(encodedMessage, 38, 40); - int numSog = bytesToInt(sog); + byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sogBytes); + double sogKnots = unpackMMperSecToKnots(numSog); - byte[] apparentWindSpeed = Arrays.copyOfRange(encodedMessage, 40, 42); - int numApparentWindSpeed = bytesToInt(apparentWindSpeed); + byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); + double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); - byte[] apparentWindAngle = Arrays.copyOfRange(encodedMessage, 42, 44); - short numApparentWindAngle = bytesToShort(apparentWindAngle); + byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); + Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); - byte[] trueWindSpeed = Arrays.copyOfRange(encodedMessage, 44, 46); - int numTrueWindSpeed = bytesToInt(trueWindSpeed); + byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); + double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); - byte[] trueWindDirection = Arrays.copyOfRange(encodedMessage, 46, 48); - short numTrueWindDirection = bytesToShort(trueWindDirection); + byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); + Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); - byte[] trueWindAngle = Arrays.copyOfRange(encodedMessage, 48, 50); - short numTrueWindAngle = bytesToShort(trueWindAngle); + byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngleBytes); + Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); - byte[] currentDrift = Arrays.copyOfRange(encodedMessage, 50, 52); - int numCurrentDrift = bytesToInt(currentDrift); + byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDriftBytes); + double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); - byte[] currentSet = Arrays.copyOfRange(encodedMessage, 52, 54); - int numCurrentSet = bytesToShort(currentSet); + byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSetBytes); + Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); - byte[] rudderAngle = Arrays.copyOfRange(encodedMessage, 54, 56); - short numRudderAngle = bytesToShort(rudderAngle); + byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngleBytes); + Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); message = new BoatLocation( - numMessageVersionNumber, - numTime, - numSourceID, - numSeqNum, - numDeviceType, - numLatitude, - numLongitude, + messageVersionNumber, + time, + sourceID, + seqNum, + deviceType, + latitude, + longitude, numAltitude, - numHeading, + heading, numPitch, numRoll, - numBoatSpeed, - numCog, - numSog, - numApparentWindSpeed, - numApparentWindAngle, - numTrueWindSpeed, - numTrueWindDirection, - numTrueWindAngle, - numCurrentDrift, - numCurrentSet, - numRudderAngle ); + boatSpeedKnots, + cog, + sogKnots, + apparentWindSpeedKnots, + apparentWindAngle, + trueWindSpeedKnots, + trueWindDirection, + trueWindAngle, + currentDriftKnots, + currentSet, + rudderAngle ); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java index 9ff3e197..9c3833ac 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -6,6 +6,7 @@ import network.Messages.BoatLocation; import java.nio.ByteBuffer; +import static network.Utils.AC35UnitConverter.*; import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; @@ -34,24 +35,24 @@ public class BoatLocationEncoder implements MessageEncoder { byte[] time = longToBytes(boatLocation.getTime(), 6); byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); - byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); - byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); + byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); + byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(boatLocation.getHeading(), 2); + byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); byte[] pitch = intToBytes(boatLocation.getPitch(), 2); byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); - byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); - byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); - byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); - byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); - byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); - byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); - byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); - byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); - byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); - byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); + byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); + byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); + byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); + byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); + byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); + byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); + byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); + byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); + byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); + byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); + byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); ByteBuffer result = ByteBuffer.allocate(56); result.put(messageVersionBytes); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index e870a802..90529d96 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -1,130 +1,188 @@ package network.Messages; +import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.Enums.MessageType; import network.Utils.AC35UnitConverter; -import shared.model.Constants; +import shared.model.Azimuth; +import shared.model.Bearing; import static network.Utils.AC35UnitConverter.unpackGPS; -import static network.Utils.AC35UnitConverter.packGPS; /** * Represents the information in a boat location message (AC streaming spec: 4.9). */ public class BoatLocation extends AC35Data { - //TODO move these to an enum. - public static final byte Unknown = 0; - public static final byte RacingYacht = 1; - public static final byte CommitteeBoat = 2; - public static final byte Mark = 3; - public static final byte Pin = 4; - public static final byte ChaseBoat = 5; - public static final byte MedicalBoat = 6; - public static final byte MarshallBoat = 7; - public static final byte UmpireBoat = 8; - public static final byte UmpireSoftwareApplication = 9; - public static final byte PrincipalRaceOfficerApplication = 10; - public static final byte WeatherStation = 11; - public static final byte Helicopter = 12; - public static final byte DataProcessingApplication = 13; - /** * The current messageVersionNumber according to the API spec. */ public static final byte currentMessageVersionNumber = 1; - ///Version number of the message. + + /** + * Version number of the message. + */ private byte messageVersionNumber; - ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. + + /** + * Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. + */ private long time; - ///Source ID of the boat. + + /** + * Source ID of the boat. + */ private int sourceID; - ///Sequence number of the message. + + /** + * Sequence number of the message. + */ private long sequenceNumber; - ///Device type of the message (physical source of the message). - private byte deviceType; - ///Latitude of the boat. - private int latitude; - ///Longitude of the boat. - private int longitude; + /** + * Device type of the message (physical source of the message). + */ + private BoatLocationDeviceEnum deviceType; - ///Altitude of the boat. + /** + * Latitude of the boat. + */ + private double latitude; + + /** + * Longitude of the boat. + */ + private double longitude; + + /** + * Altitude of the boat. + */ private int altitude; - ///Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int. - private int heading; + /** + * Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int. + */ + private Bearing heading; - ///Pitch of the boat. + /** + * Pitch of the boat. + */ private short pitch; - ///Roll of the boat. + /** + * Roll of the boat. + */ private short roll; - ///Speed of the boat. Proper type is unsigned 2 byte int. millimeters per second. - private int boatSpeed; + /** + * Speed of the boat, in knots. + */ + private double boatSpeedKnots; - ///Course over ground (COG) of the boat. Proper type is unsigned 2 byte int. - private int boatCOG; + /** + * Course over ground (COG) of the boat. + */ + private Bearing boatCOG; - ///Speed over ground (SOG) of the boat. Proper type is unsigned 2 byte int. millimeters per second. - private int boatSOG; + /** + * Speed over ground (SOG) of the boat, in knots. + */ + private double boatSOGKnots; - ///Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second. - private int apparentWindSpeed; + /** + * Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second. + */ + private double apparentWindSpeedKnots; - ///Apparent wind angle at time of the event. Wind over starboard = positive. - private short apparentWindAngle; + /** + * Apparent wind angle at time of the event. Wind over starboard = positive. + */ + private Azimuth apparentWindAngle; - ///True wind speed. Proper type is unsigned 2 byte int. millimeters per second. - private int trueWindSpeed; + /** + * True wind speed, in knots. + */ + private double trueWindSpeedKnots; - ///True wind direction. Proper type is unsigned 2 byte int. 0x0000 = North, etc.. - private int trueWindDirection; + /** + * True wind direction. + */ + private Bearing trueWindDirection; - ///True wind angle. Clockwise compass direction, 0 = north. - private short trueWindAngle; + /** + * True wind angle. Clockwise compass direction, 0 = north. + */ + private Azimuth trueWindAngle; - ///Current drift. Proper type is unsigned 2 byte int. millimeters per second. - private int currentDrift; + /** + * Current drift, in knots. + */ + private double currentDriftKnots; - ///Current set. Proper type is unsigned 2 byte int. Clockwise compass direction, 0 = north. - private int currentSet; + /** + * Current set. + */ + private Bearing currentSet; - ///Rudder angle. Positive is rudder set to turn yacht to port. - private short rudderAngle; + /** + * Rudder angle. Positive is rudder set to turn yacht to port. + */ + private Azimuth rudderAngle; /** - * Ctor, with all parameters. + * Constructs a BoatLocation message with the given parameters. * * @param messageVersionNumber message number * @param time time of message * @param sourceID id of boat * @param sequenceNumber number of boat message - * @param deviceType type of boat + * @param deviceType The source of the BoatLocation message. * @param latitude lat of boat * @param longitude lon of boat * @param altitude altitude of boat * @param heading heading of boat * @param pitch pitch of boat * @param roll roll of boat - * @param boatSpeed boats speed + * @param boatSpeedKnots boats speed * @param boatCOG boat cog - * @param boatSOG boat sog - * @param apparentWindSpeed wind speed + * @param boatSOGKnots boat sog + * @param apparentWindSpeedKnots wind speed * @param apparentWindAngle wind angle - * @param trueWindSpeed true wind speed + * @param trueWindSpeedKnots true wind speed * @param trueWindDirection true wind direction * @param trueWindAngle true wind angle - * @param currentDrift current drift + * @param currentDriftKnots current drift * @param currentSet current set * @param rudderAngle rudder angle */ - public BoatLocation(byte messageVersionNumber, long time, int sourceID, long sequenceNumber, byte deviceType, int latitude, int longitude, int altitude, int heading, short pitch, short roll, int boatSpeed, int boatCOG, int boatSOG, int apparentWindSpeed, short apparentWindAngle, int trueWindSpeed, int trueWindDirection, short trueWindAngle, int currentDrift, int currentSet, short rudderAngle) { + public BoatLocation( + byte messageVersionNumber, + long time, + int sourceID, + long sequenceNumber, + BoatLocationDeviceEnum deviceType, + double latitude, + double longitude, + int altitude, + Bearing heading, + short pitch, + short roll, + double boatSpeedKnots, + Bearing boatCOG, + double boatSOGKnots, + double apparentWindSpeedKnots, + Azimuth apparentWindAngle, + double trueWindSpeedKnots, + Bearing trueWindDirection, + Azimuth trueWindAngle, + double currentDriftKnots, + Bearing currentSet, + Azimuth rudderAngle ) { + super(MessageType.BOATLOCATION); this.messageVersionNumber = messageVersionNumber; @@ -138,348 +196,251 @@ public class BoatLocation extends AC35Data { this.heading = heading; this.pitch = pitch; this.roll = roll; - this.boatSpeed = boatSpeed; + this.boatSpeedKnots = boatSpeedKnots; this.boatCOG = boatCOG; - this.boatSOG = boatSOG; - this.apparentWindSpeed = apparentWindSpeed; + this.boatSOGKnots = boatSOGKnots; + this.apparentWindSpeedKnots = apparentWindSpeedKnots; this.apparentWindAngle = apparentWindAngle; - this.trueWindSpeed = trueWindSpeed; + this.trueWindSpeedKnots = trueWindSpeedKnots; this.trueWindDirection = trueWindDirection; this.trueWindAngle = trueWindAngle; - this.currentDrift = currentDrift; + this.currentDriftKnots = currentDriftKnots; this.currentSet = currentSet; this.rudderAngle = rudderAngle; } - public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { - super(MessageType.BOATLOCATION); - - this.messageVersionNumber = BoatLocation.currentMessageVersionNumber; - this.time = time; - this.sourceID = sourceID; - this.sequenceNumber = sequenceNumber; - this.deviceType = 1; - this.latitude = packGPS(lat); - this.longitude = packGPS(lon); - this.altitude = 0; - this.heading = convertHeadingDoubleToInt(heading); - this.pitch = 0; - this.roll = 0; - this.boatSpeed = convertBoatSpeedDoubleToInt(boatSpeed); - this.boatCOG = 0; - this.boatSOG = convertBoatSpeedDoubleToInt(boatSpeed); - this.apparentWindSpeed = 0; - this.apparentWindAngle = 0; - this.trueWindSpeed = 0; - this.trueWindDirection = 0; - this.trueWindAngle = 0; - this.currentDrift = 0; - this.currentSet = 0; - this.rudderAngle = 0; - } - - - //Getters and setters for message properties. - - /** - * Converts a double representing a latitude or longitude coordinate to an int, as required by the streaming spec format. - * - * @param coordinate Latitude or longitude to convert. Double. - * @return int representation of coordinate. - */ - public static int convertCoordinateDoubleToInt(double coordinate) { - int coordinateInt = (int) ((coordinate / 180.0) * 2147483648.0); - - return coordinateInt; - } - - /** - * Converts an int representing a latitude or longitude coordinate to a double, as required by the streaming spec format. - * - * @param coordinate Latitude or longitude to convert. int. - * @return double representation of coordinate. - */ - public static double convertCoordinateIntToDouble(int coordinate) { - double coordinateDouble = (double) ((coordinate * 180.0) / 2147483648.0); - - return coordinateDouble; - } - - /** - * Converts an int representing a heading to a double, as required by the streaming spec format. - * - * @param heading Heading to convert. int. - * @return double representation of heading. - */ - public static double convertHeadingIntToDouble(int heading) { - - double headingDouble = (double) ((heading * 360.0) / 65536.0); - - return headingDouble; - } - - /** - * Converts a double representing a heading to an int, as required by the streaming spec format. - * - * @param heading Heading to convert. double. - * @return int representation of heading. - */ - public static int convertHeadingDoubleToInt(double heading) { - int headingInt = (int) ((heading * 65536.0) / 360.0); - - return headingInt; + public BoatLocation( + int sourceID, + double lat, + double lon, + long sequenceNumber, + BoatLocationDeviceEnum deviceType, + Bearing heading, + double boatSpeedKnots, + long time ) { + + this( + BoatLocation.currentMessageVersionNumber, + time, + sourceID, + sequenceNumber, + deviceType, + lat, + lon, + 0, + heading, + (short) 0, + (short) 0, + boatSpeedKnots, + heading, + boatSpeedKnots, + 0, + Azimuth.fromDegrees(0), + 0, + Bearing.fromDegrees(0), + Azimuth.fromDegrees(0), + 0, + Bearing.fromDegrees(0), + Azimuth.fromDegrees(0) ); } - /** - * Converts a short representing the wind's true angle to a double, as required by the streaming spec format. - * - * @param angle Angle to convert. short. - * @return double representation of heading. - */ - public static double convertTrueWindAngleShortToDouble(short angle) { - - double angleDouble = (double) ((angle * 180.0) / 32768.0); - - return angleDouble; - } - - /** - * Converts a double representing the wind's true angle to a short, as required by the streaming spec format. - * - * @param angle Angle to convert. double. - * @return short representation of heading. - */ - public static short convertTrueWindAngleDoubleToShort(double angle) { - - short angleShort = (short) ((angle / 180.0) * 32768.0); - - return angleShort; - } - - /** - * Converts a double representing the speed of a boat in knots to an int in millimeters per second, as required by the streaming spec format. - * - * @param speed Speed in knots, stored as a double. - * @return Speed in millimeters per second, stored as an int (using only the two least significant bytes). - */ - public static int convertBoatSpeedDoubleToInt(double speed) { - //Calculate millimeters per second. - double millimetersPerSecond = speed * Constants.KnotsToMMPerSecond; - - //Convert to an int. - int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond); - - return millimetersPerSecondInt; - } /** - * Converts an int representing the speed of a boat in millimeters per second to a double in knots, as required by the streaming spec format. - * - * @param speed Speed in millimeters per second, stored as an int. - * @return Speed in knots, stored as a double. + * Returns the version number of the message. + * @return The version number of the message. */ - public static double convertBoatSpeedIntToDouble(int speed) { - - //Calculate knots. - double knots = speed / Constants.KnotsToMMPerSecond; - - return knots; - } - public byte getMessageVersionNumber() { return messageVersionNumber; } - public void setMessageVersionNumber(byte messageVersionNumber) { - this.messageVersionNumber = messageVersionNumber; - } + /** + * Returns the time that this message was generated at. + * @return Time message was generated at, in milliseconds since unix epoch. + */ public long getTime() { return time; } - public void setTime(long time) { - this.time = time; - } - + /** + * Returns the sourceID of the boat this message relates to. + * @return SourceID of the boat this message relates to. + */ public int getSourceID() { return sourceID; } - public void setSourceID(int sourceID) { - this.sourceID = sourceID; - } - + /** + * Returns the sequence number of this message. + * @return The sequence number of the message. + */ public long getSequenceNumber() { return sequenceNumber; } - public void setSequenceNumber(long sequenceNumber) { - this.sequenceNumber = sequenceNumber; - } - public byte getDeviceType() { + /** + * Returns the device source of this message. + * @return The device source of this message. + */ + public BoatLocationDeviceEnum getDeviceType() { return deviceType; } - public void setDeviceType(byte deviceType) { - this.deviceType = deviceType; - } - public int getLatitude() { + /** + * Returns the latitude, in degrees, that the boat is located at. + * @return Latitude, in degrees, of boat. + */ + public double getLatitude() { return latitude; } - public void setLatitude(int latitude) { - this.latitude = latitude; - } - public int getLongitude() { + /** + * Returns the longitude, in degrees, that the boat is located at. + * @return Longitude, in degrees, of boat. + */ + public double getLongitude() { return longitude; } - public double getLatitudeDouble(){ - return unpackGPS(this.latitude); - } - - public double getLongitudeDouble(){ - return unpackGPS(this.longitude); - } - - public void setLongitude(int longitude) { - this.longitude = longitude; - } - + /** + * Returns the altitude of the boat. + * @return The altitude of the boat. + */ public int getAltitude() { return altitude; } - public void setAltitude(int altitude) { - this.altitude = altitude; - } - public int getHeading() { + /** + * Returns the current heading/bearing of the boat. + * @return Heading of the boat. + */ + public Bearing getHeading() { return heading; } - public void setHeading(int heading) { - this.heading = heading; - } + /** + * Returns the current pitch of the boat. + * @return Pitch of the boat. + */ public short getPitch() { return pitch; } - public void setPitch(short pitch) { - this.pitch = pitch; - } + /** + * Returns the current roll of the boat. + * @return Roll of the boat. + */ public short getRoll() { return roll; } - public void setRoll(short roll) { - this.roll = roll; - } - public int getBoatSpeed() { - return boatSpeed; + /** + * Returns the current boat speed, in knots. + * @return Current boat speed, in knots. + */ + public double getBoatSpeedKnots() { + return boatSpeedKnots; } - public void setBoatSpeed(int boatSpeed) { - this.boatSpeed = boatSpeed; - } - public int getBoatCOG() { + /** + * Returns the boat's Course Over Ground. + * @return Boat's COG. + */ + public Bearing getBoatCOG() { return boatCOG; } - public void setBoatCOG(int boatCOG) { - this.boatCOG = boatCOG; - } - public int getBoatSOG() { - return boatSOG; - } - - public void setBoatSOG(int boatSOG) { - this.boatSOG = boatSOG; + /** + * Returns the boats Speed Over Ground, in knots. + * @return Boat's SOG. + */ + public double getBoatSOGKnots() { + return boatSOGKnots; } - public int getApparentWindSpeed() { - return apparentWindSpeed; - } - public void setApparentWindSpeed(int apparentWindSpeed) { - this.apparentWindSpeed = apparentWindSpeed; + /** + * Returns the apparent wind speed, in knots, at the boat. + * @return Wind speed, in knots, at the boat. + */ + public double getApparentWindSpeedKnots() { + return apparentWindSpeedKnots; } - public short getApparentWindAngle() { + /** + * Returns the apparent wind angle at the boat. + * @return Wind angle at the boat. + */ + public Azimuth getApparentWindAngle() { return apparentWindAngle; } - public void setApparentWindAngle(short apparentWindAngle) { - this.apparentWindAngle = apparentWindAngle; - } - public int getTrueWindSpeed() { - return trueWindSpeed; + /** + * Returns the true wind speed, in knots. + * @return True wind speed, in knots. + */ + public double getTrueWindSpeedKnots() { + return trueWindSpeedKnots; } - public void setTrueWindSpeed(int trueWindSpeed) { - this.trueWindSpeed = trueWindSpeed; - } - public int getTrueWindDirection() + /** + * Returns the true wind direction. + * @return True wind direction. + */ + public Bearing getTrueWindDirection() { return trueWindDirection; } - public void setTrueWindDirection(int trueWindDirection) - { - this.trueWindDirection = trueWindDirection; - } - public short getTrueWindAngle() { + /** + * Returns the true wind angle. + * @return True wind angle. + */ + public Azimuth getTrueWindAngle() { return trueWindAngle; } - public void setTrueWindAngle(short trueWindAngle) { - this.trueWindAngle = trueWindAngle; - } - public int getCurrentDrift() { - return currentDrift; + /** + * Returns the current drift of the boat, in knots. + * @return Current drift, in knots. + */ + public double getCurrentDriftKnots() { + return currentDriftKnots; } - public void setCurrentDrift(int currentDrift) { - this.currentDrift = currentDrift; - } - public int getCurrentSet() { + /** + * Returns the current set of the boat. + * @return Current set of the boat. + */ + public Bearing getCurrentSet() { return currentSet; } - public void setCurrentSet(int currentSet) { - this.currentSet = currentSet; - } - public short getRudderAngle() { + /** + * Returns the current rudder angle of the boat. + * @return Current rudder angle of the boat. + */ + public Azimuth getRudderAngle() { return rudderAngle; } - public void setRudderAngle(short rudderAngle) { - this.rudderAngle = rudderAngle; - } - - public double getHeadingDegrees(){ - return AC35UnitConverter.unpackHeading(getHeading()); - } - public double getTrueWindAngleDegrees(){ - return AC35UnitConverter.unpackTrueWindAngle(getTrueWindAngle()); - } @Override public String toString() { @@ -519,28 +480,28 @@ public class BoatLocation extends AC35Data { builder.append(this.getRoll()); builder.append("\nBoat speed (mm/sec): "); - builder.append(this.getBoatSpeed()); + builder.append(this.getBoatSpeedKnots()); builder.append("\nBoat COG: "); builder.append(this.getBoatCOG()); builder.append("\nBoat SOG: "); - builder.append(this.getBoatSOG()); + builder.append(this.getBoatSOGKnots()); builder.append("\nApparent wind speed: "); - builder.append(this.getApparentWindSpeed()); + builder.append(this.getApparentWindSpeedKnots()); builder.append("\nApparent wind angle: "); builder.append(this.getApparentWindAngle()); builder.append("\nTrue wind speed: "); - builder.append(this.getTrueWindSpeed()); + builder.append(this.getTrueWindSpeedKnots()); builder.append("\nTrue wind angle: "); builder.append(this.getTrueWindAngle()); builder.append("\nCurrent drift: "); - builder.append(this.getCurrentDrift()); + builder.append(this.getCurrentDriftKnots()); builder.append("\nCurrent set: "); builder.append(this.getCurrentSet()); diff --git a/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java new file mode 100644 index 00000000..42e8c733 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java @@ -0,0 +1,107 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Various device sources for a BoatLocation message. + */ +public enum BoatLocationDeviceEnum { + + + NOT_A_DEVICE(-1), + + + Unknown(0), + + /** + * A yacht particpating in the race. + */ + RacingYacht(1), + + CommitteeBoat(2), + + /** + * A marker boat. + */ + Mark(3), + + Pin(4), + + ChaseBoat(5), + + MedicalBoat(6), + + MarshallBoat(7), + + UmpireBoat(8), + + UmpireSoftwareApplication(9), + + PrincipalRaceOfficerApplication(10), + + WeatherStation(11), + + Helicopter(12), + + DataProcessingApplication(13); + + + /** + * Value of the enum. + */ + private byte value; + + /** + * Creates a BoatLocationDeviceEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private BoatLocationDeviceEnum(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 BoatLocationDeviceEnum values. + */ + private static final Map byteToDeviceMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToDeviceMap. + */ + static { + for (BoatLocationDeviceEnum type : BoatLocationDeviceEnum.values()) { + BoatLocationDeviceEnum.byteToDeviceMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param deviceValue Byte value to convert to a BoatLocationDeviceEnum value. + * @return The BoatLocationDeviceEnum value which corresponds to the given byte value. + */ + public static BoatLocationDeviceEnum fromByte(byte deviceValue) { + //Gets the corresponding BoatLocationDeviceEnum from the map. + BoatLocationDeviceEnum type = BoatLocationDeviceEnum.byteToDeviceMap.get(deviceValue); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_DEVICE BoatLocationDeviceEnum. + return BoatLocationDeviceEnum.NOT_A_DEVICE; + } else { + //Otherwise, return the BoatLocationDeviceEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 4838e42c..6d894ab6 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -24,7 +24,7 @@ public class AC35UnitConverter { * @return The packed value. */ public static int packGPS(double value) { - return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648 + return (int) (value * 2147483648.0 / 180.0);//2^31 = 2147483648 } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index faa47688..15067515 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -16,7 +16,6 @@ import shared.dataInput.RegattaDataSource; import shared.model.*; import java.time.Duration; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -198,20 +197,20 @@ public class VisualiserRace extends Race implements Runnable { if (boatLocation != null && boatStatus != null) { //Get the new position. - double latitude = boatLocation.getLatitudeDouble(); - double longitude = boatLocation.getLongitudeDouble(); + double latitude = boatLocation.getLatitude(); + double longitude = boatLocation.getLongitude(); GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); boat.setCurrentPosition(gpsCoordinate); //Bearing. - boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees())); + boat.setBearing(boatLocation.getHeading()); //Time until next mark. boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark())); //Speed. - boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond); + boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots()); //Boat status. @@ -292,8 +291,8 @@ public class VisualiserRace extends Race implements Runnable { if (boatLocation != null) { //We only update the boat's position. - double latitude = boatLocation.getLatitudeDouble(); - double longitude = boatLocation.getLongitudeDouble(); + double latitude = boatLocation.getLatitude(); + double longitude = boatLocation.getLongitude(); GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); mark.setPosition(gpsCoordinate); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 05964aaf..dcc69abe 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -2,8 +2,11 @@ package network.MessageDecoders; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatLocation; +import network.Messages.Enums.BoatLocationDeviceEnum; import org.junit.Assert; import org.junit.Test; +import shared.model.Azimuth; +import shared.model.Bearing; /** @@ -23,28 +26,28 @@ public class BoatLocationDecoderTest { long time = System.currentTimeMillis(); BoatLocation testMessage = new BoatLocation( - (byte) 1, + BoatLocation.currentMessageVersionNumber, time, 2, 3, - (byte) 1, + BoatLocationDeviceEnum.RacingYacht, 180, -180, 4, - 5, + Bearing.fromDegrees(45), (short) 6, (short) 7, 8, - 9, + Bearing.fromDegrees(40), 10, 11, - (short) 12, + Azimuth.fromDegrees(35), 13, - 14, - (short) 15, + Bearing.fromDegrees(80), + Azimuth.fromDegrees(80), 16, - 17, - (short) 18 ); + Bearing.fromDegrees(80), + Azimuth.fromDegrees(22) ); //Encode. byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); @@ -58,22 +61,22 @@ public class BoatLocationDecoderTest { Assert.assertEquals(testMessage.getTime(), decodedTest.getTime()); Assert.assertEquals(testMessage.getSequenceNumber(), decodedTest.getSequenceNumber()); Assert.assertEquals(testMessage.getDeviceType(), decodedTest.getDeviceType()); - Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude()); - Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude()); + Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude(), 0.01); + Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude(), 0.01); Assert.assertEquals(testMessage.getAltitude(), decodedTest.getAltitude()); - Assert.assertEquals(testMessage.getHeading(), decodedTest.getHeading()); + Assert.assertEquals(testMessage.getHeading().degrees(), decodedTest.getHeading().degrees(), 0.01); Assert.assertEquals(testMessage.getPitch(), decodedTest.getPitch()); Assert.assertEquals(testMessage.getRoll(), decodedTest.getRoll()); - Assert.assertEquals(testMessage.getBoatSpeed(), decodedTest.getBoatSpeed()); + Assert.assertEquals(testMessage.getBoatSpeedKnots(), decodedTest.getBoatSpeedKnots(), 0.01); - Assert.assertEquals(testMessage.getBoatCOG(), decodedTest.getBoatCOG()); - Assert.assertEquals(testMessage.getBoatSOG(), decodedTest.getBoatSOG()); - Assert.assertEquals(testMessage.getApparentWindSpeed(), decodedTest.getApparentWindSpeed()); - Assert.assertEquals(testMessage.getTrueWindSpeed(), decodedTest.getTrueWindSpeed()); - Assert.assertEquals(testMessage.getTrueWindDirection(), decodedTest.getTrueWindDirection()); - Assert.assertEquals(testMessage.getTrueWindAngle(), decodedTest.getTrueWindAngle()); - Assert.assertEquals(testMessage.getCurrentDrift(), decodedTest.getCurrentDrift()); - Assert.assertEquals(testMessage.getCurrentSet(), decodedTest.getCurrentSet()); - Assert.assertEquals(testMessage.getRudderAngle(), decodedTest.getRudderAngle()); + Assert.assertEquals(testMessage.getBoatCOG().degrees(), decodedTest.getBoatCOG().degrees(), 0.01); + Assert.assertEquals(testMessage.getBoatSOGKnots(), decodedTest.getBoatSOGKnots(), 0.01); + Assert.assertEquals(testMessage.getApparentWindSpeedKnots(), decodedTest.getApparentWindSpeedKnots(), 0.01); + Assert.assertEquals(testMessage.getTrueWindSpeedKnots(), decodedTest.getTrueWindSpeedKnots(), 0.01); + Assert.assertEquals(testMessage.getTrueWindDirection().degrees(), decodedTest.getTrueWindDirection().degrees(), 0.01); + Assert.assertEquals(testMessage.getTrueWindAngle().degrees(), decodedTest.getTrueWindAngle().degrees(), 0.01); + Assert.assertEquals(testMessage.getCurrentDriftKnots(), decodedTest.getCurrentDriftKnots(), 0.01); + Assert.assertEquals(testMessage.getCurrentSet().degrees(), decodedTest.getCurrentSet().degrees(), 0.01); + Assert.assertEquals(testMessage.getRudderAngle().degrees(), decodedTest.getRudderAngle().degrees(), 0.01); } } From 750ea5c141c81432104043a5c8960f196a16f8b8 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 18:51:13 +1200 Subject: [PATCH 16/59] Added MessageDecoder interface. All decoder implement this. Added DecoderFactory. This creates an appropriate decoder based on a MessageType. BoatActionDecoder implements MessageDecoder. BoatLocationDecoder implements MessageDecoder. HeartBeatDecoder implements MessageDecoder. JoinAcceptance implements MessageDecoder. RaceStatusDecoder implements MessageDecoder. RequestToJoinDecoder implements MessageDecoder. XMLMessageDecoder implements MessageDecoder. Refactored CourseWind decoder/encoder. CourseWind decoder/encoder is for an individual CourseWind. CourseWinds decoder/encoder is for the combined message from the API. Documented BoatAction, and it now contains a BoatActionEnum instead of a byte. Refactored CourseWind and CourseWinds classes. They now expose correct units, instead of packed units. Added CourseWindDecoderTest, and updated CourseWindsDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 35 +++-- .../MessageDecoders/BoatActionDecoder.java | 48 +++++-- .../MessageDecoders/BoatLocationDecoder.java | 19 ++- .../MessageDecoders/BoatStatusDecoder.java | 12 +- .../MessageDecoders/CourseWindDecoder.java | 120 +++++++++++------- .../MessageDecoders/CourseWindsDecoder.java | 87 +++++++++++++ .../MessageDecoders/DecoderFactory.java | 70 ++++++++++ .../MessageDecoders/HeartBeatDecoder.java | 19 ++- .../JoinAcceptanceDecoder.java | 18 ++- .../MessageDecoders/MessageDecoder.java | 21 +++ .../MessageDecoders/RaceStatusDecoder.java | 29 +++-- .../MessageDecoders/RequestToJoinDecoder.java | 18 ++- .../MessageDecoders/XMLMessageDecoder.java | 19 ++- .../MessageEncoders/BoatActionEncoder.java | 2 +- .../MessageEncoders/CourseWindEncoder.java | 73 +++++++++++ .../MessageEncoders/CourseWindsEncoder.java | 57 +++++++++ .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceVisionByteEncoder.java | 21 +-- .../java/network/Messages/BoatAction.java | 21 ++- .../java/network/Messages/CourseWind.java | 101 +++++++++++++-- .../java/network/Messages/CourseWinds.java | 58 ++++++++- .../gameController/ControllerServer.java | 8 +- .../BoatActionDecoderTest.java | 6 +- .../BoatLocationDecoderTest.java | 3 +- .../BoatStatusDecoderTest.java | 6 +- .../CourseWindDecoderTest.java | 90 ++++++++----- .../CourseWindsDecoderTest.java | 96 ++++++++++++++ .../MessageDecoders/HeartBeatDecoderTest.java | 3 +- .../JoinAcceptanceDecoderTest.java | 3 +- .../RaceStatusDecoderTest.java | 3 +- .../RequestToJoinDecoderTest.java | 3 +- .../XMLMessageDecoderTest.java | 3 +- 32 files changed, 840 insertions(+), 234 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index eb9c6997..0ee0d541 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -2,6 +2,7 @@ package network; import network.Exceptions.InvalidMessageException; +import network.Exceptions.InvalidMessageTypeException; import network.MessageDecoders.*; import network.Messages.*; import network.Messages.Enums.MessageType; @@ -130,16 +131,26 @@ public class BinaryMessageDecoder { //Now we create the message object based on what is actually in the message body. MessageType mType = MessageType.fromByte(headerMessageType); + /*MessageDecoder decoder = null; + try { + decoder = DecoderFactory.create(mType); + } catch (InvalidMessageTypeException e) { + throw new InvalidMessageException("Could not create decoder for MessageType: " + mType, e); + } + + return decoder.decode(messageBody);*/ + + switch(mType) { case HEARTBEAT: //System.out.println("Decoding HeartBeat Message!"); - HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(messageBody); - return heartBeatDecoder.getMessage(); + HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(); + return heartBeatDecoder.decode(messageBody); case RACESTATUS: //System.out.println("Race Status Message"); - RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody); - return rsdecoder.getMessage(); + RaceStatusDecoder rsdecoder = new RaceStatusDecoder(); + return rsdecoder.decode(messageBody); case DISPLAYTEXTMESSAGE: //System.out.println("Display Text Message"); @@ -148,8 +159,8 @@ public class BinaryMessageDecoder { case XMLMESSAGE: //System.out.println("XML Message!"); - XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody); - return xmdecoder.getMessage(); + XMLMessageDecoder xmdecoder = new XMLMessageDecoder(); + return xmdecoder.decode(messageBody); case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); @@ -173,8 +184,8 @@ public class BinaryMessageDecoder { case BOATLOCATION: //System.out.println("Boat Location Message!"); - BoatLocationDecoder blDecoder = new BoatLocationDecoder(messageBody); - return blDecoder.getMessage(); + BoatLocationDecoder blDecoder = new BoatLocationDecoder(); + return blDecoder.decode(messageBody); case MARKROUNDING: //System.out.println("Mark Rounding Message!"); @@ -183,8 +194,8 @@ public class BinaryMessageDecoder { case COURSEWIND: //System.out.println("Course Wind Message!"); - CourseWindDecoder cwDecoder = new CourseWindDecoder(messageBody); - return new CourseWinds(cwDecoder.getMessageVersionNumber(), cwDecoder.getByteWindID(), cwDecoder.getLoopMessages()); + CourseWindsDecoder cwDecoder = new CourseWindsDecoder(); + return cwDecoder.decode(messageBody); case AVGWIND: //System.out.println("Average Wind Message!"); @@ -192,8 +203,8 @@ public class BinaryMessageDecoder { return awDecoder.getAverageWind(); case BOATACTION: - BoatActionDecoder baDecoder = new BoatActionDecoder(messageBody); - return new BoatAction(baDecoder.getBoatAction()); + BoatActionDecoder baDecoder = new BoatActionDecoder(); + return baDecoder.decode(messageBody); default: //System.out.println("Broken Message!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java index bf2076b5..a3a5d38a 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java @@ -1,20 +1,50 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import java.util.Arrays; -public class BoatActionDecoder { - byte byteBoatAction; - BoatActionEnum boatAction; +/** + * Decodes {@link BoatAction} messages. + */ +public class BoatActionDecoder implements MessageDecoder { - public BoatActionDecoder(byte[] encodedBoatAction) { - byteBoatAction = encodedBoatAction[0]; + /** + * The encoded message. + */ + private byte[] encodedMessage; - boatAction = BoatActionEnum.fromByte(byteBoatAction); + /** + * The decoded message. + */ + private BoatAction message; + + + /** + * Constructs a decoder to decode a given message. + */ + public BoatActionDecoder() { + } + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); + + message = new BoatAction(boatActionEnum); + + return message; } - public BoatActionEnum getBoatAction() { - return boatAction; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public BoatAction getMessage() { + return message; } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index 46a4dccd..1e175588 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.BoatLocation; import network.Messages.Enums.BoatLocationDeviceEnum; import shared.model.Azimuth; @@ -15,7 +16,7 @@ import static network.Utils.ByteConverter.*; /** * Decodes {@link BoatLocation} messages. */ -public class BoatLocationDecoder { +public class BoatLocationDecoder implements MessageDecoder { /** * The encoded message. @@ -32,20 +33,14 @@ public class BoatLocationDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public BoatLocationDecoder(byte[] encodedMessage) { - - this.encodedMessage = encodedMessage; - - decode(); + public BoatLocationDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); byte messageVersionNumber = messageVersionNumberBytes[0]; @@ -152,6 +147,8 @@ public class BoatLocationDecoder { currentSet, rudderAngle ); + return message; + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index dd482486..972c1eae 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -30,19 +30,17 @@ public class BoatStatusDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public BoatStatusDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public BoatStatusDecoder() { } /** * Decodes the contained message. + * @param encodedMessage The message to decode. */ - private void decode() { + public BoatStatus decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); @@ -75,6 +73,8 @@ public class BoatStatusDecoder { estTimeAtNextMark, estTimeAtFinish ); + return message; + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 038a79d2..78bc6f1c 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -2,63 +2,93 @@ package network.MessageDecoders; import network.Messages.CourseWind; +import shared.model.Bearing; -import java.util.ArrayList; import java.util.Arrays; -import static network.Utils.ByteConverter.bytesToInt; -import static network.Utils.ByteConverter.bytesToLong; +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; /** - * Created by hba56 on 23/04/17. + * Decodes {@link CourseWind} messages. */ public class CourseWindDecoder { - byte messageVersionNumber; - byte byteWindID; - byte loopCount; - ArrayList loopMessages = new ArrayList(); - - public CourseWindDecoder(byte[] encodedCourseWind) { - final int lengthInBytesOfMessages = 20; - - messageVersionNumber = encodedCourseWind[0]; - byteWindID = encodedCourseWind[1]; - loopCount = encodedCourseWind[2]; - byte[] loopMessagesBytes = Arrays.copyOfRange(encodedCourseWind, 3, lengthInBytesOfMessages*loopCount+3); - int messageLoopIndex = 0; - - for (int i=0; i < loopCount; i++) { - byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex+20); - ArrayList test = new ArrayList(); - byte[] windId = Arrays.copyOfRange(messageBytes, 0, 1); - byte[] time = Arrays.copyOfRange(messageBytes, 1, 7); - byte[] raceID = Arrays.copyOfRange(messageBytes, 7, 11); - byte[] windDirection = Arrays.copyOfRange(messageBytes, 11, 13); - byte[] windSpeed = Arrays.copyOfRange(messageBytes, 13, 15); - byte[] bestUpwindAngle = Arrays.copyOfRange(messageBytes, 15, 17); - byte[] bestDownwindAngle = Arrays.copyOfRange(messageBytes, 17, 19); - byte[] flags = Arrays.copyOfRange(messageBytes, 19, 20); - - CourseWind message = new CourseWind(windId[0], bytesToLong(time), - bytesToInt(raceID), bytesToInt(windDirection), - bytesToInt(windSpeed), bytesToInt(bestUpwindAngle), - bytesToInt(bestDownwindAngle), flags[0]); - - loopMessages.add(message); - messageLoopIndex += 20; - } - } - public ArrayList getLoopMessages() { - return loopMessages; + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private CourseWind message; + + + + /** + * Constructs a decoder to decode a given message. + */ + public CourseWindDecoder() { } - public byte getMessageVersionNumber() { - return messageVersionNumber; + + /** + * Decodes the contained message. + * @param encodedMessage The message to decode. + */ + public CourseWind decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceIDInt = bytesToInt(raceIDBytes); + + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); + + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); + + byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); + int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); + Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); + + byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); + int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); + Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + + byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); + + + + message = new CourseWind( + windId[0], + time, + raceIDInt, + windDirection, + windSpeedKnots, + bestUpwindAngle, + bestDownwindAngle, + flags[0] ); + + return message; } - public byte getByteWindID() { - return byteWindID; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public CourseWind getMessage() { + return message; } + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java new file mode 100644 index 00000000..f478ac6b --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java @@ -0,0 +1,87 @@ +package network.MessageDecoders; + + +import network.Messages.AC35Data; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static network.Utils.ByteConverter.bytesToInt; +import static network.Utils.ByteConverter.bytesToLong; + + +/** + * Decodes {@link CourseWinds} messages. + */ +public class CourseWindsDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private CourseWinds message; + + + + /** + * Constructs a decoder to decode a given message. + */ + public CourseWindsDecoder() { + } + + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + //The header is three bytes. + byte messageVersionNumber = encodedMessage[0]; + byte byteWindID = encodedMessage[1]; + byte loopCount = encodedMessage[2]; + + + //A CourseWind object is 20 bytes. + final int courseWindByteLength = 20; + + List loopMessages = new ArrayList(); + + //The header is 3 bytes, so we need the remaining bytes. + byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); + + for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { + + byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); + + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWind = courseWindDecoder.decode(messageBytes); + + loopMessages.add(courseWind); + } + + + + message = new CourseWinds( + messageVersionNumber, + byteWindID, + loopMessages ); + + return message; + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public CourseWinds getMessage() { + return message; + } + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java new file mode 100644 index 00000000..500d20f6 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -0,0 +1,70 @@ +package network.MessageDecoders; + + +import network.Exceptions.InvalidMessageTypeException; +import network.Messages.Enums.MessageType; + +/** + * Factory to create the appropriate decoder for a given message. + */ +public class DecoderFactory { + + + /** + * Private constructor. Currently doesn't need to be constructed. + */ + private DecoderFactory(){ + + } + + + /** + * Creates the correct type of decoder for a given message type. + * @param type Type of message you want a decoder for. + * @return The decoder. + * @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised. + */ + public static MessageDecoder create(MessageType type) throws InvalidMessageTypeException { + + + switch (type) { + + case HEARTBEAT: return new HeartBeatDecoder(); + + case RACESTATUS: return new RaceStatusDecoder(); + + //case DISPLAYTEXTMESSAGE: return new DisplayTextMessageDecoder();//TODO + + case XMLMESSAGE: return new XMLMessageDecoder(); + + //case RACESTARTSTATUS: return new RaceStartStatusDecoder();//TODO + + //case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO + + //case YACHTACTIONCODE: return new YachtActionCodeDecoder();//TODO + + //case CHATTERTEXT: return new ChatterTextDecoder();//TODO + + case BOATLOCATION: return new BoatLocationDecoder(); + + //case MARKROUNDING: return new MarkRoundingDecoder()//TODO; + + case COURSEWIND: return new CourseWindsDecoder(); + + //case AVGWIND: return new AverageWindDecoder()//TODO; + + case REQUEST_TO_JOIN: return new RequestToJoinDecoder(); + + case JOIN_ACCEPTANCE: return new JoinAcceptanceDecoder(); + + case BOATACTION: return new BoatActionDecoder(); + + + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); + } + + + + } + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java index 0984a9ea..49cdcc5f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.BoatActionEnum; import network.Messages.HeartBeat; @@ -8,7 +9,7 @@ import static network.Utils.ByteConverter.bytesToLong; /** * Decodes {@link network.Messages.HeartBeat} messages. */ -public class HeartBeatDecoder { +public class HeartBeatDecoder implements MessageDecoder { /** * The encoded message. @@ -24,21 +25,17 @@ public class HeartBeatDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public HeartBeatDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public HeartBeatDecoder() { } - - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; message = new HeartBeat(bytesToLong(encodedMessage)); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java index 95c3889e..eaa82c18 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; import network.Utils.ByteConverter; @@ -10,7 +11,7 @@ import java.util.Arrays; /** * Decoder for {@link JoinAcceptance} messages. */ -public class JoinAcceptanceDecoder { +public class JoinAcceptanceDecoder implements MessageDecoder { /** * The encoded message. @@ -25,19 +26,14 @@ public class JoinAcceptanceDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public JoinAcceptanceDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public JoinAcceptanceDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; //SourceID is first four bytes. byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); @@ -55,6 +51,8 @@ public class JoinAcceptanceDecoder { message = new JoinAcceptance(acceptanceType, sourceID); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java new file mode 100644 index 00000000..39b4370f --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java @@ -0,0 +1,21 @@ +package network.MessageDecoders; + + +import network.Messages.AC35Data; + + +/** + * This is the interface that all message decoders must implement. + * It allows for {@link #decode(byte[])}ing messages. + */ +public interface MessageDecoder { + + + /** + * Decodes a given message. + * @param encodedMessage The message to decode. + * @return The decoded message. + */ + public AC35Data decode(byte[] encodedMessage); + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 307c2815..15700f49 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; @@ -20,7 +21,7 @@ import static network.Utils.ByteConverter.bytesToShort; /** * Decodes {@link RaceStatus} messages. */ -public class RaceStatusDecoder { +public class RaceStatusDecoder implements MessageDecoder { /** * The encoded message. @@ -36,19 +37,14 @@ public class RaceStatusDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public RaceStatusDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public RaceStatusDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); @@ -84,15 +80,18 @@ public class RaceStatusDecoder { List boatStatuses = new ArrayList<>(); + //BoatStatus is 20 bytes. + int boatStatusByteLength = 20; + //Decode each BoatStatus. - for (int boatLoopIndex=0; boatLoopIndex < (numberOfBoats * 20); boatLoopIndex += 20) { + for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { - byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + 20); + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusBytes); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); - boatStatuses.add(boatStatusDecoder.getMessage()); + boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); } @@ -106,6 +105,8 @@ public class RaceStatusDecoder { windSpeedKnots, raceType, boatStatuses ); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java index 2d272f05..1abf61a6 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; import network.Utils.ByteConverter; @@ -10,7 +11,7 @@ import java.util.Arrays; /** * Decoder for {@link network.Messages.RequestToJoin} messages. */ -public class RequestToJoinDecoder { +public class RequestToJoinDecoder implements MessageDecoder{ /** * The encoded message. @@ -24,19 +25,14 @@ public class RequestToJoinDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedRequest The message to decode. */ - public RequestToJoinDecoder(byte[] encodedRequest) { - this.encodedRequest = encodedRequest; - - decode(); + public RequestToJoinDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedRequest) { + this.encodedRequest = encodedRequest; //Request type is first four bytes. byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); @@ -47,6 +43,8 @@ public class RequestToJoinDecoder { message = new RequestToJoin(requestType); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 4fb985a1..99c56e28 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -12,7 +13,7 @@ import static network.Utils.ByteConverter.bytesToShort; /** * Decodes {@link network.Messages.XMLMessage} messages. */ -public class XMLMessageDecoder { +public class XMLMessageDecoder implements MessageDecoder { /** * The encoded message. @@ -28,18 +29,14 @@ public class XMLMessageDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public XMLMessageDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public XMLMessageDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); @@ -66,6 +63,8 @@ public class XMLMessageDecoder { xmlMsgSubType, sequenceNumber, xmlMessage ); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java index ba5ce901..8083487f 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -30,7 +30,7 @@ public class BoatActionEncoder implements MessageEncoder { //Message is 1 byte. ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); + boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); byte [] result = boatActionMessage.array(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java new file mode 100644 index 00000000..b6e407c0 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java @@ -0,0 +1,73 @@ +package network.MessageEncoders; + + +import network.Messages.CourseWind; +import shared.model.Bearing; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.bytesToInt; + +/** + * This encoder can encode a {@link CourseWind} message. + */ +public class CourseWindEncoder { + + + /** + * Constructor. + */ + public CourseWindEncoder() { + } + + + /** + * Encodes a given CourseWind message. + * @param message The message to encode. + * @return The encoded message. + */ + public byte[] encode(CourseWind message) { + + CourseWind courseWind = message; + + + //CourseWind is 20 bytes. + ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); + + + byte[] windId = intToBytes(courseWind.getID(), 1); + + byte[] timeBytes = longToBytes(courseWind.getTime(), 6); + + byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); + + int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); + byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); + + int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); + byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); + + int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); + byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); + + int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); + byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); + + byte[] flags = intToBytes(courseWind.getFlags(), 1); + + courseWindBuffer.put(windId); + courseWindBuffer.put(timeBytes); + courseWindBuffer.put(raceIDBytes); + courseWindBuffer.put(windDirectionBytes); + courseWindBuffer.put(windSpeedBytes); + courseWindBuffer.put(bestUpwindAngleBytes); + courseWindBuffer.put(bestDownwindAngleBytes); + courseWindBuffer.put(flags); + + return courseWindBuffer.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java new file mode 100644 index 00000000..193144c3 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java @@ -0,0 +1,57 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatAction; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link CourseWinds} message. + */ +public class CourseWindsEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public CourseWindsEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + CourseWinds courseWinds = (CourseWinds) message; + + + byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; + + byte byteWindID = courseWinds.getSelectedWindID(); + + byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); + + ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); + + result.put(messageVersionNumber); + result.put(byteWindID); + result.put(loopcount); + + //Encode each CourseWind. + for (CourseWind wind: courseWinds.getCourseWinds()){ + + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] encodedCourseWind = courseWindEncoder.encode(wind); + + result.put(encodedCourseWind); + } + return result.array(); + + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index c026efd7..6ac7ec5b 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -49,7 +49,7 @@ public class EncoderFactory { //case MARKROUNDING: return new MarkRoundingEncoder();//TODO - //case COURSEWIND: return new CourseWindEncoder();//TODO + case COURSEWIND: return new CourseWindsEncoder(); //case AVGWIND: return new AverageWindEncoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index ebd67819..a933e179 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -143,26 +143,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] courseWind(byte windID, ArrayList courseWinds){ - int messageVersionNumber = 0b1; - byte byteWindID = windID; - byte[] loopcount = intToBytes(courseWinds.size(), 1); - ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.size()); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteWindID); - result.put(loopcount); - for (CourseWind wind: courseWinds){ - result.put(intToBytes(wind.getID(), 1)); - result.put(longToBytes(wind.getTime(), 6)); - result.put(intToBytes(wind.getRaceID(), 4)); - result.put(intToBytes(wind.getWindDirection(), 2)); - result.put(intToBytes(wind.getWindSpeed(), 2)); - result.put(intToBytes(wind.getBestUpwindAngle(), 2)); - result.put(intToBytes(wind.getBestDownwindAngle(), 2)); - result.put(intToBytes(wind.getFlags(), 1)); - } - return result.array(); - } + public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ int messageVersionNumber = 0b1; diff --git a/racevisionGame/src/main/java/network/Messages/BoatAction.java b/racevisionGame/src/main/java/network/Messages/BoatAction.java index d20943a5..fcc96aa8 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatAction.java +++ b/racevisionGame/src/main/java/network/Messages/BoatAction.java @@ -4,19 +4,30 @@ import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.MessageType; /** - * Created by David on 10/07/2017. + * Represents a BoatAction message. */ public class BoatAction extends AC35Data { - private byte boatAction; + /** + * The action for this message. + */ + private BoatActionEnum boatAction; + /** + * Constructs a BoatActon message with a given action. + * @param boatAction Action to use. + */ public BoatAction(BoatActionEnum boatAction){ super(MessageType.BOATACTION); - this.boatAction = boatAction.getValue(); + this.boatAction = boatAction; } - public byte getBoatAction() { + /** + * Returns the action for this message. + * @return The action for this message. + */ + public BoatActionEnum getBoatAction() { return boatAction; } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/network/Messages/CourseWind.java b/racevisionGame/src/main/java/network/Messages/CourseWind.java index 727d5fcc..60575edb 100644 --- a/racevisionGame/src/main/java/network/Messages/CourseWind.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWind.java @@ -2,57 +2,132 @@ package network.Messages; import network.Messages.Enums.MessageType; +import shared.model.Bearing; /** - * Created by fwy13 on 21/04/17. + * Contains a single CourseWind record. + * A CourseWinds message contains one or more CourseWind messages. */ public class CourseWind extends AC35Data { - private int ID, raceID, windDirection, windSpeed, bestUpwindAngle, bestDownwindAngle, flags; + /** + * The ID for this wind source. + */ + private int ID; + + /** + * The time the wind was captured at. Milliseconds since unix epoch. + */ private long time; - public CourseWind(int ID, long time, int raceID, int windDirection, int windSpeed, int bestUpwindAngle, int bestDownwindAngle, - int flags){ + /** + * The ID of the race this applies to. + * 0 means it isn't race specific. + */ + private int raceID; + + /** + * Direction of the wind. + */ + private Bearing windDirection; + + /** + * The speed of the wind, in knots. + */ + private double windSpeedKnots; + + /** + * Optimum upwind sailing angle. + */ + private Bearing bestUpwindAngle; + + /** + * Optimum downwind sailing angle. + */ + private Bearing bestDownwindAngle; + + /** + * Various flags which determine which values are valid. + */ + private short flags; + + + + public CourseWind(int ID, long time, int raceID, Bearing windDirection, double windSpeedKnots, Bearing bestUpwindAngle, Bearing bestDownwindAngle, short flags) { super(MessageType.COURSEWIND); this.ID = ID; this.time = time; this.raceID = raceID; this.windDirection = windDirection; - this.windSpeed = windSpeed; + this.windSpeedKnots = windSpeedKnots; this.bestUpwindAngle = bestUpwindAngle; this.bestDownwindAngle = bestDownwindAngle; this.flags = flags; } + + /** + * Returns the ID of the wind source. + * @return ID of the wind source. + */ public int getID() { return ID; } + /** + * Returns the time that this was captured at. Milliseconds since unix epoch. + * @return Time this wind was captured at. + */ + public long getTime() { + return time; + } + + /** + * Returns the ID of the race this wind source belongs to. 0 means any race. + * @return ID of the race this belongs to. + */ public int getRaceID() { return raceID; } - public int getWindDirection() { + /** + * Returns the direction of the wind. + * @return The direction of the wind. + */ + public Bearing getWindDirection() { return windDirection; } - public int getWindSpeed() { - return windSpeed; + /** + * Returns the wind speed, in knots. + * @return Wind speed, in knots. + */ + public double getWindSpeedKnots() { + return windSpeedKnots; } - public int getBestUpwindAngle() { + /** + * Returns the best upwind sailing angle. + * @return Best upwind sailing angle. + */ + public Bearing getBestUpwindAngle() { return bestUpwindAngle; } - public int getBestDownwindAngle() { + /** + * Returns the best downwind sailing angle. + * @return The best downwind sailing angle. + */ + public Bearing getBestDownwindAngle() { return bestDownwindAngle; } + /** + * Returns various flags which determine which values are valid. + * @return Flag which determines which values are valid. + */ public int getFlags() { return flags; } - public long getTime() { - return time; - } } diff --git a/racevisionGame/src/main/java/network/Messages/CourseWinds.java b/racevisionGame/src/main/java/network/Messages/CourseWinds.java index fc575867..d62f3135 100644 --- a/racevisionGame/src/main/java/network/Messages/CourseWinds.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWinds.java @@ -6,19 +6,67 @@ import network.Messages.Enums.MessageType; import java.util.List; /** - * Created by fwy13 on 25/04/17. + * Represents the information in a CourseWind message (AC streaming spec: 4.11). */ public class CourseWinds extends AC35Data { - private int msgVerNum; - private int selectedWindID; + /** + * The current version number for this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number of this message. + */ + private byte messageVersionNumber; + + /** + * The ID of the wind source currently selected. + */ + private byte selectedWindID; + + /** + * A list of wind sources. + */ private List courseWinds; - public CourseWinds(int msgVerNum, int selectedWindID, List courseWinds){ + + /** + * Constructs a CourseWinds with given parameters. + * @param messageVersionNumber The version number of the message. + * @param selectedWindID The selected wind ID. + * @param courseWinds A list of wind sources. + */ + public CourseWinds(byte messageVersionNumber, byte selectedWindID, List courseWinds) { super(MessageType.COURSEWIND); - this.msgVerNum = msgVerNum; + this.messageVersionNumber = messageVersionNumber; this.selectedWindID = selectedWindID; this.courseWinds = courseWinds; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Returns the ID of the selected wind source. + * @return ID of the selected wind source. + */ + public byte getSelectedWindID() { + return selectedWindID; + } + + /** + * Returns the list of wind sources. + * @return List of wind sources. + */ + public List getCourseWinds() { + return courseWinds; + } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..df54d16d 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -2,6 +2,7 @@ package visualiser.gameController; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; +import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.KeyFactory; @@ -47,9 +48,10 @@ public class ControllerServer implements Runnable { if (inputStream.available() > 0) { inputStream.read(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); - BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); - BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + BoatActionDecoder boatActionDecoder = new BoatActionDecoder(); + boatActionDecoder.decode(encodedMessage.getMessageBody()); + BoatAction boatAction = boatActionDecoder.getMessage(); + System.out.println("Received key: " + boatAction.getBoatAction()); } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java index d7e13b97..80dacbe6 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -29,10 +29,10 @@ public class BoatActionDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - BoatActionDecoder testDecoder = new BoatActionDecoder(testEncodedMessage); - BoatActionEnum decodedBoatAction = testDecoder.getBoatAction(); + BoatActionDecoder testDecoder = new BoatActionDecoder(); + testDecoder.decode(testEncodedMessage); - BoatAction decodedMessage = new BoatAction(decodedBoatAction); + BoatAction decodedMessage = testDecoder.getMessage(); return decodedMessage; } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index dcc69abe..7fcdeb4a 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -53,7 +53,8 @@ public class BoatLocationDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); //Decode. - BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage); + BoatLocationDecoder testDecoder = new BoatLocationDecoder(); + testDecoder.decode(testEncodedMessage); BoatLocation decodedTest = testDecoder.getMessage(); //Check if valid. diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java index 635e581c..088a85cb 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -59,12 +59,10 @@ public class BoatStatusDecoderTest { private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); - byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusEncoded); - - BoatStatus boatStatusDecoded = boatStatusDecoder.getMessage(); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); + BoatStatus boatStatusDecoded = boatStatusDecoder.decode(boatStatusEncoded); return boatStatusDecoded; } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java index 53793b81..0a8753db 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java @@ -1,57 +1,77 @@ package network.MessageDecoders; -import network.MessageEncoders.RaceVisionByteEncoder; +import network.MessageEncoders.CourseWindEncoder; +import network.Messages.BoatStatus; import network.Messages.CourseWind; +import network.Messages.Enums.BoatStatusEnum; import org.junit.Assert; import org.junit.Test; - -import java.util.ArrayList; - +import shared.model.Bearing; /** - * Created by hba56 on 23/04/17. + * Test for the CourseWind encoder and decoder */ public class CourseWindDecoderTest { + + + /** + * Creates a CourseWind message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ @Test - public void getByteArrayTest(){ + public void courseWindEncodeDecodeTest() throws Exception { + long time = System.currentTimeMillis(); - CourseWind testCourseWind1 = new CourseWind(1, time, 2, - 3, 4, 5, - 7, 6); + CourseWind courseWind = new CourseWind( + 1, + time, + 2, + Bearing.fromDegrees(45), + 4, + Bearing.fromDegrees(70), + Bearing.fromDegrees(160), + (byte) 0x13 ); - long time2 = System.currentTimeMillis(); - CourseWind testCourseWind2 = new CourseWind(2, time2, 2, - 3, 4, 5, - 7, 6); - ArrayList testCourseWinds = new ArrayList(); - testCourseWinds.add(testCourseWind1); - testCourseWinds.add(testCourseWind2); + CourseWind courseWindDecoded = encodeDecodeCourseWind(courseWind); + compareCourseWindMessages(courseWind, courseWindDecoded); - byte[] testEncodedCourseWind = RaceVisionByteEncoder.courseWind((byte) 1, testCourseWinds); + } - CourseWindDecoder testDecoder = new CourseWindDecoder(testEncodedCourseWind); + /** + * Encodes and decodes a CourseWind, and returns it. + * @param courseWind The CourseWind to encode and decode. + * @return The decoded CourseWind. + */ + private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) { - ArrayList testDecodedCourseWinds = testDecoder.getLoopMessages(); + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] courseWindEncoded = courseWindEncoder.encode(courseWind); - Assert.assertEquals(testCourseWinds.get(0).getID(), testDecodedCourseWinds.get(0).getID()); - Assert.assertEquals(testCourseWinds.get(0).getTime(), testDecodedCourseWinds.get(0).getTime()); - Assert.assertEquals(testCourseWinds.get(0).getRaceID(), testDecodedCourseWinds.get(0).getRaceID()); - Assert.assertEquals(testCourseWinds.get(0).getWindDirection(), testDecodedCourseWinds.get(0).getWindDirection()); - Assert.assertEquals(testCourseWinds.get(0).getWindSpeed(), testDecodedCourseWinds.get(0).getWindSpeed()); - Assert.assertEquals(testCourseWinds.get(0).getBestUpwindAngle(), testDecodedCourseWinds.get(0).getBestUpwindAngle()); - Assert.assertEquals(testCourseWinds.get(0).getBestDownwindAngle(), testDecodedCourseWinds.get(0).getBestDownwindAngle()); - Assert.assertEquals(testCourseWinds.get(0).getFlags(), testDecodedCourseWinds.get(0).getFlags()); + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWindDecoded = courseWindDecoder.decode(courseWindEncoded); - Assert.assertEquals(testCourseWinds.get(1).getID(), testDecodedCourseWinds.get(1).getID()); - Assert.assertEquals(testCourseWinds.get(1).getTime(), testDecodedCourseWinds.get(1).getTime()); - Assert.assertEquals(testCourseWinds.get(1).getRaceID(), testDecodedCourseWinds.get(1).getRaceID()); - Assert.assertEquals(testCourseWinds.get(1).getWindDirection(), testDecodedCourseWinds.get(1).getWindDirection()); - Assert.assertEquals(testCourseWinds.get(1).getWindSpeed(), testDecodedCourseWinds.get(1).getWindSpeed()); - Assert.assertEquals(testCourseWinds.get(1).getBestUpwindAngle(), testDecodedCourseWinds.get(1).getBestUpwindAngle()); - Assert.assertEquals(testCourseWinds.get(1).getBestDownwindAngle(), testDecodedCourseWinds.get(1).getBestDownwindAngle()); - Assert.assertEquals(testCourseWinds.get(1).getFlags(), testDecodedCourseWinds.get(1).getFlags()); + return courseWindDecoded; + } + + + /** + * Compares two CourseWind messages to check that they are equal. + * @param original The original CourseWind message. + * @param decoded The decoded CourseWind message. + */ + public static void compareCourseWindMessages(CourseWind original, CourseWind decoded) { + + Assert.assertEquals(original.getID(), decoded.getID()); + Assert.assertEquals(original.getTime(), decoded.getTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01); + Assert.assertEquals(original.getWindSpeedKnots(), decoded.getWindSpeedKnots(), 0.01); + Assert.assertEquals(original.getBestUpwindAngle().degrees(), decoded.getBestUpwindAngle().degrees(), 0.01); + Assert.assertEquals(original.getBestDownwindAngle().degrees(), decoded.getBestDownwindAngle().degrees(), 0.01); + Assert.assertEquals(original.getFlags(), decoded.getFlags()); } + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java new file mode 100644 index 00000000..45c87f55 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java @@ -0,0 +1,96 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; +import org.junit.Test; +import shared.model.Bearing; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +import static org.junit.Assert.*; + + +/** + * Tests for CourseWinds encoder and decoder. + */ +public class CourseWindsDecoderTest { + + /** + * Tests if a CourseWinds message can be encoded and decoded correctly. + * @throws Exception Thrown if an error occurs. + */ + @Test + public void courseWindsEncodeDecodeTest() throws Exception { + + long time1 = System.currentTimeMillis(); + CourseWind testCourseWind1 = new CourseWind( + 1, + time1, + 2, + Bearing.fromDegrees(45), + 4, + Bearing.fromDegrees(70), + Bearing.fromDegrees(160), + (byte) 0xCE ); + + long time2 = System.currentTimeMillis(); + CourseWind testCourseWind2 = new CourseWind( + 2, + time2, + 2, + Bearing.fromDegrees(55), + 4, + Bearing.fromDegrees(80), + Bearing.fromDegrees(180), + (byte) 0x0D ); + + List testCourseWinds = new ArrayList<>(); + testCourseWinds.add(testCourseWind1); + testCourseWinds.add(testCourseWind2); + + CourseWinds courseWinds = new CourseWinds(CourseWinds.currentMessageVersionNumber, (byte) 2, testCourseWinds); + + + byte[] testEncodedCourseWind = RaceVisionByteEncoder.encode(courseWinds); + + CourseWindsDecoder courseWindsDecoder = new CourseWindsDecoder(); + courseWindsDecoder.decode(testEncodedCourseWind); + CourseWinds courseWindsDecoded = courseWindsDecoder.getMessage(); + + compareCourseWindsMessages(courseWinds, courseWindsDecoded); + + + } + + + /** + * Compares two course winds messages to ensure they are the same. + * @param original The original message. + * @param decoded The decoded message. + */ + public static void compareCourseWindsMessages(CourseWinds original, CourseWinds decoded) { + + //Compare header. + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getSelectedWindID(), decoded.getSelectedWindID()); + assertEquals(original.getCourseWinds().size(), decoded.getCourseWinds().size()); + + //Compare each CourseWind. + List originalWinds = original.getCourseWinds(); + List decodedWinds = decoded.getCourseWinds(); + + Iterator originalIterator = originalWinds.iterator(); + Iterator decodedIterator = decodedWinds.iterator(); + + while (originalIterator.hasNext() && decodedIterator.hasNext()) { + + CourseWindDecoderTest.compareCourseWindMessages(originalIterator.next(), decodedIterator.next()); + + } + + } +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java index 778a667a..ea1de885 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -28,7 +28,8 @@ public class HeartBeatDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - HeartBeatDecoder testDecoder = new HeartBeatDecoder(testEncodedMessage); + HeartBeatDecoder testDecoder = new HeartBeatDecoder(); + testDecoder.decode(testEncodedMessage); HeartBeat decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index cd31ad67..1e3a5c3d 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -27,7 +27,8 @@ public class JoinAcceptanceDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); + JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(); + testDecoder.decode(testEncodedMessage); JoinAcceptance decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 4f1e2721..d9a47b81 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -89,7 +89,8 @@ public class RaceStatusDecoderTest { byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal); - RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus); + RaceStatusDecoder decoderTest = new RaceStatusDecoder(); + decoderTest.decode(encodedRaceStatus); RaceStatus decodedMessage = decoderTest.getMessage(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index bde4ae75..fe70a709 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -28,7 +28,8 @@ public class RequestToJoinDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); + RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(); + testDecoder.decode(testEncodedMessage); RequestToJoin decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index 32d1a61e..f0c0ba0b 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -45,7 +45,8 @@ public class XMLMessageDecoderTest { - XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); + XMLMessageDecoder decoderXML = new XMLMessageDecoder(); + decoderXML.decode(encodedXML); XMLMessage decodedMessage = decoderXML.getMessage(); From e99ad00294f54da2b5ffe034960f08d589e8e54a Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 18:54:45 +1200 Subject: [PATCH 17/59] Javadoc fixes. --- .../src/main/java/network/MessageDecoders/BoatStatusDecoder.java | 1 + .../src/main/java/network/MessageDecoders/CourseWindDecoder.java | 1 + 2 files changed, 2 insertions(+) diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index 972c1eae..ed2c6cf0 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -38,6 +38,7 @@ public class BoatStatusDecoder { /** * Decodes the contained message. * @param encodedMessage The message to decode. + * @return The decoded message. */ public BoatStatus decode(byte[] encodedMessage) { this.encodedMessage = encodedMessage; diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 78bc6f1c..6a26e4ae 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -37,6 +37,7 @@ public class CourseWindDecoder { /** * Decodes the contained message. * @param encodedMessage The message to decode. + * @return The decoded message. */ public CourseWind decode(byte[] encodedMessage) { this.encodedMessage = encodedMessage; From ce63f58429f779433b71f59bff9a56fe7d6b6944 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 19:36:21 +1200 Subject: [PATCH 18/59] Added RaceStartStatusEncoder. Added RaceStartTypeEnum. Refactored RaceStartStatusDecoder to implement the MessageDecoder interface. Documented RaceStartStatus, and it actually exposes its properties now. Updated RaceStartStatusDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/DecoderFactory.java | 2 +- .../RaceStartStatusDecoder.java | 105 ++++++++++-------- .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceStartStatusEncoder.java | 51 +++++++++ .../RaceVisionByteEncoder.java | 19 ---- .../Messages/Enums/RaceStartTypeEnum.java | 97 ++++++++++++++++ .../network/Messages/RaceStartStatus.java | 97 +++++++++++++++- .../RaceStartStatusDecoderTest.java | 59 +++++++--- 9 files changed, 352 insertions(+), 84 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 0ee0d541..b0d5b2a6 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -164,8 +164,8 @@ public class BinaryMessageDecoder { case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); - RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(messageBody); - return new RaceStartStatus(rssDecoder.getTime(), rssDecoder.getAck(), rssDecoder.getStartTime(), rssDecoder.getRaceID(), rssDecoder. getNotification()); + RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(); + return rssDecoder.decode(messageBody); case YACHTEVENTCODE: //System.out.println("Yacht Action Code!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 500d20f6..3121f7f7 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -37,7 +37,7 @@ public class DecoderFactory { case XMLMESSAGE: return new XMLMessageDecoder(); - //case RACESTARTSTATUS: return new RaceStartStatusDecoder();//TODO + case RACESTARTSTATUS: return new RaceStartStatusDecoder(); //case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java index 236c5d27..062f1212 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java @@ -1,66 +1,81 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.Enums.RaceStartTypeEnum; +import network.Messages.RaceStartStatus; + import java.util.Arrays; import static network.Utils.ByteConverter.*; /** - * Created by hba56 on 21/04/17. + * Decodes {@link RaceStartStatus} messages. */ -public class RaceStartStatusDecoder { - private byte messageVersion; - private byte[] timestamp; - private byte[] ackNumber; - private byte[] raceStartTime; - private byte[] raceIdentifier; - private byte notificationType; - - private long time; - private short ack; - private long startTime; - private int raceID; - private char notification; - - - public RaceStartStatusDecoder(byte[] encodedRaceStartStatus) { - messageVersion = encodedRaceStartStatus[0]; - timestamp = Arrays.copyOfRange(encodedRaceStartStatus, 1, 7); - ackNumber = Arrays.copyOfRange(encodedRaceStartStatus, 7, 9); - raceStartTime = Arrays.copyOfRange(encodedRaceStartStatus, 9, 15); - raceIdentifier = Arrays.copyOfRange(encodedRaceStartStatus, 15, 19); - notificationType = encodedRaceStartStatus[19]; - - time = bytesToLong(timestamp); - ack = bytesToShort(ackNumber); - startTime = bytesToLong(raceStartTime); - raceID = bytesToInt(raceIdentifier); - notification = bytesToChar(notificationType); - } +public class RaceStartStatusDecoder implements MessageDecoder { + /** + * The encoded message. + */ + private byte[] encodedMessage; - public byte getMessageVersion() { - return messageVersion; - } + /** + * The decoded message. + */ + private RaceStartStatus message; - public long getTime() { - return time; - } - public short getAck() { - return ack; - } - public long getStartTime() { - return startTime; + /** + * Constructs a decoder to decode a given message. + */ + public RaceStartStatusDecoder() { } - public int getRaceID() { - return raceID; + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + + byte messageVersion = encodedMessage[0]; + + byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timestamp); + + byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); + short ack = bytesToShort(ackNumber); + + byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); + long startTime = bytesToLong(raceStartTime); + + byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); + int raceID = bytesToInt(raceIdentifier); + + byte notificationType = encodedMessage[19]; + + + + message = new RaceStartStatus( + messageVersion, + time, + ack, + startTime, + raceID, + RaceStartTypeEnum.fromByte(notificationType) + ); + + return message; + } - public char getNotification() { - return notification; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RaceStartStatus getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 6ac7ec5b..fadf6dea 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -37,7 +37,7 @@ public class EncoderFactory { case XMLMESSAGE: return new XMLMessageEncoder(); - //case RACESTARTSTATUS: return new RaceStartStatusEncoder();//TODO + case RACESTARTSTATUS: return new RaceStartStatusEncoder(); //case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java new file mode 100644 index 00000000..f2d41d20 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java @@ -0,0 +1,51 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.RaceStartStatus; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link RaceStartStatus} message. + */ +public class RaceStartStatusEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RaceStartStatusEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RaceStartStatus raceStartStatus = (RaceStartStatus) message; + + + byte messageVersion = raceStartStatus.getMessageVersionNumber(); + byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); + byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); + byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); + byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); + byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); + + ByteBuffer result = ByteBuffer.allocate(20); + result.put(messageVersion); + result.put(timestamp); + result.put(ackNumber); + result.put(raceStartTime); + result.put(raceIdentifier); + result.put(notificationType); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index a933e179..45690bca 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -63,25 +63,6 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){ - int messageVersion = 0b1; - byte[] timestamp = longToBytes(time, 6); - byte[] ackNumber = intToBytes(ack, 2); - byte[] raceStartTime = longToBytes(startTime, 6); - int raceIdentifier = raceID; - byte[] notificationType = intToBytes(notification, 1); - - ByteBuffer result = ByteBuffer.allocate(20); - result.put(intToBytes(messageVersion, 1)); - result.put(timestamp); - result.put(ackNumber); - result.put(raceStartTime); - result.put(intToBytes(raceIdentifier)); - result.put(notificationType); - - return result.array(); - } - public static byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID, int eventID){ int messageVersion = 0b10; diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java new file mode 100644 index 00000000..d9b8d69b --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java @@ -0,0 +1,97 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types race start status notifications. See AC35 streaming spec, 4.5. + */ +public enum RaceStartTypeEnum { + + + /** + * The race start time is being set. + */ + SET_RACE_START(1), + + /** + * The race has been postponed. + */ + RACE_POSTPONED(2), + + /** + * The race has been abandoned. + */ + RACE_ABANDONED(3), + + /** + * The race has been terminated. + */ + RACE_TERMINATED(4), + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RaceStartTypeEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RaceStartTypeEnum(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 RaceStartTypeEnum values. + */ + private static final Map byteToTypeMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToTypeMap. + */ + static { + for (RaceStartTypeEnum type : RaceStartTypeEnum.values()) { + RaceStartTypeEnum.byteToTypeMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param startTypeEnum Byte value to convert to a RaceStartTypeEnum value. + * @return The RaceStartTypeEnum value which corresponds to the given byte value. + */ + public static RaceStartTypeEnum fromByte(byte startTypeEnum) { + //Gets the corresponding MessageType from the map. + RaceStartTypeEnum type = RaceStartTypeEnum.byteToTypeMap.get(startTypeEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_TYPE RaceStartTypeEnum. + return RaceStartTypeEnum.NOT_A_TYPE; + } else { + //Otherwise, return the RaceStartTypeEnum. + return type; + } + + } + + +} diff --git a/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java index 8dc442ab..6ad514d5 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java @@ -2,20 +2,63 @@ package network.Messages; import network.Messages.Enums.MessageType; +import network.Messages.Enums.RaceStartTypeEnum; + /** - * Created by fwy13 on 25/04/17. + * Represents a RaceStartStatus message from the API, section 4.5. */ public class RaceStartStatus extends AC35Data { + /** + * The current version number of this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number of this message. + */ + private byte messageVersionNumber; + + /** + * The time at which this message was created. Milliseconds since unix epoch. + */ private long timestamp; + + /** + * Sequence number of message. + */ private int ackNum; + + /** + * The time the race is expected to start at. Milliseconds since unix epoch. + */ private long raceStartTime; + + /** + * The ID of the race this message relates to. + */ private int raceID; - private int notificationType; - public RaceStartStatus(long timestamp, int ackNum, long raceStartTime, int raceID, int notificationType){ + /** + * The type of notification this is. + */ + private RaceStartTypeEnum notificationType; + + + /** + * Constructs a RaceStartStatus message with the given parameters. + * @param messageVersionNumber Version number of the message. + * @param timestamp The timestamp at which this message was generated. + * @param ackNum The sequence number of this message. + * @param raceStartTime The expected race start time. + * @param raceID The ID of the race this message relates to. + * @param notificationType The type of notification this is. + */ + public RaceStartStatus(byte messageVersionNumber, long timestamp, int ackNum, long raceStartTime, int raceID, RaceStartTypeEnum notificationType) { super(MessageType.RACESTARTSTATUS); + this.messageVersionNumber = messageVersionNumber; this.timestamp = timestamp; this.ackNum = ackNum; this.raceStartTime = raceStartTime; @@ -23,4 +66,52 @@ public class RaceStartStatus extends AC35Data { this.notificationType = notificationType; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Return the time at which this message was generated. Milliseconds since unix epoch. + * @return Time at which this message was generated. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Returns the sequence number of this message. + * @return Sequence number of this message. + */ + public int getAckNum() { + return ackNum; + } + + /** + * Returns the expected race start time. Milliseconds since unix epoch. + * @return Expected race start time. + */ + public long getRaceStartTime() { + return raceStartTime; + } + + /** + * Returns the race ID this message relates to. + * @return Race ID this message relates to. + */ + public int getRaceID() { + return raceID; + } + + /** + * Returns the type of start status notification this message is. + * @return The type of notification this is. + */ + public RaceStartTypeEnum getNotificationType() { + return notificationType; + } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java index ce2fe475..b0029321 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java @@ -1,29 +1,62 @@ package network.MessageDecoders; import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.RaceStartTypeEnum; +import network.Messages.RaceStartStatus; import org.junit.Assert; import org.junit.Test; /** - * Created by hba56 on 23/04/17. + * Tests for the RaceStartStatus encoder and decoder. */ public class RaceStartStatusDecoderTest { + + + /** + * Tests if a RaceStartStatus message can be encoded and decoded correctly. + * @throws Exception Thrown when an error occurs. + */ @Test - public void getByteArrayTest(){ - long time = System.currentTimeMillis(); + public void raceStartStatusEncodeDecodeTest() throws Exception { + + long timestamp = System.currentTimeMillis(); - long time2 = System.currentTimeMillis(); - byte[] encodedRaceStartStatus = RaceVisionByteEncoder.raceStartStatus(time, (short)1, - time2, 2, (char)3); + long startTime = System.currentTimeMillis() + 10 * 1000; - RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(encodedRaceStartStatus); + RaceStartStatus raceStartStatus = new RaceStartStatus( + RaceStartStatus.currentMessageVersionNumber, + timestamp, + 55, + startTime, + 35, + RaceStartTypeEnum.SET_RACE_START + ); - Assert.assertEquals(0b1, testDecoder.getMessageVersion()); - Assert.assertEquals(time, testDecoder.getTime()); - Assert.assertEquals(1, testDecoder.getAck()); - Assert.assertEquals(time2, testDecoder.getStartTime()); - Assert.assertEquals(2, testDecoder.getRaceID()); - Assert.assertEquals((char)3, testDecoder.getNotification()); + byte[] encodedRaceStartStatus = RaceVisionByteEncoder.encode(raceStartStatus); + + RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(); + testDecoder.decode(encodedRaceStartStatus); + RaceStartStatus raceStartStatusDecoded = testDecoder.getMessage(); + + compareRaceStartStatusMessages(raceStartStatus, raceStartStatusDecoded); } + + /** + * Compares two RaceStartStatus messages to check that they are the same. + * @param original The original message. + * @param decoded The decoded message. + */ + public static void compareRaceStartStatusMessages(RaceStartStatus original, RaceStartStatus decoded) { + + Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + Assert.assertEquals(original.getTimestamp(), decoded.getTimestamp()); + Assert.assertEquals(original.getAckNum(), decoded.getAckNum()); + Assert.assertEquals(original.getRaceStartTime(), decoded.getRaceStartTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getNotificationType(), decoded.getNotificationType()); + + } + + } From da800e659a4d40b98a9c2b43029fc1f4d67b6c27 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 20:31:21 +1200 Subject: [PATCH 19/59] Added AverageWindEncoder. Refactored AverageWindDecoder - it now implements MessageDecoder. Refactored AverageWind - it now uses milliseconds and knots, instead of packed MMperSec and tenths of a second. It also exposes its attributes now. Added (un)packAverageWindPeriod to AC35UnitConverter. Added AverageWindDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/AverageWindDecoder.java | 111 +++++++--- .../MessageDecoders/DecoderFactory.java | 2 +- .../MessageEncoders/AverageWindEncoder.java | 86 ++++++++ .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceVisionByteEncoder.java | 28 --- .../java/network/Messages/AverageWind.java | 198 ++++++++++++++++-- .../java/network/Utils/AC35UnitConverter.java | 19 ++ .../AverageWindDecoderTest.java | 75 +++++++ 9 files changed, 439 insertions(+), 86 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index b0d5b2a6..71765e12 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -199,8 +199,8 @@ public class BinaryMessageDecoder { case AVGWIND: //System.out.println("Average Wind Message!"); - AverageWindDecoder awDecoder = new AverageWindDecoder(messageBody); - return awDecoder.getAverageWind(); + AverageWindDecoder awDecoder = new AverageWindDecoder(); + return awDecoder.decode(messageBody); case BOATACTION: BoatActionDecoder baDecoder = new BoatActionDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java index 13de3c80..1b0f12d5 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java @@ -1,56 +1,103 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.AverageWind; import network.Utils.ByteConverter; +import static network.Utils.AC35UnitConverter.*; + import java.util.Arrays; /** - * Created by hba56 on 23/04/17. + * Decodes {@link AverageWind} messages. */ -public class AverageWindDecoder { - byte messageVersionNumber; - byte[] byteTime; - byte[] byteRawPeriod; - byte[] byteRawSpeed; - byte[] bytePeriod2; - byte[] byteSpeed2; - byte[] bytePeriod3; - byte[] byteSpeed3; - byte[] bytePeriod4; - byte[] byteSpeed4; - - AverageWind averageWind; - - public AverageWindDecoder(byte[] encodedAverageWind) { - messageVersionNumber = encodedAverageWind[0]; - byteTime = Arrays.copyOfRange(encodedAverageWind, 1, 7); - byteRawPeriod = Arrays.copyOfRange(encodedAverageWind, 7, 9); - byteRawSpeed = Arrays.copyOfRange(encodedAverageWind, 9, 11); - bytePeriod2 = Arrays.copyOfRange(encodedAverageWind, 11, 13); - byteSpeed2 = Arrays.copyOfRange(encodedAverageWind, 13, 15); - bytePeriod3 = Arrays.copyOfRange(encodedAverageWind, 15, 17); - byteSpeed3 = Arrays.copyOfRange(encodedAverageWind, 17, 19); - bytePeriod4 = Arrays.copyOfRange(encodedAverageWind, 19, 21); - byteSpeed4 = Arrays.copyOfRange(encodedAverageWind, 21, 23); - - int msgNum = ByteConverter.bytesToInt(messageVersionNumber); - long lngTime = ByteConverter.bytesToLong(byteTime); +public class AverageWindDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private AverageWind message; + + + + public AverageWindDecoder() { + } + + + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + + byte messageVersionNumber = encodedMessage[0]; + + + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); + + byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); + long rawPeriod = unpackAverageWindPeriod(intRawPeriod); + + byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); + double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); + + byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); + long period2 = unpackAverageWindPeriod(intPeriod2); + + byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); + double speed2Knots = unpackMMperSecToKnots(intSpeed2); + + byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); + long period3 = unpackAverageWindPeriod(intPeriod3); + + byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); + double speed3Knots = unpackMMperSecToKnots(intSpeed3); + + byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); + long period4 = unpackAverageWindPeriod(intPeriod4); + + byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); + double speed4Knots = unpackMMperSecToKnots(intSpeed4); + + + + + message = new AverageWind( + messageVersionNumber, + time, + rawPeriod, + rawSpeedKnots, + period2, + speed2Knots, + period3, + speed3Knots, + period4, + speed4Knots ); - this.averageWind = new AverageWind(msgNum, lngTime, intRawPeriod, intRawSpeed, intPeriod2, intSpeed2, intPeriod3, intSpeed3, intPeriod4, intSpeed4); + return message; } - public AverageWind getAverageWind() { - return averageWind; + /** + * Returns the decoded message. + * @return The decoded message. + */ + public AverageWind getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 3121f7f7..9b04803c 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -51,7 +51,7 @@ public class DecoderFactory { case COURSEWIND: return new CourseWindsDecoder(); - //case AVGWIND: return new AverageWindDecoder()//TODO; + case AVGWIND: return new AverageWindDecoder(); case REQUEST_TO_JOIN: return new RequestToJoinDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java new file mode 100644 index 00000000..dc1966fc --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java @@ -0,0 +1,86 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.AverageWind; + +import java.nio.ByteBuffer; + +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link AverageWind} message. + */ +public class AverageWindEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public AverageWindEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + AverageWind averageWind = (AverageWind) message; + + + byte messageVersionNumber = averageWind.getMessageVersionNumber(); + + long time = averageWind.getTime(); + byte[] byteTime = longToBytes(time,6); + + long rawPeriod = averageWind.getRawPeriod(); + int rawPeriodInt = packAverageWindPeriod(rawPeriod); + byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); + + double rawSampleSpeed = averageWind.getRawSpeedKnots(); + int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); + byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); + + long period2 = averageWind.getSampleTwoPeriod(); + int period2Int = packAverageWindPeriod(period2); + byte[] bytePeriod2 = intToBytes(period2Int, 2); + + double speed2 = averageWind.getSampleTwoSpeedKnots(); + int speed2Int = packKnotsToMMperSec(speed2); + byte[] byteSpeed2 = intToBytes(speed2Int, 2); + + long period3 = averageWind.getSampleThreePeriod(); + int period3Int = packAverageWindPeriod(period3); + byte[] bytePeriod3 = intToBytes(period3Int, 2); + + double speed3 = averageWind.getSampleThreeSpeedKnots(); + int speed3Int = packKnotsToMMperSec(speed3); + byte[] byteSpeed3 = intToBytes(speed3Int, 2); + + long period4 = averageWind.getSampleFourPeriod(); + int period4Int = packAverageWindPeriod(period4); + byte[] bytePeriod4 = intToBytes(period4Int, 2); + + double speed4 = averageWind.getSampleFourSpeedKnots(); + int speed4Int = packKnotsToMMperSec(speed4); + byte[] byteSpeed4 = intToBytes(speed4Int, 2); + + + + ByteBuffer result = ByteBuffer.allocate(23); + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteRawPeriod); + result.put(byteRawSpeed); + result.put(bytePeriod2); + result.put(byteSpeed2); + result.put(bytePeriod3); + result.put(byteSpeed3); + result.put(bytePeriod4); + result.put(byteSpeed4); + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index fadf6dea..e023ffb7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -51,7 +51,7 @@ public class EncoderFactory { case COURSEWIND: return new CourseWindsEncoder(); - //case AVGWIND: return new AverageWindEncoder();//TODO + case AVGWIND: return new AverageWindEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 45690bca..154e25e4 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -126,34 +126,6 @@ public class RaceVisionByteEncoder { - public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ - int messageVersionNumber = 0b1; - byte[] byteTime = longToBytes(time,6); - byte[] byteRawPeriod = intToBytes(rawPeriod, 2); - byte[] byteRawSpeed = intToBytes(rawSampleSpeed, 2); - byte[] bytePeriod2 = intToBytes(period2, 2); - byte[] byteSpeed2 = intToBytes(speed2, 2); - byte[] bytePeriod3 = intToBytes(period3, 2); - byte[] byteSpeed3 = intToBytes(speed3, 2); - byte[] bytePeriod4 = intToBytes(period4, 2); - byte[] byteSpeed4 = intToBytes(speed4, 2); - - ByteBuffer result = ByteBuffer.allocate(23); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteTime); - result.put(byteRawPeriod); - result.put(byteRawSpeed); - result.put(bytePeriod2); - result.put(byteSpeed2); - result.put(bytePeriod3); - result.put(byteSpeed3); - result.put(bytePeriod4); - result.put(byteSpeed4); - return result.array(); - } - - - /** * Encodes a given message. * @param message Message to encode. diff --git a/racevisionGame/src/main/java/network/Messages/AverageWind.java b/racevisionGame/src/main/java/network/Messages/AverageWind.java index 1ba17bd5..1b59f30f 100644 --- a/racevisionGame/src/main/java/network/Messages/AverageWind.java +++ b/racevisionGame/src/main/java/network/Messages/AverageWind.java @@ -4,33 +4,187 @@ package network.Messages; import network.Messages.Enums.MessageType; /** - * Created by fwy13 on 25/04/17. + * Represents an AverageWind message in the streaming API (see section 4.12). */ public class AverageWind extends AC35Data { - private int msgNum; - private long lngTime; - private int rawPeriod; - private int rawSpeed; - private int period2; - private int speed2; - private int period3; - private int speed3; - private int period4; - private int speed4; - - public AverageWind(int msgNum, long lngTime, int rawPeriod, int rawSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ + /** + * The current version number for this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number for this message. + */ + private byte messageVersionNumber; + + /** + * Timestamp for when the measurement was taken. Milliseconds since unix epoch. + */ + private long time; + + /** + * Raw sample rate period. Milliseconds. + */ + private long rawPeriod; + + /** + * Raw wind speed, in knots. + */ + private double rawSpeedKnots; + + /** + * Wind speed average period for second sample. Milliseconds. + */ + private long sampleTwoPeriod; + + /** + * Wind speed of second sample. Knots. + */ + private double sampleTwoSpeedKnots; + + /** + * Wind speed average period for third sample. Milliseconds. + */ + private long sampleThreePeriod; + + /** + * Wind speed of third sample. Knots. + */ + private double sampleThreeSpeedKnots; + + /** + * Wind speed average period for fourth sample. Milliseconds. + */ + private long sampleFourPeriod; + + /** + * Wind speed of fourth sample. Knots. + */ + private double sampleFourSpeedKnots; + + + /** + * Creates an AverageWind message, with the given parameters. + * @param messageVersionNumber The version number of message. + * @param time The timestamp of the message. + * @param rawPeriod The period of the raw measurement. Milliseconds. + * @param rawSpeedKnots The speed of the raw measurement. Knots. + * @param sampleTwoPeriod The period of the second measurement. Milliseconds. + * @param sampleTwoSpeedKnots The speed of the second measurement. Knots. + * @param sampleThreePeriod The period of the third measurement. Milliseconds. + * @param sampleThreeSpeedKnots The speed of the third measurement. Knots. + * @param sampleFourPeriod The period of the fourth measurement. Milliseconds. + * @param sampleFourSpeedKnots The speed of the fourth measurement. Knots. + */ + public AverageWind( + byte messageVersionNumber, + long time, + long rawPeriod, + double rawSpeedKnots, + long sampleTwoPeriod, + double sampleTwoSpeedKnots, + long sampleThreePeriod, + double sampleThreeSpeedKnots, + long sampleFourPeriod, + double sampleFourSpeedKnots ) { + super(MessageType.AVGWIND); - this.msgNum = msgNum; - this.lngTime = lngTime; + + this.messageVersionNumber = messageVersionNumber; + this.time = time; this.rawPeriod = rawPeriod; - this.rawSpeed = rawSpeed; - this.period2 = period2; - this.speed2 = speed2; - this.period3 = period3; - this.speed3 = speed3; - this.period4 = period4; - this.speed4 = speed4; + this.rawSpeedKnots = rawSpeedKnots; + this.sampleTwoPeriod = sampleTwoPeriod; + this.sampleTwoSpeedKnots = sampleTwoSpeedKnots; + this.sampleThreePeriod = sampleThreePeriod; + this.sampleThreeSpeedKnots = sampleThreeSpeedKnots; + this.sampleFourPeriod = sampleFourPeriod; + this.sampleFourSpeedKnots = sampleFourSpeedKnots; + } + + + /** + * Returns the version number of this message. + * @return Message version number. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Returns the timestamp of this message. + * @return Timestamp of this message. Milliseconds since unix epoch. + */ + public long getTime() { + return time; + } + + /** + * Returns the period of time over which the raw sample was done. Milliseconds. + * @return Raw sample's period. + */ + public long getRawPeriod() { + return rawPeriod; + } + + /** + * Returns the wind speed of the raw sample. Knots. + * @return Wind speed of raw sample. Knots + */ + public double getRawSpeedKnots() { + return rawSpeedKnots; } + /** + * Returns the period of time over which the second sample was done. Milliseconds. + * @return Second sample's period. + */ + public long getSampleTwoPeriod() { + return sampleTwoPeriod; + } + + /** + * Returns the wind speed of the second sample. Knots. + * @return Wind speed of second sample. Knots + */ + public double getSampleTwoSpeedKnots() { + return sampleTwoSpeedKnots; + } + + /** + * Returns the period of time over which the third sample was done. Milliseconds. + * @return Third sample's period. + */ + public long getSampleThreePeriod() { + return sampleThreePeriod; + } + + /** + * Returns the wind speed of the third sample. Knots. + * @return Wind speed of third sample. Knots + */ + public double getSampleThreeSpeedKnots() { + return sampleThreeSpeedKnots; + } + + /** + * Returns the period of time over which the fourth sample was done. Milliseconds. + * @return Fourth sample's period. + */ + public long getSampleFourPeriod() { + return sampleFourPeriod; + } + + /** + * Returns the wind speed of the fourth sample. Knots. + * @return Wind speed of fourth sample. Knots + */ + public double getSampleFourSpeedKnots() { + return sampleFourSpeedKnots; + } + + + } diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 6d894ab6..40b87628 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -85,4 +85,23 @@ public class AC35UnitConverter { } + /** + * Packs an average wind period, in milliseconds, into an int, in tenths of a second. + * @param periodMilliseconds Period in milliseconds. + * @return Period in tenths of a second. + */ + public static int packAverageWindPeriod(long periodMilliseconds) { + return (int) (periodMilliseconds / 100); + } + + /** + * Unpacks an average wind period, in tenths of a second, into an long, in milliseconds. + * @param periodTenthsOfSecond Period in tenths of a second. + * @return Period in milliseconds + */ + public static long unpackAverageWindPeriod(int periodTenthsOfSecond) { + return (periodTenthsOfSecond * 100); + } + + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java new file mode 100644 index 00000000..001c54eb --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java @@ -0,0 +1,75 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AverageWind; +import static org.junit.Assert.*; +import org.junit.Test; +import shared.model.Bearing; + +import java.util.ArrayList; + +/** + * Test for the AverageWind encoder and decoder + */ +public class AverageWindDecoderTest { + + + /** + * Creates a AverageWind message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void averageWindEncodeDecodeTest() throws Exception { + + AverageWind averageWind = new AverageWind( + AverageWind.currentMessageVersionNumber, + System.currentTimeMillis(), + 3000, + 12.5, + 4050, + 12.6, + 3055, + 12.7, + 6051, + 13.37 + ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(averageWind); + + AverageWindDecoder averageWindDecoder = new AverageWindDecoder(); + averageWindDecoder.decode(encodedMessage); + AverageWind averageWindDecoded = averageWindDecoder.getMessage(); + + compareAverageWindMessages(averageWind, averageWindDecoded); + + } + + + /** + * Compares two AverageWind messages to check that they are equal. + * @param original The original AverageWind message. + * @param decoded The decoded AverageWind message. + */ + public static void compareAverageWindMessages(AverageWind original, AverageWind decoded) { + + + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getTime(), decoded.getTime()); + + assertEquals(original.getRawPeriod(), decoded.getRawPeriod(), 100); + assertEquals(original.getRawSpeedKnots(), decoded.getRawSpeedKnots(), 0.01); + + assertEquals(original.getSampleTwoPeriod(), decoded.getSampleTwoPeriod(), 100); + assertEquals(original.getSampleTwoSpeedKnots(), decoded.getSampleTwoSpeedKnots(), 0.01); + + assertEquals(original.getSampleThreePeriod(), decoded.getSampleThreePeriod(), 100); + assertEquals(original.getSampleThreeSpeedKnots(), decoded.getSampleThreeSpeedKnots(), 0.01); + + assertEquals(original.getSampleFourPeriod(), decoded.getSampleFourPeriod(), 100); + assertEquals(original.getSampleFourSpeedKnots(), decoded.getSampleFourSpeedKnots(), 0.01); + + + } + + +} From 9c64b678e315cddb82b5eeb8134f5c03c85b9268 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 21:38:12 +1200 Subject: [PATCH 20/59] Added MarkRoundingEncoder. Refactored MarkRoundingDecoder - it now implements MessageDecoder. Tidied up MarkRounding - it is now documented and has getters. Also Created MarkRoundingBoatStatusEnum, MarkRoundingIDEnum, MarkRoundingSideEnum, MarkRoundingTypeEnum. Added MarkRoundingDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/DecoderFactory.java | 2 +- .../MessageDecoders/MarkRoundingDecoder.java | 114 ++++++++---- .../MessageEncoders/EncoderFactory.java | 2 +- .../MessageEncoders/MarkRoundingEncoder.java | 57 ++++++ .../RaceVisionByteEncoder.java | 24 --- .../Enums/MarkRoundingBoatStatusEnum.java | 88 +++++++++ .../Messages/Enums/MarkRoundingIDEnum.java | 88 +++++++++ .../Messages/Enums/MarkRoundingSideEnum.java | 82 +++++++++ .../Messages/Enums/MarkRoundingTypeEnum.java | 82 +++++++++ .../java/network/Messages/MarkRounding.java | 173 ++++++++++++++---- .../MarkRoundingDecoderTest.java | 69 +++++++ 12 files changed, 685 insertions(+), 100 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 71765e12..9ae2ba47 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -189,8 +189,8 @@ public class BinaryMessageDecoder { case MARKROUNDING: //System.out.println("Mark Rounding Message!"); - MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(messageBody); - return mrDecoder.getMarkRounding(); + MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(); + return mrDecoder.decode(messageBody); case COURSEWIND: //System.out.println("Course Wind Message!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 9b04803c..09ea4b95 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -47,7 +47,7 @@ public class DecoderFactory { case BOATLOCATION: return new BoatLocationDecoder(); - //case MARKROUNDING: return new MarkRoundingDecoder()//TODO; + case MARKROUNDING: return new MarkRoundingDecoder(); case COURSEWIND: return new CourseWindsDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java index 000e86ee..58ad8f64 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java @@ -1,52 +1,90 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; import network.Messages.MarkRounding; import network.Utils.ByteConverter; import java.util.Arrays; /** - * Created by hba56 on 23/04/17. + * Decoder for {@link MarkRounding} messages. */ -public class MarkRoundingDecoder { - byte messageVersionNumber; - byte[] byteTime; - byte[] byteAck; - byte[] byteRaceID; - byte[] byteSourceID; - byte byteBoatStatus; - byte byteRoundingSide; - byte byteMarkType; - byte byteMarkID; - - MarkRounding markRounding; - - public MarkRoundingDecoder(byte[] encodedMarkRounding) { - messageVersionNumber = encodedMarkRounding[0]; - byteTime = Arrays.copyOfRange(encodedMarkRounding, 1, 7); - byteAck = Arrays.copyOfRange(encodedMarkRounding, 7, 9); - byteRaceID = Arrays.copyOfRange(encodedMarkRounding, 9, 13); - byteSourceID = Arrays.copyOfRange(encodedMarkRounding, 13, 17); - byteBoatStatus = encodedMarkRounding[17]; - byteRoundingSide = encodedMarkRounding[18]; - byteMarkType = encodedMarkRounding[19]; - byteMarkID = encodedMarkRounding[20]; - - int intMsgVer = ByteConverter.bytesToInt(messageVersionNumber); - long lngTime = ByteConverter.bytesToLong(byteTime); - int intAck = ByteConverter.bytesToInt(byteAck); - int intRaceID = ByteConverter.bytesToInt(byteRaceID); - int intSourceID = ByteConverter.bytesToInt(byteSourceID); - int intBoatState = ByteConverter.bytesToInt(byteBoatStatus); - int intRoundingSide = ByteConverter.bytesToInt(byteRoundingSide); - int intMarkType = ByteConverter.bytesToInt(byteMarkType); - int intMarkID = ByteConverter.bytesToInt(byteMarkID); - - markRounding = new MarkRounding(intMsgVer, lngTime, intAck, intRaceID, intSourceID, intBoatState, intRoundingSide, intMarkType, intMarkID); +public class MarkRoundingDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private MarkRounding message; + + + /** + * Constructs a decoder to decode a given message. + */ + public MarkRoundingDecoder() { } - public MarkRounding getMarkRounding() { - return markRounding; + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + byte messageVersionNumber = encodedMessage[0]; + + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); + + byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); + int ackNumber = ByteConverter.bytesToInt(byteAck); + + byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); + int raceID = ByteConverter.bytesToInt(byteRaceID); + + byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); + int sourceID = ByteConverter.bytesToInt(byteSourceID); + + byte byteBoatStatus = encodedMessage[17]; + MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); + + byte byteRoundingSide = encodedMessage[18]; + MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); + + byte byteMarkType = encodedMessage[19]; + MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + + byte byteMarkID = encodedMessage[20]; + + + message = new MarkRounding( + messageVersionNumber, + time, + ackNumber, + raceID, + sourceID, + boatStatus, + roundingSide, + markType, + byteMarkID); + + + return message; + } + + /** + * Returns the decoded message. + * + * @return The decoded message. + */ + public MarkRounding getMessage() { + return message; } + } + diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index e023ffb7..b59150e4 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -47,7 +47,7 @@ public class EncoderFactory { case BOATLOCATION: return new BoatLocationEncoder(); - //case MARKROUNDING: return new MarkRoundingEncoder();//TODO + case MARKROUNDING: return new MarkRoundingEncoder(); case COURSEWIND: return new CourseWindsEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java new file mode 100644 index 00000000..de86691a --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java @@ -0,0 +1,57 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.MarkRounding; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link MarkRounding} message. + */ +public class MarkRoundingEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public MarkRoundingEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + MarkRounding markRounding = (MarkRounding) message; + + byte messageVersionNumber = markRounding.getMessageVersionNumber(); + byte[] byteTime = longToBytes(markRounding.getTime(), 6); + byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); + byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); + byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); + byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); + byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); + byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); + byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); + + + ByteBuffer result = ByteBuffer.allocate(21); + + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteAck); + result.put(byteRaceID); + result.put(byteSourceID); + result.put(byteBoatStatus); + result.put(byteRoundingSide); + result.put(byteMarkType); + result.put(byteMarkID); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 154e25e4..b508030f 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -100,30 +100,6 @@ public class RaceVisionByteEncoder { } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ - int messageVersionNumber = 0b1; - byte[] byteTime = longToBytes(time, 6); - byte[] byteAck = intToBytes(ackNumber, 2); - byte[] byteRaceID = intToBytes(raceID, 4); - byte[] byteSourceID = intToBytes(sourceID, 4); - byte[] byteBoatStatus = intToBytes(boatStatus, 1); - byte[] byteRoundingSide = intToBytes(roundingSide, 1); - byte[] byteMarkType = intToBytes(markType, 1); - byte[] byteMarkID = intToBytes(markID, 1); - - ByteBuffer result = ByteBuffer.allocate(21); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteTime); - result.put(byteAck); - result.put(byteRaceID); - result.put(byteSourceID); - result.put(byteBoatStatus); - result.put(byteRoundingSide); - result.put(byteMarkType); - result.put(byteMarkID); - return result.array(); - } - /** diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java new file mode 100644 index 00000000..692dba42 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java @@ -0,0 +1,88 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various statuses a boat can have when rounding a mark. + */ +public enum MarkRoundingBoatStatusEnum { + + UNKNOWN(0), + + /** + * The boat is actively racing. + */ + RACING(1), + + /** + * The boat has been disqualified. + */ + DSQ(2), + + /** + * The boat has withdrawn from the race. + */ + WITHDRAWN(3), + + NOT_A_STATUS(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingBoatStatusEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingBoatStatusEnum(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 MarkRoundingBoatStatusEnum values. + private static final Map byteToStatusMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToStatusMap. + */ + static { + for (MarkRoundingBoatStatusEnum type : MarkRoundingBoatStatusEnum.values()) { + byteToStatusMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param boatStatusByte Byte value to convert to a {@link MarkRoundingBoatStatusEnum} value. + * @return The {@link MarkRoundingBoatStatusEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingBoatStatusEnum fromByte(byte boatStatusByte) { + //Gets the corresponding MarkRoundingBoatStatusEnum from the map. + MarkRoundingBoatStatusEnum type = byteToStatusMap.get(boatStatusByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_STATUS MarkRoundingBoatStatusEnum. + return MarkRoundingBoatStatusEnum.NOT_A_STATUS; + } + else { + //Otherwise, return the MarkRoundingBoatStatusEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java new file mode 100644 index 00000000..69b0756c --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java @@ -0,0 +1,88 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various mark identities. + */ +public enum MarkRoundingIDEnum { + + UNKNOWN(0), + + + ENTRY_LIMIT_LINE(100), + + ENTRY_LINE(101), + + START_LINE(102), + + FINISH_LINE(103), + + SPEED_TEST_START(104), + + SPEED_TEST_FINISH(105), + + CLEAR_START(106), + + NOT_AN_ID(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingIDEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingIDEnum(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 MarkRoundingIDEnum values. + private static final Map byteToIDMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToIDMap. + */ + static { + for (MarkRoundingIDEnum type : MarkRoundingIDEnum.values()) { + byteToIDMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingIDEnum} value. + * @return The {@link MarkRoundingIDEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingIDEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingIDEnum from the map. + MarkRoundingIDEnum type = byteToIDMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_AN_ID MarkRoundingIDEnum. + return MarkRoundingIDEnum.NOT_AN_ID; + } + else { + //Otherwise, return the MarkRoundingIDEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java new file mode 100644 index 00000000..83aa75be --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java @@ -0,0 +1,82 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various sides around which a boat may pass a mark. + */ +public enum MarkRoundingSideEnum { + + UNKNOWN(0), + + /** + * Boat rounded around port side of mark. + */ + PORT(1), + + /** + * Boat rounded around starboard side of mark. + */ + STARBOARD(2), + + NOT_A_SIDE(-1); + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingSideEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingSideEnum(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 MarkRoundingSideEnum values. + private static final Map byteToSideMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToSideMap. + */ + static { + for (MarkRoundingSideEnum type : MarkRoundingSideEnum.values()) { + byteToSideMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingSideEnum} value. + * @return The {@link MarkRoundingSideEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingSideEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingSideEnum from the map. + MarkRoundingSideEnum type = byteToSideMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_SIDE MarkRoundingSideEnum. + return MarkRoundingSideEnum.NOT_A_SIDE; + } + else { + //Otherwise, return the MarkRoundingSideEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java new file mode 100644 index 00000000..559c17e9 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java @@ -0,0 +1,82 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types of marks that may be passed. + */ +public enum MarkRoundingTypeEnum { + + UNKNOWN(0), + + /** + * The mark is a singular mark. + */ + MARK(1), + + /** + * The mark is a gate (windward, leeward, start, finish, etc...). + */ + GATE(2), + + NOT_A_TYPE(-1); + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingTypeEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingTypeEnum(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 MarkRoundingTypeEnum values. + private static final Map byteToTypeMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToTypeMap. + */ + static { + for (MarkRoundingTypeEnum type : MarkRoundingTypeEnum.values()) { + byteToTypeMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingTypeEnum} value. + * @return The {@link MarkRoundingTypeEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingTypeEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingTypeEnum from the map. + MarkRoundingTypeEnum type = byteToTypeMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_TYPE MarkRoundingTypeEnum. + return MarkRoundingTypeEnum.NOT_A_TYPE; + } + else { + //Otherwise, return the MarkRoundingTypeEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/MarkRounding.java b/racevisionGame/src/main/java/network/Messages/MarkRounding.java index a13f0ba7..eae2daed 100644 --- a/racevisionGame/src/main/java/network/Messages/MarkRounding.java +++ b/racevisionGame/src/main/java/network/Messages/MarkRounding.java @@ -1,47 +1,94 @@ package network.Messages; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; import network.Messages.Enums.MessageType; /** - * Created by fwy13 on 25/04/17. + * Represents a MarkRound message (see AC35 spec, 4.10). */ public class MarkRounding extends AC35Data { - private int msgVerNum; + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * Version number of the message. + */ + private byte messageVersionNumber; + + /** + * The time at which the mark was rounding. Milliseconds since unix epoch. + */ private long time; + + /** + * The ack number of the message. + */ private int ackNum; + + /** + * The raceID this message relates to. + */ private int raceID; + + /** + * The sourceID of the boat this message relates to. + */ private int sourceID; - private int boatStatus; - private int roundingSide; - private int markType; - private int markID; - - public static int BoatStatusUnknown = 0; - public static int BoatStatusRacing = 1; - public static int BoatStatusDSQ = 2; - public static int BoatStatusWithdrawn = 3; - - public static int RoundingSideUnknown = 0; - public static int RoundingSidePort = 1; - public static int RoundingSideStarboard = 2; - - public static int MarkTypeUnknown = 0; - public static int MarkTypeRoundingMark = 1; - public static int MarkTypeGate = 2; - - public static int MarkIDEntryLimitLine = 100; - public static int MarkIDEntryLine = 101; - public static int MarkIDRaceStartStartline = 102; - public static int MarkIDRaceFinishline = 103; - public static int MarkIDSpeedTestStart = 104; - public static int MarkIDSpeedTestFinish = 105; - public static int MarkIDClearStart = 106; - - public MarkRounding(int msgVerNum, long time, int ackNum, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ + + /** + * The status of the boat. + */ + private MarkRoundingBoatStatusEnum boatStatus; + + /** + * The side around which the boat rounded. + */ + private MarkRoundingSideEnum roundingSide; + + /** + * The type of mark that was rounded. + */ + private MarkRoundingTypeEnum markType; + + /** + * The ID of the mark. This is not a source ID. + */ + private byte markID; + + + /** + * Creates a MarkRounding message with the given parameters. + * @param messageVersionNumber The version number of the message. + * @param time The time at which the message was created. + * @param ackNum The ack number of the message. + * @param raceID The raceID this message relates to. + * @param sourceID The sourceID of the boat this message relates to. + * @param boatStatus The status of the boat as it rounded the mark. + * @param roundingSide The side around which the boat rounded. + * @param markType The type of mark that was rounded. + * @param markID The ID number of the mark. Not a sourceID. See {@link network.Messages.Enums.MarkRoundingIDEnum}. + */ + public MarkRounding( + byte messageVersionNumber, + long time, + int ackNum, + int raceID, + int sourceID, + MarkRoundingBoatStatusEnum boatStatus, + MarkRoundingSideEnum roundingSide, + MarkRoundingTypeEnum markType, + byte markID ) { + super(MessageType.MARKROUNDING); - this.msgVerNum = msgVerNum; + + this.messageVersionNumber = messageVersionNumber; this.time = time; this.ackNum = ackNum; this.raceID = raceID; @@ -52,6 +99,40 @@ public class MarkRounding extends AC35Data { this.markID = markID; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + + /** + * Returns the timestamp for this message. + * @return Timestamp for this message. + */ + public long getTime() { + return time; + } + + /** + * Returns the ack number of this message. + * @return Ack number of this message. + */ + public int getAckNum() { + return ackNum; + } + + /** + * Returns the raceID this message relates to. + * @return RaceID this message relates to. + */ + public int getRaceID() { + return raceID; + } + /** * Returns the boat (source) ID for this message. * @return Boat ID for this message. @@ -61,10 +142,34 @@ public class MarkRounding extends AC35Data { } /** - * Returns the timestamp for this message. - * @return Timestamp for this message. + * Returns the status of the boat as it rounded the mark. + * @return Status of boat as it rounded mark. */ - public long getTime() { - return time; + public MarkRoundingBoatStatusEnum getBoatStatus() { + return boatStatus; + } + + /** + * Returns the side to which the boat rounded the mark. + * @return Side to which boat rounded mark. + */ + public MarkRoundingSideEnum getRoundingSide() { + return roundingSide; + } + + /** + * Returns the type of mark that was rounded. + * @return The type of mark that was rounded. + */ + public MarkRoundingTypeEnum getMarkType() { + return markType; + } + + /** + * Returns ID number of the mark. This is not a source ID. See {@link network.Messages.Enums.MarkRoundingIDEnum}. + * @return ID number of the mark. + */ + public byte getMarkID() { + return markID; } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java new file mode 100644 index 00000000..3fce2176 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java @@ -0,0 +1,69 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; +import network.Messages.MarkRounding; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test for the MarkRounding encoder and decoder + */ +public class MarkRoundingDecoderTest { + + + /** + * Creates a MarkRounding message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void markRoundingEncodeDecodeTest() throws Exception { + + MarkRounding markRounding = new MarkRounding( + MarkRounding.currentMessageVersionNumber, + System.currentTimeMillis(), + 567, + 42, + 125, + MarkRoundingBoatStatusEnum.RACING, + MarkRoundingSideEnum.PORT, + MarkRoundingTypeEnum.MARK, + (byte)45 + ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(markRounding); + + MarkRoundingDecoder markRoundingDecoder = new MarkRoundingDecoder(); + markRoundingDecoder.decode(encodedMessage); + MarkRounding markRoundingDecoded = markRoundingDecoder.getMessage(); + + compareMarkRoundingMessages(markRounding, markRoundingDecoded); + + } + + + /** + * Compares two MarkRounding messages to check that they are equal. + * @param original The original MarkRounding message. + * @param decoded The decoded MarkRounding message. + */ + public static void compareMarkRoundingMessages(MarkRounding original, MarkRounding decoded) { + + + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getTime(), decoded.getTime()); + assertEquals(original.getAckNum(), decoded.getAckNum()); + assertEquals(original.getRaceID(), decoded.getRaceID()); + assertEquals(original.getSourceID(), decoded.getSourceID()); + assertEquals(original.getBoatStatus(), decoded.getBoatStatus()); + assertEquals(original.getRoundingSide(), decoded.getRoundingSide()); + assertEquals(original.getMarkType(), decoded.getMarkType()); + assertEquals(original.getMarkID(), decoded.getMarkID()); + + } + + +} From 134586f4070a0853a80d7274649524ce8624ae60 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 22:25:43 +1200 Subject: [PATCH 21/59] The message encoders and decoders now catch exceptions, and throw InvalidMessageException. Removed the big switch statement from BinaryMessageDecoder - it now uses the decoder factory instead. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 149 +++++------- .../MessageDecoders/AverageWindDecoder.java | 86 +++---- .../MessageDecoders/BoatActionDecoder.java | 14 +- .../MessageDecoders/BoatLocationDecoder.java | 219 +++++++++--------- .../MessageDecoders/BoatStatusDecoder.java | 9 +- .../MessageDecoders/CourseWindDecoder.java | 65 +++--- .../MessageDecoders/CourseWindsDecoder.java | 48 ++-- .../MessageDecoders/HeartBeatDecoder.java | 13 +- .../JoinAcceptanceDecoder.java | 28 ++- .../MessageDecoders/MarkRoundingDecoder.java | 63 ++--- .../MessageDecoders/MessageDecoder.java | 4 +- .../RaceStartStatusDecoder.java | 45 ++-- .../MessageDecoders/RaceStatusDecoder.java | 90 +++---- .../MessageDecoders/RequestToJoinDecoder.java | 23 +- .../MessageDecoders/XMLMessageDecoder.java | 53 +++-- .../MessageEncoders/AverageWindEncoder.java | 90 +++---- .../MessageEncoders/BoatActionEncoder.java | 23 +- .../MessageEncoders/BoatLocationEncoder.java | 117 +++++----- .../MessageEncoders/BoatStatusEncoder.java | 59 +++-- .../MessageEncoders/CourseWindEncoder.java | 58 +++-- .../MessageEncoders/CourseWindsEncoder.java | 39 ++-- .../MessageEncoders/HeartBeatEncoder.java | 23 +- .../JoinAcceptanceEncoder.java | 29 ++- .../MessageEncoders/MarkRoundingEncoder.java | 65 +++--- .../MessageEncoders/MessageEncoder.java | 4 +- .../RaceStartStatusEncoder.java | 41 ++-- .../MessageEncoders/RaceStatusEncoder.java | 89 +++---- .../RaceVisionByteEncoder.java | 2 + .../MessageEncoders/RequestToJoinEncoder.java | 21 +- .../MessageEncoders/XMLMessageEncoder.java | 51 ++-- .../gameController/ControllerClient.java | 15 +- .../gameController/ControllerServer.java | 19 +- .../BoatStatusDecoderTest.java | 4 +- .../CourseWindDecoderTest.java | 4 +- 34 files changed, 911 insertions(+), 751 deletions(-) diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 9ae2ba47..7bd9c233 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -17,42 +17,71 @@ import java.util.zip.CRC32; */ public class BinaryMessageDecoder { - ///Length of the header. + /** + * Length of the header. + */ private static final int headerLength = 15; - ///Length of the CRC. - private static final int CRCLength = 4;//TODO these should probably be static defined somewhere else to be shared. + /** + * Length of the CRC. + */ + private static final int CRCLength = 4; - ///The value the first sync byte should have. + /** + * The value the first sync byte should have. + */ private static final byte syncByte1 = (byte) 0x47; - //The value the second sync byte should have. + /** + * The value the second sync byte should have. + */ private static final byte syncByte2 = (byte) 0x83; - ///The full message. + /** + * The full message. + */ private byte[] fullMessage; - ///The messageHeader. + /** + * The messageHeader. + */ private byte[] messageHeader; - ///The messageBody. + /** + * The messageBody. + */ private byte[] messageBody; - ///The sync bytes from the header.. + /** + * The sync bytes from the header. + */ private byte headerSync1; private byte headerSync2; - ///The message type from the header. + /** + * The message type from the header. + */ private byte headerMessageType; - ///The timestamp from the header. + /** + * The timestamp from the header. + */ private long headerTimeStamp; - ///The source ID from the header. + /** + * The source ID from the header. + */ private int headerSourceID; - ///The message body length from the header. + /** + * The message body length from the header. + */ private int messageBodyLength; - ///CRC value read from message header. + /** + * CRC value read from message header. + */ private long messageCRCValue; - ///Calculated CRC value from message. + + /** + * Calculated CRC value from message. + */ private long calculatedCRCValue; @@ -114,104 +143,34 @@ public class BinaryMessageDecoder { //Check the message body length. throw new InvalidMessageException("MessageBody length in header does not equal the messageBody length. MessageBody length in header is: " + messageBodyLength + ", should be: " + messageBody.length); - }else if (headerSync1 != syncByte1) { + } else if (headerSync1 != syncByte1) { //Check the first sync byte. throw new InvalidMessageException("Sync byte 1 is wrong. Sync byte is: " + headerSync1 + ", should be: " + syncByte1); - }else if (headerSync2 != syncByte2) { + } else if (headerSync2 != syncByte2) { //Check the second sync byte. throw new InvalidMessageException("Sync byte 2 is wrong. Sync byte is: " + headerSync2 + ", should be: " + syncByte2); - }else if (calculatedCRCValue != messageCRCValue) { + } else if (calculatedCRCValue != messageCRCValue) { //Check the CRC value. throw new InvalidMessageException("CRC value is wrong. The calculated value is: " + calculatedCRCValue + ", should be: " + messageCRCValue); } //Now we create the message object based on what is actually in the message body. - MessageType mType = MessageType.fromByte(headerMessageType); + MessageType messageType = MessageType.fromByte(headerMessageType); - /*MessageDecoder decoder = null; + MessageDecoder decoder = null; try { - decoder = DecoderFactory.create(mType); + decoder = DecoderFactory.create(messageType); + } catch (InvalidMessageTypeException e) { - throw new InvalidMessageException("Could not create decoder for MessageType: " + mType, e); + throw new InvalidMessageException("Could not create decoder for MessageType: " + messageType, e); + } - return decoder.decode(messageBody);*/ - - - switch(mType) { - case HEARTBEAT: - //System.out.println("Decoding HeartBeat Message!"); - HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(); - return heartBeatDecoder.decode(messageBody); - - case RACESTATUS: - //System.out.println("Race Status Message"); - RaceStatusDecoder rsdecoder = new RaceStatusDecoder(); - return rsdecoder.decode(messageBody); - - case DISPLAYTEXTMESSAGE: - //System.out.println("Display Text Message"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode DISPLAYTEXTMESSAGE - no decoder."); - - case XMLMESSAGE: - //System.out.println("XML Message!"); - XMLMessageDecoder xmdecoder = new XMLMessageDecoder(); - return xmdecoder.decode(messageBody); - - case RACESTARTSTATUS: - //System.out.println("Race Start Status Message"); - RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(); - return rssDecoder.decode(messageBody); - - case YACHTEVENTCODE: - //System.out.println("Yacht Action Code!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode YACHTEVENTCODE - no decoder."); - - case YACHTACTIONCODE: - //System.out.println("Yacht Action Code!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode YACHTACTIONCODE - no decoder."); - - case CHATTERTEXT: - //System.out.println("Chatter Text Message!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode CHATTERTEXT - no decoder."); - - case BOATLOCATION: - //System.out.println("Boat Location Message!"); - BoatLocationDecoder blDecoder = new BoatLocationDecoder(); - return blDecoder.decode(messageBody); - - case MARKROUNDING: - //System.out.println("Mark Rounding Message!"); - MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(); - return mrDecoder.decode(messageBody); - - case COURSEWIND: - //System.out.println("Course Wind Message!"); - CourseWindsDecoder cwDecoder = new CourseWindsDecoder(); - return cwDecoder.decode(messageBody); - - case AVGWIND: - //System.out.println("Average Wind Message!"); - AverageWindDecoder awDecoder = new AverageWindDecoder(); - return awDecoder.decode(messageBody); - - case BOATACTION: - BoatActionDecoder baDecoder = new BoatActionDecoder(); - return baDecoder.decode(messageBody); - - default: - //System.out.println("Broken Message!"); - //throw new InvalidMessageException("Broken message! Did not recognise message type: " + headerMessageType + "."); - return null; + return decoder.decode(messageBody); - } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java index 1b0f12d5..139aed13 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.AverageWind; import network.Utils.ByteConverter; @@ -32,64 +33,67 @@ public class AverageWindDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte messageVersionNumber = encodedMessage[0]; + byte messageVersionNumber = encodedMessage[0]; - byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = ByteConverter.bytesToLong(byteTime); + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); - byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); - int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); - long rawPeriod = unpackAverageWindPeriod(intRawPeriod); + byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); + int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); + long rawPeriod = unpackAverageWindPeriod(intRawPeriod); - byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); - int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); - double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); + byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); + int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); + double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); - byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); - int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); - long period2 = unpackAverageWindPeriod(intPeriod2); + byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); + int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); + long period2 = unpackAverageWindPeriod(intPeriod2); - byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); - int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); - double speed2Knots = unpackMMperSecToKnots(intSpeed2); + byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); + int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); + double speed2Knots = unpackMMperSecToKnots(intSpeed2); - byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); - int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); - long period3 = unpackAverageWindPeriod(intPeriod3); + byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); + int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); + long period3 = unpackAverageWindPeriod(intPeriod3); - byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); - int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); - double speed3Knots = unpackMMperSecToKnots(intSpeed3); + byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); + int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); + double speed3Knots = unpackMMperSecToKnots(intSpeed3); - byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); - int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); - long period4 = unpackAverageWindPeriod(intPeriod4); + byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); + int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); + long period4 = unpackAverageWindPeriod(intPeriod4); - byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); - int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); - double speed4Knots = unpackMMperSecToKnots(intSpeed4); + byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); + int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); + double speed4Knots = unpackMMperSecToKnots(intSpeed4); + message = new AverageWind( + messageVersionNumber, + time, + rawPeriod, + rawSpeedKnots, + period2, + speed2Knots, + period3, + speed3Knots, + period4, + speed4Knots); + return message; - message = new AverageWind( - messageVersionNumber, - time, - rawPeriod, - rawSpeedKnots, - period2, - speed2Knots, - period3, - speed3Knots, - period4, - speed4Knots ); - - return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode AverageWind message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java index a3a5d38a..1df1e2f0 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -29,14 +30,19 @@ public class BoatActionDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); + try { + BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); - message = new BoatAction(boatActionEnum); + message = new BoatAction(boatActionEnum); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatAction message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index 1e175588..2965e809 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatLocation; import network.Messages.Enums.BoatLocationDeviceEnum; @@ -39,115 +40,121 @@ public class BoatLocationDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte messageVersionNumber = messageVersionNumberBytes[0]; - - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); - - byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int sourceID = bytesToInt(sourceIDBytes); - - byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); - int seqNum = bytesToInt(seqNumBytes); - - byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); - BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); - - byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); - int numLatitude = bytesToInt(latitudeBytes); - double latitude = unpackGPS(numLatitude); - - byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); - int numLongitude = bytesToInt(longitudeBytes); - double longitude = unpackGPS(numLongitude); - - byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); - int numAltitude = bytesToInt(altitudeBytes); - - byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); - int numHeading = bytesToInt(headingBytes); - Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); - - byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); - short numPitch = bytesToShort(pitchBytes); - - byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); - short numRoll = bytesToShort(rollBytes); - - byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); - int numBoatSpeed = bytesToInt(boatSpeedBytes); - double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); - - byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); - int numCog = bytesToInt(cogBytes); - Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); - - byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); - int numSog = bytesToInt(sogBytes); - double sogKnots = unpackMMperSecToKnots(numSog); - - byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); - int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); - double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); - - byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); - short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); - Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); - - byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); - int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); - double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); - - byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); - short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); - Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); - - byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); - short numTrueWindAngle = bytesToShort(trueWindAngleBytes); - Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); - - byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); - int numCurrentDrift = bytesToInt(currentDriftBytes); - double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); - - byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); - int numCurrentSet = bytesToShort(currentSetBytes); - Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); - - byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); - short numRudderAngle = bytesToShort(rudderAngleBytes); - Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); - - - message = new BoatLocation( - messageVersionNumber, - time, - sourceID, - seqNum, - deviceType, - latitude, - longitude, - numAltitude, - heading, - numPitch, - numRoll, - boatSpeedKnots, - cog, - sogKnots, - apparentWindSpeedKnots, - apparentWindAngle, - trueWindSpeedKnots, - trueWindDirection, - trueWindAngle, - currentDriftKnots, - currentSet, - rudderAngle ); + try { - return message; + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte messageVersionNumber = messageVersionNumberBytes[0]; + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int sourceID = bytesToInt(sourceIDBytes); + + byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); + int seqNum = bytesToInt(seqNumBytes); + + byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); + BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); + + byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitudeBytes); + double latitude = unpackGPS(numLatitude); + + byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitudeBytes); + double longitude = unpackGPS(numLongitude); + + byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitudeBytes); + + byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(headingBytes); + Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); + + byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitchBytes); + + byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(rollBytes); + + byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeedBytes); + double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); + + byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cogBytes); + Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); + + byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sogBytes); + double sogKnots = unpackMMperSecToKnots(numSog); + + byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); + double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); + + byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); + Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); + + byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); + double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); + + byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); + Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); + + byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngleBytes); + Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); + + byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDriftBytes); + double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); + + byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSetBytes); + Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); + + byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngleBytes); + Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); + + + message = new BoatLocation( + messageVersionNumber, + time, + sourceID, + seqNum, + deviceType, + latitude, + longitude, + numAltitude, + heading, + numPitch, + numRoll, + boatSpeedKnots, + cog, + sogKnots, + apparentWindSpeedKnots, + apparentWindAngle, + trueWindSpeedKnots, + trueWindDirection, + trueWindAngle, + currentDriftKnots, + currentSet, + rudderAngle); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatLocation message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index ed2c6cf0..27b88a39 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; @@ -39,10 +40,12 @@ public class BoatStatusDecoder { * Decodes the contained message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public BoatStatus decode(byte[] encodedMessage) { + public BoatStatus decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); int sourceID = bytesToInt(sourceIDBytes); @@ -76,6 +79,10 @@ public class BoatStatusDecoder { return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatStatus message.", e); + } + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 6a26e4ae..4941df10 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.CourseWind; import shared.model.Bearing; @@ -38,49 +39,55 @@ public class CourseWindDecoder { * Decodes the contained message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public CourseWind decode(byte[] encodedMessage) { + public CourseWind decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); + try { - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); + byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); - byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int raceIDInt = bytesToInt(raceIDBytes); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); - int windDirectionInt = bytesToInt(windDirectionBytes); - Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceIDInt = bytesToInt(raceIDBytes); - byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); - int windSpeedInt = bytesToInt(windSpeedBytes); - double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); - byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); - int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); - Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); - byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); - int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); - Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); + int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); + Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); - byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); + byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); + int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); + Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); - message = new CourseWind( - windId[0], - time, - raceIDInt, - windDirection, - windSpeedKnots, - bestUpwindAngle, - bestDownwindAngle, - flags[0] ); + message = new CourseWind( + windId[0], + time, + raceIDInt, + windDirection, + windSpeedKnots, + bestUpwindAngle, + bestDownwindAngle, + flags[0]); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode CourseWind message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java index f478ac6b..d6b7b70e 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.CourseWind; import network.Messages.CourseWinds; @@ -38,41 +39,46 @@ public class CourseWindsDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - //The header is three bytes. - byte messageVersionNumber = encodedMessage[0]; - byte byteWindID = encodedMessage[1]; - byte loopCount = encodedMessage[2]; + try { + //The header is three bytes. + byte messageVersionNumber = encodedMessage[0]; + byte byteWindID = encodedMessage[1]; + byte loopCount = encodedMessage[2]; - //A CourseWind object is 20 bytes. - final int courseWindByteLength = 20; - List loopMessages = new ArrayList(); + //A CourseWind object is 20 bytes. + final int courseWindByteLength = 20; - //The header is 3 bytes, so we need the remaining bytes. - byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); + List loopMessages = new ArrayList(); - for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { + //The header is 3 bytes, so we need the remaining bytes. + byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); - byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); + for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { - CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); - CourseWind courseWind = courseWindDecoder.decode(messageBytes); + byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); - loopMessages.add(courseWind); - } + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWind = courseWindDecoder.decode(messageBytes); + loopMessages.add(courseWind); + } - message = new CourseWinds( - messageVersionNumber, - byteWindID, - loopMessages ); + message = new CourseWinds( + messageVersionNumber, + byteWindID, + loopMessages); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode CourseWinds message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java index 49cdcc5f..bc86244b 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.BoatActionEnum; import network.Messages.HeartBeat; @@ -30,12 +31,18 @@ public class HeartBeatDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - message = new HeartBeat(bytesToLong(encodedMessage)); + try { - return message; + message = new HeartBeat(bytesToLong(encodedMessage)); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode HeartBeat message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java index eaa82c18..5766b47f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; @@ -32,27 +33,32 @@ public class JoinAcceptanceDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - //SourceID is first four bytes. - byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + try { - //Next byte is acceptance type. - byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + //SourceID is first four bytes. + byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + //Next byte is acceptance type. + byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); - //SourceID is an int. - int sourceID = ByteConverter.bytesToInt(sourceIdBytes); + //SourceID is an int. + int sourceID = ByteConverter.bytesToInt(sourceIdBytes); - //Acceptance enum is a byte. - JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); + //Acceptance enum is a byte. + JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); - message = new JoinAcceptance(acceptanceType, sourceID); + message = new JoinAcceptance(acceptanceType, sourceID); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode JoinAcceptance message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java index 58ad8f64..6db46f27 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.MarkRoundingBoatStatusEnum; import network.Messages.Enums.MarkRoundingSideEnum; @@ -33,48 +34,54 @@ public class MarkRoundingDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte messageVersionNumber = encodedMessage[0]; + try { - byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = ByteConverter.bytesToLong(byteTime); + byte messageVersionNumber = encodedMessage[0]; - byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); - int ackNumber = ByteConverter.bytesToInt(byteAck); + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); - byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); - int raceID = ByteConverter.bytesToInt(byteRaceID); + byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); + int ackNumber = ByteConverter.bytesToInt(byteAck); - byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); - int sourceID = ByteConverter.bytesToInt(byteSourceID); + byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); + int raceID = ByteConverter.bytesToInt(byteRaceID); - byte byteBoatStatus = encodedMessage[17]; - MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); + byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); + int sourceID = ByteConverter.bytesToInt(byteSourceID); - byte byteRoundingSide = encodedMessage[18]; - MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); + byte byteBoatStatus = encodedMessage[17]; + MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); - byte byteMarkType = encodedMessage[19]; - MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + byte byteRoundingSide = encodedMessage[18]; + MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); - byte byteMarkID = encodedMessage[20]; + byte byteMarkType = encodedMessage[19]; + MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + byte byteMarkID = encodedMessage[20]; - message = new MarkRounding( - messageVersionNumber, - time, - ackNumber, - raceID, - sourceID, - boatStatus, - roundingSide, - markType, - byteMarkID); + message = new MarkRounding( + messageVersionNumber, + time, + ackNumber, + raceID, + sourceID, + boatStatus, + roundingSide, + markType, + byteMarkID); - return message; + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode AverageWind message.", e); + } } /** diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java index 39b4370f..c20c653f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; @@ -15,7 +16,8 @@ public interface MessageDecoder { * Decodes a given message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public AC35Data decode(byte[] encodedMessage); + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java index 062f1212..2776ea6f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.RaceStartTypeEnum; import network.Messages.RaceStartStatus; @@ -35,38 +36,42 @@ public class RaceStartStatusDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte messageVersion = encodedMessage[0]; + byte messageVersion = encodedMessage[0]; - byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timestamp); + byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timestamp); - byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); - short ack = bytesToShort(ackNumber); + byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); + short ack = bytesToShort(ackNumber); - byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); - long startTime = bytesToLong(raceStartTime); + byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); + long startTime = bytesToLong(raceStartTime); - byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); - int raceID = bytesToInt(raceIdentifier); + byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); + int raceID = bytesToInt(raceIdentifier); - byte notificationType = encodedMessage[19]; + byte notificationType = encodedMessage[19]; + message = new RaceStartStatus( + messageVersion, + time, + ack, + startTime, + raceID, + RaceStartTypeEnum.fromByte(notificationType) + ); - message = new RaceStartStatus( - messageVersion, - time, - ack, - startTime, - raceID, - RaceStartTypeEnum.fromByte(notificationType) - ); + return message; - return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RaceStartStatus message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 15700f49..7e26e105 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; @@ -43,70 +44,75 @@ public class RaceStatusDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte versionNum = versionNumBytes[0]; + byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte versionNum = versionNumBytes[0]; - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int raceID = bytesToInt(raceIDBytes); + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceID = bytesToInt(raceIDBytes); - byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); - RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); + byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); + RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); - byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); - long expectedStart = bytesToLong(expectedStartBytes); + byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); + long expectedStart = bytesToLong(expectedStartBytes); - byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); - int windDirectionInt = bytesToInt(windDirectionBytes); - Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); - byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); - int windSpeedInt = bytesToInt(windSpeedBytes); - double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); - byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); - int numberOfBoats = bytesToInt(numberOfBoatsBytes); + byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); + int numberOfBoats = bytesToInt(numberOfBoatsBytes); - byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); - RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); + byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); + RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); - byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); - List boatStatuses = new ArrayList<>(); + byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); + List boatStatuses = new ArrayList<>(); - //BoatStatus is 20 bytes. - int boatStatusByteLength = 20; + //BoatStatus is 20 bytes. + int boatStatusByteLength = 20; - //Decode each BoatStatus. - for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { + //Decode each BoatStatus. + for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { - byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); - boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); - } + boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); + } - message = new RaceStatus( - versionNum, - time, - raceID, - raceStatus, - expectedStart, - windDirection, - windSpeedKnots, - raceType, - boatStatuses ); + message = new RaceStatus( + versionNum, + time, + raceID, + raceStatus, + expectedStart, + windDirection, + windSpeedKnots, + raceType, + boatStatuses); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RaceStatus message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java index 1abf61a6..59c881d1 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; @@ -31,20 +32,26 @@ public class RequestToJoinDecoder implements MessageDecoder{ @Override - public AC35Data decode(byte[] encodedRequest) { + public AC35Data decode(byte[] encodedRequest) throws InvalidMessageException { this.encodedRequest = encodedRequest; - //Request type is first four bytes. - byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + try { - //Request type is an integral type. - int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); - RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); + //Request type is first four bytes. + byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + //Request type is an integral type. + int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); + RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); - message = new RequestToJoin(requestType); - return message; + message = new RequestToJoin(requestType); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RequestToJoin message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 99c56e28..80a62048 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -35,36 +36,42 @@ public class XMLMessageDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); - byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); - byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); - byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); - byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); - byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); + try { + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); + byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); + byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); + byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); + byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); + byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); - byte messageVersionNumber = messageVersionNumberBytes[0]; - short ackNumber = bytesToShort(ackNumberBytes); - long timeStamp = bytesToLong(timeStampBytes); - XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); - short sequenceNumber = bytesToShort(sequenceNumberBytes); - short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); - String xmlMessage = new String(xmlMessagebytes); + byte messageVersionNumber = messageVersionNumberBytes[0]; + short ackNumber = bytesToShort(ackNumberBytes); + long timeStamp = bytesToLong(timeStampBytes); + XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); + short sequenceNumber = bytesToShort(sequenceNumberBytes); + short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); + String xmlMessage = new String(xmlMessagebytes); - message = new XMLMessage( - messageVersionNumber, - ackNumber, - timeStamp, - xmlMsgSubType, - sequenceNumber, - xmlMessage ); - return message; + message = new XMLMessage( + messageVersionNumber, + ackNumber, + timeStamp, + xmlMsgSubType, + sequenceNumber, + xmlMessage); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode XMLMessage message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java index dc1966fc..b8bce3df 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.AverageWind; @@ -24,63 +25,68 @@ public class AverageWindEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - AverageWind averageWind = (AverageWind) message; + try { + //Downcast. + AverageWind averageWind = (AverageWind) message; - byte messageVersionNumber = averageWind.getMessageVersionNumber(); - long time = averageWind.getTime(); - byte[] byteTime = longToBytes(time,6); + byte messageVersionNumber = averageWind.getMessageVersionNumber(); - long rawPeriod = averageWind.getRawPeriod(); - int rawPeriodInt = packAverageWindPeriod(rawPeriod); - byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); + long time = averageWind.getTime(); + byte[] byteTime = longToBytes(time, 6); - double rawSampleSpeed = averageWind.getRawSpeedKnots(); - int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); - byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); + long rawPeriod = averageWind.getRawPeriod(); + int rawPeriodInt = packAverageWindPeriod(rawPeriod); + byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); - long period2 = averageWind.getSampleTwoPeriod(); - int period2Int = packAverageWindPeriod(period2); - byte[] bytePeriod2 = intToBytes(period2Int, 2); + double rawSampleSpeed = averageWind.getRawSpeedKnots(); + int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); + byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); - double speed2 = averageWind.getSampleTwoSpeedKnots(); - int speed2Int = packKnotsToMMperSec(speed2); - byte[] byteSpeed2 = intToBytes(speed2Int, 2); + long period2 = averageWind.getSampleTwoPeriod(); + int period2Int = packAverageWindPeriod(period2); + byte[] bytePeriod2 = intToBytes(period2Int, 2); - long period3 = averageWind.getSampleThreePeriod(); - int period3Int = packAverageWindPeriod(period3); - byte[] bytePeriod3 = intToBytes(period3Int, 2); + double speed2 = averageWind.getSampleTwoSpeedKnots(); + int speed2Int = packKnotsToMMperSec(speed2); + byte[] byteSpeed2 = intToBytes(speed2Int, 2); - double speed3 = averageWind.getSampleThreeSpeedKnots(); - int speed3Int = packKnotsToMMperSec(speed3); - byte[] byteSpeed3 = intToBytes(speed3Int, 2); + long period3 = averageWind.getSampleThreePeriod(); + int period3Int = packAverageWindPeriod(period3); + byte[] bytePeriod3 = intToBytes(period3Int, 2); - long period4 = averageWind.getSampleFourPeriod(); - int period4Int = packAverageWindPeriod(period4); - byte[] bytePeriod4 = intToBytes(period4Int, 2); + double speed3 = averageWind.getSampleThreeSpeedKnots(); + int speed3Int = packKnotsToMMperSec(speed3); + byte[] byteSpeed3 = intToBytes(speed3Int, 2); - double speed4 = averageWind.getSampleFourSpeedKnots(); - int speed4Int = packKnotsToMMperSec(speed4); - byte[] byteSpeed4 = intToBytes(speed4Int, 2); + long period4 = averageWind.getSampleFourPeriod(); + int period4Int = packAverageWindPeriod(period4); + byte[] bytePeriod4 = intToBytes(period4Int, 2); + double speed4 = averageWind.getSampleFourSpeedKnots(); + int speed4Int = packKnotsToMMperSec(speed4); + byte[] byteSpeed4 = intToBytes(speed4Int, 2); - ByteBuffer result = ByteBuffer.allocate(23); - result.put(messageVersionNumber); - result.put(byteTime); - result.put(byteRawPeriod); - result.put(byteRawSpeed); - result.put(bytePeriod2); - result.put(byteSpeed2); - result.put(bytePeriod3); - result.put(byteSpeed3); - result.put(bytePeriod4); - result.put(byteSpeed4); - return result.array(); + ByteBuffer result = ByteBuffer.allocate(23); + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteRawPeriod); + result.put(byteRawSpeed); + result.put(bytePeriod2); + result.put(byteSpeed2); + result.put(bytePeriod3); + result.put(byteSpeed3); + result.put(bytePeriod4); + result.put(byteSpeed4); + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode AverageWind message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java index 8083487f..31e02386 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; @@ -22,19 +23,25 @@ public class BoatActionEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - BoatAction boatAction = (BoatAction) message; + try { - //Message is 1 byte. - ByteBuffer boatActionMessage = ByteBuffer.allocate(1); + //Downcast. + BoatAction boatAction = (BoatAction) message; - boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); + //Message is 1 byte. + ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - byte [] result = boatActionMessage.array(); + boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); - return result; + byte[] result = boatActionMessage.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatAction message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java index 9c3833ac..b982a592 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatLocation; @@ -24,61 +25,67 @@ public class BoatLocationEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { - - //Downcast. - BoatLocation boatLocation = (BoatLocation) message; - - - int messageVersionNumber = 0b1; - byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); - byte[] time = longToBytes(boatLocation.getTime(), 6); - byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); - byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); - byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); - byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); - byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); - byte[] pitch = intToBytes(boatLocation.getPitch(), 2); - byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); - byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); - byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); - byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); - byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); - byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); - byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); - byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); - byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); - byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); - byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); - - ByteBuffer result = ByteBuffer.allocate(56); - result.put(messageVersionBytes); - result.put(time); - result.put(sourceID); - result.put(seqNum); - result.put(deviceType); - result.put(latitude); - result.put(longitude); - result.put(altitude); - result.put(heading); - result.put(pitch); - result.put(roll); - result.put(boatSpeed); - result.put(cog); - result.put(sog); - result.put(apparentWindSpeed); - result.put(apparentWindAngle); - result.put(trueWindSpeed); - result.put(trueWindDirection); - result.put(trueWindAngle); - result.put(currentDrift); - result.put(currentSet); - result.put(rudderAngle); - - return result.array(); + public byte[] encode(AC35Data message) throws InvalidMessageException { + + try { + + //Downcast. + BoatLocation boatLocation = (BoatLocation) message; + + + int messageVersionNumber = 0b1; + byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); + byte[] time = longToBytes(boatLocation.getTime(), 6); + byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); + byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); + byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); + byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); + byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); + byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); + byte[] pitch = intToBytes(boatLocation.getPitch(), 2); + byte[] roll = intToBytes(boatLocation.getRoll(), 2); + byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); + byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); + byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); + byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); + byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); + byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); + byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); + byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); + byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); + byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); + byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); + + ByteBuffer result = ByteBuffer.allocate(56); + result.put(messageVersionBytes); + result.put(time); + result.put(sourceID); + result.put(seqNum); + result.put(deviceType); + result.put(latitude); + result.put(longitude); + result.put(altitude); + result.put(heading); + result.put(pitch); + result.put(roll); + result.put(boatSpeed); + result.put(cog); + result.put(sog); + result.put(apparentWindSpeed); + result.put(apparentWindAngle); + result.put(trueWindSpeed); + result.put(trueWindDirection); + result.put(trueWindAngle); + result.put(currentDrift); + result.put(currentSet); + result.put(rudderAngle); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatLocation message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java index 3a40cbba..fe7ecb18 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.BoatStatus; import java.nio.ByteBuffer; @@ -25,32 +26,40 @@ public class BoatStatusEncoder { * Encodes a given BoatStatus message. * @param message The message to encode. * @return The encoded message. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(BoatStatus message) { - - //Downcast. - BoatStatus boatStatus = (BoatStatus) message; - - //BoatStatus is 20 bytes. - ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); - - byte[] sourceID = intToBytes(boatStatus.getSourceID()); - byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); - byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); - byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); - byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); - byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); - byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); - - boatStatusBuffer.put(sourceID); - boatStatusBuffer.put(boatStatusBytes); - boatStatusBuffer.put(legNum); - boatStatusBuffer.put(numPenalties); - boatStatusBuffer.put(numPenaltiesServed); - boatStatusBuffer.put(estNextMarkTime); - boatStatusBuffer.put(estFinishTime); - - return boatStatusBuffer.array(); + public byte[] encode(BoatStatus message) throws InvalidMessageException { + + try { + + + //Downcast. + BoatStatus boatStatus = (BoatStatus) message; + + //BoatStatus is 20 bytes. + ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); + + byte[] sourceID = intToBytes(boatStatus.getSourceID()); + byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); + byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); + byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); + byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); + byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); + byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); + + boatStatusBuffer.put(sourceID); + boatStatusBuffer.put(boatStatusBytes); + boatStatusBuffer.put(legNum); + boatStatusBuffer.put(numPenalties); + boatStatusBuffer.put(numPenaltiesServed); + boatStatusBuffer.put(estNextMarkTime); + boatStatusBuffer.put(estFinishTime); + + return boatStatusBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java index b6e407c0..2f5f4897 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.CourseWind; import shared.model.Bearing; @@ -28,46 +29,53 @@ public class CourseWindEncoder { * Encodes a given CourseWind message. * @param message The message to encode. * @return The encoded message. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(CourseWind message) { + public byte[] encode(CourseWind message) throws InvalidMessageException { - CourseWind courseWind = message; + try { + CourseWind courseWind = message; - //CourseWind is 20 bytes. - ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); + //CourseWind is 20 bytes. + ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); - byte[] windId = intToBytes(courseWind.getID(), 1); - byte[] timeBytes = longToBytes(courseWind.getTime(), 6); + byte[] windId = intToBytes(courseWind.getID(), 1); - byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); + byte[] timeBytes = longToBytes(courseWind.getTime(), 6); - int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); - byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); + byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); - int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); - byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); + int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); + byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); - int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); - byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); + int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); + byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); - int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); - byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); + int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); + byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); - byte[] flags = intToBytes(courseWind.getFlags(), 1); + int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); + byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); - courseWindBuffer.put(windId); - courseWindBuffer.put(timeBytes); - courseWindBuffer.put(raceIDBytes); - courseWindBuffer.put(windDirectionBytes); - courseWindBuffer.put(windSpeedBytes); - courseWindBuffer.put(bestUpwindAngleBytes); - courseWindBuffer.put(bestDownwindAngleBytes); - courseWindBuffer.put(flags); + byte[] flags = intToBytes(courseWind.getFlags(), 1); - return courseWindBuffer.array(); + courseWindBuffer.put(windId); + courseWindBuffer.put(timeBytes); + courseWindBuffer.put(raceIDBytes); + courseWindBuffer.put(windDirectionBytes); + courseWindBuffer.put(windSpeedBytes); + courseWindBuffer.put(bestUpwindAngleBytes); + courseWindBuffer.put(bestDownwindAngleBytes); + courseWindBuffer.put(flags); + + return courseWindBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode CourseWind message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java index 193144c3..86bd1197 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; import network.Messages.CourseWind; @@ -24,33 +25,39 @@ public class CourseWindsEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - CourseWinds courseWinds = (CourseWinds) message; + try { + //Downcast. + CourseWinds courseWinds = (CourseWinds) message; - byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; - byte byteWindID = courseWinds.getSelectedWindID(); + byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; - byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); + byte byteWindID = courseWinds.getSelectedWindID(); - ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); + byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); - result.put(messageVersionNumber); - result.put(byteWindID); - result.put(loopcount); + ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); - //Encode each CourseWind. - for (CourseWind wind: courseWinds.getCourseWinds()){ + result.put(messageVersionNumber); + result.put(byteWindID); + result.put(loopcount); - CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); - byte[] encodedCourseWind = courseWindEncoder.encode(wind); + //Encode each CourseWind. + for (CourseWind wind : courseWinds.getCourseWinds()) { - result.put(encodedCourseWind); + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] encodedCourseWind = courseWindEncoder.encode(wind); + + result.put(encodedCourseWind); + } + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode CourseWinds message.", e); } - return result.array(); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java index dcf64b61..ecbefe41 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.HeartBeat; @@ -22,18 +23,24 @@ public class HeartBeatEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - HeartBeat heartbeat = (HeartBeat) message; + try { - //Message is 4 bytes. - ByteBuffer heartBeat = ByteBuffer.allocate(4); - heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); + //Downcast. + HeartBeat heartbeat = (HeartBeat) message; - byte[] result = heartBeat.array(); + //Message is 4 bytes. + ByteBuffer heartBeat = ByteBuffer.allocate(4); + heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); - return result; + byte[] result = heartBeat.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode HeartBeat message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java index 2b23c0dd..8f236454 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.JoinAcceptance; import network.Utils.ByteConverter; @@ -23,22 +24,28 @@ public class JoinAcceptanceEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - JoinAcceptance joinAcceptance = (JoinAcceptance) message; + try { - //Message is 5 bytes. - ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + //Downcast. + JoinAcceptance joinAcceptance = (JoinAcceptance) message; - //Source ID is first four bytes. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); - //Acceptance type is next byte. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); - byte [] result = joinAcceptanceBuffer.array(); + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); - return result; + byte[] result = joinAcceptanceBuffer.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode JoinAcceptance message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java index de86691a..2c30b0cd 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.MarkRounding; @@ -23,35 +24,41 @@ public class MarkRoundingEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { - - //Downcast. - MarkRounding markRounding = (MarkRounding) message; - - byte messageVersionNumber = markRounding.getMessageVersionNumber(); - byte[] byteTime = longToBytes(markRounding.getTime(), 6); - byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); - byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); - byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); - byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); - byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); - byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); - byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); - - - ByteBuffer result = ByteBuffer.allocate(21); - - result.put(messageVersionNumber); - result.put(byteTime); - result.put(byteAck); - result.put(byteRaceID); - result.put(byteSourceID); - result.put(byteBoatStatus); - result.put(byteRoundingSide); - result.put(byteMarkType); - result.put(byteMarkID); - - return result.array(); + public byte[] encode(AC35Data message) throws InvalidMessageException { + + try { + + //Downcast. + MarkRounding markRounding = (MarkRounding) message; + + byte messageVersionNumber = markRounding.getMessageVersionNumber(); + byte[] byteTime = longToBytes(markRounding.getTime(), 6); + byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); + byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); + byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); + byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); + byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); + byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); + byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); + + + ByteBuffer result = ByteBuffer.allocate(21); + + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteAck); + result.put(byteRaceID); + result.put(byteSourceID); + result.put(byteBoatStatus); + result.put(byteRoundingSide); + result.put(byteMarkType); + result.put(byteMarkID); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode MarkRounding message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java index d5ec4cd8..f91bb639 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; @@ -15,7 +16,8 @@ public interface MessageEncoder { * Encodes a given message. * @param message The message to encode. * @return Message in byte encoded form. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(AC35Data message); + public byte[] encode(AC35Data message) throws InvalidMessageException; } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java index f2d41d20..ad9a65f0 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.RaceStartStatus; import network.Utils.ByteConverter; @@ -24,28 +25,34 @@ public class RaceStartStatusEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RaceStartStatus raceStartStatus = (RaceStartStatus) message; + try { + //Downcast. + RaceStartStatus raceStartStatus = (RaceStartStatus) message; - byte messageVersion = raceStartStatus.getMessageVersionNumber(); - byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); - byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); - byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); - byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); - byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); - ByteBuffer result = ByteBuffer.allocate(20); - result.put(messageVersion); - result.put(timestamp); - result.put(ackNumber); - result.put(raceStartTime); - result.put(raceIdentifier); - result.put(notificationType); + byte messageVersion = raceStartStatus.getMessageVersionNumber(); + byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); + byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); + byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); + byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); + byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); - return result.array(); + ByteBuffer result = ByteBuffer.allocate(20); + result.put(messageVersion); + result.put(timestamp); + result.put(ackNumber); + result.put(raceStartTime); + result.put(raceIdentifier); + result.put(notificationType); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RaceStartStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java index fcb06d82..fd87cd0c 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.RaceStatus; @@ -28,68 +29,74 @@ public class RaceStatusEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RaceStatus raceStatus = (RaceStatus) message; + try { + //Downcast. + RaceStatus raceStatus = (RaceStatus) message; - List boatStatuses = raceStatus.getBoatStatuses(); - //24 byte header, plus 20 bytes per boat status. - ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); + List boatStatuses = raceStatus.getBoatStatuses(); - //Version Number 1 bytes. this changes with the pdf. (2) - byte versionNum = 0b10; + //24 byte header, plus 20 bytes per boat status. + ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); - //time (6 bytes) - byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); + //Version Number 1 bytes. this changes with the pdf. (2) + byte versionNum = 0b10; - //race identifier in case multiple races are going at once. - byte[] raceID = intToBytes(raceStatus.getRaceID()); + //time (6 bytes) + byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); - //race status 0 - 10 - byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); + //race identifier in case multiple races are going at once. + byte[] raceID = intToBytes(raceStatus.getRaceID()); - //number of milliseconds from Jan 1, 1970 for when the data is valid - byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); + //race status 0 - 10 + byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); - //North = 0x0000 East = 0x4000 South = 0x8000. - int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); - byte[] raceWind = intToBytes(windDirectionInt, 2); + //number of milliseconds from Jan 1, 1970 for when the data is valid + byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); - //mm/sec - int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); - byte[] windSpeed = intToBytes(windSpeedInt, 2); + //North = 0x0000 East = 0x4000 South = 0x8000. + int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); + byte[] raceWind = intToBytes(windDirectionInt, 2); + //mm/sec + int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); + byte[] windSpeed = intToBytes(windSpeedInt, 2); - byte[] numBoats = intToBytes(boatStatuses.size(), 1); - //1 match race, 2 fleet race - byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); + byte[] numBoats = intToBytes(boatStatuses.size(), 1); + //1 match race, 2 fleet race + byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); - raceStatusMessage.put(versionNum); - raceStatusMessage.put(timeBytes); - raceStatusMessage.put(raceID); - raceStatusMessage.put(raceStatusByte); - raceStatusMessage.put(expectedStart); - raceStatusMessage.put(raceWind); - raceStatusMessage.put(windSpeed); - raceStatusMessage.put(numBoats); - raceStatusMessage.put(bytesRaceType); - //Encode each BoatStatus. - for (BoatStatus boatStatus : boatStatuses) { + raceStatusMessage.put(versionNum); + raceStatusMessage.put(timeBytes); + raceStatusMessage.put(raceID); + raceStatusMessage.put(raceStatusByte); + raceStatusMessage.put(expectedStart); + raceStatusMessage.put(raceWind); + raceStatusMessage.put(windSpeed); + raceStatusMessage.put(numBoats); + raceStatusMessage.put(bytesRaceType); - BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + //Encode each BoatStatus. + for (BoatStatus boatStatus : boatStatuses) { - byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); - raceStatusMessage.put(boatStatusEncoded); - } + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + raceStatusMessage.put(boatStatusEncoded); + } - return raceStatusMessage.array(); + return raceStatusMessage.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RaceStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index b508030f..f82aff38 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -113,8 +113,10 @@ public class RaceVisionByteEncoder { MessageEncoder encoder = null; try { encoder = EncoderFactory.create(message.getType()); + } catch (InvalidMessageTypeException e) { throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e); + } byte[] encodedMessage = encoder.encode(message); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java index b01e92de..e5ef7eee 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.RequestToJoin; import network.Utils.ByteConverter; @@ -21,19 +22,25 @@ public class RequestToJoinEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RequestToJoin requestToJoin = (RequestToJoin) message; + try { + //Downcast. + RequestToJoin requestToJoin = (RequestToJoin) message; - ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - byte [] result = requestToJoinBuffer.array(); + requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); - return result; + byte[] result = requestToJoinBuffer.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RequestToJoin message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java index 92a75ca7..c0039d65 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.XMLMessage; @@ -24,39 +25,45 @@ public class XMLMessageEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - XMLMessage xmlMessage = (XMLMessage) message; + try { + //Downcast. + XMLMessage xmlMessage = (XMLMessage) message; - byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - //Message is 14 + xmlMessage.length bytes. - ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); + byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - //ackNumber converted to bytes - byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); + //Message is 14 + xmlMessage.length bytes. + ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); - //Timestamp converted to bytes. - byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); + //ackNumber converted to bytes + byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); - //sequenceNumber converted to bytes - byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + //Timestamp converted to bytes. + byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); - //xmlMsgLength converted to bytes - byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); + //sequenceNumber converted to bytes + byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + //xmlMsgLength converted to bytes + byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); - tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); - tempOutputByteBuffer.put(ackNumberBytes); - tempOutputByteBuffer.put(timestampBytes); - tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); - tempOutputByteBuffer.put(sequenceNumberBytes); - tempOutputByteBuffer.put(xmlMsgLengthBytes); - tempOutputByteBuffer.put(messageBytes); - return tempOutputByteBuffer.array(); + tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); + tempOutputByteBuffer.put(ackNumberBytes); + tempOutputByteBuffer.put(timestampBytes); + tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); + tempOutputByteBuffer.put(sequenceNumberBytes); + tempOutputByteBuffer.put(xmlMsgLengthBytes); + tempOutputByteBuffer.put(messageBytes); + + return tempOutputByteBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode XMLMessage message.", e); + } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index 6c49cd69..bb91f2a4 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -56,18 +56,21 @@ public class ControllerClient { BoatAction boatAction = new BoatAction(protocolCode); //Encode BoatAction. - byte[] encodedBoatAction = new byte[0]; try { - encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + byte[] encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + + BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, + (short) encodedBoatAction.length, encodedBoatAction); + + System.out.println("Sending out key: " + protocolCode); + outputStream.write(binaryMessage.getFullMessage()); + } catch (InvalidMessageException e) { Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e); + } - BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, - (short) encodedBoatAction.length, encodedBoatAction); - System.out.println("Sending out key: " + protocolCode); - outputStream.write(binaryMessage.getFullMessage()); } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index df54d16d..d4c62d11 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,6 +1,7 @@ package visualiser.gameController; import network.BinaryMessageDecoder; +import network.Exceptions.InvalidMessageException; import network.MessageDecoders.BoatActionDecoder; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -10,6 +11,8 @@ import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Service for dispatching key press data to race from client @@ -46,12 +49,22 @@ public class ControllerServer implements Runnable { byte[] message = new byte[20]; try { if (inputStream.available() > 0) { + inputStream.read(message); + BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(); - boatActionDecoder.decode(encodedMessage.getMessageBody()); - BoatAction boatAction = boatActionDecoder.getMessage(); - System.out.println("Received key: " + boatAction.getBoatAction()); + + try { + boatActionDecoder.decode(encodedMessage.getMessageBody()); + BoatAction boatAction = boatActionDecoder.getMessage(); + System.out.println("Received key: " + boatAction.getBoatAction()); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e); + } + + } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java index 088a85cb..45a2e1db 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.BoatStatusEncoder; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatStatus; @@ -55,8 +56,9 @@ public class BoatStatusDecoderTest { * Encodes and decodes a BoatStatus, and returns it. * @param boatStatus The BoatStatus to encode and decode. * @return The decoded BoatStatus. + * @throws InvalidMessageException Thrown if message cannot be encoded or decoded. */ - private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { + private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) throws InvalidMessageException { BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java index 0a8753db..cef452f2 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.CourseWindEncoder; import network.Messages.BoatStatus; import network.Messages.CourseWind; @@ -43,8 +44,9 @@ public class CourseWindDecoderTest { * Encodes and decodes a CourseWind, and returns it. * @param courseWind The CourseWind to encode and decode. * @return The decoded CourseWind. + * @throws InvalidMessageException Thrown if message cannot be encoded or decoded. */ - private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) { + private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) throws InvalidMessageException { CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); byte[] courseWindEncoded = courseWindEncoder.encode(courseWind); From 634d78ab707c9a13b7ce11e34888a55e8dda96d0 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Mon, 7 Aug 2017 13:42:50 +1200 Subject: [PATCH 22/59] Added names to threads created. MockOutput had a nested while loop, but it wasn't needed. Also tidied some of the error handling in MockOutput. #story[1095] --- .../java/mock/app/ConnectionAcceptor.java | 29 +++- .../src/main/java/mock/app/Event.java | 18 +- .../src/main/java/mock/app/MockOutput.java | 162 +++++++++--------- .../RaceVisionByteEncoder.java | 1 + 4 files changed, 111 insertions(+), 99 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..85548a45 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -51,10 +51,10 @@ public class ConnectionAcceptor implements Runnable { */ public ConnectionAcceptor(LatestMessages latestMessages) throws IOException { - this.latestMessages =latestMessages; + this.latestMessages = latestMessages; this.serverSocket = new ServerSocket(serverPort); CheckClientConnection checkClientConnection = new CheckClientConnection(mockOutputList); - new Thread(checkClientConnection).start(); + new Thread(checkClientConnection, "ConnectionAcceptor()->CheckClientConnection thread").start(); } public String getAddress() throws UnknownHostException { @@ -70,19 +70,30 @@ public class ConnectionAcceptor implements Runnable { */ @Override public void run() { - while(true){//should be connections not filled up + + while(mockOutputList.remainingCapacity() > 0) { + try { + System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE + Socket mockSocket = serverSocket.accept(); + //TODO at this point we need to assign the connection a boat source ID, if they requested to participate. + DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); - new Thread(mockOutput).start(); - new Thread(controllerServer).start(); + ControllerServer controllerServer = new ControllerServer(mockSocket); //TODO probably pass assigned boat source ID into ControllerServer. + + new Thread(mockOutput, "ConnectionAcceptor.run()->MockOutput thread" + mockOutput).start(); + new Thread(controllerServer, "ConnectionAcceptor.run()->ControllerServer thread" + controllerServer).start(); + mockOutputList.add(mockOutput); - System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size())); + + System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size()));//TEMP + } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace();//TODO handle this properly + } } @@ -111,7 +122,7 @@ public class ConnectionAcceptor implements Runnable { double timeSinceLastHeartBeat = System.currentTimeMillis(); while(true) { //System.out.println(mocks.size());//used to see current amount of visualisers connected. - ArrayBlockingQueue m = new ArrayBlockingQueue(16, true, mocks); + ArrayBlockingQueue m = new ArrayBlockingQueue<>(16, true, mocks); for (MockOutput mo : m) { try { mo.sendHeartBeat(); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..b98de4dc 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -35,7 +35,7 @@ public class Event { private Polars boatPolars; - private ConnectionAcceptor mockOutput; + private ConnectionAcceptor connectionAcceptor; private LatestMessages latestMessages; /** @@ -51,7 +51,7 @@ public class Event { this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); this.latestMessages = new LatestMessages(); - this.mockOutput = new ConnectionAcceptor(latestMessages); + this.connectionAcceptor = new ConnectionAcceptor(latestMessages); } catch (IOException e) { e.printStackTrace(); @@ -67,11 +67,11 @@ public class Event { } public String getAddress() throws UnknownHostException { - return mockOutput.getAddress(); + return connectionAcceptor.getAddress(); } public int getPort() { - return mockOutput.getServerPort(); + return connectionAcceptor.getServerPort(); } /** @@ -82,7 +82,7 @@ public class Event { * @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed. */ public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException { - new Thread(mockOutput).start(); + new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start(); sendXMLs(); @@ -94,7 +94,7 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); - new Thread(newRace).start(); + new Thread(newRace, "Event.Start()->RaceLogic thread").start(); } /** @@ -102,11 +102,11 @@ public class Event { */ private void sendXMLs() { - mockOutput.setRegattaXml(regattaXML); + connectionAcceptor.setRegattaXml(regattaXML); - mockOutput.setRaceXml(raceXML); + connectionAcceptor.setRaceXml(raceXML); - mockOutput.setBoatsXml(boatXML); + connectionAcceptor.setBoatsXml(boatXML); } /** diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 328c5e79..87bc9f95 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -54,8 +54,6 @@ public class MockOutput implements Runnable private int heartbeatSequenceNum = 1; - private boolean stop = false; //whether or not hte thread keeps running - /** * Ctor. * @param latestMessages Latests Messages that the Mock is to send out @@ -231,119 +229,124 @@ public class MockOutput implements Runnable */ public void run() { - try { - while (!stop){ - - //Wait until all of the xml files have been set. - if (!this.latestMessages.hasAllXMLMessages()) { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - continue; - } + //Wait until all of the xml files have been set. + while (!this.latestMessages.hasAllXMLMessages()) { - long previousFrameTime = System.currentTimeMillis(); - boolean sentXMLs = false; + try { + Thread.sleep(500); + + } catch (InterruptedException e) { + //If we get interrupted, exit the function. + Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; + + } + } - while(true) { - try { - long currentFrameTime = System.currentTimeMillis(); + long previousFrameTime = System.currentTimeMillis(); + boolean sentXMLs = false; - //This is the time elapsed, in milliseconds, since the last server "frame". - long framePeriod = currentFrameTime - previousFrameTime; + try { + while (!Thread.interrupted()) { - //We only attempt to send packets every X milliseconds. - long minimumFramePeriod = 16; - if (framePeriod >= minimumFramePeriod) { + try { - //Send XML messages. - if (!sentXMLs) { - //Serialise them. + long currentFrameTime = System.currentTimeMillis(); - try { - byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); - byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); - byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); + //This is the time elapsed, in milliseconds, since the last server "frame". + long framePeriod = currentFrameTime - previousFrameTime; - //Send them. - outToVisualiser.write(raceXMLBlob); - outToVisualiser.write(regattaXMLBlob); - outToVisualiser.write(boatsXMLBlob); - sentXMLs = true; + //We only attempt to send packets every X milliseconds. + long minimumFramePeriod = 16; + if (framePeriod >= minimumFramePeriod) { - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); - continue; //Go to next iteration. - } + //Send XML messages. + if (!sentXMLs) { + //Serialise them. + try { + 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; + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); + continue; //Go to next iteration. } - //Sends the RaceStatus message. - if (this.latestMessages.getRaceStatus() != null) { + } - try { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + //Sends the RaceStatus message. + if (this.latestMessages.getRaceStatus() != null) { - this.outToVisualiser.write(raceStatusBlob); + try { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); - } + this.outToVisualiser.write(raceStatusBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); } + } - //Send all of the BoatLocation messages. - for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) { + //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) { + //Get the message. + BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); + if (boatLocation != null) { - try { - //Encode. - byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); + try { + //Encode. + byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); - //Write it. - this.outToVisualiser.write(boatLocationBlob); + //Write it. + this.outToVisualiser.write(boatLocationBlob); - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); - } + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); + } - } } + } - previousFrameTime = currentFrameTime; + 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; - } + } 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. + Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; } - } catch (SocketException e) { - break; } + } catch (SocketException e) { + break; } + } } catch (IOException e) { @@ -351,8 +354,5 @@ public class MockOutput implements Runnable } } - public void stop(){ - stop = true; - } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index f82aff38..54c10272 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -4,6 +4,7 @@ package network.MessageEncoders; import network.Exceptions.InvalidMessageException; import network.Exceptions.InvalidMessageTypeException; import network.Messages.*; +import network.Messages.Enums.MessageType; import static network.Utils.ByteConverter.*; From 03713d3699fd76cc7b64463093c4c7573d6cde9e Mon Sep 17 00:00:00 2001 From: zwu18 Date: Wed, 9 Aug 2017 16:37:29 +1200 Subject: [PATCH 23/59] Added checkPosition into MockRace which was removed. Boat now updates legs again. #Story[1097] --- .../src/main/java/mock/model/MockRace.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 3ac8418e..87c0fa7c 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -346,7 +346,7 @@ public class MockRace extends Race { this.updateEstimatedTime(boat); } - + checkPosition(boat, totalElapsedMilliseconds); } private void newOptimalVMG(MockBoat boat) { @@ -443,6 +443,44 @@ public class MockRace extends Race { } + /** + * Checks if a boat has finished any legs, or has pulled out of race (DNF). + * @param boat The boat to check. + * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. + */ + protected void checkPosition(MockBoat boat, long timeElapsed) { + + //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. + double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + + if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { + //Boat has reached its target marker, and has moved on to a new leg. + + //Calculate how much the boat overshot the marker by. + double overshootMeters = boat.calculateDistanceToNextMarker(); + + //Move boat on to next leg. + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + + //Add overshoot distance into the distance travelled for the next leg. + boat.setDistanceTravelledInLeg(overshootMeters); + + //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. + boat.setTimeSinceTackChange(999999); + + //Check if the boat has finished + if (this.isLastLeg(boat.getCurrentLeg())) { + //Boat has finished. + boat.setTimeFinished(timeElapsed); + boat.setCurrentSpeed(0); + boat.setStatus(BoatStatusEnum.FINISHED); + } + } + + } + + public List getCompoundMarks() { return compoundMarks; } From 7cc39abe5772bf29bc541535d0076eb7c8ce8a6b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 10 Aug 2017 12:13:40 +1200 Subject: [PATCH 24/59] WIP. Probably need to cherry pick stuff out of here. Added ClientConnection and server-side handshake. Added MessageSerialiser and Deserialiser. #story[1095] --- .../java/mock/app/ConnectionAcceptor.java | 100 ++++-- .../src/main/java/mock/app/Event.java | 90 +++-- .../src/main/java/mock/app/MockOutput.java | 307 ++---------------- .../java/mock/enums/ConnectionStateEnum.java | 84 +++++ .../EventConstructionException.java | 24 ++ .../SourceIDAllocationException.java | 24 ++ .../java/mock/model/ClientConnection.java | 243 ++++++++++++++ .../java/mock/model/HeartBeatService.java | 110 +++++++ .../src/main/java/mock/model/MockRace.java | 20 ++ .../src/main/java/mock/model/RaceLogic.java | 39 ++- .../src/main/java/mock/model/RaceServer.java | 76 +++-- .../java/mock/model/SourceIdAllocator.java | 70 ++++ .../commandFactory/CompositeCommand.java | 25 ++ .../MessageControllers/MessageController.java | 9 + .../RaceVisionByteEncoder.java | 28 +- .../network/MessageRouters/MessageRouter.java | 11 + .../java/network/Messages/BoatAction.java | 21 ++ .../java/network/Messages/LatestMessages.java | 26 +- .../java/network/Messages/RaceSnapshot.java | 41 +++ .../StreamRelated/MessageDeserialiser.java | 156 +++++++++ .../StreamRelated/MessageSerialiser.java | 116 +++++++ .../exceptions/BoatNotFoundException.java | 15 + .../shared/exceptions/HandshakeException.java | 24 ++ .../shared/model/RunnableWithFramePeriod.java | 64 ++++ .../Controllers/ConnectionController.java | 29 +- .../Controllers/HostController.java | 16 +- .../Controllers/RaceController.java | 7 +- .../Controllers/StartController.java | 25 +- .../java/visualiser/app/VisualiserInput.java | 306 ++--------------- .../gameController/ControllerClient.java | 40 +-- .../gameController/ControllerServer.java | 81 +++-- .../gameController/Keys/KeyFactory.java | 4 +- .../visualiser/model/ServerConnection.java | 228 +++++++++++++ .../mock/model/SourceIdAllocatorTest.java | 126 +++++++ .../model/commandFactory/WindCommandTest.java | 31 ++ 35 files changed, 1830 insertions(+), 786 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/enums/ConnectionStateEnum.java create mode 100644 racevisionGame/src/main/java/mock/exceptions/EventConstructionException.java create mode 100644 racevisionGame/src/main/java/mock/exceptions/SourceIDAllocationException.java create mode 100644 racevisionGame/src/main/java/mock/model/ClientConnection.java create mode 100644 racevisionGame/src/main/java/mock/model/HeartBeatService.java create mode 100644 racevisionGame/src/main/java/mock/model/SourceIdAllocator.java create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java create mode 100644 racevisionGame/src/main/java/network/MessageControllers/MessageController.java create mode 100644 racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java create mode 100644 racevisionGame/src/main/java/network/Messages/RaceSnapshot.java create mode 100644 racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java create mode 100644 racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java create mode 100644 racevisionGame/src/main/java/shared/exceptions/BoatNotFoundException.java create mode 100644 racevisionGame/src/main/java/shared/exceptions/HandshakeException.java create mode 100644 racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java create mode 100644 racevisionGame/src/main/java/visualiser/model/ServerConnection.java create mode 100644 racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java create mode 100644 racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 85548a45..120da194 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,21 +1,22 @@ package mock.app; +import mock.enums.ConnectionStateEnum; +import mock.model.ClientConnection; +import mock.model.SourceIdAllocator; +import mock.model.commandFactory.CompositeCommand; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; +import network.Messages.RaceSnapshot; import network.Messages.XMLMessage; -import org.mockito.Mock; -import visualiser.gameController.ControllerServer; -import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Connection acceptor for multiple clients @@ -31,10 +32,31 @@ public class ConnectionAcceptor implements Runnable { * Socket used to listen for clients on. */ private ServerSocket serverSocket; - //mock outputs - private ArrayBlockingQueue mockOutputList = new ArrayBlockingQueue<>(16, true); - //latest messages + + + /** + * List of client connections. + */ + private ArrayBlockingQueue clientConnections = new ArrayBlockingQueue<>(16, true); + + /** + * Snapshot of the race. + */ private LatestMessages latestMessages; + + /** + * Collection of commands from clients for race to execute. + */ + private CompositeCommand compositeCommand; + + /** + * Used to allocate source IDs to clients. + */ + private SourceIdAllocator sourceIdAllocator; + + + + //Acknowledgement number for packets private int ackNumber = 0; //race xml sequence number @@ -47,14 +69,20 @@ public class ConnectionAcceptor implements Runnable { /** * Connection Acceptor Constructor * @param latestMessages Latest messages to be sent + * @param compositeCommand Collection of commands for race to execute. + * @param sourceIdAllocator Object used to allocate source IDs for clients. * @throws IOException if a server socket cannot be instantiated. */ - public ConnectionAcceptor(LatestMessages latestMessages) throws IOException { + public ConnectionAcceptor(LatestMessages latestMessages, CompositeCommand compositeCommand, SourceIdAllocator sourceIdAllocator) throws IOException { this.latestMessages = latestMessages; + this.compositeCommand = compositeCommand; + this.sourceIdAllocator = sourceIdAllocator; + this.serverSocket = new ServerSocket(serverPort); - CheckClientConnection checkClientConnection = new CheckClientConnection(mockOutputList); + CheckClientConnection checkClientConnection = new CheckClientConnection(clientConnections); new Thread(checkClientConnection, "ConnectionAcceptor()->CheckClientConnection thread").start(); + } public String getAddress() throws UnknownHostException { @@ -71,28 +99,26 @@ public class ConnectionAcceptor implements Runnable { @Override public void run() { - while(mockOutputList.remainingCapacity() > 0) { + while(clientConnections.remainingCapacity() > 0) { try { System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE Socket mockSocket = serverSocket.accept(); - //TODO at this point we need to assign the connection a boat source ID, if they requested to participate. - DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); - MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); //TODO probably pass assigned boat source ID into ControllerServer. + ClientConnection clientConnection = new ClientConnection(mockSocket, sourceIdAllocator, latestMessages, compositeCommand); + + clientConnections.add(clientConnection); + + new Thread(clientConnection, "ConnectionAcceptor.run()->ClientConnection thread " + clientConnection).start(); - new Thread(mockOutput, "ConnectionAcceptor.run()->MockOutput thread" + mockOutput).start(); - new Thread(controllerServer, "ConnectionAcceptor.run()->ControllerServer thread" + controllerServer).start(); - mockOutputList.add(mockOutput); - System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size()));//TEMP + Logger.getGlobal().log(Level.INFO, String.format("%d number of Visualisers Connected.", clientConnections.size())); } catch (IOException e) { - e.printStackTrace();//TODO handle this properly + Logger.getGlobal().log(Level.WARNING, "Got an IOException while a client was attempting to connect.", e); } @@ -104,14 +130,14 @@ public class ConnectionAcceptor implements Runnable { */ class CheckClientConnection implements Runnable{ - private ArrayBlockingQueue mocks; + private ArrayBlockingQueue connections; /** * Constructor - * @param mocks Mocks "connected" + * @param connections Clients "connected" */ - public CheckClientConnection(ArrayBlockingQueue mocks){ - this.mocks = mocks; + public CheckClientConnection(ArrayBlockingQueue connections){ + this.connections = connections; } /** @@ -119,21 +145,27 @@ public class ConnectionAcceptor implements Runnable { */ @Override public void run() { - double timeSinceLastHeartBeat = System.currentTimeMillis(); + while(true) { - //System.out.println(mocks.size());//used to see current amount of visualisers connected. - ArrayBlockingQueue m = new ArrayBlockingQueue<>(16, true, mocks); - for (MockOutput mo : m) { - try { - mo.sendHeartBeat(); - } catch (IOException e) { - mocks.remove(mo); + //System.out.println(connections.size());//used to see current amount of visualisers connected. + ArrayBlockingQueue clientConnections = new ArrayBlockingQueue<>(16, true, connections); + + for (ClientConnection client : clientConnections) { + if (!client.isAlive()) { + connections.remove(client); + + Logger.getGlobal().log(Level.WARNING, "CheckClientConnection is removing the dead connection: " + client); } } + try { Thread.sleep(100); + } catch (InterruptedException e) { - e.printStackTrace(); + Logger.getGlobal().log(Level.WARNING, "CheckClientConnection was interrupted while sleeping.", e); + Thread.currentThread().interrupt(); + return; + } } } diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b98de4dc..7d7c940b 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -1,10 +1,14 @@ package mock.app; import mock.dataInput.PolarParser; +import mock.exceptions.EventConstructionException; import mock.model.MockRace; import mock.model.Polars; import mock.model.RaceLogic; +import mock.model.SourceIdAllocator; +import mock.model.commandFactory.CompositeCommand; import network.Messages.LatestMessages; +import network.Messages.RaceSnapshot; import shared.dataInput.*; import shared.enums.XMLFileType; import shared.exceptions.InvalidBoatDataException; @@ -19,14 +23,18 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A Race Event, this holds all of the race's information as well as handling the connection to its clients. */ public class Event { - private static Event theEvent = new Event(); + /** + * Contents of the various xml files. + */ private String raceXML; private String regattaXML; private String boatXML; @@ -35,36 +43,75 @@ public class Event { private Polars boatPolars; + /** + * Data sources containing data from the xml files. + */ + RaceDataSource raceDataSource; + BoatDataSource boatDataSource; + RegattaDataSource regattaDataSource; + + private ConnectionAcceptor connectionAcceptor; private LatestMessages latestMessages; + private CompositeCommand compositeCommand; + + /** + * This is used to allocate source IDs. + */ + private SourceIdAllocator sourceIdAllocator; + + + + + /** * Constructs an event, using various XML files. + * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ - private Event() { + public Event() throws EventConstructionException { + + //Read XML files. try { this.raceXML = getRaceXMLAtCurrentTime(XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8)); this.boatXML = XMLReader.readXMLFileToString("mock/mockXML/boatsSinglePlayer.xml", StandardCharsets.UTF_8); this.regattaXML = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8); - this.xmlFileType = XMLFileType.Contents; - this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); + } catch (TransformerException | XMLReaderException e) { + throw new EventConstructionException("Could not read XML files.", e); + } + + this.xmlFileType = XMLFileType.Contents; + + this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); + + + //Parse the XML files into data sources. + try { + this.raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType); + this.boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType); + this.regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType); + + + } catch (XMLReaderException | InvalidRaceDataException | InvalidRegattaDataException | InvalidBoatDataException e) { + throw new EventConstructionException("Could not parse XML files.", e); - this.latestMessages = new LatestMessages(); - this.connectionAcceptor = new ConnectionAcceptor(latestMessages); } - catch (IOException e) { - e.printStackTrace(); - } catch (XMLReaderException e) { - e.printStackTrace(); - } catch (TransformerException e) { - e.printStackTrace(); + + //Create connection acceptor. + this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants()); + this.compositeCommand = new CompositeCommand(); + this.latestMessages = new LatestMessages(); + + try { + this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator); + + } catch (IOException e) { + throw new EventConstructionException("Could not create ConnectionAcceptor.", e); } } - public static Event getEvent() { - return theEvent; - } + public String getAddress() throws UnknownHostException { return connectionAcceptor.getAddress(); @@ -76,23 +123,16 @@ public class Event { /** * 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 { + public void start() { new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start(); 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. - RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages, this.compositeCommand); new Thread(newRace, "Event.Start()->RaceLogic thread").start(); } diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 87bc9f95..9536507b 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -2,37 +2,25 @@ package mock.app; -import network.BinaryMessageEncoder; -import network.Exceptions.InvalidMessageException; -import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.*; -import network.Messages.Enums.MessageType; +import shared.model.RunnableWithFramePeriod; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.SocketException; +import java.util.List; +import java.util.concurrent.BlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; /** * TCP server to send race information to connected clients. */ -public class MockOutput implements Runnable -{ - /** - * Timestamp of the last sent heartbeat message. - */ - private long lastHeartbeatTime; +public class MockOutput implements RunnableWithFramePeriod { + - /** - * Period for the heartbeat - that is, how often we send it. - */ - private double heartbeatPeriod = 5.0; /** - * Output stream which wraps around mockSocket outstream. + * A queue to send messages to client. */ - private DataOutputStream outToVisualiser; + private BlockingQueue outgoingMessages; /** @@ -43,187 +31,21 @@ public class MockOutput implements Runnable - /** - * Ack numbers used in messages. - */ - private int ackNumber = 1; - /** - * Sequence number for heartbeat messages. - */ - private int heartbeatSequenceNum = 1; /** * Ctor. - * @param latestMessages Latests Messages that the Mock is to send out - * @param outToVisualiser DataStream to output to Visualisers - * @throws IOException if server socket cannot be opened. + * @param latestMessages Latest Messages that the Mock is to send out + * @param outgoingMessages A queue to place outgoing messages on. */ - public MockOutput(LatestMessages latestMessages, DataOutputStream outToVisualiser) throws IOException { - - this.outToVisualiser = outToVisualiser; - - this.lastHeartbeatTime = System.currentTimeMillis(); - + public MockOutput(LatestMessages latestMessages, BlockingQueue outgoingMessages) { + this.outgoingMessages = outgoingMessages; 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. - * @throws InvalidMessageException Thrown if the message cannot be encoded. - */ - private byte[] parseHeartbeat(HeartBeat heartBeat) throws InvalidMessageException { - - //Serializes the heartbeat message. - byte[] heartbeatMessage = RaceVisionByteEncoder.encode(heartBeat); - - //Places the serialized message in a packet. - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( - MessageType.HEARTBEAT, - System.currentTimeMillis(), - getNextAckNumber(), - (short) heartbeatMessage.length, - heartbeatMessage ); - - return binaryMessageEncoder.getFullMessage(); - - } - - /** - * Encodes/serialises a XMLMessage message, and returns it. - * @param xmlMessage The XMLMessage message to serialise. - * @return The XMLMessage message in a serialised form. - * @throws InvalidMessageException Thrown if the message cannot be encoded. - */ - private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) throws InvalidMessageException { - - //Serialize the xml message. - byte[] encodedXML = RaceVisionByteEncoder.encode(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. - * @throws InvalidMessageException If the message cannot be encoded. - */ - private synchronized byte[] parseBoatLocation(BoatLocation boatLocation) throws InvalidMessageException { - - - //Encodes the message. - byte[] encodedBoatLoc = RaceVisionByteEncoder.encode(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. - * @throws InvalidMessageException Thrown if the message cannot be encoded. - */ - private synchronized byte[] parseRaceStatus(RaceStatus raceStatus) throws InvalidMessageException { - - //Encodes the messages. - byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatus); - - //Encodes the full message with header. - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( - MessageType.RACESTATUS, - System.currentTimeMillis(), - getNextAckNumber(), - (short) encodedRaceStatus.length, - encodedRaceStatus ); - - - return binaryMessageEncoder.getFullMessage(); - - - } - - /** - * Sends a heartbeat - * @throws IOException if the socket is no longer open at both ends the heartbeat returns an error. - */ - public void sendHeartBeat() throws IOException { - //Sends a heartbeat every so often. - if (timeSinceHeartbeat() >= heartbeatPeriod) { - - HeartBeat heartBeat = createHeartbeatMessage(); - - try { - outToVisualiser.write(parseHeartbeat(heartBeat)); - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode HeartBeat: " + heartBeat, e); - } - - lastHeartbeatTime = System.currentTimeMillis(); - } - } - /** * Sending loop of the Server */ @@ -251,107 +73,40 @@ public class MockOutput implements Runnable long previousFrameTime = System.currentTimeMillis(); boolean sentXMLs = false; - try { - while (!Thread.interrupted()) { - try { - long currentFrameTime = System.currentTimeMillis(); + while (!Thread.interrupted()) { - //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) { - - //Send XML messages. - if (!sentXMLs) { - //Serialise them. - - try { - 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; - - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); - continue; //Go to next iteration. - } - - } - - //Sends the RaceStatus message. - if (this.latestMessages.getRaceStatus() != null) { - - try { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - - this.outToVisualiser.write(raceStatusBlob); - - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); - } - } - - //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) { - - - try { - //Encode. - byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); - - //Write it. - this.outToVisualiser.write(boatLocationBlob); - - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); - } - - - - } - } + try { - previousFrameTime = currentFrameTime; + long currentFrameTime = System.currentTimeMillis(); + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + previousFrameTime = currentFrameTime; - } else { - //Wait until the frame period will be large enough. - long timeToWait = minimumFramePeriod - framePeriod; + //Send XML messages. + if (!sentXMLs) { - try { - Thread.sleep(timeToWait); - } catch (InterruptedException e) { - //If we get interrupted, exit the function. - Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); - //Re-set the interrupt flag. - Thread.currentThread().interrupt(); - return; - } + outgoingMessages.put(latestMessages.getRaceXMLMessage()); + outgoingMessages.put(latestMessages.getRegattaXMLMessage()); + outgoingMessages.put(latestMessages.getBoatXMLMessage()); - } + sentXMLs = true; + } - } catch (SocketException e) { - break; + List snapshot = latestMessages.getSnapshot(); + for (AC35Data message : snapshot) { + outgoingMessages.put(message); } + } catch (InterruptedException e) { + Logger.getGlobal().log(Level.WARNING, "MockOutput.run() interrupted while putting message in queue.", e); + Thread.currentThread().interrupt(); + return; } - } catch (IOException e) { - e.printStackTrace(); } + } diff --git a/racevisionGame/src/main/java/mock/enums/ConnectionStateEnum.java b/racevisionGame/src/main/java/mock/enums/ConnectionStateEnum.java new file mode 100644 index 00000000..4d4961bb --- /dev/null +++ b/racevisionGame/src/main/java/mock/enums/ConnectionStateEnum.java @@ -0,0 +1,84 @@ +package mock.enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * The states in which a connection to a client may have. + */ +public enum ConnectionStateEnum { + + UNKNOWN(0), + + /** + * We're waiting for the client to complete the joining handshake (see {@link network.Messages.RequestToJoin}. + */ + WAITING_FOR_HANDSHAKE(1), + + /** + * The client has completed the handshake, and is connected. + */ + CONNECTED(2), + + /** + * The client has timed out. + */ + TIMED_OUT(3); + + + + + private byte value; + + /** + * Ctor. Creates a ConnectionStateEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private ConnectionStateEnum(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 ConnectionStateEnum values. + */ + private static final Map byteToStatusMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToStatusMap. + */ + static { + for (ConnectionStateEnum type : ConnectionStateEnum.values()) { + ConnectionStateEnum.byteToStatusMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param connectionState Byte value to convert to a ConnectionStateEnum value. + * @return The ConnectionStateEnum value which corresponds to the given byte value. + */ + public static ConnectionStateEnum fromByte(byte connectionState) { + //Gets the corresponding MessageType from the map. + ConnectionStateEnum type = ConnectionStateEnum.byteToStatusMap.get(connectionState); + + if (type == null) { + //If the byte value wasn't found, return the UNKNOWN connectionState. + return ConnectionStateEnum.UNKNOWN; + } else { + //Otherwise, return the connectionState. + return type; + } + + } +} diff --git a/racevisionGame/src/main/java/mock/exceptions/EventConstructionException.java b/racevisionGame/src/main/java/mock/exceptions/EventConstructionException.java new file mode 100644 index 00000000..0f1d9b9f --- /dev/null +++ b/racevisionGame/src/main/java/mock/exceptions/EventConstructionException.java @@ -0,0 +1,24 @@ +package mock.exceptions; + +/** + * An exception thrown when we cannot create an {@link mock.app.Event}. + */ +public class EventConstructionException extends Exception { + + /** + * Constructs the exception with a given message. + * @param message Message to store. + */ + public EventConstructionException(String message) { + super(message); + } + + /** + * Constructs the exception with a given message and cause. + * @param message Message to store. + * @param cause Cause to store. + */ + public EventConstructionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/mock/exceptions/SourceIDAllocationException.java b/racevisionGame/src/main/java/mock/exceptions/SourceIDAllocationException.java new file mode 100644 index 00000000..6623d9cb --- /dev/null +++ b/racevisionGame/src/main/java/mock/exceptions/SourceIDAllocationException.java @@ -0,0 +1,24 @@ +package mock.exceptions; + +/** + * An exception thrown when we cannot allocate a source ID. + */ +public class SourceIDAllocationException extends Exception { + + /** + * Constructs the exception with a given message. + * @param message Message to store. + */ + public SourceIDAllocationException(String message) { + super(message); + } + + /** + * Constructs the exception with a given message and cause. + * @param message Message to store. + * @param cause Cause to store. + */ + public SourceIDAllocationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/mock/model/ClientConnection.java b/racevisionGame/src/main/java/mock/model/ClientConnection.java new file mode 100644 index 00000000..71d85348 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/ClientConnection.java @@ -0,0 +1,243 @@ +package mock.model; + + +import mock.app.MockOutput; +import mock.enums.ConnectionStateEnum; +import shared.exceptions.HandshakeException; +import mock.exceptions.SourceIDAllocationException; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.*; +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.RequestToJoinEnum; +import network.StreamRelated.MessageDeserialiser; +import network.StreamRelated.MessageSerialiser; +import visualiser.gameController.ControllerServer; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class handles the client connection handshake, and creation of MockOutput and ControllerServer. + */ +public class ClientConnection implements Runnable { + + /** + * The socket for the client's connection. + */ + private Socket socket; + + /** + * Periodically sends HeartBeat messages to client. + */ + private HeartBeatService heartBeatService; + + + /** + * Used to allocate source ID to client, if they request to participate. + */ + private SourceIdAllocator sourceIdAllocator; + + /** + * Latest snapshot of the race, to send to client. Currently only used for XML messages. + */ + private LatestMessages latestMessages; + + + /** + * Collection of commands from client for race to execute. + */ + private CompositeCommand compositeCommand; + + /** + * Used to send the race snapshot to client. + */ + private MockOutput mockOutput; + + /** + * Used to receive client input, and turn it into commands. + */ + private ControllerServer controllerServer; + + + /** + * Used to write messages to socket. + */ + private MessageSerialiser messageSerialiser; + + /** + * Stores messages to write to socket. + */ + private BlockingQueue outputQueue; + + /** + * Used to read messages from socket. + */ + private MessageDeserialiser messageDeserialiser; + + /** + * Stores messages read from socket. + */ + private BlockingQueue inputQueue; + + /** + * The state of the connection to the client. + */ + private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN; + + + + + + + /** + * Creates a client connection, using a given socket. + * @param socket The socket which connects to the client. + * @param sourceIdAllocator Used to allocate a source ID for the client. + * @param latestMessages Latest race snapshot to send to client. + * @param compositeCommand Collection of commands for race to execute. + * @throws IOException Thrown if there is a problem with the client socket. + */ + public ClientConnection(Socket socket, SourceIdAllocator sourceIdAllocator, LatestMessages latestMessages, CompositeCommand compositeCommand) throws IOException { + this.socket = socket; + this.sourceIdAllocator = sourceIdAllocator; + this.latestMessages = latestMessages; + this.compositeCommand = compositeCommand; + + this.outputQueue = new LinkedBlockingQueue<>(); + this.inputQueue = new LinkedBlockingQueue<>(); + + + this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue); + this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue); + + new Thread(messageSerialiser, "ClientConnection()->MessageSerialiser thread " + messageSerialiser).start(); + new Thread(messageDeserialiser, "ClientConnection()->MessageDeserialiser thread " + messageDeserialiser).start(); + + + this.heartBeatService = new HeartBeatService(outputQueue); + new Thread(heartBeatService, "ClientConnection()->HeartBeatService thread " + heartBeatService).start(); + + } + + + + @Override + public void run() { + try { + handshake(); + + } catch (HandshakeException | SourceIDAllocationException e) { + Logger.getGlobal().log(Level.WARNING, "Client handshake failed.", e); + Thread.currentThread().interrupt(); + return; + } + + } + + + /** + * Initiates the handshake with the client. + * @throws HandshakeException Thrown if something goes wrong with the handshake. + * @throws SourceIDAllocationException Thrown if we cannot allocate a sourceID. + */ + private void handshake() throws SourceIDAllocationException, HandshakeException { + + //This function is a bit messy, and could probably be refactored a bit. + + connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE; + + + + RequestToJoin requestToJoin = waitForRequestToJoin(); + + int allocatedSourceID = 0; + + //If they want to participate, give them a source ID number. + if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) { + + allocatedSourceID = sourceIdAllocator.allocateSourceID(); + + this.controllerServer = new ControllerServer(compositeCommand, inputQueue, allocatedSourceID); + new Thread(controllerServer, "ClientConnection.run()->ControllerServer thread" + controllerServer).start(); + + } + + this.mockOutput = new MockOutput(latestMessages, outputQueue); + new Thread(mockOutput, "ClientConnection.run()->MockOutput thread" + mockOutput).start(); + + sendJoinAcceptanceMessage(allocatedSourceID); + + connectionState = ConnectionStateEnum.CONNECTED; + + } + + + /** + * Waits until the client sends a {@link RequestToJoin} message, and returns it. + * @return The {@link RequestToJoin} message. + * @throws HandshakeException Thrown if we get interrupted while waiting. + */ + private RequestToJoin waitForRequestToJoin() throws HandshakeException { + + try { + + + while (connectionState == ConnectionStateEnum.WAITING_FOR_HANDSHAKE) { + + AC35Data message = inputQueue.take(); + + //We need to wait until they actually send a join request. + if (message.getType() == MessageType.REQUEST_TO_JOIN) { + return (RequestToJoin) message; + } + + } + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " was interrupted while waiting on the incoming message queue.", e); + + } + + + throw new HandshakeException("Handshake was cancelled. Connection state is now: " + connectionState); + + } + + + /** + * Sends the client a {@link JoinAcceptance} message, containing their assigned sourceID. + * @param sourceID The sourceID to assign to client. + * @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue. + */ + private void sendJoinAcceptanceMessage(int sourceID) throws HandshakeException { + + //Send them the source ID. + JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID); + + try { + outputQueue.put(joinAcceptance); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " interrupted while placing JoinAcceptance message on outgoing message queue.", e); + } + + } + + + /** + * Determines whether or not this connection is still alive. + * This is based off whether the {@link MessageSerialiser} is still alive. + * @return True if it is alive, false otherwise. + */ + public boolean isAlive() { + return messageSerialiser.isRunning(); + } + + +} diff --git a/racevisionGame/src/main/java/mock/model/HeartBeatService.java b/racevisionGame/src/main/java/mock/model/HeartBeatService.java new file mode 100644 index 00000000..232eb9ad --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/HeartBeatService.java @@ -0,0 +1,110 @@ +package mock.model; + +import network.Messages.AC35Data; +import network.Messages.HeartBeat; +import shared.model.RunnableWithFramePeriod; + +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * This class is responsible for sending {@link HeartBeat} messages to queue. + */ +public class HeartBeatService implements RunnableWithFramePeriod { + + /** + * Timestamp of the last sent heartbeat message. + */ + private long lastHeartbeatTime; + + /** + * Period for the heartbeat - that is, how often we send it. Milliseconds. + */ + private long heartbeatPeriod = 5000; + + + /** + * The messages we're writing to the stream. + */ + private BlockingQueue messagesToSend; + + + + /** + * Sequence number for heartbeat messages. + */ + private int heartbeatSequenceNum = 1; + + + /** + * Constructs a new HeartBeatService to send heartBeat messages to a given outputStream. + * @param messagesToSend The queue to send heartBeat messages to. + */ + public HeartBeatService(BlockingQueue messagesToSend) { + this.messagesToSend = messagesToSend; + this.lastHeartbeatTime = System.currentTimeMillis(); + } + + + + + /** + * Increments the {@link #heartbeatSequenceNum} value, and returns it. + * @return Incremented heat beat number. + */ + private int getNextHeartBeatNumber(){ + this.heartbeatSequenceNum++; + + return this.heartbeatSequenceNum; + } + + + + /** + * Generates the next heartbeat message and returns it. Increments the heartbeat sequence number. + * @return The next heartbeat message. + */ + private HeartBeat createHeartbeatMessage() { + + HeartBeat heartBeat = new HeartBeat(getNextHeartBeatNumber()); + + return heartBeat; + } + + + /** + * Puts a HeartBeat message on the message queue. + * @throws InterruptedException Thrown if the thread is interrupted. + */ + private void sendHeartBeat() throws InterruptedException { + + HeartBeat heartBeat = createHeartbeatMessage(); + + messagesToSend.put(heartBeat); + } + + + + @Override + public void run() { + + while (!Thread.interrupted()) { + long currentFrameTime = System.currentTimeMillis(); + waitForFramePeriod(lastHeartbeatTime, currentFrameTime, heartbeatPeriod); + lastHeartbeatTime = currentFrameTime; + + try { + sendHeartBeat(); + + } catch (InterruptedException e) { + Logger.getGlobal().log(Level.WARNING, "HeartBeatService: " + this + " sendHeartBeat() was interrupted on thread: " + Thread.currentThread(), e); + Thread.currentThread().interrupt(); + return; + + } + } + + } +} diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 9755099d..e389f474 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -7,6 +7,7 @@ import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import network.Messages.Enums.RaceStatusEnum; import shared.dataInput.RegattaDataSource; +import shared.exceptions.BoatNotFoundException; import shared.model.*; import shared.model.Bearing; @@ -411,6 +412,25 @@ public class MockRace extends Race { return boats; } + /** + * Returns a boat by sourceID. + * @param sourceID The source ID the boat. + * @return The boat. + * @throws BoatNotFoundException Thrown if there is not boat with the specified sourceID. + */ + public MockBoat getBoat(int sourceID) throws BoatNotFoundException { + + for (MockBoat boat : boats) { + + if (boat.getSourceID() == sourceID) { + return boat; + } + + } + + throw new BoatNotFoundException("Boat with sourceID: " + sourceID + " was not found."); + } + /** * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 9e810761..42bea8ef 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,12 +1,15 @@ package mock.model; import javafx.animation.AnimationTimer; +import mock.model.commandFactory.CompositeCommand; import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; -import shared.model.Race; + + public class RaceLogic implements Runnable { + /** * State of current race modified by this object */ @@ -16,14 +19,18 @@ public class RaceLogic implements Runnable { */ private RaceServer server; + private CompositeCommand commands; + /** * Initialises race loop with state and server message queue * @param race state of race to modify * @param messages to send to server + * @param compositeCommand Commands from clients to execute. */ - public RaceLogic(MockRace race, LatestMessages messages) { + public RaceLogic(MockRace race, LatestMessages messages, CompositeCommand compositeCommand) { this.race = race; this.server = new RaceServer(race, messages); + this.commands = compositeCommand; } /** @@ -56,17 +63,13 @@ public class RaceLogic implements Runnable { //Provide boat's with an estimated time at next mark until the race starts. race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime()); - //Parse the boat locations. - server.parseBoatLocations(); - - //Parse the marks. - server.parseMarks(); + //Parse the race snapshot. + server.parseSnapshot(); // Change wind direction race.changeWindDirection(); - //Parse the race status. - server.parseRaceStatus(); + if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) { @@ -109,6 +112,9 @@ public class RaceLogic implements Runnable { //Get the current time. currentTime = System.currentTimeMillis(); + //Execute commands from clients. + commands.execute(race); + //Update race time. race.updateRaceTime(currentTime); @@ -123,7 +129,6 @@ public class RaceLogic implements Runnable { //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { - race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); } @@ -141,15 +146,8 @@ public class RaceLogic implements Runnable { // Change wind direction race.changeWindDirection(); - //Parse the boat locations. - server.parseBoatLocations(); - - //Parse the marks. - server.parseMarks(); - - //Parse the race status. - server.parseRaceStatus(); - + //Parse the race snapshot. + server.parseSnapshot(); //Update the last frame time. this.lastFrameTime = currentTime; @@ -165,7 +163,7 @@ public class RaceLogic implements Runnable { @Override public void handle(long now) { - server.parseRaceStatus(); + server.parseSnapshot(); if (iters > 500) { stop(); @@ -173,4 +171,5 @@ public class RaceLogic implements Runnable { iters++; } }; + } diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index d776b693..969a4c71 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -1,10 +1,7 @@ package mock.model; -import network.Messages.BoatLocation; -import network.Messages.BoatStatus; +import network.Messages.*; import network.Messages.Enums.BoatLocationDeviceEnum; -import network.Messages.LatestMessages; -import network.Messages.RaceStatus; import network.Utils.AC35UnitConverter; import shared.model.Bearing; import shared.model.CompoundMark; @@ -21,10 +18,6 @@ public class RaceServer { private MockRace race; private LatestMessages latestMessages; - /** - * The sequence number of the latest RaceStatus message sent or received. - */ - private int raceStatusSequenceNumber = 1; /** * The sequence number of the latest BoatLocation message sent or received. @@ -39,10 +32,31 @@ public class RaceServer { /** - * Parses an individual marker boat, and sends it to mockOutput. + * Parses the race to create a snapshot, and places it in latestMessages. + */ + public void parseSnapshot() { + + List snapshotMessages = new ArrayList<>(); + + //Parse the boat locations. + snapshotMessages.addAll(parseBoatLocations()); + + //Parse the marks. + snapshotMessages.addAll(parseMarks()); + + //Parse the race status. + snapshotMessages.add(parseRaceStatus()); + + latestMessages.setSnapshot(snapshotMessages); + } + + + /** + * Parses an individual marker boat, and returns it. * @param mark The marker boat to parse. + * @return The BoatLocation message. */ - private void parseIndividualMark(Mark mark) { + private BoatLocation parseIndividualMark(Mark mark) { //Create message. BoatLocation boatLocation = new BoatLocation( mark.getSourceID(), @@ -57,13 +71,17 @@ public class RaceServer { //Iterates the sequence number. this.boatLocationSequenceNumber++; - this.latestMessages.setBoatLocation(boatLocation); + return boatLocation; } /** - * Parse the compound marker boats through mock output. + * Parse the compound marker boats, and returns a list of BoatLocation messages. + * @return BoatLocation messages for each mark. */ - public void parseMarks() { + private List parseMarks() { + + List markLocations = new ArrayList<>(race.getCompoundMarks().size()); + for (CompoundMark compoundMark : race.getCompoundMarks()) { //Get the individual marks from the compound mark. @@ -72,31 +90,40 @@ public class RaceServer { //If they aren't null, parse them (some compound marks only have one mark). if (mark1 != null) { - this.parseIndividualMark(mark1); + markLocations.add(this.parseIndividualMark(mark1)); } if (mark2 != null) { - this.parseIndividualMark(mark2); + markLocations.add(this.parseIndividualMark(mark2)); } } + + return markLocations; } /** - * Parse the boats in the race, and send it to mockOutput. + * Parse the boats in the race, and returns all of their BoatLocation messages. + * @return List of BoatLocation messages, for each boat. */ - public void parseBoatLocations() { + private List parseBoatLocations() { + + List boatLocations = new ArrayList<>(race.getBoats().size()); + //Parse each boat. for (MockBoat boat : race.getBoats()) { - this.parseIndividualBoatLocation(boat); + boatLocations.add(this.parseIndividualBoatLocation(boat)); } + + return boatLocations; } /** - * Parses an individual boat, and sends it to mockOutput. + * Parses an individual boat, and returns it. * @param boat The boat to parse. + * @return The BoatLocation message. */ - private void parseIndividualBoatLocation(MockBoat boat) { + private BoatLocation parseIndividualBoatLocation(MockBoat boat) { BoatLocation boatLocation = new BoatLocation( boat.getSourceID(), @@ -111,16 +138,17 @@ public class RaceServer { //Iterates the sequence number. this.boatLocationSequenceNumber++; - this.latestMessages.setBoatLocation(boatLocation); + return boatLocation; } /** - * Parses the race status, and sends it to mockOutput. + * Parses the race status, and returns it. + * @return The race status message. */ - public void parseRaceStatus() { + private RaceStatus parseRaceStatus() { //A race status message contains a list of boat statuses. List boatStatuses = new ArrayList<>(); @@ -151,6 +179,6 @@ public class RaceServer { race.getRaceType(), boatStatuses); - this.latestMessages.setRaceStatus(raceStatus); + return raceStatus; } } diff --git a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java new file mode 100644 index 00000000..3b62a8a7 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java @@ -0,0 +1,70 @@ +package mock.model; + + +import mock.exceptions.SourceIDAllocationException; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is responsible for allocating boat source IDs for use in a race, upon request. + */ +public class SourceIdAllocator { + + + /** + * This list contains all unallocated source IDs. + */ + List unallocatedIDs = new ArrayList<>(); + + + /** + * This list contains all allocated source IDs. + */ + List allocatedIDs = new ArrayList<>(); + + + /** + * Creates a source ID allocator, using the given list of unallocated source IDs. + * @param unallocatedIDs List of unallocated source IDs. + */ + public SourceIdAllocator(List unallocatedIDs) { + //We need to copy the list. + this.unallocatedIDs.addAll(unallocatedIDs); + } + + + /** + * Allocates a source ID for a boat. + * @return The allocated source ID. + * @throws SourceIDAllocationException Thrown if we cannot allocate any more source IDs. + */ + public synchronized int allocateSourceID() throws SourceIDAllocationException { + + if (!unallocatedIDs.isEmpty()) { + + int sourceID = unallocatedIDs.remove(0); + + allocatedIDs.add(sourceID); + + return sourceID; + + } else { + throw new SourceIDAllocationException("Could not allocate a source ID."); + + } + } + + + /** + * Returns a source ID to the source ID allocator, so that it can be reused. + * @param sourceID Source ID to return. + */ + public void returnSourceID(Integer sourceID) { + + //We remove an Integer, not an int, so that we remove by value not by index. + allocatedIDs.remove(sourceID); + + unallocatedIDs.add(sourceID); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java new file mode 100644 index 00000000..ff09103d --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -0,0 +1,25 @@ +package mock.model.commandFactory; + +import mock.model.MockRace; + +import java.util.Stack; + +/** + * Wraps multiple commands into a composite to execute queued commands during a frame. + */ +public class CompositeCommand implements Command { + private Stack commands; + + public CompositeCommand() { + this.commands = new Stack<>(); + } + + public void addCommand(Command command) { + commands.push(command); + } + + @Override + public void execute(MockRace race) { + while(!commands.isEmpty()) commands.pop().execute(race); + } +} diff --git a/racevisionGame/src/main/java/network/MessageControllers/MessageController.java b/racevisionGame/src/main/java/network/MessageControllers/MessageController.java new file mode 100644 index 00000000..7b6cca14 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageControllers/MessageController.java @@ -0,0 +1,9 @@ +package network.MessageControllers; + + + +public class MessageController { + + + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 54c10272..303b30db 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.BinaryMessageEncoder; import network.Exceptions.InvalidMessageException; import network.Exceptions.InvalidMessageTypeException; import network.Messages.*; @@ -104,7 +105,7 @@ public class RaceVisionByteEncoder { /** - * Encodes a given message. + * Encodes a given message, to be placed inside a binary message (see {@link BinaryMessageEncoder}). * @param message Message to encode. * @return Encoded message. * @throws InvalidMessageException If the message cannot be encoded. @@ -126,4 +127,29 @@ public class RaceVisionByteEncoder { } + /** + * Encodes a given messages, using a given ackNumber, and returns a binary message ready to be sent over-the-wire. + * @param message The message to send. + * @param ackNumber The ackNumber of the message. + * @return A binary message ready to be transmitted. + * @throws InvalidMessageException Thrown if the message cannot be encoded. + */ + public static byte[] encodeBinaryMessage(AC35Data message, int ackNumber) throws InvalidMessageException { + + //Encodes the message. + byte[] encodedMessage = RaceVisionByteEncoder.encode(message); + + //Encodes the full message with header. + BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( + message.getType(), + System.currentTimeMillis(), + ackNumber, + (short) encodedMessage.length, + encodedMessage ); + + + return binaryMessageEncoder.getFullMessage(); + } + + } diff --git a/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java b/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java new file mode 100644 index 00000000..4eaa6dce --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java @@ -0,0 +1,11 @@ +package network.MessageRouters; + + +/** + * This class routes {@link network.Messages.AC35Data} messages to an appropriate message controller. + */ +public class MessageRouter { + + + +} diff --git a/racevisionGame/src/main/java/network/Messages/BoatAction.java b/racevisionGame/src/main/java/network/Messages/BoatAction.java index fcc96aa8..93c6a310 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatAction.java +++ b/racevisionGame/src/main/java/network/Messages/BoatAction.java @@ -13,6 +13,12 @@ public class BoatAction extends AC35Data { */ private BoatActionEnum boatAction; + + /** + * The source ID of the boat this action relates to. + */ + private int sourceID = 0; + /** * Constructs a BoatActon message with a given action. * @param boatAction Action to use. @@ -30,4 +36,19 @@ public class BoatAction extends AC35Data { return boatAction; } + /** + * Returns the boat source ID for this message. + * @return The source ID for this message. + */ + public int getSourceID() { + return sourceID; + } + + /** + * Sets the boat source ID for this message. + * @param sourceID The source for this message. + */ + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } } diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index f35fc52e..147f58e7 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -3,9 +3,7 @@ package network.Messages; import network.Messages.Enums.XMLMessageType; import shared.dataInput.RaceDataSource; -import java.util.HashMap; -import java.util.Map; -import java.util.Observable; +import java.util.*; /** * This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...). @@ -44,6 +42,12 @@ public class LatestMessages extends Observable { private CourseWinds courseWinds; + /** + * A list of messages containing a snapshot of the race. + */ + private List snapshot = new ArrayList<>(); + + /** * The latest race data XML message. */ @@ -69,6 +73,22 @@ public class LatestMessages extends Observable { } + /** + * Returns a copy of the race snapshot. + * @return Copy of the race snapshot. + */ + public List getSnapshot() { + return new ArrayList<>(snapshot); + } + + + /** + * Sets the snapshot of the race. + * @param snapshot New snapshot of race. + */ + public void setSnapshot(List snapshot) { + this.snapshot = snapshot; + } /** diff --git a/racevisionGame/src/main/java/network/Messages/RaceSnapshot.java b/racevisionGame/src/main/java/network/Messages/RaceSnapshot.java new file mode 100644 index 00000000..212c8dab --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/RaceSnapshot.java @@ -0,0 +1,41 @@ +package network.Messages; + + +import java.util.ArrayList; +import java.util.List; + + +/** + * Represents a snapshot of the race's state. + * Contains a list of {@link AC35Data} messages. + * Send a copy of each message to a connected client. + */ +public class RaceSnapshot { + + /** + * The contents of the snapshot. + */ + private List snapshot; + + + /** + * Constructs a snapshot using a given list of messages. + * @param snapshot Messages to use as snapshot. + */ + public RaceSnapshot(List snapshot) { + this.snapshot = snapshot; + } + + + /** + * Gets the contents of the snapshot. + * This is a shallow copy. + * @return Contents of the snapshot. + */ + public List getSnapshot() { + + List copy = new ArrayList<>(snapshot); + + return copy; + } +} diff --git a/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java b/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java new file mode 100644 index 00000000..39cb0024 --- /dev/null +++ b/racevisionGame/src/main/java/network/StreamRelated/MessageDeserialiser.java @@ -0,0 +1,156 @@ +package network.StreamRelated; + + +import network.BinaryMessageDecoder; +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AC35Data; +import shared.model.RunnableWithFramePeriod; + +import java.io.*; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static network.Utils.ByteConverter.bytesToShort; + +/** + * This class is responsible for converting data from an input stream into a queue of {@link AC35Data} messages. + */ +public class MessageDeserialiser implements RunnableWithFramePeriod { + + + /** + * The stream we're reading from. + */ + private DataInputStream inputStream; + + /** + * The messages we've read. + */ + private BlockingQueue messagesRead; + + + /** + * Ack numbers used in messages. + */ + private int ackNumber = 1; + + + /** + * Constructs a new MessageSerialiser to write a queue of messages to a given stream. + * @param inputStream The stream to write to. + * @param messagesRead The messages to send. + */ + public MessageDeserialiser(InputStream inputStream, BlockingQueue messagesRead) { + this.inputStream = new DataInputStream(inputStream); + this.messagesRead = messagesRead; + } + + + /** + * Increments the ackNumber value, and returns it. + * @return Incremented ackNumber. + */ + private int getNextAckNumber(){ + this.ackNumber++; + + return this.ackNumber; + } + + + + /** + * Reads and returns the next message as an array of bytes from the input stream. 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 input stream. + */ + private byte[] getNextMessageBytes() throws IOException { + inputStream.mark(0); + short CRCLength = 4; + short headerLength = 15; + + //Read the header of the next message. + byte[] headerBytes = new byte[headerLength]; + inputStream.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]; + inputStream.readFully(messageBodyBytes); + + //Read the message CRC. + byte[] messageCRCBytes = new byte[CRCLength]; + inputStream.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 input stream. + * @return The message object. + * @throws IOException Thrown when an error occurs while reading from the input stream. + * @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(); + + } + + + + @Override + public void run() { + + long previousFrameTime = System.currentTimeMillis(); + + while (!Thread.interrupted()) { + + + long currentFrameTime = System.currentTimeMillis(); + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + previousFrameTime = currentFrameTime; + + + //Reads the next message. + try { + AC35Data message = this.getNextMessage(); + messagesRead.add(message); + } + catch (InvalidMessageException | IOException e) { + + Logger.getGlobal().log(Level.WARNING, "Unable to read message.", e); + + try { + inputStream.reset(); + } catch (IOException e1) { + Logger.getGlobal().log(Level.WARNING, "Unable to reset inputStream.", e); + } + + } + + } + + } +} diff --git a/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java new file mode 100644 index 00000000..02e6f7a6 --- /dev/null +++ b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java @@ -0,0 +1,116 @@ +package network.StreamRelated; + + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AC35Data; +import shared.model.RunnableWithFramePeriod; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is responsible for writing a queue of {@link network.Messages.AC35Data} messages to an output stream. + */ +public class MessageSerialiser implements RunnableWithFramePeriod { + + + /** + * The stream we're writing to. + */ + private DataOutputStream outputStream; + + /** + * The messages we're writing to the stream. + */ + private BlockingQueue messagesToSend; + + + /** + * Ack numbers used in messages. + */ + private int ackNumber = 1; + + /** + * Determines whether or not this runnable is currently running. + */ + private boolean isRunning; + + + + /** + * Constructs a new MessageSerialiser to write a queue of messages to a given stream. + * @param outputStream The stream to write to. + * @param messagesToSend The messages to send. + */ + public MessageSerialiser(OutputStream outputStream, BlockingQueue messagesToSend) { + this.outputStream = new DataOutputStream(outputStream); + this.messagesToSend = messagesToSend; + } + + + /** + * Increments the ackNumber value, and returns it. + * @return Incremented ackNumber. + */ + private int getNextAckNumber(){ + this.ackNumber++; + + return this.ackNumber; + } + + /** + * Determines whether or not this runnable is running. + * @return True means that it is still running, false means that it has stopped. + */ + public boolean isRunning() { + return isRunning; + } + + + @Override + public void run() { + + long previousFrameTime = System.currentTimeMillis(); + + isRunning = true; + + while (isRunning) { + + + long currentFrameTime = System.currentTimeMillis(); + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + previousFrameTime = currentFrameTime; + + + //Send the messages. + List messages = new ArrayList<>(); + messagesToSend.drainTo(messages); + + for (AC35Data message : messages) { + try { + byte[] messageBytes = RaceVisionByteEncoder.encodeBinaryMessage(message, getNextAckNumber()); + + outputStream.write(messageBytes); + + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode message: " + message, e); + + } catch (IOException e) { + Logger.getGlobal().log(Level.WARNING, "Could not write message to outputStream: " + outputStream, e); + isRunning = false; + + } + } + + } + + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/BoatNotFoundException.java b/racevisionGame/src/main/java/shared/exceptions/BoatNotFoundException.java new file mode 100644 index 00000000..f3fed55c --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/BoatNotFoundException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when a specific boat cannot be found. + */ +public class BoatNotFoundException extends Exception { + + public BoatNotFoundException(String message) { + super(message); + } + + public BoatNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/HandshakeException.java b/racevisionGame/src/main/java/shared/exceptions/HandshakeException.java new file mode 100644 index 00000000..2f62e286 --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/HandshakeException.java @@ -0,0 +1,24 @@ +package shared.exceptions; + +/** + * An exception thrown when we the client-server handshake fails. + */ +public class HandshakeException extends Exception { + + /** + * Constructs the exception with a given message. + * @param message Message to store. + */ + public HandshakeException(String message) { + super(message); + } + + /** + * Constructs the exception with a given message and cause. + * @param message Message to store. + * @param cause Cause to store. + */ + public HandshakeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java new file mode 100644 index 00000000..af633af3 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java @@ -0,0 +1,64 @@ +package shared.model; + + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AC35Data; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This interface is a {@link Runnable} interface, with the ability to sleep until a given time period has elapsed. + */ +public interface RunnableWithFramePeriod extends Runnable { + + + + + + + /** + * Waits for enough time for the period of this frame to be greater than minimumFramePeriod. + * @param previousFrameTime The timestamp of the previous frame. + * @param currentFrameTime The timestamp of the current frame. + * @param minimumFramePeriod The minimum period the frame must be. + */ + default void waitForFramePeriod(long previousFrameTime, long currentFrameTime, long minimumFramePeriod) { + + + //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. + if (framePeriod >= minimumFramePeriod) { + return; + + } 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. + Logger.getGlobal().log(Level.SEVERE, "RunnableWithFramePeriod.waitForFramePeriod().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; + + } + + } + + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java index ae8c682c..5f1e2d8d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java @@ -144,32 +144,5 @@ public class ConnectionController extends Controller { } } - /** - * Sets up a new host - */ - public void addLocal() { - try { - //We don't want to host more than one game. - if (!currentlyHostingGame) { - Event game = Event.getEvent(); - urlField.textProperty().set(game.getAddress()); - portField.textProperty().set(Integer.toString(game.getPort())); - - game.start(); - addConnection(); - - currentlyHostingGame = true; - } - } catch (InvalidRaceDataException e) { - e.printStackTrace(); - } catch (XMLReaderException e) { - e.printStackTrace(); - } catch (InvalidBoatDataException e) { - e.printStackTrace(); - } catch (InvalidRegattaDataException e) { - e.printStackTrace(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java index e87ea689..7873e8e6 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java @@ -6,6 +6,7 @@ import javafx.scene.control.*; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; import mock.app.Event; +import mock.exceptions.EventConstructionException; import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRegattaDataException; @@ -17,6 +18,8 @@ import java.net.Socket; import java.net.URL; import java.net.UnknownHostException; import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Controller for Hosting a game. @@ -44,17 +47,12 @@ public class HostController extends Controller { */ public void hostGamePressed() throws IOException{ try { - Event game = Event.getEvent(); + Event game = new Event(); game.start(); connectSocket("localhost", 4942); - } catch (InvalidRaceDataException e) { - e.printStackTrace(); - } catch (XMLReaderException e) { - e.printStackTrace(); - } catch (InvalidBoatDataException e) { - e.printStackTrace(); - } catch (InvalidRegattaDataException e) { - e.printStackTrace(); + } catch (EventConstructionException e) { + Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); + throw new RuntimeException(e); } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index f34c57a8..5a1836f6 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -26,6 +26,8 @@ import visualiser.model.*; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Controller used to display a running race. @@ -115,8 +117,9 @@ public class RaceController extends Controller { controllerClient.sendKey(controlKey); controlKey.onAction(); // Change key state if applicable event.consume(); - } catch (IOException e) { - e.printStackTrace(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); } } }); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java index 8db4ec60..2f13aae2 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java @@ -20,6 +20,7 @@ import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import visualiser.app.VisualiserInput; import visualiser.gameController.ControllerClient; +import visualiser.model.ServerConnection; import visualiser.model.VisualiserBoat; import visualiser.model.VisualiserRace; @@ -27,6 +28,8 @@ import java.io.IOException; import java.net.Socket; import java.net.URL; import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Controller to for waiting for the race to start. @@ -66,18 +69,18 @@ public class StartController extends Controller implements Observer { @FXML private Label raceStatusLabel; - /** - * The object used to read packets from the connected server. + * Our connection to the server. */ - private VisualiserInput visualiserInput; + private ServerConnection serverConnection; + /** * The race object which describes the currently occurring race. */ private VisualiserRace visualiserRace; - private ControllerClient controllerClient; + /** * An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor. @@ -309,17 +312,17 @@ public class StartController extends Controller implements Observer { public void enterLobby(Socket socket) { startWrapper.setVisible(true); try { - //Begin reading packets from the socket/server. - this.visualiserInput = new VisualiserInput(socket); - //Send controller input to server - this.controllerClient = new ControllerClient(socket); + + LatestMessages latestMessages = new LatestMessages(); + this.serverConnection = new ServerConnection(socket, latestMessages); + + //Store a reference to latestMessages so that we can observe it. - LatestMessages latestMessages = this.visualiserInput.getLatestMessages(); latestMessages.addObserver(this); - new Thread(this.visualiserInput).start(); + new Thread(this.serverConnection).start(); } catch (IOException e) { - e.printStackTrace(); + Logger.getGlobal().log(Level.WARNING, "Could not connection to server.", e); } } diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java index e77a2fef..8ad14a1b 100644 --- a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java +++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java @@ -2,12 +2,14 @@ package visualiser.app; import network.BinaryMessageDecoder; import network.Exceptions.InvalidMessageException; import network.Messages.*; +import shared.model.RunnableWithFramePeriod; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.concurrent.BlockingQueue; import static network.Utils.ByteConverter.bytesToShort; @@ -15,7 +17,7 @@ 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 { +public class VisualiserInput implements RunnableWithFramePeriod { /** * Timestamp of the last heartbeat. @@ -27,40 +29,28 @@ public class VisualiserInput implements Runnable { private long lastHeartbeatSequenceNum = -1; - /** - * The socket that we have connected to. - */ - private Socket connectionSocket; - /** - * InputStream (from the socket). + * Incoming messages from server. */ - private DataInputStream inStream; + private BlockingQueue incomingMessages; /** * 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. + * Every server frame, VisualiserInput reads messages from its incomingMessages, 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. + * Constructs a visualiserInput to convert an incoming stream of messages into LatestMessages. + * @param latestMessages Object to place messages in. + * @param incomingMessages The incoming queue of messages. */ - 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(); - + public VisualiserInput(LatestMessages latestMessages, BlockingQueue incomingMessages) { + this.latestMessages = latestMessages; + this.incomingMessages = incomingMessages; this.lastHeartbeatTime = System.currentTimeMillis(); } @@ -85,279 +75,21 @@ public class VisualiserInput implements Runnable { - /** - * 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; - } + @Override + public void run() { - //AverageWind. - case AVGWIND: { + //Handshake. - //System.out.println("Average Wind Message!"); - AverageWind averageWind = (AverageWind) message; + //Main loop. + // take message + // create command + // place in command queue - this.latestMessages.setAverageWind(averageWind); - break; - } - //Unrecognised message. - default: { - System.out.println("Broken Message!"); - break; - } - } - } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index bb91f2a4..9b38a5ca 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -3,6 +3,7 @@ package visualiser.gameController; import network.BinaryMessageEncoder; import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AC35Data; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.MessageType; @@ -13,6 +14,7 @@ import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; +import java.util.concurrent.BlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; @@ -20,28 +22,18 @@ import java.util.logging.Logger; * Basic service for sending key presses to game server */ public class ControllerClient { - /** - * Socket to server - */ - Socket socket; /** - * Output stream wrapper for socket to server + * Queue of messages to be sent to server. */ - DataOutputStream outputStream; + private BlockingQueue outgoingMessages; /** * Initialise controller client with live socket. - * @param socket to server + * @param outgoingMessages Queue to place messages on to send to server. */ - public ControllerClient(Socket socket) { - this.socket = socket; - - try { - this.outputStream = new DataOutputStream(socket.getOutputStream()); - } catch (IOException e) { - e.printStackTrace(); - } + public ControllerClient(BlockingQueue outgoingMessages) { + this.outgoingMessages = outgoingMessages; } /** @@ -49,27 +41,13 @@ public class ControllerClient { * @param key to send * @throws IOException if socket write fails */ - public void sendKey(ControlKey key) throws IOException { + public void sendKey(ControlKey key) throws InterruptedException { BoatActionEnum protocolCode = key.getProtocolCode(); if(protocolCode != BoatActionEnum.NOT_A_STATUS) { BoatAction boatAction = new BoatAction(protocolCode); - //Encode BoatAction. - try { - byte[] encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); - - BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, - (short) encodedBoatAction.length, encodedBoatAction); - - System.out.println("Sending out key: " + protocolCode); - outputStream.write(binaryMessage.getFullMessage()); - - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e); - - } - + outgoingMessages.put(boatAction); } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index d4c62d11..3757dc01 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,16 +1,19 @@ package visualiser.gameController; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; +import mock.model.commandFactory.CompositeCommand; import network.BinaryMessageDecoder; import network.Exceptions.InvalidMessageException; import network.MessageDecoders.BoatActionDecoder; +import network.Messages.AC35Data; import network.Messages.BoatAction; -import network.Messages.Enums.BoatActionEnum; -import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; +import network.Messages.Enums.MessageType; import java.io.DataInputStream; import java.io.IOException; -import java.net.Socket; +import java.io.InputStream; +import java.util.concurrent.BlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; @@ -18,57 +21,69 @@ import java.util.logging.Logger; * Service for dispatching key press data to race from client */ public class ControllerServer implements Runnable { + + /** - * Socket to client + * Queue of incoming messages from client. */ - private Socket socket; + private BlockingQueue inputQueue; + + /** - * Wrapper for input from client + * Collection of commands from client for race to execute. */ - private DataInputStream inputStream; + private CompositeCommand compositeCommand; /** - * Initialise server-side controller with live client socket - * @param socket to client + * This is the source ID associated with the client. */ - public ControllerServer(Socket socket) { - this.socket = socket; - try { - this.inputStream = new DataInputStream(this.socket.getInputStream()); - } catch (IOException e) { - e.printStackTrace(); - } + private int clientSourceID; + + + + /** + * Initialise server-side controller with live client socket. + * @param compositeCommand Commands for the race to execute. + * @param inputQueue The queue of messages to read from. + * @param clientSourceID The source ID of the client's boat. + */ + public ControllerServer(CompositeCommand compositeCommand, BlockingQueue inputQueue, int clientSourceID) { + this.compositeCommand = compositeCommand; + this.inputQueue = inputQueue; + this.clientSourceID = clientSourceID; } + + /** * Wait for controller key input from client and loop. */ @Override public void run() { - while(true) { - byte[] message = new byte[20]; - try { - if (inputStream.available() > 0) { + while(!Thread.interrupted()) { - inputStream.read(message); + try { - BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); - BoatActionDecoder boatActionDecoder = new BoatActionDecoder(); + AC35Data message = inputQueue.take(); - try { - boatActionDecoder.decode(encodedMessage.getMessageBody()); - BoatAction boatAction = boatActionDecoder.getMessage(); - System.out.println("Received key: " + boatAction.getBoatAction()); + if (message.getType() == MessageType.BOATACTION) { - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e); - } + BoatAction boatAction = (BoatAction) message; + boatAction.setSourceID(clientSourceID); + Command command = CommandFactory.createCommand(boatAction); + compositeCommand.addCommand(command); } - } catch (IOException e) { - e.printStackTrace(); + + + } catch (InterruptedException e) { + Logger.getGlobal().log(Level.WARNING, "ControllerServer Interrupted while waiting for message on incoming message queue.", e); + Thread.currentThread().interrupt(); + return; } + } + } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java index ef1368f0..be95abd3 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java @@ -27,8 +27,8 @@ public class KeyFactory { keyState.put("SPACE", new VMGKey("VMG")); keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); - keyState.put("PAGE_UP", new UpWindKey("Upwind")); - keyState.put("PAGE_DOWN", new DownWindKey("Downwind")); + keyState.put("UP", new UpWindKey("Upwind")); + keyState.put("DOWN", new DownWindKey("Downwind")); } /** diff --git a/racevisionGame/src/main/java/visualiser/model/ServerConnection.java b/racevisionGame/src/main/java/visualiser/model/ServerConnection.java new file mode 100644 index 00000000..ececdd4c --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/ServerConnection.java @@ -0,0 +1,228 @@ +package visualiser.model; + + +import mock.app.MockOutput; +import mock.enums.ConnectionStateEnum; +import mock.exceptions.SourceIDAllocationException; +import mock.model.HeartBeatService; +import mock.model.SourceIdAllocator; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.AC35Data; +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.JoinAcceptance; +import network.Messages.LatestMessages; +import network.Messages.RequestToJoin; +import network.StreamRelated.MessageDeserialiser; +import network.StreamRelated.MessageSerialiser; +import shared.exceptions.HandshakeException; +import visualiser.app.VisualiserInput; +import visualiser.gameController.ControllerClient; +import visualiser.gameController.ControllerServer; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class handles the client-server connection handshake, and creation of VisualiserInput and ControllerClient. + */ +public class ServerConnection implements Runnable { + + /** + * The socket for the connection to server. + */ + private Socket socket; + + + /** + * Latest snapshot of the race, received from the server. + */ + private LatestMessages latestMessages; + + + /** + * Used to convert incoming messages into a race snapshot. + */ + private VisualiserInput visualiserInput; + + /** + * Used to send client input to server. + */ + private ControllerClient controllerClient; + + + /** + * Used to write messages to socket. + */ + private MessageSerialiser messageSerialiser; + + /** + * Stores messages to write to socket. + */ + private BlockingQueue outputQueue; + + /** + * Used to read messages from socket. + */ + private MessageDeserialiser messageDeserialiser; + + /** + * Stores messages read from socket. + */ + private BlockingQueue inputQueue; + + /** + * The state of the connection to the client. + */ + private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN; + + + + + + + /** + * Creates a server connection, using a given socket. + * @param socket The socket which connects to the client. + * @param latestMessages Latest race snapshot to send to client. + * @throws IOException Thrown if there is a problem with the client socket. + */ + public ServerConnection(Socket socket, LatestMessages latestMessages) throws IOException { + this.socket = socket; + this.latestMessages = latestMessages; + + this.outputQueue = new LinkedBlockingQueue<>(); + this.inputQueue = new LinkedBlockingQueue<>(); + + + this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue); + this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue); + + new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser).start(); + new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser).start(); + + } + + + + @Override + public void run() { + try { + handshake(); + + } catch (HandshakeException e) { + Logger.getGlobal().log(Level.WARNING, "Server handshake failed.", e); + Thread.currentThread().interrupt(); + return; + } + + } + + + /** + * Initiates the handshake with the server. + * @throws HandshakeException Thrown if something goes wrong with the handshake. + */ + private void handshake() throws HandshakeException { + + //This function is a bit messy, and could probably be refactored a bit. + + connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE; + + + sendJoinAcceptanceMessage(RequestToJoinEnum.PARTICIPANT); + + + JoinAcceptance joinAcceptance = waitForJoinAcceptance(); + + int allocatedSourceID = 0; + + //If we join successfully... + if (joinAcceptance.getAcceptanceType() == JoinAcceptanceEnum.JOIN_SUCCESSFUL) { + + allocatedSourceID = joinAcceptance.getSourceID(); + //TODO need to do something with the ID - maybe flag the correct visualiser boat as being the client's boat? + + this.controllerClient = new ControllerClient(inputQueue); + //new Thread(controllerClient, "ServerConnection.run()->ControllerClient thread " + controllerClient).start(); + + } + + this.visualiserInput = new VisualiserInput(latestMessages, outputQueue); + new Thread(visualiserInput, "ServerConnection.run()->VisualiserInput thread " + visualiserInput).start(); + + + connectionState = ConnectionStateEnum.CONNECTED; + + } + + + /** + * Waits until the server sends a {@link JoinAcceptance} message, and returns it. + * @return The {@link JoinAcceptance} message. + * @throws HandshakeException Thrown if we get interrupted while waiting. + */ + private JoinAcceptance waitForJoinAcceptance() throws HandshakeException { + + try { + + + while (connectionState == ConnectionStateEnum.WAITING_FOR_HANDSHAKE) { + + AC35Data message = inputQueue.take(); + + //We need to wait until they actually send a join request. + if (message.getType() == MessageType.JOIN_ACCEPTANCE) { + return (JoinAcceptance) message; + } + + } + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " was interrupted while waiting on the incoming message queue.", e); + + } + + + throw new HandshakeException("Handshake was cancelled. Connection state is now: " + connectionState); + + } + + + /** + * Sends the server a {@link RequestToJoin} message. + * @param requestType The type of request to send + * @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue. + */ + private void sendJoinAcceptanceMessage(RequestToJoinEnum requestType) throws HandshakeException { + + //Send them the source ID. + RequestToJoin requestToJoin = new RequestToJoin(requestType); + + try { + outputQueue.put(requestToJoin); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " interrupted while placing RequestToJoin message on outgoing message queue.", e); + } + + } + + + /** + * Determines whether or not this connection is still alive. + * This is based off whether the {@link MessageSerialiser} is still alive. + * @return True if it is alive, false otherwise. + */ + public boolean isAlive() { + return messageSerialiser.isRunning(); + } + + +} diff --git a/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java new file mode 100644 index 00000000..7240e01b --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java @@ -0,0 +1,126 @@ +package mock.model; + +import mock.exceptions.SourceIDAllocationException; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + + +/** + * Tests if allocating source IDs works. + */ +public class SourceIdAllocatorTest { + + /** + * This is the list of source IDs that we start with. + */ + private List originalSourceIDs; + + /** + * Used to allocate source IDs. + */ + private SourceIdAllocator sourceIdAllocator; + + + @Before + public void setUp() throws Exception { + + originalSourceIDs = new ArrayList<>(); + originalSourceIDs.add(120); + originalSourceIDs.add(121); + originalSourceIDs.add(122); + originalSourceIDs.add(123); + originalSourceIDs.add(124); + originalSourceIDs.add(125); + + + sourceIdAllocator = new SourceIdAllocator(originalSourceIDs); + + } + + + /** + * Tests that allocation fails when we don't have any source IDs to allocate. + */ + @Test + public void emptyAllocationTest() { + + SourceIdAllocator allocator = new SourceIdAllocator(new ArrayList<>()); + + + try { + int sourceID = allocator.allocateSourceID(); + + fail("Exception should have been thrown, but wasn't."); + + } catch (SourceIDAllocationException e) { + + //We expect this exception to be thrown - success. + + } + + } + + + /** + * Tests that we can allocate a source ID. + * @throws Exception Thrown in case of error. + */ + @Test + public void allocationTest() throws Exception { + + + int sourceID = sourceIdAllocator.allocateSourceID(); + + } + + + /** + * Tests that we can allocate source IDs, but it will eventually be unable to allocate source IDs. + */ + @Test + public void allocationEventuallyFailsTest() { + + while (true) { + + try { + int sourceID = sourceIdAllocator.allocateSourceID(); + + } catch (SourceIDAllocationException e) { + //We expect to encounter this exception after enough allocations - success. + break; + + } + + } + + } + + + /** + * Tests if we can allocate a source ID, return it, and reallocate it. + * @throws Exception Thrown in case of error. + */ + @Test + public void reallocationTest() throws Exception { + + List sourceIDList = new ArrayList<>(); + sourceIDList.add(123); + + SourceIdAllocator sourceIdAllocator = new SourceIdAllocator(sourceIDList); + + //Allocate. + int sourceID = sourceIdAllocator.allocateSourceID(); + + //Return. + sourceIdAllocator.returnSourceID(sourceID); + + //Reallocate. + int sourceID2 = sourceIdAllocator.allocateSourceID(); + + } +} diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java new file mode 100644 index 00000000..c3d0df04 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -0,0 +1,31 @@ +package mock.model.commandFactory; + +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; +import org.junit.Before; +import org.junit.Test; +import shared.model.Boat; +import shared.model.Race; +import visualiser.model.VisualiserRace; + +import static org.testng.Assert.*; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommandTest { + private Race race; + private Boat boat; + private Command upwind; + private Command downwind; + + @Before + public void setUp() { + boat = new Boat(0, "Bob", "NZ"); + } + + @Test + public void upwindCommandDecreasesAngle() { + + } +} \ No newline at end of file From 4da37348046ccb232a47b750e0542d7f61539178 Mon Sep 17 00:00:00 2001 From: hba56 Date: Thu, 10 Aug 2017 12:17:19 +1200 Subject: [PATCH 25/59] added the ability to toggle the race guide line on and off #story[1087] --- .../java/visualiser/model/Annotations.java | 11 ++++++++++ .../visualiser/model/ResizableRaceCanvas.java | 12 +++++++++- .../resources/visualiser/scenes/race.fxml | 22 ++++++++++++------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/model/Annotations.java b/racevisionGame/src/main/java/visualiser/model/Annotations.java index 09e3831a..54976c35 100644 --- a/racevisionGame/src/main/java/visualiser/model/Annotations.java +++ b/racevisionGame/src/main/java/visualiser/model/Annotations.java @@ -40,6 +40,7 @@ public class Annotations { private static String pathCheckAnno = "showBoatPath"; private static String timeCheckAnno = "showTime"; private static String estTimeCheckAnno = "showEstTime"; + private static String guideLineAnno = "showGuideline"; // string values match the fx:id value of radio buttons private static String noBtn = "noBtn"; @@ -160,6 +161,16 @@ public class Annotations { raceMap.draw(); } }); + + //listener to show estimated time for annotation + checkBoxes.get(guideLineAnno).selectedProperty() + .addListener((ov, old_val, new_val) -> { + if (old_val != new_val) { + raceMap.toggleGuideLine(); + storeCurrentAnnotationState(guideLineAnno, new_val); + raceMap.draw(); + } + }); } /** diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 333ed54b..a44dd8eb 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -49,6 +49,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { private boolean annoPath = true; private boolean annoEstTime = true; private boolean annoTimeSinceLastMark = true; + private boolean annoGuideLine = false; @@ -116,6 +117,13 @@ public class ResizableRaceCanvas extends ResizableCanvas { annoSpeed = !annoSpeed; } + /** + * Toggle the guideline annotation + */ + public void toggleGuideLine() { + annoGuideLine = !annoGuideLine; + } + @@ -470,7 +478,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { drawBoundary(); //Guiding Line - drawRaceLine(); + if (annoGuideLine){ + drawRaceLine(); + } //Boats. drawBoats(); diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml index 159d725c..d7615b76 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -1,12 +1,17 @@ + + + + - + + @@ -30,16 +35,17 @@ - -