001    /*
002     * Copyright (c) 2003, The Regents of the University of California, through
003     * Lawrence Berkeley National Laboratory (subject to receipt of any required
004     * approvals from the U.S. Dept. of Energy). All rights reserved.
005     */
006    package gov.lbl.dsd.sea.nio.util;
007    
008    import java.nio.Buffer;
009    import java.nio.ByteBuffer;
010    import java.nio.CharBuffer;
011    import java.nio.InvalidMarkException;
012    import java.nio.charset.Charset;
013    
014    /**
015     * Various utilities related to the {@link java.nio.ByteBuffer} buffers; in particular
016     * String conversions using a {@link Charset}.
017     * 
018     * @author whoschek@lbl.gov
019     * @author $Author: hoschek3 $
020     * @version $Revision: 1.5 $, $Date: 2004/06/01 20:47:20 $
021     */
022    public class BufferUtil {
023    
024            private BufferUtil() {} // not instantiable
025    
026            /**
027             * Ensures that a given buffer can hold up to <tt>minCapacity</tt>
028             * bytes.
029             * 
030             * Returns the identical buffer if it can hold at least the number of
031             * bytes specified. Otherwise, returns a new buffer with increased
032             * capacity containing the same bytes, ensuring that it can hold at least
033             * the number of elements specified by the minimum capacity argument.
034             * The new buffer will have a limit equal to the new capacity.
035             * 
036             * Leaves the position and byte order unmodified.
037             * 
038             * @param minCapacity
039             *            the desired minimum capacity.
040             */
041            public static ByteBuffer ensureCapacity(ByteBuffer buffer, int minCapacity) {
042                    int oldCapacity = buffer.capacity();
043                    if (minCapacity > oldCapacity) {
044                            int newCapacity = (oldCapacity * 3) / 2 + 1;
045                            if (newCapacity < minCapacity) {
046                                    newCapacity = minCapacity;
047                            }
048    
049                            ByteBuffer newBuffer = buffer.isDirect() ? ByteBuffer.allocateDirect(newCapacity) : ByteBuffer.allocate(newCapacity);
050                            newBuffer.order(buffer.order());
051                            int pos = buffer.position();
052                            buffer.position(0);
053                            
054                            newBuffer.put(buffer);
055                            
056                            buffer.position(pos);
057                            newBuffer.position(pos);
058                            //newBuffer.limit(buffer.limit());
059                            return newBuffer;
060                    } else {
061                            return buffer;
062                    }
063            }
064    
065            /**
066             * Creates and returns a new buffer containing the remaining bytes in the
067             * given buffers; Leaves the input buffers unmodified.
068             * 
069             * @param buffers -
070             *            the buffers to concatenate
071             * @return the new buffer, ready to read from
072             */
073            public static ByteBuffer concat(ByteBuffer[] buffers) {
074                    int n = 0;
075                    for (int i = 0; i < buffers.length; i++)
076                            n += buffers[i].remaining();
077    
078                    ByteBuffer buf = (n > 0 && buffers[0].isDirect()) ? ByteBuffer
079                                    .allocateDirect(n) : ByteBuffer.allocate(n);
080                    if (n > 0) buf.order(buffers[0].order());
081                    
082                    for (int i = 0; i < buffers.length; i++)
083                            buf.put(buffers[i].duplicate());
084    
085                    buf.flip();
086                    return buf;
087            }
088    
089            /**
090             * The buffer equivalent of {@link System#arraycopy}; 
091             * copies src[srcPos]..src[srcPos+length-1] to dest[destPos]..dest[destPos+length-1],
092             * leaving the mark, position and limit of both src and dest unmodified;
093             * Behaves as expected even if src==dest.
094             * 
095             * @param src the buffer to read from
096             * @param srcPos the index of the first byte to read
097             * @param dest the buffer to write to
098             * @param destPos the index of the first byte to write
099             * @param length the number of bytes to copy
100             */
101            public static void copy(ByteBuffer src, int srcPos, ByteBuffer dest, int destPos, int length) {
102                    if (length == 0) return;
103                    src = src.duplicate();
104                    src.position(srcPos);
105                    src.limit(srcPos + length);
106                    
107                    dest = dest.duplicate();
108                    dest.position(destPos);
109                    dest.limit(destPos + length);
110                    
111                    dest.put(src);
112            }
113            
114            /**
115             * Creates a new buffer that is a deep copy of the remaining bytes in the
116             * given buffer (between index buf.position() and buf.limit()); leaves the
117             * src buffer unmodified. High performance implementation.
118             * 
119             * @param src
120             *            the buffer to copy
121             * @return the new buffer, ready to read from
122             */
123            public static ByteBuffer copy(ByteBuffer src) {
124                    ByteBuffer copy = src.isDirect() ? ByteBuffer
125                                    .allocateDirect(src.remaining()) : ByteBuffer.allocate(src.remaining());
126                    copy.order(src.order());
127                    copy.put(src.duplicate());
128                    copy.flip();
129                    return copy;
130    
131                    //              src = src.duplicate(); // leave unmodified
132                    //              ByteBuffer copy = ByteBuffer.allocate(src.position());
133                    //              src.flip();
134                    //              copy.put(src);
135                    //              return copy;
136            }
137    
138            /**
139             * Returns a string holding a decoded representation of the remaining bytes
140             * in the given buffer (relative bulk operation).
141             * 
142             * @param buffer
143             *            the buffer to convert.
144             * @param  charset the requested charset to convert with 
145             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
146             * @return the string
147             */
148            public static String getString(ByteBuffer buffer, Charset charset) {
149                    return charset.decode(buffer).toString();
150            }
151    
152            /**
153             * Returns whether or not a given buffer has the mark defined.
154             * @param buffer the buffer to check
155             * @return true if it has a mark
156             */
157            public static boolean hasMark(Buffer buffer) {
158                    // unfortunately this seems to be the only way to figure it out :-(
159                    boolean hasMark = true;
160                    int pos = buffer.position();
161                    try {
162                            buffer.reset();
163                            buffer.position(pos);
164                    }
165                    catch (InvalidMarkException e) {
166                            hasMark = false;
167                    }
168                    return hasMark;
169            }
170    
171            /**
172             * Fills the bytes of the encoded string into the given buffer (relative
173             * bulk operation).
174             * 
175             * @param buffer
176             *            the buffer to fill into.
177             * @param str
178             *            the string to convert.
179             * @param  charset the requested charset to convert with 
180             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
181             */
182            public static void putString(ByteBuffer buffer, String str, Charset charset) {
183                    charset.newEncoder().encode(CharBuffer.wrap(str), buffer, true);
184                    
185                    // inefficient, would allocate temporary buffer memory:
186                    // buffer.put(toByteBuffer(str, charset));
187            }       
188    
189            /**
190             * Creates and returns a byte array filled with the remaining bytes of given
191             * buffer (between index buf.position() and buf.limit()); leaves the src
192             * buffer unmodified. High performance implementation.
193             * 
194             * @param src
195             *            the bytebuffer to read from
196             * @return the byte array
197             */
198            public static byte[] toByteArray(ByteBuffer src) {
199                    byte[] copy = new byte[src.remaining()];
200                    src.duplicate().get(copy);
201                    return copy;
202    
203                    //              src = src.duplicate(); // leave unmodified
204                    //              byte[] copy = new byte[src.position()];
205                    //              src.flip();
206                    //              src.get(copy);
207                    //              return copy;
208            }
209    
210            /**
211             * Returns a bytebuffer holding the encoded bytes of the given string.
212             * 
213             * @param str the string to convert.
214             * @param  charset the requested charset to convert with 
215             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
216             * @return the byte buffer
217             */
218            public static ByteBuffer toByteBuffer(String str, Charset charset) {
219                    return charset.encode(CharBuffer.wrap(str));
220            }
221    
222            /**
223             * Returns a byte array holding the encoded bytes of the given string.
224             * 
225             * @param str the string to convert.
226             * @param  charset the requested charset to convert with 
227             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
228             * @return the byte array
229             */
230            public static byte[] toByteArray(String str, Charset charset) {
231                    return toByteArray(toByteBuffer(str, charset));
232            }
233    
234            /**
235             * Returns a decoded string representation of the bytes in the given buffer;
236             * leaves the buffer unmodified.
237             * 
238             * @param buffer the buffer to convert
239             * @param  charset the requested charset to convert with 
240             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
241             * @return the string representation
242             */
243            public static String toString(ByteBuffer buffer, Charset charset) {
244                    return charset.decode(buffer.duplicate()).toString();
245            }
246    
247            /**
248             * Returns a decoded string representation of the given bytes.
249             * 
250             * @param bytes
251             *            the bytes to convert
252             * @param offset
253             *            The offset of the subarray to be used; must be non-negative
254             *            and no larger than <tt>bytes</tt>.
255             * @param length
256             *            The length of the subarray to be used; must be non-negative
257             *            and no larger than <tt>bytes.length - offset</tt>.
258             * @param charset
259             *            the requested charset to convert with (e.g.
260             *            Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
261             * @return the string representation
262             */
263            public static String toString(byte[] bytes, int offset, int length, Charset charset) {
264                    return toString(ByteBuffer.wrap(bytes, offset, length), charset);
265            }
266    
267            /**
268             * Returns a decoded string representation of the given bytes.
269             * 
270             * @param bytes the bytes to convert
271             * @param  charset the requested charset to convert with 
272             *                      (e.g. Charset.forName("US-ASCII"), Charset.forName("UTF-8"))
273             * @return the string representation
274             */
275            public static String toString(byte[] bytes, Charset charset) {
276                    return toString(bytes, 0, bytes.length, charset);
277            }
278    
279    //      public static void main(String[] args) {
280    //      int runs = Integer.parseInt(args[0]);
281    //      ByteBuffer buf = ByteBuffer.allocate(1);
282    //      buf.mark();
283    //      System.out.println("hasMark = "+ hasMark(buf));
284    //      long start = System.currentTimeMillis();
285    //      int k=0;
286    //      for (int i=0; i < runs; i++) {
287    //              if (hasMark(buf)) k++;
288    //      }
289    //      long time = System.currentTimeMillis() - start;
290    //      System.out.println("time = "+ (time / 1000.0f));
291    //      System.out.println("iters/s = "+ runs / (time / 1000.0f));
292    //      System.out.println(k);
293    //}
294    
295    }
296