Added boat location, and race start messages to the mock data interface

- Added proper support for signed and unsigned types. This includes automatic conversion to the correct data type (long to int, short, or byte).

- Moved code related to adding values into the byte buffer into the abstract Message class

Tags: #story[29]
This commit is contained in:
Michael Rausch
2017-04-25 21:49:51 +12:00
parent 6874f288ee
commit 1f8f1f0f86
12 changed files with 573 additions and 121 deletions
+32 -2
View File
@@ -15,6 +15,7 @@ public class ServerThread implements Runnable{
private StreamingServerSocket server;
private final int HEARTBEAT_PERIOD = 5000;
private final int RACE_STATUS_PERIOD = 1000;
private final int BOAT_LOCATION_PERIOD = 1000/5;
private final int PORT_NUMBER = 8085;
public ServerThread(String threadName){
@@ -68,6 +69,19 @@ public class ServerThread implements Runnable{
100, 3, RaceType.MATCH_RACE, 1, boats);
}
/**
* @return A list of sample boat location messages
*/
public List<Message> getTestBoatLocationMessages(){
List<Message> messages = new ArrayList<>();
messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23));
messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13));
return messages;
}
public void run() {
try{
server = new StreamingServerSocket(PORT_NUMBER);
@@ -105,7 +119,7 @@ public class ServerThread implements Runnable{
try {
server.send(hb);
} catch (IOException e) {
e.printStackTrace();
System.out.print("");
}
}
}, 0, HEARTBEAT_PERIOD);
@@ -120,11 +134,27 @@ public class ServerThread implements Runnable{
try {
server.send(statusMessage);
} catch (IOException e) {
e.printStackTrace();
System.out.print("");
}
}
}, 100, RACE_STATUS_PERIOD);
t1.schedule(new TimerTask() {
@Override
public void run() {
List<Message> messages = getTestBoatLocationMessages();
for (Message m : messages){
try {
server.send(m);
} catch (IOException e) {
System.out.print("");
}
}
}
}, 100, BOAT_LOCATION_PERIOD);
} catch (IOException e) {
System.err.println(e.getMessage());
}
@@ -0,0 +1,109 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
private long messageVersionNumber;
private long time;
private long sourceId;
private long sequenceNum;
private DeviceType deviceType;
private long latitude;
private long longitude;
private long altitude;
private long heading;
private long pitch;
private long roll;
private long boatSpeed;
private long COG;
private long SOG;
private long apparentWindSpeed;
private long apparentWindAngle;
private long trueWindSpeed;
private long trueWindDirection;
private long trueWindAngle;
private long currentDrift;
private long currentSet;
private long rudderAngle;
/**
* Describes the location, altitude and sensor data from the boat.
* @param sourceId ID of the boat
* @param sequenceNum Sequence number of the message
* @param latitude The boats latitude
* @param longitude The boats longitude
* @param heading The boats heading
* @param boatSpeed The boats speed
*/
public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){
messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L;
this.sourceId = sourceId;
this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT;
this.latitude = -latitude;
this.longitude = longitude;
this.altitude = 0;
this.heading = heading;
this.pitch = 0;
this.roll = 0;
this.boatSpeed = boatSpeed;
this.COG = 0;
this.SOG = 0;
this.apparentWindSpeed = 0;
this.apparentWindAngle = 0;
this.trueWindSpeed = 0;
this.trueWindDirection = 0;
this.trueWindAngle = 0;
this.currentDrift = 0;
this.currentSet = 0;
this.rudderAngle = 0;
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
}
@Override
public int getSize() {
return 56;
}
@Override
public void send(DataOutputStream outputStream) {
allocateBuffer();
writeHeaderToBuffer();
putByte((byte) messageVersionNumber);
putInt((int) time, 6);
putInt((int) sourceId, 4);
putUnsignedInt((int) sequenceNum, 4);
putByte((byte) deviceType.getCode());
putInt((int) latitude, 4);
putInt((int) longitude, 4);
putInt((int) altitude, 4);
putUnsignedInt((int) heading, 2);
putInt((int) pitch, 2);
putInt((int) roll, 2);
putUnsignedInt((int) boatSpeed, 2);
putUnsignedInt((int) COG, 2);
putUnsignedInt((int) SOG, 2);
putUnsignedInt((int) apparentWindSpeed, 2);
putInt((int) apparentWindAngle, 2);
putUnsignedInt((int) trueWindSpeed, 2);
putUnsignedInt((int) trueWindDirection, 2);
putInt((int) trueWindAngle, 2);
putUnsignedInt((int) currentDrift, 2);
putUnsignedInt((int) currentSet, 2);
putInt((int) rudderAngle, 2);
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
System.out.print("");
}
}
}
@@ -1,11 +1,12 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.nio.ByteBuffer;
/**
* The status of each boat, sent within a race status message
*/
public class BoatSubMessage {
public class BoatSubMessage{
private final int MESSAGE_SIZE = 20;
private long sourceId;
@@ -57,22 +58,22 @@ public class BoatSubMessage {
buff.position(buffPos);
// Boat Status, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array());
buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array());
buffPos += 1;
buff.position(buffPos);
// Leg number, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array());
buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array());
buffPos += 1;
buff.position(buffPos);
// Number of penalties awarded, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array());
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array());
buffPos += 1;
buff.position(buffPos);
// Number of penalties served, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array());
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array());
buffPos += 1;
buff.position(buffPos);
@@ -0,0 +1,16 @@
package seng302.server.messages;
public enum DeviceType {
UNKNOWN(0),
RACING_YACHT(1);
private long code;
DeviceType(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -26,24 +26,15 @@ public class Heartbeat extends Message {
public void send(DataOutputStream outputStream) {
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize());
allocateBuffer();
writeHeaderToBuffer();
// Write header
buff.put(getHeader().getByteBuffer());
buff.position(Header.getSize());
putUnsignedInt(seqNo, 4);
// Write seq num
buff.put(ByteBuffer.allocate(4).putInt(seqNo).array());
buff.position(Header.getSize()+4);
// Write CRC
CRC32 crc = new CRC32();
crc.update(buff.array());
buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array());
writeCRC();
try {
outputStream.write(buff.array());
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
@@ -1,21 +1,27 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
public abstract class Message {
Header header;
private final int CRC_SIZE = 4;
private Header header;
private ByteBuffer buffer;
private int bufferPosition;
private CRC32 crc;
/**
* @param header Set the header for this message
*/
public void setHeader(Header header){
void setHeader(Header header){
this.header = header;
}
/**
* @return the header specified for this message
*/
public Header getHeader(){
Header getHeader(){
return header;
}
@@ -28,4 +34,124 @@ public abstract class Message {
* Send the message as through the outputStream
*/
public abstract void send(DataOutputStream outputStream);
/**
* Allocate byte buffer to correct size
*/
void allocateBuffer(){
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
bufferPosition = 0;
}
/**
* Write the set header to the byte buffer
*/
void writeHeaderToBuffer(){
buffer.put(getHeader().getByteBuffer());
bufferPosition += Header.getSize();
buffer.position(bufferPosition);
}
/**
* Move the buffer position by n bytes
* @param size Number of bytes to move the buffer by
*/
private void moveBufferPositionBy(int size){
bufferPosition += size;
buffer.position(bufferPosition);
}
/**
* Put an unsigned byte in the buffer
*/
void putUnsignedByte(byte b){
buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array());
moveBufferPositionBy(1);
}
/**
* Put an signed byte in the buffer
*/
void putByte(byte b){
buffer.put(ByteBuffer.allocate(1).put(b).array());
moveBufferPositionBy(1);
}
/**
* Place an unsigned integer of the specified length in the buffer
* @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers)
* @param size The size of the int to be added to the buffer
*/
void putUnsignedInt(long val, int size){
if (size <= 1){
putUnsignedByte((byte) val);
}
else if (size < 4){
// Use short
buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array());
moveBufferPositionBy(size);
}
else{
// Use int
buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array());
moveBufferPositionBy(size);
}
}
/**
* Put a signed int of a specified length in the buffer
* @param val The integer value to add
* @param size The size of the integer to be added to the buffer
*/
void putInt(int val, int size){
if (size < 4){
buffer.put(ByteBuffer.allocate(size).putShort((short) val).array());
}
else{
buffer.put(ByteBuffer.allocate(size).putInt((short) val).array());
}
moveBufferPositionBy(size);
}
/**
* Write an array of bytes to the buffer
* @param bytes to write
*/
void putBytes(byte[] bytes){
buffer.put(bytes);
moveBufferPositionBy(bytes.length);
}
/**
* Write a ByteBuffer of bytes to the buffer
* @param bytes to write
* @param size number of bytes
*/
void putBytes(ByteBuffer bytes, int size){
buffer.put(bytes);
moveBufferPositionBy(size);
}
/**
* Calculate the CRC of the buffer and append it to the end of the buffer
*/
void writeCRC(){
crc = new CRC32();
buffer.position(0);
crc.update(buffer.array());
buffer.position(bufferPosition);
putInt((int) crc.getValue(), CRC_SIZE);
}
/**
* @return The current buffer
*/
public ByteBuffer getBuffer(){
return buffer;
}
}
@@ -0,0 +1,21 @@
package seng302.server.messages;
/**
* The types of race start status messages
*/
public enum RaceStartNotificationType {
SET_RACE_START_TIME(1),
RACE_POSTPONED(2),
RACE_ABANDONED(3),
RACE_TERMINATED(4);
private final long type;
RaceStartNotificationType(long type) {
this.type = type;
}
long getType(){
return type;
}
}
@@ -0,0 +1,59 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20;
private long version;
private long timeStamp;
private long ackNumber;
private long raceStartTime;
private long raceId;
private RaceStartNotificationType notificationType;
/**
* Message sent to clients with the expected start time of the race
* @param ackNumber Sequence number of message.
* @param raceStartTime Expected race start time
* @param raceId Race ID#
* @param notificationType Type of this notification
*/
public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){
this.version = 1;
this.timeStamp = System.currentTimeMillis() / 1000L;
this.ackNumber = ackNumber;
this.raceStartTime = raceStartTime;
this.notificationType = notificationType;
this.raceId = raceId;
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(DataOutputStream outputStream) {
allocateBuffer();
writeHeaderToBuffer();
putUnsignedByte((byte) version);
putInt((int) timeStamp, 6);
putInt((int) ackNumber, 2);
putInt((int) raceStartTime, 6);
putInt((int) raceId, 4);
putUnsignedByte((byte) notificationType.getType());
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -11,7 +11,6 @@ public class RaceStatusMessage extends Message{
private final int MESSAGE_VERSION = 2; //Always set to 1
private final int MESSAGE_BASE_SIZE = 24;
// fields
private long currentTime;
private long raceId;
private RaceStatus raceStatus;
@@ -56,7 +55,7 @@ public class RaceStatusMessage extends Message{
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace);
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
/**
@@ -65,64 +64,28 @@ public class RaceStatusMessage extends Message{
*/
@Override
public void send(DataOutputStream outputStream) {
ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/);
allocateBuffer();
writeHeaderToBuffer();
buff.put(getHeader().getByteBuffer());
buff.position(Header.getSize());
// Version Number, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array());
buff.position(Header.getSize() + 1);
// Current time, 2 bytes
buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array());
buff.position(Header.getSize() + 7);
// Race id, 4 bytes
buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array());
buff.position(Header.getSize() + 11);
// Race status, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array());
buff.position(Header.getSize() + 12);
// Expected start time, 6 bytes
buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array());
buff.position(Header.getSize() + 18);
// Wind direction, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array());
buff.position(Header.getSize() + 20);
// Wind Speed, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array());
buff.position(Header.getSize() + 22);
// Number of boats, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array());
buff.position(Header.getSize() + 23);
// Race Type, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array());
buff.position(Header.getSize() + 24);
int buffPosition = Header.getSize() + 24;
putByte((byte) MESSAGE_VERSION);
putInt((int) currentTime, 6);
putInt((int) raceId, 4);
putByte((byte) raceStatus.getCode());
putInt((int) expectedStartTime, 6);
putInt((int) raceWindDirection.getCode(), 2);
putInt((int) windSpeed, 2);
putByte((byte) numBoatsInRace);
putByte((byte) raceType.getCode());
for (BoatSubMessage boatSubMessage : boats){
buff.put(boatSubMessage.getByteBuffer());
buffPosition += boatSubMessage.getSize();
buff.position(buffPosition);
putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize());
}
// calculate CRC
crc.update(buff.array());
// Add CRC to message
buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array());
writeCRC();
// Send
try {
outputStream.write(buff.array());
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
@@ -11,26 +11,24 @@ public class XMLMessage extends Message{
private final int MESSAGE_SIZE = 14;
// Message fields
private int timeStamp;
private short ack = 0x00; //Unused
private long timeStamp;
private long ack = 0x00; //Unused
private XMLMessageSubType xmlMessageSubType;
private Short length;
private Short sequence;
private long length;
private long sequence;
private String content;
private CRC32 crc;
/**
* XML Message from the AC35 Streaming data spec
* @param content The XML content
* @param type The XML Message Sub Type
*/
public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
this.content = content;
this.xmlMessageSubType = type;
crc = new CRC32();
timeStamp = (int) (System.currentTimeMillis() / 1000L);
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
length = (short) this.content.length();
length = this.content.length();
sequence = sequenceNum;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
@@ -48,47 +46,23 @@ public class XMLMessage extends Message{
* @param outputStream The output stream to send the message
*/
public void send(DataOutputStream outputStream) {
ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4);
buff.put(getHeader().getByteBuffer());
buff.position(Header.getSize());
allocateBuffer();
writeHeaderToBuffer();
// Version Number, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array());
buff.position(Header.getSize() + 1);
// Write message fields
putUnsignedByte((byte) MESSAGE_VERSION);
putInt((int) ack, 2);
putInt((int) timeStamp, 6);
putByte((byte)xmlMessageSubType.getType());
putInt((int) sequence, 2);
putInt((int) length, 2);
putBytes(content.getBytes());
// Ack, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort(ack).array());
buff.position(Header.getSize() + 3);
// Timestamp, 6 bytes
buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array());
buff.position(Header.getSize() + 9);
// XML message sub type, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array());
buff.position(Header.getSize() + 10);
// Seq num, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort(sequence).array());
buff.position(Header.getSize() + 12);
// Message length, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort(length).array());
buff.position(Header.getSize() + 14);
// XML Content
buff.put(this.content.getBytes());
buff.position(Header.getSize() + 14 + this.content.getBytes().length);
// calculate CRC
crc.update(buff.array());
// Add CRC to message
buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array());
writeCRC();
// Send
try {
outputStream.write(buff.array());
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
@@ -0,0 +1,26 @@
package seng302.server;
import org.junit.Test;
import seng302.server.messages.Header;
import seng302.server.messages.MessageType;
import static junit.framework.TestCase.assertTrue;
/**
* Tests message header
*/
public class TestHeader {
@Test
public void testHeaderSizeEqualsActualSize(){
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
assertTrue(h.getSize() == h.getByteBuffer().array().length);
}
@Test
public void headerSizeIsSameAsSpec(){
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
assertTrue(h.getSize() == 15); // Spec specifies 15 bytes
}
}
@@ -0,0 +1,136 @@
package seng302.server;
import org.junit.Test;
import seng302.server.messages.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.TestCase.assertTrue;
public class TestMessage {
private static int XML_MESSAGE_LEN = 14;
private static int RACE_STATUS_BASE_LEN = 24;
private static int BOAT_SUB_MESSAGE_LEN = 20;
private static int CRC_LEN = 4;
/**
* Test generated output is the same as the expected output
*/
@Test
public void testHeatBetBufferOutputLength(){
Message m = new Heartbeat(1);
List<Integer> output = new ArrayList<>();
DataOutputStream ds = new DataOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
output.add(b);
}
});
m.send(ds);
assertTrue(output.size() == (m.getSize() + CRC_LEN + Header.getSize()));
}
/**
* Test output expected is the same as the spec
*/
@Test
public void testXmlMessageSize(){
Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1);
assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length()));
}
/**
* Ensure that when no boats are in the race, that only the base message is sent
*/
@Test
public void testRaceStatusMessageBufferLenNoBoats(){
Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1,
0,RaceType.MATCH_RACE,1, new ArrayList<BoatSubMessage>());
List<Integer> output = new ArrayList<>();
DataOutputStream ds = new DataOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
output.add(b);
}
});
m.send(ds);
assertTrue(output.size() == RACE_STATUS_BASE_LEN + Header.getSize() + CRC_LEN);
}
/**
* Test that each boat status is added to the message
*/
@Test
public void testRaceStatusMessageBufferLenWithBoats(){
List<BoatSubMessage> boatMessages = new ArrayList<>();
List<Integer> output = new ArrayList<>();
BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
boatMessages.add(boat1);
boatMessages.add(boat2);
boatMessages.add(boat3);
Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1,
3,RaceType.MATCH_RACE,1, boatMessages);
DataOutputStream ds = new DataOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
output.add(b);
}
});
m.send(ds);
assertTrue(output.size() == (RACE_STATUS_BASE_LEN + (BOAT_SUB_MESSAGE_LEN * 3) + CRC_LEN + Header.getSize()));
}
/**
* IllegalArgumentException should be thrown when numBoatsInRace is smaller
* than the number of boats actually in the race
*/
@Test(expected = IllegalArgumentException.class)
public void testRaceStatusTooManyBoats(){
List<BoatSubMessage> boatMessages = new ArrayList<>();
List<Integer> output = new ArrayList<>();
BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
boatMessages.add(boat1);
boatMessages.add(boat2);
boatMessages.add(boat3);
Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1,
1,RaceType.MATCH_RACE,1, boatMessages);
m.send(new DataOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
System.out.print("");
}
}));
}
}