View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.nio.ByteBuffer;
23  import java.nio.charset.Charset;
24  
25  import org.eclipse.jetty.util.log.Log;
26  import org.eclipse.jetty.util.log.Logger;
27  
28  /** Fast String Utilities.
29   *
30   * These string utilities provide both conveniance methods and
31   * performance improvements over most standard library versions. The
32   * main aim of the optimizations is to avoid object creation unless
33   * absolutely required.
34   *
35   * 
36   */
37  public class StringUtil
38  {
39      private static final Logger LOG = Log.getLogger(StringUtil.class);
40      
41      
42      private final static StringMap<String> CHARSETS= new StringMap<String>(true);
43      
44      public static final String ALL_INTERFACES="0.0.0.0";
45      public static final String CRLF="\015\012";
46      public static final String __LINE_SEPARATOR=
47          System.getProperty("line.separator","\n");
48         
49      public static final String __ISO_8859_1="ISO-8859-1";
50      public final static String __UTF8="UTF-8";
51      public final static String __UTF16="UTF-16";
52      
53      public final static Charset __UTF8_CHARSET;
54      public final static Charset __ISO_8859_1_CHARSET;
55      public final static Charset __UTF16_CHARSET;
56      
57      static
58      {
59          __UTF8_CHARSET=Charset.forName(__UTF8);
60          __ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
61          __UTF16_CHARSET=Charset.forName(__UTF16);
62          
63          CHARSETS.put("UTF-8",__UTF8);
64          CHARSETS.put("UTF8",__UTF8);
65          CHARSETS.put("UTF-16",__UTF16);
66          CHARSETS.put("UTF16",__UTF16);
67          CHARSETS.put("ISO-8859-1",__ISO_8859_1);
68          CHARSETS.put("ISO_8859_1",__ISO_8859_1);
69      }
70      
71      /* ------------------------------------------------------------ */
72      /** Convert alternate charset names (eg utf8) to normalized
73       * name (eg UTF-8).
74       */
75      public static String normalizeCharset(String s)
76      {
77          String n=CHARSETS.get(s);
78          return (n==null)?s:n;
79      }
80      
81      /* ------------------------------------------------------------ */
82      /** Convert alternate charset names (eg utf8) to normalized
83       * name (eg UTF-8).
84       */
85      public static String normalizeCharset(String s,int offset,int length)
86      {
87          String n=CHARSETS.get(s,offset,length);       
88          return (n==null)?s.substring(offset,offset+length):n;
89      }
90  
91      /* ------------------------------------------------------------ */
92      /** Convert alternate charset names (eg utf8) to normalized
93       * name (eg UTF-8).
94       */
95      public static String normalizeCharset(ByteBuffer b,int position,int length)
96      {
97          ByteBuffer ro=b.asReadOnlyBuffer();
98          ro.limit(ro.capacity());
99          ro.position(position);
100         ro.limit(position+length);
101         String n=CHARSETS.get(ro); 
102         if (n!=null)
103             return n;
104         ByteBuffer slice = b.slice();
105         slice.position(position);
106         slice.limit(position+length);
107         return BufferUtil.toString(slice,__UTF8_CHARSET);
108     }
109     
110     
111     
112     public static char[] lowercases = {
113           '\000','\001','\002','\003','\004','\005','\006','\007',
114           '\010','\011','\012','\013','\014','\015','\016','\017',
115           '\020','\021','\022','\023','\024','\025','\026','\027',
116           '\030','\031','\032','\033','\034','\035','\036','\037',
117           '\040','\041','\042','\043','\044','\045','\046','\047',
118           '\050','\051','\052','\053','\054','\055','\056','\057',
119           '\060','\061','\062','\063','\064','\065','\066','\067',
120           '\070','\071','\072','\073','\074','\075','\076','\077',
121           '\100','\141','\142','\143','\144','\145','\146','\147',
122           '\150','\151','\152','\153','\154','\155','\156','\157',
123           '\160','\161','\162','\163','\164','\165','\166','\167',
124           '\170','\171','\172','\133','\134','\135','\136','\137',
125           '\140','\141','\142','\143','\144','\145','\146','\147',
126           '\150','\151','\152','\153','\154','\155','\156','\157',
127           '\160','\161','\162','\163','\164','\165','\166','\167',
128           '\170','\171','\172','\173','\174','\175','\176','\177' };
129 
130     /* ------------------------------------------------------------ */
131     /**
132      * fast lower case conversion. Only works on ascii (not unicode)
133      * @param s the string to convert
134      * @return a lower case version of s
135      */
136     public static String asciiToLowerCase(String s)
137     {
138         char[] c = null;
139         int i=s.length();
140 
141         // look for first conversion
142         while (i-->0)
143         {
144             char c1=s.charAt(i);
145             if (c1<=127)
146             {
147                 char c2=lowercases[c1];
148                 if (c1!=c2)
149                 {
150                     c=s.toCharArray();
151                     c[i]=c2;
152                     break;
153                 }
154             }
155         }
156 
157         while (i-->0)
158         {
159             if(c[i]<=127)
160                 c[i] = lowercases[c[i]];
161         }
162         
163         return c==null?s:new String(c);
164     }
165 
166 
167     /* ------------------------------------------------------------ */
168     public static boolean startsWithIgnoreCase(String s,String w)
169     {
170         if (w==null)
171             return true;
172         
173         if (s==null || s.length()<w.length())
174             return false;
175         
176         for (int i=0;i<w.length();i++)
177         {
178             char c1=s.charAt(i);
179             char c2=w.charAt(i);
180             if (c1!=c2)
181             {
182                 if (c1<=127)
183                     c1=lowercases[c1];
184                 if (c2<=127)
185                     c2=lowercases[c2];
186                 if (c1!=c2)
187                     return false;
188             }
189         }
190         return true;
191     }
192     
193     /* ------------------------------------------------------------ */
194     public static boolean endsWithIgnoreCase(String s,String w)
195     {
196         if (w==null)
197             return true;
198 
199         if (s==null)
200             return false;
201             
202         int sl=s.length();
203         int wl=w.length();
204         
205         if (sl<wl)
206             return false;
207         
208         for (int i=wl;i-->0;)
209         {
210             char c1=s.charAt(--sl);
211             char c2=w.charAt(i);
212             if (c1!=c2)
213             {
214                 if (c1<=127)
215                     c1=lowercases[c1];
216                 if (c2<=127)
217                     c2=lowercases[c2];
218                 if (c1!=c2)
219                     return false;
220             }
221         }
222         return true;
223     }
224     
225     /* ------------------------------------------------------------ */
226     /**
227      * returns the next index of a character from the chars string
228      */
229     public static int indexFrom(String s,String chars)
230     {
231         for (int i=0;i<s.length();i++)
232            if (chars.indexOf(s.charAt(i))>=0)
233               return i;
234         return -1;
235     }
236     
237     /* ------------------------------------------------------------ */
238     /**
239      * replace substrings within string.
240      */
241     public static String replace(String s, String sub, String with)
242     {
243         int c=0;
244         int i=s.indexOf(sub,c);
245         if (i == -1)
246             return s;
247     
248         StringBuilder buf = new StringBuilder(s.length()+with.length());
249 
250         do
251         {
252             buf.append(s.substring(c,i));
253             buf.append(with);
254             c=i+sub.length();
255         } while ((i=s.indexOf(sub,c))!=-1);
256 
257         if (c<s.length())
258             buf.append(s.substring(c,s.length()));
259 
260         return buf.toString();
261         
262     }
263 
264 
265     /* ------------------------------------------------------------ */
266     /** Remove single or double quotes.
267      */
268     public static String unquote(String s)
269     {
270         return QuotedStringTokenizer.unquote(s);
271     }
272 
273 
274     /* ------------------------------------------------------------ */
275     /** Append substring to StringBuilder 
276      * @param buf StringBuilder to append to
277      * @param s String to append from
278      * @param offset The offset of the substring
279      * @param length The length of the substring
280      */
281     public static void append(StringBuilder buf,
282                               String s,
283                               int offset,
284                               int length)
285     {
286         synchronized(buf)
287         {
288             int end=offset+length;
289             for (int i=offset; i<end;i++)
290             {
291                 if (i>=s.length())
292                     break;
293                 buf.append(s.charAt(i));
294             }
295         }
296     }
297 
298     
299     /* ------------------------------------------------------------ */
300     /**
301      * append hex digit
302      * 
303      */
304     public static void append(StringBuilder buf,byte b,int base)
305     {
306         int bi=0xff&b;
307         int c='0'+(bi/base)%base;
308         if (c>'9')
309             c= 'a'+(c-'0'-10);
310         buf.append((char)c);
311         c='0'+bi%base;
312         if (c>'9')
313             c= 'a'+(c-'0'-10);
314         buf.append((char)c);
315     }
316 
317     /* ------------------------------------------------------------ */
318     public static void append2digits(StringBuffer buf,int i)
319     {
320         if (i<100)
321         {
322             buf.append((char)(i/10+'0'));
323             buf.append((char)(i%10+'0'));
324         }
325     }
326     
327     /* ------------------------------------------------------------ */
328     public static void append2digits(StringBuilder buf,int i)
329     {
330         if (i<100)
331         {
332             buf.append((char)(i/10+'0'));
333             buf.append((char)(i%10+'0'));
334         }
335     }
336     
337     /* ------------------------------------------------------------ */
338     /** Return a non null string.
339      * @param s String
340      * @return The string passed in or empty string if it is null. 
341      */
342     public static String nonNull(String s)
343     {
344         if (s==null)
345             return "";
346         return s;
347     }
348     
349     /* ------------------------------------------------------------ */
350     public static boolean equals(String s,char[] buf, int offset, int length)
351     {
352         if (s.length()!=length)
353             return false;
354         for (int i=0;i<length;i++)
355             if (buf[offset+i]!=s.charAt(i))
356                 return false;
357         return true;
358     }
359 
360     /* ------------------------------------------------------------ */
361     public static String toUTF8String(byte[] b,int offset,int length)
362     {
363         return new String(b,offset,length,__UTF8_CHARSET);
364     }
365 
366     /* ------------------------------------------------------------ */
367     public static String toString(byte[] b,int offset,int length,String charset)
368     {
369         try
370         {
371             return new String(b,offset,length,charset);
372         }
373         catch (UnsupportedEncodingException e)
374         {
375             throw new IllegalArgumentException(e);
376         }
377     }
378 
379     /* ------------------------------------------------------------ */
380     /**
381      * Test if a string is null or only has whitespace characters in it.
382      * <p>
383      * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
384      * 
385      * <pre>
386      *   isBlank(null)   == true
387      *   isBlank("")     == true
388      *   isBlank("\r\n") == true
389      *   isBlank("\t")   == true
390      *   isBlank("   ")  == true
391      *   isBlank("a")    == false
392      *   isBlank(".")    == false
393      *   isBlank(";\n")  == false
394      * </pre>
395      * 
396      * @param str
397      *            the string to test.
398      * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
399      */
400     public static boolean isBlank(String str)
401     {
402         if (str == null)
403         {
404             return true;
405         }
406         int len = str.length();
407         for (int i = 0; i < len; i++)
408         {
409             if (!Character.isWhitespace(str.codePointAt(i)))
410             {
411                 // found a non-whitespace, we can stop searching  now
412                 return false;
413             }
414         }
415         // only whitespace
416         return true;
417     }
418     
419     /* ------------------------------------------------------------ */
420     /**
421      * Test if a string is not null and contains at least 1 non-whitespace characters in it.
422      * <p>
423      * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
424      * 
425      * <pre>
426      *   isNotBlank(null)   == false
427      *   isNotBlank("")     == false
428      *   isNotBlank("\r\n") == false
429      *   isNotBlank("\t")   == false
430      *   isNotBlank("   ")  == false
431      *   isNotBlank("a")    == true
432      *   isNotBlank(".")    == true
433      *   isNotBlank(";\n")  == true
434      * </pre>
435      * 
436      * @param str
437      *            the string to test.
438      * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
439      */
440     public static boolean isNotBlank(String str)
441     {
442         if (str == null)
443         {
444             return false;
445         }
446         int len = str.length();
447         for (int i = 0; i < len; i++)
448         {
449             if (!Character.isWhitespace(str.codePointAt(i)))
450             {
451                 // found a non-whitespace, we can stop searching  now
452                 return true;
453             }
454         }
455         // only whitespace
456         return false;
457     }
458 
459     /* ------------------------------------------------------------ */
460     public static boolean isUTF8(String charset)
461     {
462         return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
463     }
464 
465 
466     /* ------------------------------------------------------------ */
467     public static String printable(String name)
468     {
469         if (name==null)
470             return null;
471         StringBuilder buf = new StringBuilder(name.length());
472         for (int i=0;i<name.length();i++)
473         {
474             char c=name.charAt(i);
475             if (!Character.isISOControl(c))
476                 buf.append(c);
477         }
478         return buf.toString();
479     }
480     
481     /* ------------------------------------------------------------ */
482     public static String printable(byte[] b)
483     {
484         StringBuilder buf = new StringBuilder();
485         for (int i=0;i<b.length;i++)
486         {
487             char c=(char)b[i];
488             if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
489                 buf.append(c);
490             else 
491             {
492                 buf.append("0x");
493                 TypeUtil.toHex(b[i],buf);
494             }
495         }
496         return buf.toString();
497     }
498     
499     public static byte[] getBytes(String s)
500     {
501         return s.getBytes(__ISO_8859_1_CHARSET);
502     }
503     
504     public static byte[] getUtf8Bytes(String s)
505     {
506         return s.getBytes(__UTF8_CHARSET);
507     }
508     
509     public static byte[] getBytes(String s,String charset)
510     {
511         try
512         {
513             return s.getBytes(charset);
514         }
515         catch(Exception e)
516         {
517             LOG.warn(e);
518             return s.getBytes();
519         }
520     }
521     
522     
523     
524     /**
525      * Converts a binary SID to a string SID
526      * 
527      * http://en.wikipedia.org/wiki/Security_Identifier
528      * 
529      * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
530      */
531     public static String sidBytesToString(byte[] sidBytes)
532     {
533         StringBuilder sidString = new StringBuilder();
534         
535         // Identify this as a SID
536         sidString.append("S-");
537         
538         // Add SID revision level (expect 1 but may change someday)
539         sidString.append(Byte.toString(sidBytes[0])).append('-');
540         
541         StringBuilder tmpBuilder = new StringBuilder();
542         
543         // crunch the six bytes of issuing authority value
544         for (int i = 2; i <= 7; ++i)
545         {
546             tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
547         }
548         
549         sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
550    
551         // the number of subAuthorities we need to attach
552         int subAuthorityCount = sidBytes[1];
553 
554         // attach each of the subAuthorities
555         for (int i = 0; i < subAuthorityCount; ++i)
556         {
557             int offset = i * 4;
558             tmpBuilder.setLength(0);
559             // these need to be zero padded hex and little endian
560             tmpBuilder.append(String.format("%02X%02X%02X%02X", 
561                     (sidBytes[11 + offset] & 0xFF),
562                     (sidBytes[10 + offset] & 0xFF),
563                     (sidBytes[9 + offset] & 0xFF),
564                     (sidBytes[8 + offset] & 0xFF)));  
565             sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
566         }
567         
568         return sidString.toString();
569     }
570     
571     /**
572      * Converts a string SID to a binary SID
573      * 
574      * http://en.wikipedia.org/wiki/Security_Identifier
575      * 
576      * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
577      */
578     public static byte[] sidStringToBytes( String sidString )
579     {
580         String[] sidTokens = sidString.split("-");
581         
582         int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
583         
584         int byteCount = 0;
585         byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
586         
587         // the revision byte
588         sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
589 
590         // the # of sub authorities byte
591         sidBytes[byteCount++] = (byte)subAuthorityCount;
592 
593         // the certAuthority
594         String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
595         
596         while( hexStr.length() < 12) // pad to 12 characters
597         {
598             hexStr = "0" + hexStr;
599         }
600 
601         // place the certAuthority 6 bytes
602         for ( int i = 0 ; i < hexStr.length(); i = i + 2)
603         {
604             sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
605         }
606                 
607         
608         for ( int i = 3; i < sidTokens.length ; ++i)
609         {
610             hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
611             
612             while( hexStr.length() < 8) // pad to 8 characters
613             {
614                 hexStr = "0" + hexStr;
615             }     
616             
617             // place the inverted sub authorities, 4 bytes each
618             for ( int j = hexStr.length(); j > 0; j = j - 2)
619             {          
620                 sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
621             }
622         }
623       
624         return sidBytes;
625     }
626     
627 
628     /**
629      * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
630      * 
631      * @param string
632      *            A String containing an integer.
633      * @return an int
634      */
635     public static int toInt(String string)
636     {
637         int val = 0;
638         boolean started = false;
639         boolean minus = false;
640 
641         for (int i = 0; i < string.length(); i++)
642         {
643             char b = string.charAt(i);
644             if (b <= ' ')
645             {
646                 if (started)
647                     break;
648             }
649             else if (b >= '0' && b <= '9')
650             {
651                 val = val * 10 + (b - '0');
652                 started = true;
653             }
654             else if (b == '-' && !started)
655             {
656                 minus = true;
657             }
658             else
659                 break;
660         }
661 
662         if (started)
663             return minus?(-val):val;
664         throw new NumberFormatException(string);
665     }
666 
667     /**
668      * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
669      * 
670      * @param string
671      *            A String containing an integer.
672      * @return an int
673      */
674     public static long toLong(String string)
675     {
676         long val = 0;
677         boolean started = false;
678         boolean minus = false;
679 
680         for (int i = 0; i < string.length(); i++)
681         {
682             char b = string.charAt(i);
683             if (b <= ' ')
684             {
685                 if (started)
686                     break;
687             }
688             else if (b >= '0' && b <= '9')
689             {
690                 val = val * 10L + (b - '0');
691                 started = true;
692             }
693             else if (b == '-' && !started)
694             {
695                 minus = true;
696             }
697             else
698                 break;
699         }
700 
701         if (started)
702             return minus?(-val):val;
703         throw new NumberFormatException(string);
704     }
705     
706     /**
707      * Truncate a string to a max size.
708      * 
709      * @param str the string to possibly truncate
710      * @param maxSize the maximum size of the string
711      * @return the truncated string.  if <code>str</code> param is null, then the returned string will also be null.
712      */
713     public static String truncate(String str, int maxSize)
714     {
715         if (str == null)
716         {
717             return null;
718         }
719 
720         if (str.length() <= maxSize)
721         {
722             return str;
723         }
724 
725         return str.substring(0,maxSize);
726     }
727 
728 }