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    *   http://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.netty.util.internal;
17  
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Formatter;
22  import java.util.List;
23  
24  import static io.netty.util.internal.ObjectUtil.checkNotNull;
25  
26  /**
27   * String utility class.
28   */
29  public final class StringUtil {
30  
31      public static final String NEWLINE;
32      public static final char DOUBLE_QUOTE = '\"';
33      public static final char COMMA = ',';
34      public static final char LINE_FEED = '\n';
35      public static final char CARRIAGE_RETURN = '\r';
36      private static final String[] BYTE2HEX_PAD = new String[256];
37      private static final String[] BYTE2HEX_NOPAD = new String[256];
38      private static final String EMPTY_STRING = "";
39      /**
40       * 2 - Quote character at beginning and end.
41       * 5 - Extra allowance for anticipated escape characters that may be added.
42       */
43      private static final int CSV_NUMBER_ESCAPE_CHARACTERS = 2 + 5;
44  
45      static {
46          // Determine the newline character of the current platform.
47          String newLine;
48  
49          try {
50              newLine = new Formatter().format("%n").toString();
51          } catch (Exception e) {
52              // Should not reach here, but just in case.
53              newLine = "\n";
54          }
55  
56          NEWLINE = newLine;
57  
58          // Generate the lookup table that converts a byte into a 2-digit hexadecimal integer.
59          int i;
60          for (i = 0; i < 10; i ++) {
61              StringBuilder buf = new StringBuilder(2);
62              buf.append('0');
63              buf.append(i);
64              BYTE2HEX_PAD[i] = buf.toString();
65              BYTE2HEX_NOPAD[i] = String.valueOf(i);
66          }
67          for (; i < 16; i ++) {
68              StringBuilder buf = new StringBuilder(2);
69              char c = (char) ('a' + i - 10);
70              buf.append('0');
71              buf.append(c);
72              BYTE2HEX_PAD[i] = buf.toString();
73              BYTE2HEX_NOPAD[i] = String.valueOf(c);
74          }
75          for (; i < BYTE2HEX_PAD.length; i ++) {
76              StringBuilder buf = new StringBuilder(2);
77              buf.append(Integer.toHexString(i));
78              String str = buf.toString();
79              BYTE2HEX_PAD[i] = str;
80              BYTE2HEX_NOPAD[i] = str;
81          }
82      }
83  
84      /**
85       * Splits the specified {@link String} with the specified delimiter.  This operation is a simplified and optimized
86       * version of {@link String#split(String)}.
87       */
88      public static String[] split(String value, char delim) {
89          final int end = value.length();
90          final List<String> res = new ArrayList<String>();
91  
92          int start = 0;
93          for (int i = 0; i < end; i ++) {
94              if (value.charAt(i) == delim) {
95                  if (start == i) {
96                      res.add(EMPTY_STRING);
97                  } else {
98                      res.add(value.substring(start, i));
99                  }
100                 start = i + 1;
101             }
102         }
103 
104         if (start == 0) { // If no delimiter was found in the value
105             res.add(value);
106         } else {
107             if (start != end) {
108                 // Add the last element if it's not empty.
109                 res.add(value.substring(start, end));
110             } else {
111                 // Truncate trailing empty elements.
112                 for (int i = res.size() - 1; i >= 0; i --) {
113                     if (res.get(i).isEmpty()) {
114                         res.remove(i);
115                     } else {
116                         break;
117                     }
118                 }
119             }
120         }
121 
122         return res.toArray(new String[res.size()]);
123     }
124 
125     /**
126      * Splits the specified {@link String} with the specified delimiter in maxParts maximum parts.
127      * This operation is a simplified and optimized
128      * version of {@link String#split(String, int)}.
129      */
130     public static String[] split(String value, char delim, int maxParts) {
131         final int end = value.length();
132         final List<String> res = new ArrayList<String>();
133 
134         int start = 0;
135         int cpt = 1;
136         for (int i = 0; i < end && cpt < maxParts; i ++) {
137             if (value.charAt(i) == delim) {
138                 if (start == i) {
139                     res.add(EMPTY_STRING);
140                 } else {
141                     res.add(value.substring(start, i));
142                 }
143                 start = i + 1;
144                 cpt++;
145             }
146         }
147 
148         if (start == 0) { // If no delimiter was found in the value
149             res.add(value);
150         } else {
151             if (start != end) {
152                 // Add the last element if it's not empty.
153                 res.add(value.substring(start, end));
154             } else {
155                 // Truncate trailing empty elements.
156                 for (int i = res.size() - 1; i >= 0; i --) {
157                     if (res.get(i).isEmpty()) {
158                         res.remove(i);
159                     } else {
160                         break;
161                     }
162                 }
163             }
164         }
165 
166         return res.toArray(new String[res.size()]);
167     }
168 
169     /**
170      * Get the item after one char delim if the delim is found (else null).
171      * This operation is a simplified and optimized
172      * version of {@link String#split(String, int)}.
173      */
174     public static String substringAfter(String value, char delim) {
175         int pos = value.indexOf(delim);
176         if (pos >= 0) {
177             return value.substring(pos + 1);
178         }
179         return null;
180     }
181 
182     /**
183      * Converts the specified byte value into a 2-digit hexadecimal integer.
184      */
185     public static String byteToHexStringPadded(int value) {
186         return BYTE2HEX_PAD[value & 0xff];
187     }
188 
189     /**
190      * Converts the specified byte value into a 2-digit hexadecimal integer and appends it to the specified buffer.
191      */
192     public static <T extends Appendable> T byteToHexStringPadded(T buf, int value) {
193         try {
194             buf.append(byteToHexStringPadded(value));
195         } catch (IOException e) {
196             PlatformDependent.throwException(e);
197         }
198         return buf;
199     }
200 
201     /**
202      * Converts the specified byte array into a hexadecimal value.
203      */
204     public static String toHexStringPadded(byte[] src) {
205         return toHexStringPadded(src, 0, src.length);
206     }
207 
208     /**
209      * Converts the specified byte array into a hexadecimal value.
210      */
211     public static String toHexStringPadded(byte[] src, int offset, int length) {
212         return toHexStringPadded(new StringBuilder(length << 1), src, offset, length).toString();
213     }
214 
215     /**
216      * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer.
217      */
218     public static <T extends Appendable> T toHexStringPadded(T dst, byte[] src) {
219         return toHexStringPadded(dst, src, 0, src.length);
220     }
221 
222     /**
223      * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer.
224      */
225     public static <T extends Appendable> T toHexStringPadded(T dst, byte[] src, int offset, int length) {
226         final int end = offset + length;
227         for (int i = offset; i < end; i ++) {
228             byteToHexStringPadded(dst, src[i]);
229         }
230         return dst;
231     }
232 
233     /**
234      * Converts the specified byte value into a hexadecimal integer.
235      */
236     public static String byteToHexString(int value) {
237         return BYTE2HEX_NOPAD[value & 0xff];
238     }
239 
240     /**
241      * Converts the specified byte value into a hexadecimal integer and appends it to the specified buffer.
242      */
243     public static <T extends Appendable> T byteToHexString(T buf, int value) {
244         try {
245             buf.append(byteToHexString(value));
246         } catch (IOException e) {
247             PlatformDependent.throwException(e);
248         }
249         return buf;
250     }
251 
252     /**
253      * Converts the specified byte array into a hexadecimal value.
254      */
255     public static String toHexString(byte[] src) {
256         return toHexString(src, 0, src.length);
257     }
258 
259     /**
260      * Converts the specified byte array into a hexadecimal value.
261      */
262     public static String toHexString(byte[] src, int offset, int length) {
263         return toHexString(new StringBuilder(length << 1), src, offset, length).toString();
264     }
265 
266     /**
267      * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer.
268      */
269     public static <T extends Appendable> T toHexString(T dst, byte[] src) {
270         return toHexString(dst, src, 0, src.length);
271     }
272 
273     /**
274      * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer.
275      */
276     public static <T extends Appendable> T toHexString(T dst, byte[] src, int offset, int length) {
277         assert length >= 0;
278         if (length == 0) {
279             return dst;
280         }
281 
282         final int end = offset + length;
283         final int endMinusOne = end - 1;
284         int i;
285 
286         // Skip preceding zeroes.
287         for (i = offset; i < endMinusOne; i ++) {
288             if (src[i] != 0) {
289                 break;
290             }
291         }
292 
293         byteToHexString(dst, src[i ++]);
294         int remaining = end - i;
295         toHexStringPadded(dst, src, i, remaining);
296 
297         return dst;
298     }
299 
300     /**
301      * The shortcut to {@link #simpleClassName(Class) simpleClassName(o.getClass())}.
302      */
303     public static String simpleClassName(Object o) {
304         if (o == null) {
305             return "null_object";
306         } else {
307             return simpleClassName(o.getClass());
308         }
309     }
310 
311     /**
312      * Generates a simplified name from a {@link Class}.  Similar to {@link Class#getSimpleName()}, but it works fine
313      * with anonymous classes.
314      */
315     public static String simpleClassName(Class<?> clazz) {
316         if (clazz == null) {
317             return "null_class";
318         }
319 
320         Package pkg = clazz.getPackage();
321         if (pkg != null) {
322             return clazz.getName().substring(pkg.getName().length() + 1);
323         } else {
324             return clazz.getName();
325         }
326     }
327 
328     /**
329      * Escapes the specified value, if necessary according to
330      * <a href="https://tools.ietf.org/html/rfc4180#section-2">RFC-4180</a>.
331      *
332      * @param value The value which will be escaped according to
333      *              <a href="https://tools.ietf.org/html/rfc4180#section-2">RFC-4180</a>
334      * @return {@link CharSequence} the escaped value if nesessary, or the value unchanged
335      */
336     public static CharSequence escapeCsv(CharSequence value) {
337         int length = checkNotNull(value, "value").length();
338         if (length == 0) {
339             return value;
340         }
341         int last = length - 1;
342         boolean quoted = isDoubleQuote(value.charAt(0)) && isDoubleQuote(value.charAt(last)) && length != 1;
343         boolean foundSpecialCharacter = false;
344         boolean escapedDoubleQuote = false;
345         StringBuilder escaped = new StringBuilder(length + CSV_NUMBER_ESCAPE_CHARACTERS).append(DOUBLE_QUOTE);
346         for (int i = 0; i < length; i++) {
347             char current = value.charAt(i);
348             switch (current) {
349                 case DOUBLE_QUOTE:
350                     if (i == 0 || i == last) {
351                         if (!quoted) {
352                             escaped.append(DOUBLE_QUOTE);
353                         } else {
354                             continue;
355                         }
356                     } else {
357                         boolean isNextCharDoubleQuote = isDoubleQuote(value.charAt(i + 1));
358                         if (!isDoubleQuote(value.charAt(i - 1)) &&
359                                 (!isNextCharDoubleQuote || isNextCharDoubleQuote && i + 1 == last)) {
360                             escaped.append(DOUBLE_QUOTE);
361                             escapedDoubleQuote = true;
362                         }
363                         break;
364                     }
365                 case LINE_FEED:
366                 case CARRIAGE_RETURN:
367                 case COMMA:
368                     foundSpecialCharacter = true;
369             }
370             escaped.append(current);
371         }
372         return escapedDoubleQuote || foundSpecialCharacter && !quoted ?
373                 escaped.append(DOUBLE_QUOTE) : value;
374     }
375 
376     private static boolean isDoubleQuote(char c) {
377         return c == DOUBLE_QUOTE;
378     }
379 
380     private StringUtil() {
381         // Unused.
382     }
383 }