View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.buffer;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.buffer.api.BufferAllocator;
20  import io.netty5.buffer.api.DefaultBufferAllocators;
21  import io.netty5.buffer.api.internal.Statics;
22  import io.netty5.util.concurrent.FastThreadLocal;
23  import io.netty5.util.internal.PlatformDependent;
24  import io.netty5.util.internal.StringUtil;
25  import io.netty5.util.internal.SystemPropertyUtil;
26  import io.netty5.util.internal.logging.InternalLogger;
27  import io.netty5.util.internal.logging.InternalLoggerFactory;
28  
29  import java.nio.charset.StandardCharsets;
30  
31  import static io.netty5.util.internal.MathUtil.isOutOfBounds;
32  import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero;
33  import static io.netty5.util.internal.StringUtil.NEWLINE;
34  
35  /**
36   * A collection of utility methods that is related with handling {@code ByteBuf},
37   * such as the generation of hex dump and swapping an integer's byte order.
38   */
39  public final class BufferUtil {
40  
41      private static final InternalLogger logger = InternalLoggerFactory.getInstance(BufferUtil.class);
42  
43      private static final int MAX_CHAR_BUFFER_SIZE;
44      private static final int THREAD_LOCAL_BUFFER_SIZE;
45  
46      static {
47          THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty5.threadLocalDirectBufferSize", 0);
48          logger.debug("-Dio.netty5.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
49  
50          MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty5.maxThreadLocalCharBufferSize", 16 * 1024);
51          logger.debug("-Dio.netty5.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
52      }
53  
54      /**
55       * Reverses the byte order (endianness) of an unsigned short
56       * @param value the number with original byte order
57       * @return the number with reversed byte order
58       */
59      public static int reverseUnsignedShort(int value) {
60          return Integer.reverseBytes(value) >>> Short.SIZE;
61      }
62  
63      /**
64       * Reverses the byte order (endianness) of a medium
65       * @param value the number with original byte order
66       * @return the number with reversed byte order
67       */
68      public static int reverseMedium(int value) {
69          return Integer.reverseBytes(value) >> Byte.SIZE;
70      }
71  
72      /**
73       * Reverses the byte order (endianness) of an unsigned medium
74       * @param value the number with original byte order
75       * @return the number with reversed byte order
76       */
77      public static int reverseUnsignedMedium(int value) {
78          return Integer.reverseBytes(value) >>> Byte.SIZE;
79      }
80  
81      /**
82       * Reverses the byte order (endianness) of an unsigned integer
83       * @param value the number with original byte order
84       * @return the number with reversed byte order
85       */
86      public static long reverseUnsignedInt(long value) {
87          return Long.reverseBytes(value) >>> Integer.SIZE;
88      }
89  
90      /**
91       * Returns a <a href="https://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
92       * of the specified buffer's readable bytes.
93       */
94      public static String hexDump(Buffer buffer) {
95          return hexDump(buffer, buffer.readerOffset(), buffer.readableBytes());
96      }
97  
98      /**
99       * Returns a <a href="https://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
100      * of the specified buffer's sub-region.
101      */
102     public static String hexDump(Buffer buffer, int fromIndex, int length) {
103         return HexUtil.hexDump(buffer, fromIndex, length);
104     }
105 
106     /**
107      * Returns a <a href="https://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
108      * of the specified byte array.
109      */
110     public static String hexDump(byte[] array) {
111         return hexDump(array, 0, array.length);
112     }
113 
114     /**
115      * Returns a <a href="https://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
116      * of the specified byte array's sub-region.
117      */
118     public static String hexDump(byte[] array, int fromIndex, int length) {
119         return HexUtil.hexDump(array, fromIndex, length);
120     }
121 
122     /**
123      * Encode a {@link CharSequence} in <a href="https://en.wikipedia.org/wiki/ASCII">ASCII</a> and write
124      * it to a {@link Buffer} allocated with {@code alloc}.
125      * @param alloc The allocator used to allocate a new {@link Buffer}.
126      * @param seq The characters to write into a buffer.
127      * @return The {@link Buffer} which contains the <a href="https://en.wikipedia.org/wiki/ASCII">ASCII</a> encoded
128      * result.
129      */
130     public static Buffer writeAscii(BufferAllocator alloc, CharSequence seq) {
131         return alloc.copyOf(seq.toString(), StandardCharsets.US_ASCII);
132     }
133 
134     /**
135      * Create a copy of the underlying storage from {@code buf} into a byte array.
136      * The copy will start at {@link Buffer#readerOffset()} and copy {@link Buffer#readableBytes()} bytes.
137      */
138     public static byte[] getBytes(Buffer buf) {
139         return getBytes(buf,  buf.readerOffset(), buf.readableBytes());
140     }
141 
142     /**
143      * Create a copy of the underlying storage from {@code buf} into a byte array.
144      * The copy will start at {@code start} and copy {@code length} bytes.
145      */
146     public static byte[] getBytes(Buffer buf, int start, int length) {
147         int capacity = buf.capacity();
148         if (isOutOfBounds(start, length, capacity)) {
149             throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
150                                                 + ") <= " + "buf.capacity(" + capacity + ')');
151         }
152 
153         byte[] bytes = PlatformDependent.allocateUninitializedArray(length);
154         buf.copyInto(start, bytes, 0, length);
155         return bytes;
156     }
157 
158     /**
159      * Returns {@code true} if and only if the two specified buffers are
160      * identical to each other for {@code length} bytes starting at {@code firstReaderOffset}
161      * index for the {@code first} buffer and {@code secondReaderOffset} index for the {@code second} buffer.
162      * A more compact way to express this is:
163      * <p>
164      * {@code first[firstRoff : firstRoff + length] == second[secondRoff : secondRoff + length]}
165      */
166     public static boolean equals(Buffer first, int firstReaderOffset, Buffer second, int secondReaderOffset,
167                                  int length) {
168         return Statics.equals(first, firstReaderOffset, second, secondReaderOffset, length);
169     }
170 
171     /**
172      * Appends the prettified multi-line hexadecimal dump of the specified {@link Buffer} to the specified
173      * {@link StringBuilder} that is easy to read by humans.
174      */
175     public static void appendPrettyHexDump(StringBuilder dump, Buffer buf) {
176         appendPrettyHexDump(dump, buf, buf.readerOffset(), buf.readableBytes());
177     }
178 
179     /**
180      * Appends the prettified multi-line hexadecimal dump of the specified {@link Buffer} to the specified
181      * {@link StringBuilder} that is easy to read by humans, starting at the given {@code offset} using
182      * the given {@code length}.
183      */
184     public static void appendPrettyHexDump(StringBuilder dump, Buffer buf, int offset, int length) {
185         HexUtil.appendPrettyHexDump(dump, buf, offset, length);
186     }
187 
188     /**
189      * Returns a cached thread-local direct buffer, if available.
190      *
191      * @return a cached thread-local direct buffer, if available.  {@code null} otherwise.
192      */
193     public static Buffer threadLocalDirectBuffer() {
194         if (THREAD_LOCAL_BUFFER_SIZE <= 0) {
195             return null;
196         }
197 
198         return ThreadLocalDirectBufferHolder.BUFFER.get();
199     }
200 
201     /* Separate class so that the expensive static initialization is only done when needed */
202     private static final class HexUtil {
203 
204         private static final char[] BYTE2CHAR = new char[256];
205         private static final char[] HEXDUMP_TABLE = new char[256 * 4];
206         private static final String[] HEXPADDING = new String[16];
207         private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
208         private static final String[] BYTE2HEX = new String[256];
209         private static final String[] BYTEPADDING = new String[16];
210 
211         static {
212             final char[] DIGITS = "0123456789abcdef".toCharArray();
213             for (int i = 0; i < 256; i++) {
214                 HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
215                 HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
216             }
217 
218             int i;
219 
220             // Generate the lookup table for hex dump paddings
221             for (i = 0; i < HEXPADDING.length; i++) {
222                 int padding = HEXPADDING.length - i;
223                 HEXPADDING[i] = "   ".repeat(padding);
224             }
225 
226             // Generate the lookup table for the start-offset header in each row (up to 64KiB).
227             for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
228                 StringBuilder buf = new StringBuilder(12);
229                 buf.append(NEWLINE);
230                 buf.append(Long.toHexString(i << 4L & 0xFFFFFFFFL | 0x100000000L));
231                 buf.setCharAt(buf.length() - 9, '|');
232                 buf.append('|');
233                 HEXDUMP_ROWPREFIXES[i] = buf.toString();
234             }
235 
236             // Generate the lookup table for byte-to-hex-dump conversion
237             for (i = 0; i < BYTE2HEX.length; i++) {
238                 BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
239             }
240 
241             // Generate the lookup table for byte dump paddings
242             for (i = 0; i < BYTEPADDING.length; i++) {
243                 int padding = BYTEPADDING.length - i;
244                 BYTEPADDING[i] = " ".repeat(padding);
245             }
246 
247             // Generate the lookup table for byte-to-char conversion
248             for (i = 0; i < BYTE2CHAR.length; i++) {
249                 if (i <= 0x1f || i >= 0x7f) {
250                     BYTE2CHAR[i] = '.';
251                 } else {
252                     BYTE2CHAR[i] = (char) i;
253                 }
254             }
255         }
256 
257         private static String hexDump(Buffer buffer, int fromIndex, int length) {
258             checkPositiveOrZero(length, "length");
259             if (length == 0) {
260               return "";
261             }
262 
263             int endIndex = fromIndex + length;
264             char[] buf = new char[length << 1];
265 
266             int srcIdx = fromIndex;
267             int dstIdx = 0;
268             for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) {
269               System.arraycopy(
270                   HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1,
271                   buf, dstIdx, 2);
272             }
273 
274             return new String(buf);
275         }
276 
277         private static String hexDump(byte[] array, int fromIndex, int length) {
278             checkPositiveOrZero(length, "length");
279             if (length == 0) {
280                 return "";
281             }
282 
283             int endIndex = fromIndex + length;
284             char[] buf = new char[length << 1];
285 
286             int srcIdx = fromIndex;
287             int dstIdx = 0;
288             for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) {
289                 System.arraycopy(
290                     HEXDUMP_TABLE, (array[srcIdx] & 0xFF) << 1,
291                     buf, dstIdx, 2);
292             }
293 
294             return new String(buf);
295         }
296 
297         private static void appendPrettyHexDump(StringBuilder dump, Buffer buf, int offset, int length) {
298             if (isOutOfBounds(offset, length, buf.capacity())) {
299                 throw new IndexOutOfBoundsException(
300                         "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
301                         + ") <= " + "buf.capacity(" + buf.capacity() + ')');
302             }
303             if (length == 0) {
304                 return;
305             }
306             dump.append(
307                     "         +-------------------------------------------------+" +
308                     NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
309                     NEWLINE + "+--------+-------------------------------------------------+----------------+");
310 
311             final int fullRows = length >>> 4;
312             final int remainder = length & 0xF;
313 
314             // Dump the rows which have 16 bytes.
315             for (int row = 0; row < fullRows; row ++) {
316                 int rowStartIndex = (row << 4) + offset;
317 
318                 // Per-row prefix.
319                 appendHexDumpRowPrefix(dump, row, rowStartIndex);
320 
321                 // Hex dump
322                 int rowEndIndex = rowStartIndex + 16;
323                 for (int j = rowStartIndex; j < rowEndIndex; j ++) {
324                     dump.append(BYTE2HEX[buf.getUnsignedByte(j)]);
325                 }
326                 dump.append(" |");
327 
328                 // ASCII dump
329                 for (int j = rowStartIndex; j < rowEndIndex; j ++) {
330                     dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
331                 }
332                 dump.append('|');
333             }
334 
335             // Dump the last row which has less than 16 bytes.
336             if (remainder != 0) {
337                 int rowStartIndex = (fullRows << 4) + offset;
338                 appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
339 
340                 // Hex dump
341                 int rowEndIndex = rowStartIndex + remainder;
342                 for (int j = rowStartIndex; j < rowEndIndex; j ++) {
343                     dump.append(BYTE2HEX[buf.getUnsignedByte(j)]);
344                 }
345                 dump.append(HEXPADDING[remainder]);
346                 dump.append(" |");
347 
348                 // Ascii dump
349                 for (int j = rowStartIndex; j < rowEndIndex; j ++) {
350                     dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
351                 }
352                 dump.append(BYTEPADDING[remainder]);
353                 dump.append('|');
354             }
355 
356             dump.append(NEWLINE +
357                         "+--------+-------------------------------------------------+----------------+");
358         }
359 
360         private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
361             if (row < HEXDUMP_ROWPREFIXES.length) {
362                 dump.append(HEXDUMP_ROWPREFIXES[row]);
363             } else {
364                 dump.append(NEWLINE);
365                 dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
366                 dump.setCharAt(dump.length() - 9, '|');
367                 dump.append('|');
368             }
369         }
370     }
371 
372     /* Separate class so that the expensive static initialisation is only done when needed */
373     private static final class ThreadLocalDirectBufferHolder {
374         static final FastThreadLocal<Buffer> BUFFER = new FastThreadLocal<>() {
375             @Override
376             protected Buffer initialValue() throws Exception {
377                 return DefaultBufferAllocators.offHeapAllocator().allocate(1024);
378             }
379 
380             @Override
381             protected void onRemoval(Buffer value) throws Exception {
382                 if (value.isAccessible()) {
383                     value.close();
384                 }
385                 super.onRemoval(value);
386             }
387         };
388     }
389 
390     private BufferUtil() { }
391 }