
org.simpleframework.transport.Appender Maven / Gradle / Ivy
/*
* Appender.java February 2008
*
* Copyright (C) 2008, Niall Gallagher
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package org.simpleframework.transport;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.Charset;
/**
* The Appender
represents a packet that wraps a pooled
* byte buffer. This implementation provides write access to the
* underlying buffer, so it can modify its contents if required. This
* means that the append
methods can add data to the
* buffer, and that the buffer can be compacted if required.
*
* @author Niall Gallagher
*/
class Appender implements Packet {
/**
* This is the buffer used to store the contents of the packet.
*/
private ByteBuffer buffer;
/**
* This is the manager object that is used to recycle buffers.
*/
private Recycler manager;
/**
* This represents the sequence number used by this packet.
*/
private long sequence;
/**
* This determines whether the packet has been closed already.
*/
private boolean closed;
/**
* Constructor for the Appender
object. This creates
* a packet with a recyclable buffer. The buffer provided can be
* modified up until such point as it is recycled. To recycle the
* buffer the packet must be closed.
*
* @param buffer this is the buffer used by this packet
* @param manager this is the buffer pool to pass the buffer to
* @param sequence this is the unique sequence number for this
*/
public Appender(ByteBuffer buffer, Recycler manager, long sequence) {
this.sequence = sequence;
this.manager = manager;
this.buffer = buffer;
}
/**
* The sequence number represents the order with which this is
* to be delivered to the underlying network. This allows safer
* transfer of packets in an asynchronous environment where it may
* be possible for a packet to be written out of sequence. The
* sequence number also determines the order of closure.
*
* @return this returns an increasing packet sequence number
*/
public long sequence() {
return sequence;
}
/**
* This is used to determine how much space is left to append
* data to this packet. This is typically equivalent to capacity
* minus the length. However in the event that the packet uses
* a private memory store that can not be written to then this
* can return zero regardless of the capacity and length.
*
* @return the space left within the buffer to append data to
*/
public int space() {
if(closed) {
return 0;
}
return buffer.remaining();
}
/**
* This represents the capacity of the backing store. The buffer
* is full when length is equal to capacity and it can typically
* be appended to when the length is less than the capacity. The
* only exception is when space
returns zero, which
* means that the packet can not have bytes appended to it.
*
* @return this is the capacity of other backing byte storage
*/
public int capacity() {
if(closed) {
return 0;
}
return buffer.capacity();
}
/**
* This is used to determine how mnay bytes remain within this
* packet. It represents the number of write ready bytes, so if
* the length is greater than zero the packet can be written to
* a byte channel. When length is zero the packet can be closed.
*
* @return this is the number of bytes remaining in this packet
*/
public int length() {
if(closed) {
return 0;
}
return capacity() - space();
}
/**
* This is used to encode the underlying byte sequence to text.
* Converting the byte sequence to text can be useful when either
* debugging what exactly is being sent. Also, for transports
* that require string delivery of packets this can be used.
*
* @return this returns the bytes sequence as a string object
*/
public String encode() throws Exception {
return encode("ISO-8859-1");
}
/**
* This is used to encode the underlying byte sequence to text.
* Converting the byte sequence to text can be useful when either
* debugging what exactly is being sent. Also, for transports
* that require string delivery of packets this can be used.
*
* @param encoding this is the character set to use for encoding
*
* @return this returns the bytes sequence as a string object
*/
public String encode(String encoding) throws Exception {
ByteBuffer segment = buffer.duplicate();
if(segment != null) {
segment.flip();
}
return encode(encoding, segment);
}
/**
* This is used to encode the underlying byte sequence to text.
* Converting the byte sequence to text can be useful when either
* debugging what exactly is being sent. Also, for transports
* that require string delivery of packets this can be used.
*
* @param charset this is the character set to use for encoding
* @param segment this is the buffer that is to be encoded
*
* @return this returns the bytes sequence as a string object
*/
private String encode(String encoding, ByteBuffer segment) throws Exception {
Charset charset = Charset.forName(encoding);
CharBuffer text = charset.decode(segment);
return text.toString();
}
/**
* This will append bytes within the given buffer to the packet.
* Once invoked the packet will contain the buffer bytes, which
* will have been drained from the buffer. This effectively moves
* the bytes in the buffer to the end of the packet instance.
*
* @param data this is the buffer containing the bytes
*
* @return returns the number of bytes that have been moved
*/
public int append(ByteBuffer data) throws Exception {
int require = data.remaining();
int space = space();
if(require > space) {
require = space;
}
return append(data, require);
}
/**
* This will append bytes within the given buffer to the packet.
* Once invoked the packet will contain the buffer bytes, which
* will have been drained from the buffer. This effectively moves
* the bytes in the buffer to the end of the packet instance.
*
* @param data this is the buffer containing the bytes
* @param count this is the number of bytes that should be used
*
* @return returns the number of bytes that have been moved
*/
public int append(ByteBuffer data, int count) throws Exception {
ByteBuffer segment = data.slice();
if(closed) {
throw new PacketException("Packet has been closed");
}
int mark = data.position();
int size = mark + count;
if(count > 0) {
data.position(size);
segment.limit(count);
buffer.put(segment);
}
return count;
}
/**
* This write method will write the contents of the packet to the
* provided byte channel. If the whole packet can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the packet after a write
* can be acquired from the length
method. Once all
* of the bytes are written the packet must be closed.
*
* @param channel this is the channel to write the packet to
*
* @return this returns the number of bytes that were written
*/
public int write(ByteChannel channel) throws Exception {
int size = length();
if(size <= 0) {
return 0;
}
return write(channel, size);
}
/**
* This write method will write the contents of the packet to the
* provided byte channel. If the whole packet can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the packet after a write
* can be acquired from the length
method. Once all
* of the bytes are written the packet must be closed.
*
* @param channel this is the channel to write the packet to
* @param count the number of bytes to write to the channel
*
* @return this returns the number of bytes that were written
*/
public int write(ByteChannel channel, int count) throws Exception {
if(closed) {
throw new PacketException("Packet has been closed");
}
if(count > 0) {
buffer.flip();
} else {
return 0;
}
return write(channel, buffer);
}
/**
* This write method will write the contents of the packet to the
* provided byte channel. If the whole packet can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the packet after a write
* can be acquired from the length
method. Once all
* of the bytes are written the packet must be closed.
*
* @param channel this is the channel to write the packet to
* @param segment this is the buffer that is to be written
*
* @return this returns the number of bytes that were written
*/
private int write(ByteChannel channel, ByteBuffer segment) throws Exception {
int require = segment.remaining();
int count = 0;
while(count < require) {
int size = channel.write(segment);
if(size <= 0) {
break;
}
count += size;
}
if(count >= 0) {
segment.compact();
}
return count;
}
/**
* The close
method for the packet is used to ensure
* that any resources occupied by the packet are released. The
* resources held by this instance include pooled buffers. If the
* packet is not closed on completion then this can result in a
* leak of resources within the associated transport.
*/
public void close() {
if(!closed) {
manager.recycle(buffer);
}
closed = true;
}
/**
* Provides a string representation of the state of the packet.
* This can be useful for debugging the state transitions that a
* packet will go through when being written and appended to.
*
* @return this returns a string representation for the packet
*/
@Override
public String toString() {
return String.format("%s %s", sequence, buffer);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy