001    /*
002     * Copyright (C) 2008  Trustin Heuiseung Lee
003     *
004     * This library is free software; you can redistribute it and/or
005     * modify it under the terms of the GNU Lesser General Public
006     * License as published by the Free Software Foundation; either
007     * version 2.1 of the License, or (at your option) any later version.
008     *
009     * This library is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this library; if not, write to the Free Software
016     * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA
017     */
018    package net.gleamynode.netty.buffer;
019    
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.OutputStream;
023    import java.nio.ByteBuffer;
024    import java.nio.ByteOrder;
025    import java.nio.channels.GatheringByteChannel;
026    import java.nio.channels.ScatteringByteChannel;
027    import java.util.ArrayList;
028    import java.util.List;
029    
030    
031    /**
032     * @author The Netty Project (netty@googlegroups.com)
033     * @author Trustin Lee (trustin@gmail.com)
034     *
035     * @version $Rev$, $Date$
036     *
037     */
038    public class CompositeChannelBuffer extends AbstractChannelBuffer {
039    
040        private final ChannelBuffer[] slices;
041        private final int[] indices;
042        private int lastSliceId;
043    
044        public CompositeChannelBuffer(ChannelBuffer... buffers) {
045            if (buffers.length == 0) {
046                throw new IllegalArgumentException("buffers should not be empty.");
047            }
048    
049            ByteOrder expectedEndianness = buffers[0].order();
050            slices = new ChannelBuffer[buffers.length];
051            for (int i = 0; i < buffers.length; i ++) {
052                if (buffers[i].order() != expectedEndianness) {
053                    throw new IllegalArgumentException(
054                            "All buffers must have the same endianness.");
055                }
056                slices[i] = buffers[i].slice();
057            }
058            indices = new int[buffers.length + 1];
059            for (int i = 1; i <= buffers.length; i ++) {
060                indices[i] = indices[i - 1] + slices[i - 1].capacity();
061            }
062            writerIndex(capacity());
063        }
064    
065        private CompositeChannelBuffer(CompositeChannelBuffer buffer) {
066            slices = buffer.slices.clone();
067            indices = buffer.indices.clone();
068            setIndex(buffer.readerIndex(), buffer.writerIndex());
069        }
070    
071        public ByteOrder order() {
072            return slices[0].order();
073        }
074    
075        public int capacity() {
076            return indices[slices.length];
077        }
078    
079        public byte getByte(int index) {
080            int sliceId = sliceId(index);
081            return slices[sliceId].getByte(index - indices[sliceId]);
082        }
083    
084        public short getShort(int index) {
085            int sliceId = sliceId(index);
086            if (index + 2 <= indices[sliceId + 1]) {
087                return slices[sliceId].getShort(index - indices[sliceId]);
088            } else if (order() == ByteOrder.BIG_ENDIAN) {
089                return (short) ((getByte(index) & 0xff) << 8 | getByte(index + 1) & 0xff);
090            } else {
091                return (short) (getByte(index) & 0xff | (getByte(index + 1) & 0xff) << 8);
092            }
093        }
094    
095        public int getMedium(int index) {
096            int sliceId = sliceId(index);
097            if (index + 3 <= indices[sliceId + 1]) {
098                return slices[sliceId].getMedium(index - indices[sliceId]);
099            } else if (order() == ByteOrder.BIG_ENDIAN) {
100                return (getShort(index) & 0xffff) << 8 | getByte(index + 2) & 0xff;
101            } else {
102                return getShort(index) & 0xFFFF | (getByte(index + 2) & 0xFF) << 16;
103            }
104        }
105    
106        public int getInt(int index) {
107            int sliceId = sliceId(index);
108            if (index + 4 <= indices[sliceId + 1]) {
109                return slices[sliceId].getInt(index - indices[sliceId]);
110            } else if (order() == ByteOrder.BIG_ENDIAN) {
111                return (getShort(index) & 0xffff) << 16 | getShort(index + 2) & 0xffff;
112            } else {
113                return getShort(index) & 0xFFFF | (getShort(index + 2) & 0xFFFF) << 16;
114            }
115        }
116    
117        public long getLong(int index) {
118            int sliceId = sliceId(index);
119            if (index + 8 <= indices[sliceId + 1]) {
120                return slices[sliceId].getLong(index - indices[sliceId]);
121            } else if (order() == ByteOrder.BIG_ENDIAN) {
122                return (getInt(index) & 0xffffffffL) << 32 | getInt(index + 4) & 0xffffffffL;
123            } else {
124                return getInt(index) & 0xFFFFFFFFL | (getInt(index + 4) & 0xFFFFFFFFL) << 32;
125            }
126        }
127    
128        public void getBytes(int index, byte[] dst, int dstIndex, int length) {
129            int sliceId = sliceId(index);
130            if (index + length >= capacity()) {
131                throw new IndexOutOfBoundsException();
132            }
133    
134            int i = sliceId;
135            while (length > 0) {
136                ChannelBuffer s = slices[i];
137                int adjustment = indices[i];
138                int localLength = Math.min(length, s.capacity() - (index - adjustment));
139                s.getBytes(index - adjustment, dst, dstIndex, localLength);
140                index += localLength;
141                dstIndex += localLength;
142                length -= localLength;
143                i ++;
144            }
145        }
146    
147        public void getBytes(int index, ByteBuffer dst) {
148            int sliceId = sliceId(index);
149            int limit = dst.limit();
150            int length = dst.remaining();
151            if (index + length >= capacity()) {
152                throw new IndexOutOfBoundsException();
153            }
154    
155            int i = sliceId;
156            try {
157                while (length > 0) {
158                    ChannelBuffer s = slices[i];
159                    int adjustment = indices[i];
160                    int localLength = Math.min(length, s.capacity() - (index - adjustment));
161                    dst.limit(dst.position() + localLength);
162                    s.getBytes(index - adjustment, dst);
163                    index += localLength;
164                    length -= localLength;
165                    i ++;
166                }
167            } finally {
168                dst.limit(limit);
169            }
170        }
171    
172        public void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) {
173            int sliceId = sliceId(index);
174            if (index + length >= capacity()) {
175                throw new IndexOutOfBoundsException();
176            }
177    
178            int i = sliceId;
179            while (length > 0) {
180                ChannelBuffer s = slices[i];
181                int adjustment = indices[i];
182                int localLength = Math.min(length, s.capacity() - (index - adjustment));
183                s.getBytes(index - adjustment, dst, dstIndex, localLength);
184                index += localLength;
185                dstIndex += localLength;
186                length -= localLength;
187                i ++;
188            }
189        }
190    
191        public int getBytes(int index, GatheringByteChannel out, int length)
192                throws IOException {
193            // XXX Gathering write is not supported because of a known issue.
194            //     See http://bugs.sun.com/view_bug.do?bug_id=6210541
195            return out.write(toByteBuffer());
196        }
197    
198        public void getBytes(int index, OutputStream out, int length)
199                throws IOException {
200            int sliceId = sliceId(index);
201            if (index + length >= capacity()) {
202                throw new IndexOutOfBoundsException();
203            }
204    
205            int i = sliceId;
206            while (length > 0) {
207                ChannelBuffer s = slices[i];
208                int adjustment = indices[i];
209                int localLength = Math.min(length, s.capacity() - (index - adjustment));
210                s.getBytes(index - adjustment, out, localLength);
211                index += localLength;
212                length -= localLength;
213                i ++;
214            }
215        }
216    
217        public void setByte(int index, byte value) {
218            int sliceId = sliceId(index);
219            slices[sliceId].setByte(index - indices[sliceId], value);
220        }
221    
222        public void setShort(int index, short value) {
223            int sliceId = sliceId(index);
224            if (index + 2 <= indices[sliceId + 1]) {
225                slices[sliceId].setShort(index - indices[sliceId], value);
226            } else if (order() == ByteOrder.BIG_ENDIAN) {
227                setByte(index, (byte) (value >>> 8));
228                setByte(index + 1, (byte) value);
229            } else {
230                setByte(index    , (byte) value);
231                setByte(index + 1, (byte) (value >>> 8));
232            }
233        }
234    
235        public void setMedium(int index, int value) {
236            int sliceId = sliceId(index);
237            if (index + 3 <= indices[sliceId + 1]) {
238                slices[sliceId].setMedium(index - indices[sliceId], value);
239            } else if (order() == ByteOrder.BIG_ENDIAN) {
240                setShort(index, (short) (value >>> 8));
241                setByte(index + 2, (byte) value);
242            } else {
243                setShort(index    , (short) value);
244                setByte (index + 2, (byte) (value >>> 16));
245            }
246        }
247    
248        public void setInt(int index, int value) {
249            int sliceId = sliceId(index);
250            if (index + 4 <= indices[sliceId + 1]) {
251                slices[sliceId].setInt(index - indices[sliceId], value);
252            } else if (order() == ByteOrder.BIG_ENDIAN) {
253                setShort(index, (short) (value >>> 16));
254                setShort(index + 2, (short) value);
255            } else {
256                setShort(index    , (short) value);
257                setShort(index + 2, (short) (value >>> 16));
258            }
259        }
260    
261        public void setLong(int index, long value) {
262            int sliceId = sliceId(index);
263            if (index + 8 <= indices[sliceId + 1]) {
264                slices[sliceId].setLong(index - indices[sliceId], value);
265            } else if (order() == ByteOrder.BIG_ENDIAN) {
266                setInt(index, (int) (value >>> 32));
267                setInt(index + 4, (int) value);
268            } else {
269                setInt(index    , (int) value);
270                setInt(index + 4, (int) (value >>> 32));
271            }
272        }
273    
274        public void setBytes(int index, byte[] src, int srcIndex, int length) {
275            int sliceId = sliceId(index);
276            if (index + length >= capacity()) {
277                throw new IndexOutOfBoundsException();
278            }
279    
280            int i = sliceId;
281            while (length > 0) {
282                ChannelBuffer s = slices[i];
283                int adjustment = indices[i];
284                int localLength = Math.min(length, s.capacity() - (index - adjustment));
285                s.setBytes(index - adjustment, src, srcIndex, localLength);
286                index += localLength;
287                srcIndex += localLength;
288                length -= localLength;
289                i ++;
290            }
291        }
292    
293        public void setBytes(int index, ByteBuffer src) {
294            int sliceId = sliceId(index);
295            int limit = src.limit();
296            int length = src.remaining();
297            if (index + length >= capacity()) {
298                throw new IndexOutOfBoundsException();
299            }
300    
301            int i = sliceId;
302            try {
303                while (length > 0) {
304                    ChannelBuffer s = slices[i];
305                    int adjustment = indices[i];
306                    int localLength = Math.min(length, s.capacity() - (index - adjustment));
307                    src.limit(src.position() + localLength);
308                    s.setBytes(index - adjustment, src);
309                    index += localLength;
310                    length -= localLength;
311                    i ++;
312                }
313            } finally {
314                src.limit(limit);
315            }
316        }
317    
318        public void setBytes(int index, ChannelBuffer src, int srcIndex, int length) {
319            int sliceId = sliceId(index);
320            if (index + length >= capacity()) {
321                throw new IndexOutOfBoundsException();
322            }
323    
324            int i = sliceId;
325            while (length > 0) {
326                ChannelBuffer s = slices[i];
327                int adjustment = indices[i];
328                int localLength = Math.min(length, s.capacity() - (index - adjustment));
329                s.setBytes(index - adjustment, src, srcIndex, localLength);
330                index += localLength;
331                srcIndex += localLength;
332                length -= localLength;
333                i ++;
334            }
335        }
336    
337        public void setBytes(int index, InputStream in, int length)
338                throws IOException {
339            int sliceId = sliceId(index);
340            if (index + length >= capacity()) {
341                throw new IndexOutOfBoundsException();
342            }
343    
344            int i = sliceId;
345            while (length > 0) {
346                ChannelBuffer s = slices[i];
347                int adjustment = indices[i];
348                int localLength = Math.min(length, s.capacity() - (index - adjustment));
349                s.setBytes(index - adjustment, in, localLength);
350                index += localLength;
351                length -= localLength;
352                i ++;
353            }
354        }
355    
356        public int setBytes(int index, ScatteringByteChannel in, int length)
357                throws IOException {
358            int sliceId = sliceId(index);
359            if (index + length >= capacity()) {
360                throw new IndexOutOfBoundsException();
361            }
362    
363            int i = sliceId;
364            int writtenBytes = 0;
365            while (length > 0) {
366                ChannelBuffer s = slices[i];
367                int adjustment = indices[i];
368                int localLength = Math.min(length, s.capacity() - (index - adjustment));
369                int localWrittenBytes = s.setBytes(index - adjustment, in, localLength);
370                writtenBytes += localWrittenBytes;
371                if (localLength != localWrittenBytes) {
372                    break;
373                }
374                index += localLength;
375                length -= localLength;
376                i ++;
377            }
378    
379            return writtenBytes;
380        }
381    
382        public ChannelBuffer duplicate() {
383            return new CompositeChannelBuffer(this);
384        }
385    
386        public ChannelBuffer copy(int index, int length) {
387            int sliceId = sliceId(index);
388            if (index + length >= capacity()) {
389                throw new IndexOutOfBoundsException();
390            }
391    
392            ChannelBuffer dst = ChannelBuffers.buffer(length);
393            int dstIndex = 0;
394            int i = sliceId;
395    
396            while (length > 0) {
397                ChannelBuffer s = slices[i];
398                int adjustment = indices[i];
399                int localLength = Math.min(length, s.capacity() - (index - adjustment));
400                s.getBytes(index - adjustment, dst, dstIndex, localLength);
401                index += localLength;
402                dstIndex += localLength;
403                length -= localLength;
404                i ++;
405            }
406    
407            dst.writerIndex(dst.capacity());
408            return dst;
409        }
410    
411        public ChannelBuffer slice(int index, int length) {
412            return new SlicedChannelBuffer(this, index, length);
413        }
414    
415        public ByteBuffer toByteBuffer(int index, int length) {
416            ByteBuffer[] buffers = toByteBuffers(index, length);
417            ByteBuffer merged = ByteBuffer.allocate(length);
418            for (ByteBuffer b: buffers) {
419                merged.put(b);
420            }
421            merged.flip();
422            return merged;
423        }
424    
425        @Override
426        public ByteBuffer[] toByteBuffers(int index, int length) {
427            int sliceId = sliceId(index);
428            if (index + length > capacity()) {
429                throw new IndexOutOfBoundsException();
430            }
431    
432            List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(slices.length);
433    
434            int i = sliceId;
435            while (length > 0) {
436                ChannelBuffer s = slices[i];
437                int adjustment = indices[i];
438                int localLength = Math.min(length, s.capacity() - (index - adjustment));
439                buffers.add(s.toByteBuffer(index - adjustment, localLength));
440                index += localLength;
441                length -= localLength;
442                i ++;
443            }
444    
445            return buffers.toArray(new ByteBuffer[buffers.size()]);
446        }
447    
448        private int sliceId(int index) {
449            int lastSliceId = this.lastSliceId;
450            if (index >= indices[lastSliceId]) {
451                if (index < indices[lastSliceId + 1]) {
452                    return lastSliceId;
453                }
454    
455                // Search right
456                for (int i = lastSliceId + 1; i < slices.length; i ++) {
457                    if (index < indices[i + 1]) {
458                        this.lastSliceId = i;
459                        return i;
460                    }
461                }
462            } else {
463                // Search left
464                for (int i = lastSliceId - 1; i >= 0; i --) {
465                    if (index >= indices[i]) {
466                        this.lastSliceId = i;
467                        return i;
468                    }
469                }
470            }
471    
472            throw new IndexOutOfBoundsException();
473        }
474    }