View Javadoc
1   package org.apache.maven.surefire.util.internal;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.nio.ByteBuffer;
23  import java.nio.CharBuffer;
24  import java.nio.charset.CharacterCodingException;
25  import java.nio.charset.Charset;
26  import java.util.StringTokenizer;
27  
28  import static java.nio.charset.StandardCharsets.ISO_8859_1;
29  
30  /**
31   * <p>
32   * Common {@link String java.lang.String} manipulation routines.
33   * </p>
34   * <br>
35   * <p>
36   * Originally from <a href="http://jakarta.apache.org/turbine/">Turbine</a> and the GenerationJavaCore library.
37   * </p>
38   * <br>
39   * NOTE: This class is not part of any api and is public purely for technical reasons !
40   *
41   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
42   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
43   * @author <a href="mailto:gcoladonato@yahoo.com">Greg Coladonato</a>
44   * @author <a href="mailto:bayard@generationjava.com">Henri Yandell</a>
45   * @author <a href="mailto:ed@codehaus.org">Ed Korthof</a>
46   * @author <a href="mailto:rand_mcneely@yahoo.com">Rand McNeely</a>
47   * @author Stephen Colebourne
48   * @author <a href="mailto:fredrik@westermarck.com">Fredrik Westermarck</a>
49   * @author Holger Krauth
50   * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
51   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
52   * @version $Id: StringUtils.java 8001 2009-01-03 13:17:09Z vsiveton $
53   * @since 1.0
54   */
55  public final class StringUtils
56  {
57      public static final String NL = System.getProperty( "line.separator" );
58  
59      private static final byte[] HEX_CHARS = {
60                      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
61  
62      private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
63  
64      private StringUtils()
65      {
66          throw new IllegalStateException( "no instantiable constructor" );
67      }
68  
69      public static String[] split( String text, String separator )
70      {
71          final StringTokenizer tok;
72          if ( separator == null )
73          {
74              // Null separator means we're using StringTokenizer's default
75              // delimiter, which comprises all whitespace characters.
76              tok = new StringTokenizer( text );
77          }
78          else
79          {
80              tok = new StringTokenizer( text, separator );
81          }
82  
83          String[] list = new String[tok.countTokens()];
84          for ( int i = 0; tok.hasMoreTokens(); i++ )
85          {
86              list[i] = tok.nextToken();
87          }
88          return list;
89      }
90  
91      /**
92       * <p>
93       * Checks if a (trimmed) String is {@code null} or blank.
94       * </p>
95       *
96       * @param str the String to check
97       * @return {@code true} if the String is {@code null}, or length zero once trimmed
98       */
99      public static boolean isBlank( String str )
100     {
101         return str == null || str.trim().isEmpty();
102     }
103 
104     /**
105      * <p>
106      * Checks if a (trimmed) String is not {@code null} and not blank.
107      * </p>
108      *
109      * @param str the String to check
110      * @return {@code true} if the String is not {@code null} and length of trimmed {@code str} is not zero.
111      */
112     public static boolean isNotBlank( String str )
113     {
114         return !isBlank( str );
115     }
116 
117     /**
118      * Escape the specified string to a representation that only consists of nicely printable characters, without any
119      * newlines and without a comma.
120      * <p>
121      * The reverse-method is {@link #unescapeString(StringBuilder, CharSequence)}.
122      *
123      * @param target target string buffer. The required space will be up to {@code str.getBytes().length * 5} chars.
124      * @param str String to escape values in, may be {@code null}.
125      */
126     @SuppressWarnings( "checkstyle:magicnumber" )
127     public static void escapeToPrintable( StringBuilder target, CharSequence str )
128     {
129         if ( target == null )
130         {
131             throw new IllegalArgumentException( "The target buffer must not be null" );
132         }
133         if ( str == null )
134         {
135             return;
136         }
137 
138         for ( int i = 0; i < str.length(); i++ )
139         {
140             char c = str.charAt( i );
141 
142             // handle non-nicely printable chars and the comma
143             if ( c < 32 || c > 126 || c == '\\' || c == ',' )
144             {
145                 target.append( '\\' );
146                 target.append( (char) HEX_CHARS[( 0xF000 & c ) >> 12] );
147                 target.append( (char) HEX_CHARS[( 0x0F00 & c ) >> 8] );
148                 target.append( (char) HEX_CHARS[( 0x00F0 & c ) >> 4] );
149                 target.append( (char) HEX_CHARS[( 0x000F & c )] );
150             }
151             else
152             {
153                 target.append( c );
154             }
155         }
156     }
157 
158     /**
159      * Reverses the effect of {@link #escapeToPrintable(StringBuilder, CharSequence)}.
160      *
161      * @param target target string buffer
162      * @param str the String to un-escape, as created by {@link #escapeToPrintable(StringBuilder, CharSequence)}
163      */
164     public static void unescapeString( StringBuilder target, CharSequence str )
165     {
166         if ( target == null )
167         {
168             throw new IllegalArgumentException( "The target buffer must not be null" );
169         }
170         if ( str == null )
171         {
172             return;
173         }
174 
175         for ( int i = 0; i < str.length(); i++ )
176         {
177             char ch = str.charAt( i );
178 
179             if ( ch == '\\' )
180             {
181                 target.append( (char) (
182                                   digit( str.charAt( ++i ) ) << 12
183                                 | digit( str.charAt( ++i ) ) << 8
184                                 | digit( str.charAt( ++i ) ) << 4
185                                 | digit( str.charAt( ++i ) )
186                                 ) );
187             }
188             else
189             {
190                 target.append( ch );
191             }
192         }
193     }
194 
195     private static int digit( char ch )
196     {
197         if ( ch >= 'a' )
198         {
199             return 10 + ch - 'a';
200         }
201         else if ( ch >= 'A' )
202         {
203             return 10 + ch - 'A';
204         }
205         else
206         {
207             return ch - '0';
208         }
209     }
210 
211     /**
212      * Escapes the bytes in the array {@code input} to contain only 'printable' bytes.
213      * <br>
214      * Escaping is done by encoding the non-nicely printable bytes to {@code '\' + upperCaseHexBytes(byte)}.
215      * <br>
216      * The reverse-method is {@link #unescapeBytes(String, String)}.
217      * <br>
218      * The returned byte array is started with aligned sequence {@code header} and finished by {@code \n}.
219      *
220      * @param header prefix header
221      * @param input input buffer
222      * @param off offset in the input buffer
223      * @param len number of bytes to copy from the input buffer
224      * @return number of bytes written to {@code out}
225      * @throws NullPointerException if the specified parameter {@code header} or {@code input} is null
226      * @throws IndexOutOfBoundsException if {@code off} or {@code len} is out of range
227      *         ({@code off < 0 || len < 0 || off >= input.length || len > input.length || off + len > input.length})
228      */
229     @SuppressWarnings( "checkstyle:magicnumber" )
230     public static EncodedArray escapeBytesToPrintable( final byte[] header, final byte[] input, final int off,
231                                                        final int len )
232     {
233         if ( input.length == 0 )
234         {
235             return EncodedArray.EMPTY;
236         }
237         if ( off < 0 || len < 0 || off >= input.length || len > input.length || off + len > input.length )
238         {
239             throw new IndexOutOfBoundsException(
240                     "off < 0 || len < 0 || off >= input.length || len > input.length || off + len > input.length" );
241         }
242         // Hex-escaping can be up to 3 times length of a regular byte. Last character is '\n', see (+1).
243         final byte[] encodeBytes = new byte[header.length + 3 * len + 1];
244         System.arraycopy( header, 0, encodeBytes, 0, header.length );
245         int outputPos = header.length;
246         final int end = off + len;
247         for ( int i = off; i < end; i++ )
248         {
249             final byte b = input[i];
250 
251             // handle non-nicely printable bytes
252             if ( b < 32 || b > 126 || b == '\\' || b == ',' )
253             {
254                 final int upper = ( 0xF0 & b ) >> 4;
255                 final int lower = ( 0x0F & b );
256                 encodeBytes[outputPos++] = '\\';
257                 encodeBytes[outputPos++] = HEX_CHARS[upper];
258                 encodeBytes[outputPos++] = HEX_CHARS[lower];
259             }
260             else
261             {
262                 encodeBytes[outputPos++] = b;
263             }
264         }
265         encodeBytes[outputPos++] = (byte) '\n';
266 
267         return new EncodedArray( encodeBytes, outputPos );
268     }
269 
270     /**
271      * Reverses the effect of {@link #escapeBytesToPrintable(byte[], byte[], int, int)}.
272      *
273      * @param str the input String
274      * @param charsetName the charset name
275      * @return the number of bytes written to {@code out}
276      */
277     public static ByteBuffer unescapeBytes( String str, String charsetName  )
278     {
279         int outPos = 0;
280 
281         if ( str == null )
282         {
283             return ByteBuffer.wrap( new byte[0] );
284         }
285 
286         byte[] out = new byte[str.length()];
287         for ( int i = 0; i < str.length(); i++ )
288         {
289             char ch = str.charAt( i );
290 
291             if ( ch == '\\' )
292             {
293                 int upper = digit( str.charAt( ++i ) );
294                 int lower = digit( str.charAt( ++i ) );
295                 out[outPos++] = (byte) ( upper << 4 | lower );
296             }
297             else
298             {
299                 out[outPos++] = (byte) ch;
300             }
301         }
302 
303         Charset sourceCharset = Charset.forName( charsetName );
304         if ( !DEFAULT_CHARSET.equals( sourceCharset ) )
305         {
306             CharBuffer decodedFromSourceCharset;
307             try
308             {
309                 decodedFromSourceCharset = sourceCharset.newDecoder().decode( ByteBuffer.wrap( out, 0, outPos ) );
310                 return DEFAULT_CHARSET.encode( decodedFromSourceCharset );
311             }
312             catch ( CharacterCodingException e )
313             {
314                 // ignore and fall through to the non-recoded version
315             }
316         }
317 
318         return ByteBuffer.wrap( out, 0, outPos );
319     }
320 
321     public static byte[] encodeStringForForkCommunication( String string )
322     {
323         return string.getBytes( ISO_8859_1 );
324     }
325 
326     /**
327      * Determines if {@code buffer} starts with specific literal(s).
328      *
329      * @param buffer     Examined StringBuffer
330      * @param pattern    a pattern which should start in {@code buffer}
331      * @return    {@code true} if buffer's literal starts with given {@code pattern}, or both are empty.
332      */
333     public static boolean startsWith( StringBuffer buffer, String pattern )
334     {
335         if ( buffer.length() < pattern.length() )
336         {
337             return false;
338         }
339         else
340         {
341             for ( int i = 0, len = pattern.length(); i < len; i++ )
342             {
343                 if ( buffer.charAt( i ) != pattern.charAt( i ) )
344                 {
345                     return false;
346                 }
347             }
348             return true;
349         }
350     }
351 
352     /**
353      * Escaped string to byte array with offset 0 and certain length.
354      */
355     public static final class EncodedArray
356     {
357         private static final EncodedArray EMPTY = new EncodedArray( new byte[]{}, 0 );
358 
359         private final byte[] array;
360         private final int size;
361 
362         private EncodedArray( byte[] array, int size )
363         {
364             this.array = array;
365             this.size = size;
366         }
367 
368         public byte[] getArray()
369         {
370             return array;
371         }
372 
373         public int getSize()
374         {
375             return size;
376         }
377     }
378 }