|
|
|
|
@ -10,6 +10,8 @@ import java.nio.charset.Charset;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
|
|
import static seng302.Networking.Utils.ByteConverter.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Created by fwy13 on 19/04/17.
|
|
|
|
|
*/
|
|
|
|
|
@ -26,14 +28,14 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20*boats.size());
|
|
|
|
|
//Version Number 1 bytes
|
|
|
|
|
byte versionNum = 0b10; //this changes with the pdf. (2)
|
|
|
|
|
byte[] timeBytes = convert(time, 6);//time (6 bytes)
|
|
|
|
|
byte[] timeBytes = longToBytes(time, 6);//time (6 bytes)
|
|
|
|
|
byte[] raceID = ByteBuffer.allocate(4).putInt(race).array();//race identifier incase multiple races are going at once.
|
|
|
|
|
byte[] raceStatus = convert(raceState, 1);//race status 0 - 10
|
|
|
|
|
byte[] expectedStart = convert(startTime, 6);//number of milliseconds from Jan 1, 1970 for when the data is valid
|
|
|
|
|
byte[] raceStatus = intToBytes(raceState, 1);//race status 0 - 10
|
|
|
|
|
byte[] expectedStart = longToBytes(startTime, 6);//number of milliseconds from Jan 1, 1970 for when the data is valid
|
|
|
|
|
byte[] raceWind = ByteBuffer.allocate(2).putShort(raceWindDir).array();//North = 0x0000 East = 0x4000 South = 0x8000
|
|
|
|
|
byte[] windSpeed = ByteBuffer.allocate(2).putShort(raceWindSpeed).array();//mm/sec
|
|
|
|
|
byte[] numBoats = convert(boats.size(), 1);
|
|
|
|
|
byte[] bytesRaceType = convert(raceType, 1);//1 match race, 2 fleet race
|
|
|
|
|
byte[] numBoats = intToBytes(boats.size(), 1);
|
|
|
|
|
byte[] bytesRaceType = intToBytes(raceType, 1);//1 match race, 2 fleet race
|
|
|
|
|
|
|
|
|
|
raceStatusMessage.put(versionNum);
|
|
|
|
|
raceStatusMessage.put(timeBytes);
|
|
|
|
|
@ -47,11 +49,11 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < boats.size(); i++){
|
|
|
|
|
int sourceID = 0; //TODO use boats source id.
|
|
|
|
|
byte[] legNum = convert(boats.get(i).getCurrentLeg().getLegNumber(), 1);
|
|
|
|
|
byte[] numPenalties = convert(0, 1); //TODO use boats in race penalties class
|
|
|
|
|
byte[] numPenaltiesServed = convert(0, 1);//TODO use boats in race penalites served.
|
|
|
|
|
byte[] estNextMarkTime = convert((long)0, 6);//TODO use boats estimated time to next mark.
|
|
|
|
|
byte[] estFinishTime = convert((long) 0, 6);//TODO use boats estimated time to the finish.
|
|
|
|
|
byte[] legNum = intToBytes(boats.get(i).getCurrentLeg().getLegNumber(), 1);
|
|
|
|
|
byte[] numPenalties = intToBytes(0, 1); //TODO use boats in race penalties class
|
|
|
|
|
byte[] numPenaltiesServed = intToBytes(0, 1);//TODO use boats in race penalites served.
|
|
|
|
|
byte[] estNextMarkTime = longToBytes((long)0, 6);//TODO use boats estimated time to next mark.
|
|
|
|
|
byte[] estFinishTime = longToBytes((long) 0, 6);//TODO use boats estimated time to the finish.
|
|
|
|
|
|
|
|
|
|
raceStatusMessage.putInt(sourceID);
|
|
|
|
|
raceStatusMessage.put(legNum);
|
|
|
|
|
@ -68,7 +70,7 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
//ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32);
|
|
|
|
|
int messageVersionNumber = 0b1;//version number
|
|
|
|
|
short ackNum = 0;//no clue what this does just a placeholder for 2 bytes.
|
|
|
|
|
byte[] messLines = convert(message.length, 1);
|
|
|
|
|
byte[] messLines = intToBytes(message.length, 1);
|
|
|
|
|
|
|
|
|
|
// result.putInt(messageVersionNumber);
|
|
|
|
|
// result.putShort(ackNum);
|
|
|
|
|
@ -84,8 +86,8 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
messageLen = 30;
|
|
|
|
|
}
|
|
|
|
|
ByteBuffer mess = ByteBuffer.allocate(2 + messageLen);
|
|
|
|
|
mess.put(convert(message[i].getLineNumber(), 1));
|
|
|
|
|
mess.put(convert(messageLen, 1));
|
|
|
|
|
mess.put(intToBytes(message[i].getLineNumber(), 1));
|
|
|
|
|
mess.put(intToBytes(messageLen, 1));
|
|
|
|
|
for (int j = 0; j < messageLen; j ++){
|
|
|
|
|
mess.put(messageAsBytes[j]);
|
|
|
|
|
}
|
|
|
|
|
@ -94,7 +96,7 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(size);
|
|
|
|
|
result.put(convert(messageVersionNumber, 1));
|
|
|
|
|
result.put(intToBytes(messageVersionNumber, 1));
|
|
|
|
|
result.putShort(ackNum);
|
|
|
|
|
result.put(messLines);
|
|
|
|
|
|
|
|
|
|
@ -107,14 +109,14 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
|
|
|
|
|
public byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){
|
|
|
|
|
int messageVersion = 0b1;
|
|
|
|
|
byte[] timestamp = convert(time, 6);
|
|
|
|
|
byte[] ackNumber = convert(ack, 2);
|
|
|
|
|
byte[] raceStartTime = convert(startTime, 6);
|
|
|
|
|
byte[] timestamp = longToBytes(time, 6);
|
|
|
|
|
byte[] ackNumber = intToBytes(ack, 2);
|
|
|
|
|
byte[] raceStartTime = longToBytes(startTime, 6);
|
|
|
|
|
int raceIdentifier = raceID;
|
|
|
|
|
byte[] notificationType = convert(notification, 1);
|
|
|
|
|
byte[] notificationType = intToBytes(notification, 1);
|
|
|
|
|
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(20);
|
|
|
|
|
result.put(convert(messageVersion, 1));
|
|
|
|
|
result.put(intToBytes(messageVersion, 1));
|
|
|
|
|
result.put(timestamp);
|
|
|
|
|
result.put(ackNumber);
|
|
|
|
|
result.put(raceStartTime);
|
|
|
|
|
@ -127,15 +129,15 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
public byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID,
|
|
|
|
|
int eventID){
|
|
|
|
|
int messageVersion = 0b10;
|
|
|
|
|
byte[] encodeTime = convert(time, 6);
|
|
|
|
|
byte[] encodeTime = longToBytes(time, 6);
|
|
|
|
|
short ackNum = acknowledgeNumber;
|
|
|
|
|
int raceUID = raceID;//TODO chekc if this is an into for a 4 char string.
|
|
|
|
|
int destSource = destSourceID;
|
|
|
|
|
int incident = incidentID;
|
|
|
|
|
byte[] event = convert(eventID, 1);
|
|
|
|
|
byte[] event = intToBytes(eventID, 1);
|
|
|
|
|
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(22);
|
|
|
|
|
result.put(convert(messageVersion, 1));
|
|
|
|
|
result.put(intToBytes(messageVersion, 1));
|
|
|
|
|
result.put(encodeTime);
|
|
|
|
|
result.putShort(ackNum);
|
|
|
|
|
result.putInt(raceUID);
|
|
|
|
|
@ -147,12 +149,12 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
|
|
|
|
|
public byte[] chatterText(int messageType, String message){
|
|
|
|
|
int messageVersion = 0b1;
|
|
|
|
|
byte[] type = convert(messageType, 1);
|
|
|
|
|
byte[] length = convert(message.length(), 1);
|
|
|
|
|
byte[] text = convert(message, length[0]);
|
|
|
|
|
byte[] type = intToBytes(messageType, 1);
|
|
|
|
|
byte[] text = message.getBytes();
|
|
|
|
|
byte[] length = intToBytes(text.length, 1);
|
|
|
|
|
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(3 + text.length);
|
|
|
|
|
result.put(convert(messageVersion, 1));
|
|
|
|
|
result.put(intToBytes(messageVersion, 1));
|
|
|
|
|
result.put(type);
|
|
|
|
|
result.put(length);
|
|
|
|
|
result.put(text);
|
|
|
|
|
@ -162,30 +164,30 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
|
|
|
|
|
public byte[] boatLocation(BoatLocationMessage boatLocationMessage){
|
|
|
|
|
int messageVersionNumber = 0b1;
|
|
|
|
|
byte[] time = convert(boatLocationMessage.getTime(), 6);
|
|
|
|
|
byte[] sourceID = convert(boatLocationMessage.getSourceID(), 4);
|
|
|
|
|
byte[] seqNum = convert(boatLocationMessage.getSequenceNumber(), 4);
|
|
|
|
|
byte[] deviceType = convert(boatLocationMessage.getDeviceType(), 1);
|
|
|
|
|
byte[] latitude = convert(boatLocationMessage.getLatitude(), 4);
|
|
|
|
|
byte[] longitude = convert(boatLocationMessage.getLongitude(), 4);
|
|
|
|
|
byte[] altitude = convert(boatLocationMessage.getAltitude(), 4);
|
|
|
|
|
byte[] heading = convert(boatLocationMessage.getHeading(), 2);
|
|
|
|
|
byte[] pitch = convert(boatLocationMessage.getPitch(), 2);
|
|
|
|
|
byte[] roll = convert(boatLocationMessage.getRoll(), 2);
|
|
|
|
|
byte[] boatSpeed = convert(boatLocationMessage.getBoatSpeed(), 2);
|
|
|
|
|
byte[] cog = convert(boatLocationMessage.getBoatCOG(), 2);
|
|
|
|
|
byte[] sog = convert(boatLocationMessage.getBoatSOG(), 2);
|
|
|
|
|
byte[] apparentWindSpeed = convert(boatLocationMessage.getApparentWindSpeed(), 2);
|
|
|
|
|
byte[] apparentWindAngle = convert(boatLocationMessage.getApparentWindAngle(), 2);
|
|
|
|
|
byte[] trueWindSpeed = convert(boatLocationMessage.getTrueWindSpeed(), 2);
|
|
|
|
|
byte[] trueWindDirection = convert(boatLocationMessage.getTrueWindDirection(), 2);
|
|
|
|
|
byte[] trueWindAngle = convert(boatLocationMessage.getTrueWindAngle(), 2);
|
|
|
|
|
byte[] currentDrift = convert(boatLocationMessage.getCurrentDrift(), 2);
|
|
|
|
|
byte[] currentSet = convert(boatLocationMessage.getCurrentSet(), 2);
|
|
|
|
|
byte[] rudderAngle = convert(boatLocationMessage.getRudderAngle(), 2);
|
|
|
|
|
byte[] time = longToBytes(boatLocationMessage.getTime(), 6);
|
|
|
|
|
byte[] sourceID = intToBytes(boatLocationMessage.getSourceID(), 4);
|
|
|
|
|
byte[] seqNum = intToBytes(boatLocationMessage.getSequenceNumber(), 4);
|
|
|
|
|
byte[] deviceType = intToBytes(boatLocationMessage.getDeviceType(), 1);
|
|
|
|
|
byte[] latitude = intToBytes(boatLocationMessage.getLatitude(), 4);
|
|
|
|
|
byte[] longitude = intToBytes(boatLocationMessage.getLongitude(), 4);
|
|
|
|
|
byte[] altitude = intToBytes(boatLocationMessage.getAltitude(), 4);
|
|
|
|
|
byte[] heading = intToBytes(boatLocationMessage.getHeading(), 2);
|
|
|
|
|
byte[] pitch = intToBytes(boatLocationMessage.getPitch(), 2);
|
|
|
|
|
byte[] roll = intToBytes(boatLocationMessage.getRoll(), 2);
|
|
|
|
|
byte[] boatSpeed = intToBytes(boatLocationMessage.getBoatSpeed(), 2);
|
|
|
|
|
byte[] cog = intToBytes(boatLocationMessage.getBoatCOG(), 2);
|
|
|
|
|
byte[] sog = intToBytes(boatLocationMessage.getBoatSOG(), 2);
|
|
|
|
|
byte[] apparentWindSpeed = intToBytes(boatLocationMessage.getApparentWindSpeed(), 2);
|
|
|
|
|
byte[] apparentWindAngle = intToBytes(boatLocationMessage.getApparentWindAngle(), 2);
|
|
|
|
|
byte[] trueWindSpeed = intToBytes(boatLocationMessage.getTrueWindSpeed(), 2);
|
|
|
|
|
byte[] trueWindDirection = intToBytes(boatLocationMessage.getTrueWindDirection(), 2);
|
|
|
|
|
byte[] trueWindAngle = intToBytes(boatLocationMessage.getTrueWindAngle(), 2);
|
|
|
|
|
byte[] currentDrift = intToBytes(boatLocationMessage.getCurrentDrift(), 2);
|
|
|
|
|
byte[] currentSet = intToBytes(boatLocationMessage.getCurrentSet(), 2);
|
|
|
|
|
byte[] rudderAngle = intToBytes(boatLocationMessage.getRudderAngle(), 2);
|
|
|
|
|
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(56);
|
|
|
|
|
result.put(convert(messageVersionNumber, 1));
|
|
|
|
|
result.put(intToBytes(messageVersionNumber, 1));
|
|
|
|
|
result.put(time);
|
|
|
|
|
result.put(sourceID);
|
|
|
|
|
result.put(seqNum);
|
|
|
|
|
@ -212,17 +214,17 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
|
|
|
|
|
public byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
|
|
|
|
|
int messageVersionNumber = 0b1;
|
|
|
|
|
byte[] byteTime = convert(time, 6);
|
|
|
|
|
byte[] byteAck = convert(ackNumber, 2);
|
|
|
|
|
byte[] byteRaceID = convert(raceID, 4);
|
|
|
|
|
byte[] byteSourceID = convert(sourceID, 4);
|
|
|
|
|
byte[] byteBoatStatus = convert(boatStatus, 1);
|
|
|
|
|
byte[] byteRoundingSide = convert(roundingSide, 1);
|
|
|
|
|
byte[] byteMarkType = convert(markType, 1);
|
|
|
|
|
byte[] byteMarkID = convert(markID, 1);
|
|
|
|
|
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(convert(messageVersionNumber, 1));
|
|
|
|
|
result.put(intToBytes(messageVersionNumber, 1));
|
|
|
|
|
result.put(byteTime);
|
|
|
|
|
result.put(byteAck);
|
|
|
|
|
result.put(byteRaceID);
|
|
|
|
|
@ -237,38 +239,38 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
public byte[] courseWind(byte windID, ArrayList<CourseWind> courseWinds){
|
|
|
|
|
int messageVersionNumber = 0b1;
|
|
|
|
|
byte byteWindID = windID;
|
|
|
|
|
byte[] loopcount = convert(courseWinds.size(), 1);
|
|
|
|
|
byte[] loopcount = intToBytes(courseWinds.size(), 1);
|
|
|
|
|
ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.size());
|
|
|
|
|
result.put(convert(messageVersionNumber, 1));
|
|
|
|
|
result.put(intToBytes(messageVersionNumber, 1));
|
|
|
|
|
result.put(byteWindID);
|
|
|
|
|
result.put(loopcount);
|
|
|
|
|
for (CourseWind wind: courseWinds){
|
|
|
|
|
result.put(convert(wind.getID(), 1));
|
|
|
|
|
result.put(convert(wind.getTime(), 6));
|
|
|
|
|
result.put(convert(wind.getRaceID(), 4));
|
|
|
|
|
result.put(convert(wind.getWindDirection(), 2));
|
|
|
|
|
result.put(convert(wind.getWindSpeed(), 2));
|
|
|
|
|
result.put(convert(wind.getBestUpwindAngle(), 2));
|
|
|
|
|
result.put(convert(wind.getBestDownwindAngle(), 2));
|
|
|
|
|
result.put(convert(wind.getFlags(), 1));
|
|
|
|
|
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 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 = convert(time,6);
|
|
|
|
|
byte[] byteRawPeriod = convert(rawPeriod, 2);
|
|
|
|
|
byte[] byteRawSpeed = convert(rawSampleSpeed, 2);
|
|
|
|
|
byte[] bytePeriod2 = convert(period2, 2);
|
|
|
|
|
byte[] byteSpeed2 = convert(speed2, 2);
|
|
|
|
|
byte[] bytePeriod3 = convert(period3, 2);
|
|
|
|
|
byte[] byteSpeed3 = convert(speed3, 2);
|
|
|
|
|
byte[] bytePeriod4 = convert(period4, 2);
|
|
|
|
|
byte[] byteSpeed4 = convert(speed4, 2);
|
|
|
|
|
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(convert(messageVersionNumber, 1));
|
|
|
|
|
result.put(intToBytes(messageVersionNumber, 1));
|
|
|
|
|
result.put(byteTime);
|
|
|
|
|
result.put(byteRawPeriod);
|
|
|
|
|
result.put(byteRawSpeed);
|
|
|
|
|
@ -281,67 +283,4 @@ public class RaceVisionByteEncoder {
|
|
|
|
|
return result.array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] convert(String s, int size){
|
|
|
|
|
byte[] m = s.getBytes(Charset.forName("UTF-8"));
|
|
|
|
|
int length = m.length;
|
|
|
|
|
byte[] result;
|
|
|
|
|
if (length > 255){
|
|
|
|
|
length = 255;
|
|
|
|
|
} else if (size < 1){
|
|
|
|
|
result = new byte[0];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result = Arrays.copyOfRange(m, 0, length + 1);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] convert(int n, int size){
|
|
|
|
|
byte[] result;
|
|
|
|
|
if (size > 4){
|
|
|
|
|
result = new byte[4];
|
|
|
|
|
return result;
|
|
|
|
|
} else if (size < 1){
|
|
|
|
|
result = new byte[0];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
ByteBuffer byteBuffer = ByteBuffer.allocate(4);
|
|
|
|
|
byteBuffer.putInt(n);
|
|
|
|
|
byte[] bytes = byteBuffer.array();
|
|
|
|
|
result = Arrays.copyOfRange(bytes, 4 - size, 4);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] convert(long n, int size){
|
|
|
|
|
byte[] result;
|
|
|
|
|
if (size > 8){
|
|
|
|
|
result = new byte[8];
|
|
|
|
|
return result;
|
|
|
|
|
} else if (size < 1){
|
|
|
|
|
result = new byte[0];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
|
|
|
|
|
byteBuffer.putLong(n);
|
|
|
|
|
byte[] bytes = byteBuffer.array();
|
|
|
|
|
result = Arrays.copyOfRange(bytes, 8 - size, 8);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public byte[] convert(short n, int size){
|
|
|
|
|
byte[] result;
|
|
|
|
|
if (size > 2){
|
|
|
|
|
result = new byte[2];
|
|
|
|
|
return result;
|
|
|
|
|
} else if (size < 1){
|
|
|
|
|
result = new byte[0];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
ByteBuffer byteBuffer = ByteBuffer.allocate(2);
|
|
|
|
|
byteBuffer.putShort(n);
|
|
|
|
|
byte[] bytes = byteBuffer.array();
|
|
|
|
|
result = Arrays.copyOfRange(bytes, 2 - size, 2);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|