/*
* SocketBufferAppender.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 static org.simpleframework.transport.TransportEvent.WRITE;
import static org.simpleframework.transport.TransportEvent.WRITE_BUFFER;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.Charset;
import org.simpleframework.transport.trace.Trace;
/**
* The <code>SocketBufferAppender</code> represents a buffer fragment
* collector. This provides write access to a direct byte buffer which
* is used to collect fragments. Once a sufficient amount of data
* has been collected by this then can be written out to a channel.
*
* @author Niall Gallagher
*/
class SocketBufferAppender {
/**
* This is the buffer used to store the contents of the buffer.
*/
private ByteBuffer buffer;
/**
* This is the trace used to watch the buffering events.
*/
private Trace trace;
/**
* This represents the the initial size of the buffer to use.
*/
private int chunk;
/**
* This represents the largest this appender can grow to.
*/
private int limit;
/**
* Constructor for the <code>SocketBufferAppender</code> object. This
* is used to create an appender that can collect smaller fragments
* in to a larger buffer so that it can be delivered more efficiently.
*
* @param socket this is the socket to append data for
* @param chunk this is the initial size of the buffer
* @param limit this is the maximum size of the buffer
*/
public SocketBufferAppender(Socket socket, int chunk, int limit) {
this.buffer = ByteBuffer.allocateDirect(chunk);
this.trace = socket.getTrace();
this.chunk = chunk;
this.limit = limit;
}
/**
* This is used to determine how much space is left to append
* data to this buffer. This is typically equivalent to capacity
* minus the length. However in the event that the buffer 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() {
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 <code>space</code> returns zero, which
* means that the buffer can not have bytes appended to it.
*
* @return this is the capacity of other backing byte storage
*/
public int capacity() {
return buffer.capacity();
}
/**
* This is used to determine how mnay bytes remain within this
* buffer. It represents the number of write ready bytes, so if
* the length is greater than zero the buffer can be written to
* a byte channel. When length is zero the buffer can be closed.
*
* @return this is the number of bytes remaining in this buffer
*/
public int length() {
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 buffers this can be used.
*
* @return this returns the bytes sequence as a string object
*/
public String encode() throws IOException {
return 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 buffers 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 IOException {
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 buffers this can be used.
*
* @param encoding 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 IOException {
Charset charset = Charset.forName(encoding);
CharBuffer text = charset.decode(segment);
return text.toString();
}
/**
* This will append bytes within the given buffer to the buffer.
* Once invoked the buffer 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 buffer 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 IOException {
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 buffer.
* Once invoked the buffer 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 buffer 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 IOException {
ByteBuffer segment = data.slice();
int mark = data.position();
int size = mark + count;
if(count > 0) {
if(trace != null) {
trace.trace(WRITE_BUFFER, count);
}
data.position(size);
segment.limit(count);
buffer.put(segment);
}
return count;
}
/**
* This write method will write the contents of the buffer to the
* provided byte channel. If the whole buffer can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the buffer after a write
* can be acquired from the <code>length</code> method. Once all
* of the bytes are written the buffer must be closed.
*
* @param channel this is the channel to write the buffer to
*
* @return this returns the number of bytes that were written
*/
public int write(ByteChannel channel) throws IOException {
int size = length();
if(size <= 0) {
return 0;
}
return write(channel, size);
}
/**
* This write method will write the contents of the buffer to the
* provided byte channel. If the whole buffer can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the buffer after a write
* can be acquired from the <code>length</code> method. Once all
* of the bytes are written the buffer must be closed.
*
* @param channel this is the channel to write the buffer 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 IOException {
if(count > 0) {
buffer.flip();
} else {
return 0;
}
return write(channel, buffer);
}
/**
* This write method will write the contents of the buffer to the
* provided byte channel. If the whole buffer can be be written
* then this will simply return the number of bytes that have.
* The number of bytes remaining within the buffer after a write
* can be acquired from the <code>length</code> method. Once all
* of the bytes are written the buffer must be closed.
*
* @param channel this is the channel to write the buffer 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 IOException {
int require = segment.remaining();
int count = 0;
while(count < require) {
int size = channel.write(segment);
if(size <= 0) {
break;
}
if(trace != null) {
trace.trace(WRITE, size);
}
count += size;
}
if(count >= 0) {
segment.compact();
}
return count;
}
}