summaryrefslogtreecommitdiffstats
path: root/simple/simple-transport/src/main/java/org/simpleframework/transport/SocketBufferAppender.java
blob: 1b9c27952e92e8947c38384ae00e039a02b2467f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
 * 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;
   }
}