View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truecommons.shed;
6   
7   import java.nio.ByteBuffer;
8   import java.nio.CharBuffer;
9   import java.nio.charset.CharacterCodingException;
10  import java.nio.charset.CharsetDecoder;
11  import java.nio.charset.CharsetEncoder;
12  import java.nio.charset.CoderResult;
13  import static java.nio.charset.CodingErrorAction.*;
14  import static java.nio.charset.StandardCharsets.*;
15  import javax.annotation.Nullable;
16  import javax.annotation.Nullable;
17  
18  /**
19   * Provides utility functions for encoding and decoding
20   * {@linkplain String strings}, {@linkplain CharBuffer char buffers} and char
21   * arrays to and from {@linkplain ByteBuffer byte buffers} using the
22   * {@linkplain java.nio.charset.StandardCharsets#UTF_8 UTF-8} character set.
23   * Note that all allocated buffers are direct buffers.
24   *
25   * @since  TrueCommons 2.2
26   * @author Christian Schlichtherle
27   */
28  public final class Buffers {
29  
30      private Buffers() { }
31  
32      public static @Nullable ByteBuffer byteBuffer(@Nullable String string) {
33          return null == string ? null : byteBuffer(CharBuffer.wrap(string));
34      }
35  
36      public static @Nullable ByteBuffer byteBuffer(@Nullable char[] password) {
37          return null == password ? null : byteBuffer(CharBuffer.wrap(password));
38      }
39  
40      public static @Nullable ByteBuffer byteBuffer(
41              final @Nullable CharBuffer cb) {
42          if (null == cb) return null;
43          try {
44              return encode(cb, UTF_8.newEncoder()
45                      .onMalformedInput(REPLACE)
46                      .onUnmappableCharacter(REPLACE));
47          } catch (final CharacterCodingException cannotHappen) {
48              throw new AssertionError(cannotHappen);
49          }
50      }
51  
52      private static ByteBuffer encode(
53              final CharBuffer icb,
54              final CharsetEncoder enc)
55      throws CharacterCodingException {
56          int bytes = (int) (icb.remaining() * enc.averageBytesPerChar());
57          while (true) {
58              final ByteBuffer obb = ByteBuffer.allocateDirect(bytes);
59              final CoderResult cr = enc.encode(icb.duplicate(), obb, true);
60              if (cr.isUnderflow())
61                  enc.flush(obb);
62              obb.flip();
63              if (cr.isUnderflow())
64                  return obb;
65              if (!cr.isOverflow())  {
66                  cr.throwException();
67                  throw new AssertionError();
68              }
69              fill(obb, (byte) 0);
70              bytes = 2 * bytes + 1; // ensure progress
71          }
72      }
73  
74      public static @Nullable String string(@Nullable ByteBuffer bb) {
75          return null == bb ? null : charBuffer(bb).toString();
76      }
77  
78      public static @Nullable char[] charArray(
79              final @Nullable ByteBuffer bb) {
80          if (null == bb)
81              return null;
82          final CharBuffer ocb = charBuffer(bb);
83          final char[] oca = new char[ocb.remaining()];
84          ocb.duplicate().get(oca);
85          fill(ocb, (char) 0);
86          return oca;
87      }
88  
89      public static @Nullable CharBuffer charBuffer(
90              final @Nullable ByteBuffer bb) {
91          if (null == bb)
92              return null;
93          final CharsetDecoder dec = UTF_8.newDecoder()
94              .onMalformedInput(REPLACE)
95              .onUnmappableCharacter(REPLACE);
96          try {
97              return decode(bb, dec);
98          } catch (final CharacterCodingException cannotHappen) {
99              throw new AssertionError(cannotHappen);
100         }
101     }
102 
103     private static CharBuffer decode(ByteBuffer ibb, CharsetDecoder dec)
104     throws CharacterCodingException {
105         int bytes = (int) (2 * ibb.remaining() * dec.averageCharsPerByte());
106         while (true) {
107             final CharBuffer ocb = ByteBuffer
108                     .allocateDirect(bytes)
109                     .asCharBuffer();
110             final CoderResult cr = dec.decode(ibb.duplicate(), ocb, true);
111             if (cr.isUnderflow())
112                 dec.flush(ocb);
113             ocb.flip();
114             if (cr.isUnderflow())
115                 return ocb;
116             if (!cr.isOverflow())  {
117                 cr.throwException();
118                 throw new AssertionError();
119             }
120             fill(ocb, (char) 0);
121             bytes = 2 * bytes + 2; // ensure progress
122         }
123     }
124 
125     /**
126      * Overwrites the remaining bytes of the given byte buffer with the
127      * given value.
128      *
129      * @param bb the byte buffer to fill.
130      *        The properties of this buffer remain unchanged.
131      * @param value the byte value to use for filling the buffer.
132      */
133     public static void fill(
134             final @Nullable ByteBuffer bb,
135             final byte value) {
136         if (null == bb)
137             return;
138         final int position = bb.position();
139         final int limit = bb.limit();
140         for (int i = position; i < limit; i++)
141             bb.put(i, value);
142     }
143 
144     /**
145      * Overwrites the remaining characters of the given char buffer with the
146      * given value.
147      *
148      * @param cb the char buffer to fill.
149      *        The properties of this buffer remain unchanged.
150      * @param value the char value to use for filling the buffer.
151      */
152     public static void fill(
153             final @Nullable CharBuffer cb,
154             final char value) {
155         if (null == cb)
156             return;
157         final int position = cb.position();
158         final int limit = cb.limit();
159         for (int i = position; i < limit; i++)
160             cb.put(i, value);
161     }
162 
163     /**
164      * Copies the given byte buffer into a new direct byte buffer.
165      *
166      * @param  bb the byte buffer to copy.
167      *         The properties of this buffer remain unchanged.
168      * @return the new direct byte buffer with the copied data.
169      */
170     public static @Nullable ByteBuffer copy(@Nullable ByteBuffer bb) {
171         return null == bb
172                 ? null
173                 : (ByteBuffer) ByteBuffer
174                     .allocateDirect(bb.remaining())
175                     .put(bb.duplicate())
176                     .rewind();
177     }
178 }