/*
* Wrapper.java February 2008
*
* Copyright (C) 2008, Niall Gallagher <niallg@users.sf.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.simpleframework.transport;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.Charset;
/**
* The <code>Wrapper</code> object represents a packet that wraps an
* unmodifiable buffer. This ensures that the contents of the buffer are not
* modified during the use of the packet. To ensure that the buffer can not be
* modified the <code>append</code> methods will always append zero bytes, also
* the <code>write</code> methods do no compact the buffers when some content
* has been written.
*
* @author Niall Gallagher
*/
class Wrapper implements Packet {
/**
* This is the ready only byte buffer that this packet wraps.
*/
private ByteBuffer buffer;
/**
* This is the unique sequence number for this packet.
*/
private long sequence;
/**
* This determines if the packet has already been closed.
*/
private boolean closed;
/**
* This determines if this packet represents a shared one.
*/
private boolean shared;
/**
* Constructor for the <code>Wrapper</code> object. This will create a
* wrapper for the provided buffer which will enable the buffer to be
* written to a byte channel without being modified by the write process.
*
* @param buffer
* this is the buffer that is to be wrapped
* @param sequence
* this is the sequence number for this packet
*/
public Wrapper(ByteBuffer buffer, long sequence) {
this(buffer, sequence, true);
}
/**
* Constructor for the <code>Wrapper</code> object. This will create a
* wrapper for the provided buffer which will enable the buffer to be
* written to a byte channel without being modified by the write process.
*
* @param buffer
* this is the buffer that is to be wrapped
* @param sequence
* this is the sequence number for this packet
*/
public Wrapper(ByteBuffer buffer, long sequence, boolean shared) {
this.sequence = sequence;
this.buffer = buffer;
this.shared = shared;
}
/**
* 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
*/
@Override
public long sequence() {
return this.sequence;
}
/**
* This is used to determine how much space is left to append data to this
* packet. This is typically equivilant 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
*/
@Override
public int space() {
return 0;
}
/**
* 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
* <code>space</code> returns zero, which means that the packet can not have
* bytes appended to it.
*
* @return this is the capacity of other backing byte storage
*/
@Override
public int capacity() {
return this.length();
}
/**
* This is used to determine how many 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
*/
@Override
public int length() {
int offset = this.buffer.position();
int limit = this.buffer.limit();
if (this.closed) return 0;
return limit - offset;
}
/**
* This is used to that packets can be entered in to a priority queue such
* that they are ordered based on their sequence numbers. Ordering based on
* sequence numbers ensures that packets can be remove and inserted back in
* to the equeue without concern for othe order of their insertion.
*
* @param packet
* this is the packet that is to be compared
*
* @return this is negative is less than otherwise its positive
*/
@Override
public int compareTo(Packet packet) {
long other = packet.sequence();
if (other > this.sequence) return -1;
if (this.sequence > other) return 1;
return 0;
}
/**
* This method is used to extract the contents of the packet in to a
* duplicate packet. The purpose of this is to ensure that when a packet
* wraps a shared buffer the contents of that buffer can be drained in to an
* allocated buffer, resulting in a packet that can be used without read
* write conflicts.
*
* @return this returns the packets contents in a new buffer
*/
@Override
public Packet extract() throws IOException {
int length = this.length();
if (length <= 0) throw new PacketException("Buffer is empty");
if (!this.shared) return this;
return this.extract(length);
}
/**
* This method is used to extract the contents of the packet in to a
* duplicate packet. The purpose of this is to ensure that when a packet
* wraps a shared buffer the contents of that buffer can be drained in to an
* allocated buffer, resulting in a packet that can be used without read
* write conflicts.
*
* @param size
* this is the size of the buffer to be extracted
*
* @return this returns the packets contents in a new buffer
*/
private Packet extract(int size) throws IOException {
ByteBuffer data = ByteBuffer.allocate(size);
if (size > 0) {
data.put(this.buffer);
data.position(0);
}
return new Wrapper(data, this.sequence, false);
}
/**
* 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
*/
@Override
public String encode() throws IOException {
return this.encode("UTF-8");
}
/**
* 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
*/
@Override
public String encode(String encoding) throws IOException {
ByteBuffer segment = this.buffer.duplicate();
if (segment == null) return new String();
return this.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 encoding
* this is the character set to use for encoding
* @param buffer
* this is the buffer that will be encoded
*
* @return this returns the bytes sequence as a string object
*/
private String encode(String encoding, ByteBuffer buffer)
throws IOException {
Charset charset = Charset.forName(encoding);
CharBuffer text = charset.decode(buffer);
return text.toString();
}
/**
* This will not append any bytes to the packet. Because this is an
* immutable implementation of the <code>Packet</code> it can not modify the
* underlying buffer. So this will simply return having made no changes to
* either the buffer of the packet.
*
* @param buffer
* this is the buffer containing the bytes
*
* @return returns the number of bytes that have been moved
*/
@Override
public int append(ByteBuffer buffer) throws IOException {
return this.append(buffer, 0);
}
/**
* This will not append any bytes to the packet. Because this is an
* immutable implementation of the <code>Packet</code> it can not modify the
* underlying buffer. So this will simply return having made no changes to
* either the buffer of the packet.
*
* @param buffer
* 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
*/
@Override
public int append(ByteBuffer buffer, int count) throws IOException {
if (this.closed) throw new PacketException("Packet is closed");
return 0;
}
/**
* 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
* <code>length</code> 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
*/
@Override
public int write(ByteChannel channel) throws IOException {
int size = this.length();
if (this.closed) throw new PacketException("Packet is closed");
if (size <= 0) return 0;
return this.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
* <code>length</code> 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
*/
@Override
public int write(ByteChannel channel, int count) throws IOException {
if (this.closed) throw new PacketException("Packet is closed");
return this.write(channel, this.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
* <code>length</code> 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 segment that is to be written
*
* @return this returns the number of bytes that were written
*/
private int write(ByteChannel channel, ByteBuffer segment)
throws IOException {
int require = segment.remaining();
int count = 0;
while (count < require) {
int size = channel.write(segment);
if (size <= 0) {
break;
}
count += size;
}
return count;
}
/**
* This method is used to determine if the buffer is shared with another
* thread or service. It is important to know whether a packet is shared as
* it tells the writer whether it needs to block the writing thread whilst
* the packet is pending a write to the socket channel.
*
* @return true if the buffer is shared with another service
*/
@Override
public boolean isReference() {
return this.shared;
}
/**
* The <code>close</code> method for the packet is used to ensure that any
* resources occupied by the packet are released. This can be subclassed to
* introduce such functionality, however the current implementation does not
* hold any releasable resources.
*/
@Override
public void close() throws IOException {
this.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", this.sequence, this.buffer);
}
}