Moved most of the message handling to other classes.
I'm finding that this code is being used multiple times, so inheritance seems to be warranted.
This commit is contained in:
30
browserCommands/build.gradle
Normal file
30
browserCommands/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
// In this section you declare where to find the dependencies of your project
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// In this section you declare the dependencies for your production and test code
|
||||
dependencies {
|
||||
// The production code uses the SLF4J logging API at compile time
|
||||
implementation 'org.slf4j:slf4j-api:1.7.30'
|
||||
implementation 'org.apache.logging.log4j:log4j-api:2.13.3'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
|
||||
implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3'
|
||||
|
||||
// Declare the dependency for your favourite test framework you want to use in your tests.
|
||||
// TestNG is also supported by the Gradle Test task. Just change the
|
||||
// testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
|
||||
// 'test.useTestNG()' to your build script.
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_16
|
||||
targetCompatibility = JavaVersion.VERSION_16
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MessageHandler implements Runnable, MessageSerializer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
|
||||
|
||||
private final InputStream input;
|
||||
private final OutputStream output;
|
||||
|
||||
private final ExecutorService executor;
|
||||
private final LinkedList<MessageListener> listeners = new LinkedList<>();
|
||||
private final HashMap<Integer, Future<Object>> messageResponses = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a message handler.
|
||||
*
|
||||
* @param input - The input from our external source.
|
||||
* @param output - The output to the external source.
|
||||
*/
|
||||
public MessageHandler(InputStream input, OutputStream output)
|
||||
{
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
BufferedInputStream browserIn = null;
|
||||
BufferedOutputStream browserOut = null;
|
||||
|
||||
boolean running = true;
|
||||
|
||||
byte[][] message;
|
||||
byte[] messageByte;
|
||||
Object messageOb;
|
||||
Future<Object> messageResponse;
|
||||
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
||||
HashSet<Integer> toRemove = new HashSet<>();
|
||||
|
||||
try
|
||||
{
|
||||
browserIn = new BufferedInputStream(this.input);
|
||||
browserOut = new BufferedOutputStream(this.output);
|
||||
|
||||
while (running)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (browserIn.available() > 0)
|
||||
{
|
||||
message = this.readMessage(browserIn);
|
||||
if (message == null)
|
||||
{
|
||||
logger.info("Connection closed.");
|
||||
running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
numBuffer.clear();
|
||||
numBuffer.put(message[0]);
|
||||
messageOb = this.deserializeObject(message[1]);
|
||||
messageResponse = this.triggerListeners(messageOb);
|
||||
synchronized (this.messageResponses)
|
||||
{
|
||||
this.messageResponses.put(numBuffer.getInt(), messageResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e)
|
||||
{
|
||||
logger.error("Could not retrieve message", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns any finished responses.
|
||||
*/
|
||||
synchronized (this.messageResponses)
|
||||
{
|
||||
if (this.messageResponses.size() > 0)
|
||||
{
|
||||
for (Map.Entry<Integer, Future<Object>> responses : this.messageResponses.entrySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (responses.getValue().isDone())
|
||||
{
|
||||
toRemove.add(responses.getKey());
|
||||
messageByte = serializeObject(responses.getValue());
|
||||
writeMessage(browserOut, responses.getKey(), messageByte);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send response message for " + responses.getKey(), e);
|
||||
}
|
||||
}
|
||||
toRemove.forEach(this.messageResponses::remove);
|
||||
toRemove.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
/*
|
||||
* Release locks for any messages still waiting.
|
||||
*/
|
||||
synchronized (this.messageResponses)
|
||||
{
|
||||
this.messageResponses.values().forEach(future -> future.cancel(false));
|
||||
}
|
||||
/*
|
||||
* Close the streams.
|
||||
*/
|
||||
try
|
||||
{
|
||||
if (browserIn != null)
|
||||
{
|
||||
browserIn.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
logger.error("Could not close browser input", e1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (browserOut != null)
|
||||
{
|
||||
browserOut.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
logger.error("Could not close browser output", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the message listeners.
|
||||
*
|
||||
* @param message - The message to send.
|
||||
* @return The value returned by the listeners.
|
||||
*/
|
||||
protected Future<Object> triggerListeners(Object message)
|
||||
{
|
||||
MessageListener[] listeners;
|
||||
Future<Object> returnVal;
|
||||
/*
|
||||
* Copies the list of listeners, so we have an unchanging array without
|
||||
* holding onto the list for the whole time of execution.
|
||||
*/
|
||||
synchronized (this.listeners)
|
||||
{
|
||||
listeners = this.listeners.toArray(MessageListener[]::new);
|
||||
}
|
||||
returnVal = this.executor.submit(() -> {
|
||||
Object returnValue = null;
|
||||
for (MessageListener listener : listeners)
|
||||
{
|
||||
returnValue = listener.onMessage(message, returnValue);
|
||||
}
|
||||
return returnValue;
|
||||
});
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message listener
|
||||
*
|
||||
* @param listener - A callback for when a message arrives. A value needs to
|
||||
* be returned from one of the listeners.
|
||||
*/
|
||||
public void addListener(MessageListener listener)
|
||||
{
|
||||
synchronized (this.listeners)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a listener has been added to the list.
|
||||
*
|
||||
* @param listener - A callback for when a message arrives.
|
||||
* @return True if the provided listener is part of the list, false otherwise.
|
||||
*/
|
||||
public boolean hasListener(MessageListener listener)
|
||||
{
|
||||
synchronized (this.listeners)
|
||||
{
|
||||
return this.listeners.contains(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a message listener from the list.
|
||||
*
|
||||
* @param listener - A callback for when a message arrives.
|
||||
*/
|
||||
public void removeListener(MessageListener listener)
|
||||
{
|
||||
synchronized (this.listeners)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A message listener is called when a message is sent from the remote.
|
||||
*
|
||||
* @see #addListener(MessageListener)
|
||||
* @see #hasListener(MessageListener)
|
||||
* @see #removeListener(MessageListener)
|
||||
*/
|
||||
public interface MessageListener
|
||||
{
|
||||
/**
|
||||
* Called when the remote sends a message and wants a response.
|
||||
*
|
||||
* @param providedValue - The message sent from the remote.
|
||||
* @param previousReturn The value that was returned by the previous
|
||||
* listener called. A value must be returned to
|
||||
* the remote. The listener either has the option
|
||||
* of returning this value, or returning a new
|
||||
* value. If null, then this is either the first
|
||||
* listener called or none of the other listeners
|
||||
* have generated return values.
|
||||
* @return The value to be returned to the remote.
|
||||
*/
|
||||
Object onMessage(Object providedValue, Object previousReturn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class serves as a general inter-process object messaging system.
|
||||
*/
|
||||
public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageRunner.class);
|
||||
|
||||
private final InputStream input;
|
||||
private final OutputStream output;
|
||||
private final Object readLock = new Object();
|
||||
|
||||
/**
|
||||
* This queue serves as a cache for objects we need to send.
|
||||
*/
|
||||
private final LinkedList<MessagePacket> sendQueue = new LinkedList<>();
|
||||
/**
|
||||
* This queue serves as a cache for objects that are waiting for a response.
|
||||
*/
|
||||
private final HashMap<Integer, MessagePacket> sentQueue = new HashMap<>();
|
||||
private int messagesSent = 0;
|
||||
|
||||
/**
|
||||
* Creates a message runner.
|
||||
*
|
||||
* @param input - The input from our external source.
|
||||
* @param output - The output to the external source.
|
||||
*/
|
||||
public MessageRunner(InputStream input, OutputStream output)
|
||||
{
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
BufferedInputStream browserIn = null;
|
||||
BufferedOutputStream browserOut = null;
|
||||
MessagePacket packet;
|
||||
|
||||
boolean running = true;
|
||||
|
||||
byte[][] returnMessage;
|
||||
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
||||
|
||||
try
|
||||
{
|
||||
browserIn = new BufferedInputStream(this.input);
|
||||
browserOut = new BufferedOutputStream(this.output);
|
||||
|
||||
while (running)
|
||||
{
|
||||
/*
|
||||
* Sends a messages
|
||||
*/
|
||||
synchronized (this.sendQueue)
|
||||
{
|
||||
packet = this.sendQueue.poll();
|
||||
}
|
||||
if (packet != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
packet.returnValue.index = this.messagesSent;
|
||||
writeMessage(browserOut, this.messagesSent, packet.message);
|
||||
|
||||
synchronized (this.sentQueue)
|
||||
{
|
||||
this.sentQueue.put(this.messagesSent, packet);
|
||||
this.messagesSent++;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send message " + new String(packet.message, StandardCharsets.UTF_8), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads any available messages
|
||||
*/
|
||||
try
|
||||
{
|
||||
if (!this.sentQueue.isEmpty())
|
||||
{
|
||||
if (browserIn.available() > 0)
|
||||
{
|
||||
/*
|
||||
* Wait for a response from the browser.
|
||||
*/
|
||||
returnMessage = this.readMessage(browserIn);
|
||||
if (returnMessage == null)
|
||||
{
|
||||
logger.info("Connection closed.");
|
||||
running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
numBuffer.clear();
|
||||
numBuffer.put(returnMessage[0]);
|
||||
numBuffer.clear();
|
||||
packet = this.sentQueue.get(numBuffer.getInt());
|
||||
if (packet == null)
|
||||
{
|
||||
numBuffer.clear();
|
||||
logger.warn("Received message {} for nonexistant packet", numBuffer.getInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.returnMessage = returnMessage[1];
|
||||
numBuffer.clear();
|
||||
logger.debug("Reading message {} {}", numBuffer.getInt(), packet.returnMessage);
|
||||
synchronized (this.readLock)
|
||||
{
|
||||
logger.trace("Received message {}, notifying futures.", packet.returnValue.index);
|
||||
this.readLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not retrieve message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
/*
|
||||
* Release locks for any messages still waiting.
|
||||
*/
|
||||
synchronized (this.sendQueue)
|
||||
{
|
||||
while (this.sendQueue.size() > 0)
|
||||
{
|
||||
packet = this.sendQueue.poll();
|
||||
packet.returnMessage = null;
|
||||
}
|
||||
}
|
||||
synchronized (this.readLock)
|
||||
{
|
||||
this.readLock.notifyAll();
|
||||
}
|
||||
/*
|
||||
* Close the streams.
|
||||
*/
|
||||
try
|
||||
{
|
||||
if (browserIn != null)
|
||||
{
|
||||
browserIn.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
logger.error("Could not close browser input", e1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (browserOut != null)
|
||||
{
|
||||
browserOut.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
logger.error("Could not close browser output", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an object to the remote.
|
||||
*
|
||||
* @param message - The message to send.
|
||||
* @return A future for whatever the remote may return.
|
||||
* @throws IOException Should an error occur in serializing the object.
|
||||
*/
|
||||
public Future<Object> sendObject(Object message) throws IOException
|
||||
{
|
||||
byte[] messageData = this.serializeObject(message);
|
||||
MessagePacket packetEntry = new MessagePacket(messageData);
|
||||
MessageFuture future = new MessageFuture(packetEntry);
|
||||
|
||||
synchronized (this.sendQueue)
|
||||
{
|
||||
sendQueue.add(packetEntry);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* This represents a sent message in storage.
|
||||
*/
|
||||
public static class MessagePacket
|
||||
{
|
||||
public final byte[] message;
|
||||
public byte[] returnMessage;
|
||||
public MessageFuture returnValue;
|
||||
|
||||
public MessagePacket(byte[] message)
|
||||
{
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public MessagePacket(byte[] message, MessageFuture returnValue)
|
||||
{
|
||||
this.message = message;
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This future waits for a return message from the runner.
|
||||
*/
|
||||
public class MessageFuture implements Future<Object>
|
||||
{
|
||||
private final MessagePacket packetEntry;
|
||||
private int index;
|
||||
private boolean canceled = false;
|
||||
|
||||
private MessageFuture(MessagePacket packet)
|
||||
{
|
||||
this.packetEntry = packet;
|
||||
packet.returnValue = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
{
|
||||
if (sendQueue.remove(this.packetEntry))
|
||||
{
|
||||
this.canceled = true;
|
||||
if (mayInterruptIfRunning)
|
||||
{
|
||||
logger.trace("Canceling future, notifying self futures.");
|
||||
synchronized (sentQueue)
|
||||
{
|
||||
sentQueue.remove(this.index);
|
||||
}
|
||||
synchronized (readLock)
|
||||
{
|
||||
readLock.notifyAll();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("Canceling future.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
return this.packetEntry.returnMessage != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
if (this.isDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
return deserializeObject(this.packetEntry.returnMessage);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e)
|
||||
{
|
||||
throw new ExecutionException("Error parsing object", e);
|
||||
}
|
||||
}
|
||||
synchronized (readLock)
|
||||
{
|
||||
while (!this.canceled && packetEntry.returnMessage == null)
|
||||
{
|
||||
logger.trace("Future {} waiting for message", this.index);
|
||||
readLock.wait();
|
||||
}
|
||||
}
|
||||
if (packetEntry.returnMessage == null)
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
logger.trace("Future {} has received message", this.index);
|
||||
try
|
||||
{
|
||||
return deserializeObject(this.packetEntry.returnMessage);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e)
|
||||
{
|
||||
throw new ExecutionException("Error parsing object", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
if (this.isDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
return deserializeObject(this.packetEntry.returnMessage);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e)
|
||||
{
|
||||
throw new ExecutionException("Error parsing object", e);
|
||||
}
|
||||
}
|
||||
synchronized (readLock)
|
||||
{
|
||||
while (!this.canceled && packetEntry.returnMessage == null)
|
||||
{
|
||||
logger.trace("Future {} waiting for message", this.index);
|
||||
readLock.wait(unit.toMillis(timeout));
|
||||
}
|
||||
}
|
||||
if (packetEntry.returnMessage == null)
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
logger.trace("Future {} has received message", this.index);
|
||||
try
|
||||
{
|
||||
return deserializeObject(this.packetEntry.returnMessage);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e)
|
||||
{
|
||||
throw new ExecutionException("Error parsing object", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface MessageSerializer
|
||||
{
|
||||
Logger logger = LoggerFactory.getLogger(MessageSerializer.class);
|
||||
|
||||
/**
|
||||
* Converts an object into a form that can be sent.
|
||||
*
|
||||
* @param message - The message to send.
|
||||
* @return The byte array representation of that object.
|
||||
* @throws IOException Should a serialization error occur.
|
||||
*/
|
||||
default byte[] serializeObject(Object message) throws IOException
|
||||
{
|
||||
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream())
|
||||
{
|
||||
try (ObjectOutputStream stream = new ObjectOutputStream(byteStream))
|
||||
{
|
||||
stream.writeObject(message);
|
||||
}
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte stream into an object.
|
||||
*
|
||||
* @param message - The message received.
|
||||
* @return An object.
|
||||
* @throws IOException If there is an error in parsing the message.
|
||||
* @throws ClassNotFoundException If the object is not recognized
|
||||
*/
|
||||
default Object deserializeObject(byte[] message) throws IOException, ClassNotFoundException
|
||||
{
|
||||
try (ByteArrayInputStream byteStream = new ByteArrayInputStream(message))
|
||||
{
|
||||
try (ObjectInputStream stream = new ObjectInputStream(byteStream))
|
||||
{
|
||||
return stream.readObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a message to the output stream.
|
||||
*
|
||||
* @param out - The output stream to write to.
|
||||
* @param messageNum - The ID of the message being sent. This will help keep track of responses.
|
||||
* @param message - The actual message contents to write.
|
||||
* @throws IOException Thrown when an exception occures
|
||||
*/
|
||||
default void writeMessage(OutputStream out, int messageNum, byte[] message) throws IOException
|
||||
{
|
||||
ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
|
||||
logger.debug("Writing message {} {}", messageNum, message);
|
||||
/*
|
||||
* Writes the message number.
|
||||
*/
|
||||
lengthBuffer.clear();
|
||||
lengthBuffer.putInt(messageNum);
|
||||
out.write(lengthBuffer.array());
|
||||
/*
|
||||
* Writes the message length
|
||||
*/
|
||||
/*
|
||||
* Wipe out the buffer
|
||||
*/
|
||||
lengthBuffer.clear();
|
||||
lengthBuffer.put(new byte[4]);
|
||||
lengthBuffer.clear();
|
||||
lengthBuffer.putInt(message.length);
|
||||
out.write(lengthBuffer.array());
|
||||
/*
|
||||
* Writes the message
|
||||
*/
|
||||
out.write(message);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a message from the input stream
|
||||
*
|
||||
* @param in - The input stream to read from.
|
||||
* @return Two byte arrays, each with their own value encoded. The first is
|
||||
* a 4-byte representation of the message number this response corresponds
|
||||
* to. The second array is the message itself. If the stream has been
|
||||
* closed, null is returned.
|
||||
* @throws IOException Thrown when an exception occurs reading a message.
|
||||
*/
|
||||
default byte[][] readMessage(InputStream in) throws IOException
|
||||
{
|
||||
int readLength;
|
||||
ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
|
||||
byte[] messageNum = new byte[4];
|
||||
byte[] message;
|
||||
/*
|
||||
* Reads the message number.
|
||||
*/
|
||||
readLength = in.read(messageNum);
|
||||
if (readLength == 0)
|
||||
{
|
||||
logger.debug("Input stream closed, no more messages.");
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
* Reads the message length
|
||||
*/
|
||||
lengthBuffer.clear();
|
||||
readLength = in.read(lengthBuffer.array());
|
||||
if (readLength == 0)
|
||||
{
|
||||
logger.error("Malformed message, could not get message length.");
|
||||
return null;
|
||||
}
|
||||
message = new byte[lengthBuffer.getInt()];
|
||||
/*
|
||||
* Writes the message
|
||||
*/
|
||||
readLength = in.read(message);
|
||||
if (readLength < message.length)
|
||||
{
|
||||
logger.warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength);
|
||||
}
|
||||
logger.debug("Reading message");
|
||||
return new byte[][] {messageNum, message};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user