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.io.UnsupportedEncodingException;
23  import java.nio.ByteBuffer;
24  import java.nio.CharBuffer;
25  import java.nio.charset.CharacterCodingException;
26  import java.nio.charset.Charset;
27  import java.util.StringTokenizer;
28  
29  /**
30   * <p>
31   * Common <code>String</code> manipulation routines.
32   * </p>
33   * <p/>
34   * <p>
35   * Originally from <a href="http://jakarta.apache.org/turbine/">Turbine</a> and the GenerationJavaCore library.
36   * </p>
37   *
38   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
39   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
40   * @author <a href="mailto:gcoladonato@yahoo.com">Greg Coladonato</a>
41   * @author <a href="mailto:bayard@generationjava.com">Henri Yandell</a>
42   * @author <a href="mailto:ed@codehaus.org">Ed Korthof</a>
43   * @author <a href="mailto:rand_mcneely@yahoo.com">Rand McNeely</a>
44   * @author Stephen Colebourne
45   * @author <a href="mailto:fredrik@westermarck.com">Fredrik Westermarck</a>
46   * @author Holger Krauth
47   * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
48   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
49   * @version $Id: StringUtils.java 8001 2009-01-03 13:17:09Z vsiveton $
50   * @noinspection JavaDoc
51   *               <p/>
52   *               A quick borrow from plexus-utils by Kristian Rosenvold, to restore jdk1.3 compat Threw away all the
53   *               unused stuff.
54   *               <p/>
55   *               NOTE: This class is not part of any api and is public purely for technical reasons !
56   * @since 1.0
57   */
58  public final class StringUtils
59  {
60      public static final String NL = System.getProperty( "line.separator" );
61  
62      private static final byte[] HEX_CHARS = {
63                      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
64  
65      private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
66  
67      // 8-bit charset Latin-1
68      public static final String FORK_STREAM_CHARSET_NAME = "ISO-8859-1";
69  
70      private StringUtils()
71      {
72          throw new IllegalStateException( "no instantiable constructor" );
73      }
74  
75      public static String[] split( String text, String separator )
76      {
77          int max = -1;
78          StringTokenizer tok;
79          if ( separator == null )
80          {
81              // Null separator means we're using StringTokenizer's default
82              // delimiter, which comprises all whitespace characters.
83              tok = new StringTokenizer( text );
84          }
85          else
86          {
87              tok = new StringTokenizer( text, separator );
88          }
89  
90          int listSize = tok.countTokens();
91          if ( max > 0 && listSize > max )
92          {
93              listSize = max;
94          }
95  
96          String[] list = new String[listSize];
97          int i = 0;
98          int lastTokenBegin;
99          int lastTokenEnd = 0;
100         while ( tok.hasMoreTokens() )
101         {
102             if ( max > 0 && i == listSize - 1 )
103             {
104                 // In the situation where we hit the max yet have
105                 // tokens left over in our input, the last list
106                 // element gets all remaining text.
107                 String endToken = tok.nextToken();
108                 lastTokenBegin = text.indexOf( endToken, lastTokenEnd );
109                 list[i] = text.substring( lastTokenBegin );
110                 break;
111             }
112             else
113             {
114                 list[i] = tok.nextToken();
115                 lastTokenBegin = text.indexOf( list[i], lastTokenEnd );
116                 lastTokenEnd = lastTokenBegin + list[i].length();
117             }
118             i++;
119         }
120         return list;
121     }
122 
123     /**
124      * <p>
125      * Checks if a (trimmed) String is <code>null</code> or blank.
126      * </p>
127      *
128      * @param str the String to check
129      * @return <code>true</code> if the String is <code>null</code>, or length zero once trimmed
130      */
131     public static boolean isBlank( String str )
132     {
133         return str == null || str.trim().length() == 0;
134     }
135 
136     /**
137      * <p>
138      * Checks if a (trimmed) String is not <code>null</code> and not blank.
139      * </p>
140      *
141      * @param str the String to check
142      * @return <code>true</code> if the String is not <code>null</code> and length of trimmed
143      * <code>str</code> is not zero.
144      */
145     public static boolean isNotBlank( String str )
146     {
147         return !isBlank( str );
148     }
149 
150     /**
151      * Escape the specified string to a representation that only consists of nicely printable characters, without any
152      * newlines and without a comma.
153      * <p>
154      * The reverse-method is {@link #unescapeString(StringBuilder, CharSequence)}.
155      *
156      * @param target target string buffer. The required space will be up to {@code str.getBytes().length * 5} chars.
157      * @param str String to escape values in, may be {@code null}.
158      */
159     @SuppressWarnings( "checkstyle:magicnumber" )
160     public static void escapeToPrintable( StringBuilder target, CharSequence str )
161     {
162         if ( target == null )
163         {
164             throw new IllegalArgumentException( "The target buffer must not be null" );
165         }
166         if ( str == null )
167         {
168             return;
169         }
170 
171         for ( int i = 0; i < str.length(); i++ )
172         {
173             char c = str.charAt( i );
174 
175             // handle non-nicely printable chars and the comma
176             if ( c < 32 || c > 126 || c == '\\' || c == ',' )
177             {
178                 target.append( '\\' );
179                 target.append( (char) HEX_CHARS[( 0xF000 & c ) >> 12] );
180                 target.append( (char) HEX_CHARS[( 0x0F00 & c ) >> 8] );
181                 target.append( (char) HEX_CHARS[( 0x00F0 & c ) >> 4] );
182                 target.append( (char) HEX_CHARS[( 0x000F & c )] );
183             }
184             else
185             {
186                 target.append( c );
187             }
188         }
189     }
190 
191     /**
192      * Reverses the effect of {@link #escapeToPrintable(StringBuilder, CharSequence)}.
193      *
194      * @param target target string buffer
195      * @param str the String to un-escape, as created by {@link #escapeToPrintable(StringBuilder, CharSequence)}
196      */
197     public static void unescapeString( StringBuilder target, CharSequence str )
198     {
199         if ( target == null )
200         {
201             throw new IllegalArgumentException( "The target buffer must not be null" );
202         }
203         if ( str == null )
204         {
205             return;
206         }
207 
208         for ( int i = 0; i < str.length(); i++ )
209         {
210             char ch = str.charAt( i );
211 
212             if ( ch == '\\' )
213             {
214                 target.append( (char) (
215                                   digit( str.charAt( ++i ) ) << 12
216                                 | digit( str.charAt( ++i ) ) << 8
217                                 | digit( str.charAt( ++i ) ) << 4
218                                 | digit( str.charAt( ++i ) )
219                                 ) );
220             }
221             else
222             {
223                 target.append( ch );
224             }
225         }
226     }
227 
228     private static int digit( char ch )
229     {
230         if ( ch >= 'a' )
231         {
232             return 10 + ch - 'a';
233         }
234         else if ( ch >= 'A' )
235         {
236             return 10 + ch - 'A';
237         }
238         else
239         {
240             return ch - '0';
241         }
242     }
243 
244     /**
245      * Escapes the bytes in the array {@code str} to contain only 'printable' bytes.
246      * <p>
247      * Escaping is done by encoding the non-nicely printable bytes to {@code '\' + upperCaseHexBytes(byte)}.
248      * <p>
249      * A save length of {@code out} is {@code len * 3 + outoff}.
250      * <p>
251      * The reverse-method is {@link #unescapeBytes(byte[], String)}.
252      *
253      * @param out output buffer
254      * @param outoff offset in the output buffer
255      * @param input input buffer
256      * @param off offset in the input buffer
257      * @param len number of bytes to copy from the input buffer
258      * @return number of bytes written to {@code out}
259      */
260     @SuppressWarnings( "checkstyle:magicnumber" )
261     public static int escapeBytesToPrintable( byte[] out, int outoff, byte[] input, int off, int len )
262     {
263         if ( out == null )
264         {
265             throw new IllegalArgumentException( "The output array must not be null" );
266         }
267         if ( input == null || input.length == 0 )
268         {
269             return 0;
270         }
271         int outputPos = outoff;
272         int end = off + len;
273         for ( int i = off; i < end; i++ )
274         {
275             byte b = input[i];
276 
277             // handle non-nicely printable bytes
278             if ( b < 32 || b > 126 || b == '\\' || b == ',' )
279             {
280                 int upper = ( 0xF0 & b ) >> 4;
281                 int lower = ( 0x0F & b );
282                 out[outputPos++] = '\\';
283                 out[outputPos++] = HEX_CHARS[upper];
284                 out[outputPos++] = HEX_CHARS[lower];
285             }
286             else
287             {
288                 out[outputPos++] = b;
289             }
290         }
291 
292         return outputPos - outoff;
293     }
294 
295     /**
296      * Reverses the effect of {@link #escapeBytesToPrintable(byte[], int, byte[], int, int)}.
297      *
298      * @param str the input String
299      * @param charsetName the charset name
300      * @return the number of bytes written to {@code out}
301      */
302     public static ByteBuffer unescapeBytes( String str, String charsetName  )
303     {
304         int outPos = 0;
305 
306         if ( str == null )
307         {
308             return ByteBuffer.wrap( new byte[0] );
309         }
310 
311         byte[] out = new byte[str.length()];
312         for ( int i = 0; i < str.length(); i++ )
313         {
314             char ch = str.charAt( i );
315 
316             if ( ch == '\\' )
317             {
318                 int upper = digit( str.charAt( ++i ) );
319                 int lower = digit( str.charAt( ++i ) );
320                 out[outPos++] = (byte) ( upper << 4 | lower );
321             }
322             else
323             {
324                 out[outPos++] = (byte) ch;
325             }
326         }
327 
328         Charset sourceCharset = Charset.forName( charsetName );
329         if ( !DEFAULT_CHARSET.equals( sourceCharset ) )
330         {
331             CharBuffer decodedFromSourceCharset;
332             try
333             {
334                 decodedFromSourceCharset = sourceCharset.newDecoder().decode( ByteBuffer.wrap( out, 0, outPos ) );
335                 ByteBuffer defaultEncoded = DEFAULT_CHARSET.encode( decodedFromSourceCharset );
336 
337                 return defaultEncoded;
338             }
339             catch ( CharacterCodingException e )
340             {
341                 // ignore and fall through to the non-recoded version
342             }
343         }
344 
345         return ByteBuffer.wrap( out, 0, outPos );
346     }
347 
348     public static byte[] encodeStringForForkCommunication( String string )
349     {
350         try
351         {
352             return string.getBytes( FORK_STREAM_CHARSET_NAME );
353         }
354         catch ( UnsupportedEncodingException e )
355         {
356            throw new RuntimeException( "The JVM must support Charset " + FORK_STREAM_CHARSET_NAME, e );
357         }
358     }
359 
360     /**
361      *
362      * @param buffer     Examined StringBuffer
363      * @param pattern    a pattern which should start in <code>buffer</code>
364      * @return <tt>true</tt> if buffer's literal starts with given pattern
365      */
366     public static boolean startsWith( StringBuffer buffer, String pattern )
367     {
368         if ( buffer.length() < pattern.length() )
369         {
370             return false;
371         }
372         else
373         {
374             for ( int i = 0, len = pattern.length(); i < len; i++ )
375             {
376                 if ( buffer.charAt( i ) != pattern.charAt( i ) )
377                 {
378                     return false;
379                 }
380             }
381             return true;
382         }
383     }
384 }