001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.internal; 014 015import static org.apache.juneau.internal.ThrowableUtils.*; 016 017import java.io.*; 018import java.lang.reflect.*; 019import java.math.*; 020import java.net.*; 021import java.nio.*; 022import java.nio.charset.*; 023import java.text.*; 024import java.util.*; 025import java.util.concurrent.atomic.*; 026import java.util.regex.*; 027 028import javax.xml.bind.*; 029 030import org.apache.juneau.*; 031import org.apache.juneau.json.*; 032import org.apache.juneau.parser.*; 033import org.apache.juneau.parser.ParseException; 034 035/** 036 * Reusable string utility methods. 037 */ 038public final class StringUtils { 039 040 private static final AsciiSet numberChars = AsciiSet.create("-xX.+-#pP0123456789abcdefABCDEF"); 041 private static final AsciiSet firstNumberChars =AsciiSet.create("+-.#0123456789"); 042 private static final AsciiSet octChars = AsciiSet.create("01234567"); 043 private static final AsciiSet decChars = AsciiSet.create("0123456789"); 044 private static final AsciiSet hexChars = AsciiSet.create("0123456789abcdefABCDEF"); 045 046 // Maps 6-bit nibbles to BASE64 characters. 047 private static final char[] base64m1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 048 049 // Characters that do not need to be URL-encoded 050 private static final AsciiSet unencodedChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.!~*'()\\").build(); 051 052 // Maps BASE64 characters to 6-bit nibbles. 053 private static final byte[] base64m2 = new byte[128]; 054 static { 055 for (int i = 0; i < 64; i++) 056 base64m2[base64m1[i]] = (byte)i; 057 } 058 059 /** 060 * Parses a number from the specified reader stream. 061 * 062 * @param r The reader to parse the string from. 063 * @param type 064 * The number type to created. 065 * Can be any of the following: 066 * <ul> 067 * <li> Integer 068 * <li> Double 069 * <li> Float 070 * <li> Long 071 * <li> Short 072 * <li> Byte 073 * <li> BigInteger 074 * <li> BigDecimal 075 * </ul> 076 * If <jk>null</jk>, uses the best guess. 077 * @throws IOException If a problem occurred trying to read from the reader. 078 * @return The parsed number. 079 * @throws Exception 080 */ 081 public static Number parseNumber(ParserReader r, Class<? extends Number> type) throws Exception { 082 return parseNumber(parseNumberString(r), type); 083 } 084 085 /** 086 * Reads a numeric string from the specified reader. 087 * 088 * @param r The reader to read form. 089 * @return The parsed number string. 090 * @throws Exception 091 */ 092 public static String parseNumberString(ParserReader r) throws Exception { 093 r.mark(); 094 int c = 0; 095 while (true) { 096 c = r.read(); 097 if (c == -1) 098 break; 099 if (! numberChars.contains((char)c)) { 100 r.unread(); 101 break; 102 } 103 } 104 return r.getMarked(); 105 } 106 107 /** 108 * Parses a number from the specified string. 109 * 110 * @param s The string to parse the number from. 111 * @param type 112 * The number type to created. 113 * Can be any of the following: 114 * <ul> 115 * <li> Integer 116 * <li> Double 117 * <li> Float 118 * <li> Long 119 * <li> Short 120 * <li> Byte 121 * <li> BigInteger 122 * <li> BigDecimal 123 * </ul> 124 * If <jk>null</jk> or <code>Number</code>, uses the best guess. 125 * @return The parsed number, or <jk>null</jk> if the string was null. 126 * @throws ParseException 127 */ 128 public static Number parseNumber(String s, Class<? extends Number> type) throws ParseException { 129 if (s == null) 130 return null; 131 if (s.isEmpty()) 132 s = "0"; 133 if (type == null) 134 type = Number.class; 135 136 try { 137 // Determine the data type if it wasn't specified. 138 boolean isAutoDetect = (type == Number.class); 139 boolean isDecimal = false; 140 if (isAutoDetect) { 141 // If we're auto-detecting, then we use either an Integer, Long, or Double depending on how 142 // long the string is. 143 // An integer range is -2,147,483,648 to 2,147,483,647 144 // An long range is -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 145 isDecimal = isDecimal(s); 146 if (isDecimal) { 147 if (s.length() > 20) 148 type = Double.class; 149 else if (s.length() >= 10) 150 type = Long.class; 151 else 152 type = Integer.class; 153 } 154 else if (isFloat(s)) 155 type = Double.class; 156 else 157 throw new NumberFormatException(s); 158 } 159 160 if (type == Double.class || type == Double.TYPE) { 161 Double d = Double.valueOf(s); 162 Float f = Float.valueOf(s); 163 if (isAutoDetect && (!isDecimal) && d.toString().equals(f.toString())) 164 return f; 165 return d; 166 } 167 if (type == Float.class || type == Float.TYPE) 168 return Float.valueOf(s); 169 if (type == BigDecimal.class) 170 return new BigDecimal(s); 171 if (type == Long.class || type == Long.TYPE || type == AtomicLong.class) { 172 try { 173 Long l = Long.decode(s); 174 if (type == AtomicLong.class) 175 return new AtomicLong(l); 176 if (isAutoDetect && l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { 177 // This occurs if the string is 10 characters long but is still a valid integer value. 178 return l.intValue(); 179 } 180 return l; 181 } catch (NumberFormatException e) { 182 if (isAutoDetect) { 183 // This occurs if the string is 20 characters long but still falls outside the range of a valid long. 184 return Double.valueOf(s); 185 } 186 throw e; 187 } 188 } 189 if (type == Integer.class || type == Integer.TYPE) 190 return Integer.decode(s); 191 if (type == Short.class || type == Short.TYPE) 192 return Short.decode(s); 193 if (type == Byte.class || type == Byte.TYPE) 194 return Byte.decode(s); 195 if (type == BigInteger.class) 196 return new BigInteger(s); 197 if (type == AtomicInteger.class) 198 return new AtomicInteger(Integer.decode(s)); 199 throw new ParseException("Unsupported Number type: {0}", type.getName()); 200 } catch (NumberFormatException e) { 201 throw new ParseException("Invalid number: ''{0}'', class=''{1}''", s, type.getSimpleName()).initCause(e); 202 } 203 } 204 205 private static final Pattern fpRegex = Pattern.compile( 206 "[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*" 207 ); 208 209 /** 210 * Returns <jk>true</jk> if this string can be parsed by {@link #parseNumber(String, Class)}. 211 * 212 * @param s The string to check. 213 * @return <jk>true</jk> if this string can be parsed without causing an exception. 214 */ 215 public static boolean isNumeric(String s) { 216 if (s == null || s.isEmpty()) 217 return false; 218 if (! isFirstNumberChar(s.charAt(0))) 219 return false; 220 return isDecimal(s) || isFloat(s); 221 } 222 223 /** 224 * Returns <jk>true</jk> if the specified character is a valid first character for a number. 225 * 226 * @param c The character to test. 227 * @return <jk>true</jk> if the specified character is a valid first character for a number. 228 */ 229 public static boolean isFirstNumberChar(char c) { 230 return firstNumberChars.contains(c); 231 } 232 233 /** 234 * Returns <jk>true</jk> if the specified string is a floating point number. 235 * 236 * @param s The string to check. 237 * @return <jk>true</jk> if the specified string is a floating point number. 238 */ 239 public static boolean isFloat(String s) { 240 if (s == null || s.isEmpty()) 241 return false; 242 if (! firstNumberChars.contains(s.charAt(0))) 243 return (s.equals("NaN") || s.equals("Infinity")); 244 int i = 0; 245 int length = s.length(); 246 char c = s.charAt(0); 247 if (c == '+' || c == '-') 248 i++; 249 if (i == length) 250 return false; 251 c = s.charAt(i++); 252 if (c == '.' || decChars.contains(c)) { 253 return fpRegex.matcher(s).matches(); 254 } 255 return false; 256 } 257 258 /** 259 * Returns <jk>true</jk> if the specified string is numeric. 260 * 261 * @param s The string to check. 262 * @return <jk>true</jk> if the specified string is numeric. 263 */ 264 public static boolean isDecimal(String s) { 265 if (s == null || s.isEmpty()) 266 return false; 267 if (! firstNumberChars.contains(s.charAt(0))) 268 return false; 269 int i = 0; 270 int length = s.length(); 271 char c = s.charAt(0); 272 boolean isPrefixed = false; 273 if (c == '+' || c == '-') { 274 isPrefixed = true; 275 i++; 276 } 277 if (i == length) 278 return false; 279 c = s.charAt(i++); 280 if (c == '0' && length > (isPrefixed ? 2 : 1)) { 281 c = s.charAt(i++); 282 if (c == 'x' || c == 'X') { 283 for (int j = i; j < length; j++) { 284 if (! hexChars.contains(s.charAt(j))) 285 return false; 286 } 287 } else if (octChars.contains(c)) { 288 for (int j = i; j < length; j++) 289 if (! octChars.contains(s.charAt(j))) 290 return false; 291 } else { 292 return false; 293 } 294 } else if (c == '#') { 295 for (int j = i; j < length; j++) { 296 if (! hexChars.contains(s.charAt(j))) 297 return false; 298 } 299 } else if (decChars.contains(c)) { 300 for (int j = i; j < length; j++) 301 if (! decChars.contains(s.charAt(j))) 302 return false; 303 } else { 304 return false; 305 } 306 return true; 307 } 308 309 /** 310 * Convenience method for getting a stack trace as a string. 311 * 312 * @param t The throwable to get the stack trace from. 313 * @return The same content that would normally be rendered via <code>t.printStackTrace()</code> 314 */ 315 public static String getStackTrace(Throwable t) { 316 StringWriter sw = new StringWriter(); 317 try (PrintWriter pw = new PrintWriter(sw)) { 318 t.printStackTrace(pw); 319 } 320 return sw.toString(); 321 } 322 323 /** 324 * Join the specified tokens into a delimited string. 325 * 326 * @param tokens The tokens to join. 327 * @param separator The delimiter. 328 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 329 */ 330 public static String join(Object[] tokens, String separator) { 331 if (tokens == null) 332 return null; 333 StringBuilder sb = new StringBuilder(); 334 for (int i = 0; i < tokens.length; i++) { 335 if (i > 0) 336 sb.append(separator); 337 sb.append(tokens[i]); 338 } 339 return sb.toString(); 340 } 341 342 /** 343 * Join the specified tokens into a delimited string. 344 * 345 * @param tokens The tokens to join. 346 * @param d The delimiter. 347 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 348 */ 349 public static String join(int[] tokens, String d) { 350 if (tokens == null) 351 return null; 352 StringBuilder sb = new StringBuilder(); 353 for (int i = 0; i < tokens.length; i++) { 354 if (i > 0) 355 sb.append(d); 356 sb.append(tokens[i]); 357 } 358 return sb.toString(); 359 } 360 361 /** 362 * Join the specified tokens into a delimited string. 363 * 364 * @param tokens The tokens to join. 365 * @param d The delimiter. 366 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 367 */ 368 public static String join(Collection<?> tokens, String d) { 369 if (tokens == null) 370 return null; 371 return join(tokens, d, new StringBuilder()).toString(); 372 } 373 374 /** 375 * Joins the specified tokens into a delimited string and writes the output to the specified string builder. 376 * 377 * @param tokens The tokens to join. 378 * @param d The delimiter. 379 * @param sb The string builder to append the response to. 380 * @return The same string builder passed in as <code>sb</code>. 381 */ 382 public static StringBuilder join(Collection<?> tokens, String d, StringBuilder sb) { 383 if (tokens == null) 384 return sb; 385 for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) { 386 sb.append(iter.next()); 387 if (iter.hasNext()) 388 sb.append(d); 389 } 390 return sb; 391 } 392 393 /** 394 * Joins the specified tokens into a delimited string. 395 * 396 * @param tokens The tokens to join. 397 * @param d The delimiter. 398 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 399 */ 400 public static String join(Object[] tokens, char d) { 401 if (tokens == null) 402 return null; 403 return join(tokens, d, new StringBuilder()).toString(); 404 } 405 406 /** 407 * Join the specified tokens into a delimited string and writes the output to the specified string builder. 408 * 409 * @param tokens The tokens to join. 410 * @param d The delimiter. 411 * @param sb The string builder to append the response to. 412 * @return The same string builder passed in as <code>sb</code>. 413 */ 414 public static StringBuilder join(Object[] tokens, char d, StringBuilder sb) { 415 if (tokens == null) 416 return sb; 417 for (int i = 0; i < tokens.length; i++) { 418 if (i > 0) 419 sb.append(d); 420 sb.append(tokens[i]); 421 } 422 return sb; 423 } 424 425 /** 426 * Join the specified tokens into a delimited string. 427 * 428 * @param tokens The tokens to join. 429 * @param d The delimiter. 430 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 431 */ 432 public static String join(int[] tokens, char d) { 433 if (tokens == null) 434 return null; 435 StringBuilder sb = new StringBuilder(); 436 for (int i = 0; i < tokens.length; i++) { 437 if (i > 0) 438 sb.append(d); 439 sb.append(tokens[i]); 440 } 441 return sb.toString(); 442 } 443 444 /** 445 * Join the specified tokens into a delimited string. 446 * 447 * @param tokens The tokens to join. 448 * @param d The delimiter. 449 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 450 */ 451 public static String join(Collection<?> tokens, char d) { 452 if (tokens == null) 453 return null; 454 StringBuilder sb = new StringBuilder(); 455 for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) { 456 sb.append(iter.next()); 457 if (iter.hasNext()) 458 sb.append(d); 459 } 460 return sb.toString(); 461 } 462 463 /** 464 * Shortcut for calling <code>split(s, <js>','</js>)</code> 465 * 466 * @param s The string to split. Can be <jk>null</jk>. 467 * @return The tokens, or <jk>null</jk> if the string was null. 468 */ 469 public static String[] split(String s) { 470 return split(s, ','); 471 } 472 473 /** 474 * Splits a character-delimited string into a string array. 475 * 476 * <p> 477 * Does not split on escaped-delimiters (e.g. "\,"); 478 * Resulting tokens are trimmed of whitespace. 479 * 480 * <p> 481 * <b>NOTE:</b> This behavior is different than the Jakarta equivalent. 482 * split("a,b,c",',') -> {"a","b","c"} 483 * split("a, b ,c ",',') -> {"a","b","c"} 484 * split("a,,c",',') -> {"a","","c"} 485 * split(",,",',') -> {"","",""} 486 * split("",',') -> {} 487 * split(null,',') -> null 488 * split("a,b\,c,d", ',', false) -> {"a","b\,c","d"} 489 * split("a,b\\,c,d", ',', false) -> {"a","b\","c","d"} 490 * split("a,b\,c,d", ',', true) -> {"a","b,c","d"} 491 * 492 * @param s The string to split. Can be <jk>null</jk>. 493 * @param c The character to split on. 494 * @return The tokens, or <jk>null</jk> if the string was null. 495 */ 496 public static String[] split(String s, char c) { 497 return split(s, c, Integer.MAX_VALUE); 498 } 499 500 /** 501 * Same as {@link #split(String, char)} but limits the number of tokens returned. 502 * 503 * @param s The string to split. Can be <jk>null</jk>. 504 * @param c The character to split on. 505 * @param limit The maximum number of tokens to return. 506 * @return The tokens, or <jk>null</jk> if the string was null. 507 */ 508 public static String[] split(String s, char c, int limit) { 509 510 char[] unEscapeChars = new char[]{'\\', c}; 511 512 if (s == null) 513 return null; 514 if (isEmpty(s)) 515 return new String[0]; 516 if (s.indexOf(c) == -1) 517 return new String[]{s}; 518 519 List<String> l = new LinkedList<>(); 520 char[] sArray = s.toCharArray(); 521 int x1 = 0, escapeCount = 0; 522 limit--; 523 for (int i = 0; i < sArray.length && limit > 0; i++) { 524 if (sArray[i] == '\\') escapeCount++; 525 else if (sArray[i]==c && escapeCount % 2 == 0) { 526 String s2 = new String(sArray, x1, i-x1); 527 String s3 = unEscapeChars(s2, unEscapeChars); 528 l.add(s3.trim()); 529 limit--; 530 x1 = i+1; 531 } 532 if (sArray[i] != '\\') escapeCount = 0; 533 } 534 String s2 = new String(sArray, x1, sArray.length-x1); 535 String s3 = unEscapeChars(s2, unEscapeChars); 536 l.add(s3.trim()); 537 538 return l.toArray(new String[l.size()]); 539 } 540 541 /** 542 * Same as {@link #split(String, char)} except splits all strings in the input and returns a single result. 543 * 544 * @param s The string to split. Can be <jk>null</jk>. 545 * @param c The character to split on. 546 * @return The tokens. 547 */ 548 public static String[] split(String[] s, char c) { 549 if (s == null) 550 return null; 551 List<String> l = new LinkedList<>(); 552 for (String ss : s) { 553 if (ss == null || ss.indexOf(c) == -1) 554 l.add(ss); 555 else 556 l.addAll(Arrays.asList(split(ss, c))); 557 } 558 return l.toArray(new String[l.size()]); 559 } 560 561 /** 562 * Splits a list of key-value pairs into an ordered map. 563 * 564 * <p> 565 * Example: 566 * <p class='bcode'> 567 * String in = <js>"foo=1;bar=2"</js>; 568 * Map m = StringUtils.<jsm>splitMap</jsm>(in, <js>';'</js>, <js>'='</js>, <jk>true</jk>); 569 * </p> 570 * 571 * @param s The string to split. 572 * @param delim The delimiter between the key-value pairs. 573 * @param eq The delimiter between the key and value. 574 * @param trim Trim strings after parsing. 575 * @return The parsed map. Never <jk>null</jk>. 576 */ 577 public static Map<String,String> splitMap(String s, char delim, char eq, boolean trim) { 578 579 char[] unEscapeChars = new char[]{'\\', delim, eq}; 580 581 if (s == null) 582 return null; 583 if (isEmpty(s)) 584 return Collections.EMPTY_MAP; 585 586 Map<String,String> m = new LinkedHashMap<>(); 587 588 int 589 S1 = 1, // Found start of key, looking for equals. 590 S2 = 2; // Found equals, looking for delimiter (or end). 591 592 int state = S1; 593 594 char[] sArray = s.toCharArray(); 595 int x1 = 0, escapeCount = 0; 596 String key = null; 597 for (int i = 0; i < sArray.length + 1; i++) { 598 char c = i == sArray.length ? delim : sArray[i]; 599 if (c == '\\') 600 escapeCount++; 601 if (escapeCount % 2 == 0) { 602 if (state == S1) { 603 if (c == eq) { 604 key = s.substring(x1, i); 605 if (trim) 606 key = trim(key); 607 key = unEscapeChars(key, unEscapeChars); 608 state = S2; 609 x1 = i+1; 610 } else if (c == delim) { 611 key = s.substring(x1, i); 612 if (trim) 613 key = trim(key); 614 key = unEscapeChars(key, unEscapeChars); 615 m.put(key, ""); 616 state = S1; 617 x1 = i+1; 618 } 619 } else if (state == S2) { 620 if (c == delim) { 621 String val = s.substring(x1, i); 622 if (trim) 623 val = trim(val); 624 val = unEscapeChars(val, unEscapeChars); 625 m.put(key, val); 626 key = null; 627 x1 = i+1; 628 state = S1; 629 } 630 } 631 } 632 if (c != '\\') escapeCount = 0; 633 } 634 635 return m; 636 } 637 638 /** 639 * Returns <jk>true</jk> if the specified string contains any of the specified characters. 640 * 641 * @param s The string to test. 642 * @param chars The characters to look for. 643 * @return 644 * <jk>true</jk> if the specified string contains any of the specified characters. 645 * <br><jk>false</jk> if the string is <jk>null</jk>. 646 */ 647 public static boolean containsAny(String s, char...chars) { 648 if (s == null) 649 return false; 650 for (int i = 0; i < s.length(); i++) { 651 char c = s.charAt(i); 652 for (char c2 : chars) 653 if (c == c2) 654 return true; 655 } 656 return false; 657 } 658 659 /** 660 * Splits a space-delimited string with optionally quoted arguments. 661 * 662 * <p> 663 * Examples: 664 * <ul> 665 * <li><js>"foo"</js> => <code>["foo"]</code> 666 * <li><js>" foo "</js> => <code>["foo"]</code> 667 * <li><js>"foo bar baz"</js> => <code>["foo","bar","baz"]</code> 668 * <li><js>"foo 'bar baz'"</js> => <code>["foo","bar baz"]</code> 669 * <li><js>"foo \"bar baz\""</js> => <code>["foo","bar baz"]</code> 670 * <li><js>"foo 'bar\'baz'"</js> => <code>["foo","bar'baz"]</code> 671 * </ul> 672 * 673 * @param s The input string. 674 * @return 675 * The results, or <jk>null</jk> if the input was <jk>null</jk>. 676 * <br>An empty string results in an empty array. 677 */ 678 public static String[] splitQuoted(String s) { 679 char[] unEscapeChars = new char[]{'\\', '\'', '"'}; 680 681 if (s == null) 682 return null; 683 684 s = s.trim(); 685 686 if (isEmpty(s)) 687 return new String[0]; 688 689 if (! containsAny(s, ' ', '\t', '\'', '"')) 690 return new String[]{s}; 691 692 int 693 S1 = 1, // Looking for start of token. 694 S2 = 2, // Found ', looking for end ' 695 S3 = 3, // Found ", looking for end " 696 S4 = 4; // Found non-whitespace, looking for end whitespace. 697 698 int state = S1; 699 700 boolean isInEscape = false, needsUnescape = false; 701 int mark = 0; 702 703 List<String> l = new ArrayList<>(); 704 for (int i = 0; i < s.length(); i++) { 705 char c = s.charAt(i); 706 707 if (state == S1) { 708 if (c == '\'') { 709 state = S2; 710 mark = i+1; 711 } else if (c == '"') { 712 state = S3; 713 mark = i+1; 714 } else if (c != ' ' && c != '\t') { 715 state = S4; 716 mark = i; 717 } 718 } else if (state == S2 || state == S3) { 719 if (c == '\\') { 720 isInEscape = ! isInEscape; 721 needsUnescape = true; 722 } else if (! isInEscape) { 723 if (c == (state == S2 ? '\'' : '"')) { 724 String s2 = s.substring(mark, i); 725 if (needsUnescape) 726 s2 = unEscapeChars(s2, unEscapeChars, '\\'); 727 l.add(s2); 728 state = S1; 729 isInEscape = needsUnescape = false; 730 } 731 } else { 732 isInEscape = false; 733 } 734 } else if (state == S4) { 735 if (c == ' ' || c == '\t') { 736 l.add(s.substring(mark, i)); 737 state = S1; 738 } 739 } 740 } 741 if (state == S4) 742 l.add(s.substring(mark)); 743 else if (state == S2 || state == S3) 744 throw new RuntimeException("Unmatched string quotes: " + s); 745 return l.toArray(new String[l.size()]); 746 } 747 748 /** 749 * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty. 750 * 751 * @param s The string to check. 752 * @return <jk>true</jk> if specified string is <jk>null</jk> or empty. 753 */ 754 public static boolean isEmpty(String s) { 755 return s == null || s.isEmpty(); 756 } 757 758 /** 759 * Returns <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty 760 * string. 761 * 762 * @param s The string to check. 763 * @return 764 * <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty string. 765 */ 766 public static boolean isEmpty(Object s) { 767 return s == null || s.toString().isEmpty(); 768 } 769 770 /** 771 * Returns <jk>null</jk> if the specified string is <jk>null</jk> or empty. 772 * 773 * @param s The string to check. 774 * @return <jk>null</jk> if the specified string is <jk>null</jk> or empty, or the same string if not. 775 */ 776 public static String nullIfEmpty(String s) { 777 if (s == null || s.isEmpty()) 778 return null; 779 return s; 780 } 781 782 /** 783 * Returns an empty string if the specified string is <jk>null</jk>. 784 * 785 * @param s The string to check. 786 * @return An empty string if the specified string is <jk>null</jk>, or the same string otherwise. 787 */ 788 public static String emptyIfNull(String s) { 789 if (s == null) 790 return ""; 791 return s; 792 } 793 794 /** 795 * Removes escape characters (\) from the specified characters. 796 * 797 * @param s The string to remove escape characters from. 798 * @param toEscape The characters escaped. 799 * @return A new string if characters were removed, or the same string if not or if the input was <jk>null</jk>. 800 */ 801 public static String unEscapeChars(String s, char[] toEscape) { 802 return unEscapeChars(s, toEscape, '\\'); 803 } 804 805 /** 806 * Removes escape characters (specified by escapeChar) from the specified characters. 807 * 808 * @param s The string to remove escape characters from. 809 * @param toEscape The characters escaped. 810 * @param escapeChar The escape character. 811 * @return A new string if characters were removed, or the same string if not or if the input was <jk>null</jk>. 812 */ 813 public static String unEscapeChars(String s, char[] toEscape, char escapeChar) { 814 if (s == null) return null; 815 if (s.length() == 0 || toEscape == null || toEscape.length == 0 || escapeChar == 0) return s; 816 StringBuffer sb = new StringBuffer(s.length()); 817 char[] sArray = s.toCharArray(); 818 for (int i = 0; i < sArray.length; i++) { 819 char c = sArray[i]; 820 821 if (c == escapeChar) { 822 if (i+1 != sArray.length) { 823 char c2 = sArray[i+1]; 824 boolean isOneOf = false; 825 for (int j = 0; j < toEscape.length && ! isOneOf; j++) 826 isOneOf = (c2 == toEscape[j]); 827 if (isOneOf) { 828 i++; 829 } else if (c2 == escapeChar) { 830 sb.append(escapeChar); 831 i++; 832 } 833 } 834 } 835 sb.append(sArray[i]); 836 } 837 return sb.toString(); 838 } 839 840 /** 841 * Debug method for rendering non-ASCII character sequences. 842 * 843 * @param s The string to decode. 844 * @return A string with non-ASCII characters converted to <js>"[hex]"</js> sequences. 845 */ 846 public static String decodeHex(String s) { 847 if (s == null) 848 return null; 849 StringBuilder sb = new StringBuilder(); 850 for (char c : s.toCharArray()) { 851 if (c < ' ' || c > '~') 852 sb.append("["+Integer.toHexString(c)+"]"); 853 else 854 sb.append(c); 855 } 856 return sb.toString(); 857 } 858 859 /** 860 * An efficient method for checking if a string starts with a character. 861 * 862 * @param s The string to check. Can be <jk>null</jk>. 863 * @param c The character to check for. 864 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and starts with the specified character. 865 */ 866 public static boolean startsWith(String s, char c) { 867 if (s != null) { 868 int i = s.length(); 869 if (i > 0) 870 return s.charAt(0) == c; 871 } 872 return false; 873 } 874 875 /** 876 * An efficient method for checking if a string ends with a character. 877 * 878 * @param s The string to check. Can be <jk>null</jk>. 879 * @param c The character to check for. 880 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character. 881 */ 882 public static boolean endsWith(String s, char c) { 883 if (s != null) { 884 int i = s.length(); 885 if (i > 0) 886 return s.charAt(i-1) == c; 887 } 888 return false; 889 } 890 891 /** 892 * Same as {@link #endsWith(String, char)} except check for multiple characters. 893 * 894 * @param s The string to check. Can be <jk>null</jk>. 895 * @param c The characters to check for. 896 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character. 897 */ 898 public static boolean endsWith(String s, char...c) { 899 if (s != null) { 900 int i = s.length(); 901 if (i > 0) { 902 char c2 = s.charAt(i-1); 903 for (char cc : c) 904 if (c2 == cc) 905 return true; 906 } 907 } 908 return false; 909 } 910 911 /** 912 * Converts the specified number into a 4 hexadecimal characters. 913 * 914 * @param num The number to convert to hex. 915 * @return A <code><jk>char</jk>[4]</code> containing the specified characters. 916 */ 917 public static final char[] toHex(int num) { 918 char[] n = new char[4]; 919 int a = num%16; 920 n[3] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 921 int base = 16; 922 for (int i = 1; i < 4; i++) { 923 a = (num/base)%16; 924 base <<= 4; 925 n[3-i] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 926 } 927 return n; 928 } 929 930 /** 931 * Tests two strings for equality, but gracefully handles nulls. 932 * 933 * @param s1 String 1. 934 * @param s2 String 2. 935 * @return <jk>true</jk> if the strings are equal. 936 */ 937 public static boolean isEquals(String s1, String s2) { 938 if (s1 == null) 939 return s2 == null; 940 if (s2 == null) 941 return false; 942 return s1.equals(s2); 943 } 944 945 /** 946 * Shortcut for calling <code>base64Encode(in.getBytes(<js>"UTF-8"</js>))</code> 947 * 948 * @param in The input string to convert. 949 * @return The string converted to BASE-64 encoding. 950 */ 951 public static String base64EncodeToString(String in) { 952 if (in == null) 953 return null; 954 return base64Encode(in.getBytes(IOUtils.UTF8)); 955 } 956 957 /** 958 * BASE64-encodes the specified byte array. 959 * 960 * @param in The input byte array to convert. 961 * @return The byte array converted to a BASE-64 encoded string. 962 */ 963 public static String base64Encode(byte[] in) { 964 int outLength = (in.length * 4 + 2) / 3; // Output length without padding 965 char[] out = new char[((in.length + 2) / 3) * 4]; // Length includes padding. 966 int iIn = 0; 967 int iOut = 0; 968 while (iIn < in.length) { 969 int i0 = in[iIn++] & 0xff; 970 int i1 = iIn < in.length ? in[iIn++] & 0xff : 0; 971 int i2 = iIn < in.length ? in[iIn++] & 0xff : 0; 972 int o0 = i0 >>> 2; 973 int o1 = ((i0 & 3) << 4) | (i1 >>> 4); 974 int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); 975 int o3 = i2 & 0x3F; 976 out[iOut++] = base64m1[o0]; 977 out[iOut++] = base64m1[o1]; 978 out[iOut] = iOut < outLength ? base64m1[o2] : '='; 979 iOut++; 980 out[iOut] = iOut < outLength ? base64m1[o3] : '='; 981 iOut++; 982 } 983 return new String(out); 984 } 985 986 /** 987 * Shortcut for calling <code>base64Decode(String)</code> and converting the result to a UTF-8 encoded string. 988 * 989 * @param in The BASE-64 encoded string to decode. 990 * @return The decoded string. 991 */ 992 public static String base64DecodeToString(String in) { 993 byte[] b = base64Decode(in); 994 if (b == null) 995 return null; 996 return new String(b, IOUtils.UTF8); 997 } 998 999 /** 1000 * BASE64-decodes the specified string. 1001 * 1002 * @param in The BASE-64 encoded string. 1003 * @return The decoded byte array. 1004 */ 1005 public static byte[] base64Decode(String in) { 1006 if (in == null) 1007 return null; 1008 1009 byte bIn[] = in.getBytes(IOUtils.UTF8); 1010 1011 if (bIn.length % 4 != 0) 1012 illegalArg("Invalid BASE64 string length. Must be multiple of 4."); 1013 1014 // Strip out any trailing '=' filler characters. 1015 int inLength = bIn.length; 1016 while (inLength > 0 && bIn[inLength - 1] == '=') 1017 inLength--; 1018 1019 int outLength = (inLength * 3) / 4; 1020 byte[] out = new byte[outLength]; 1021 int iIn = 0; 1022 int iOut = 0; 1023 while (iIn < inLength) { 1024 int i0 = bIn[iIn++]; 1025 int i1 = bIn[iIn++]; 1026 int i2 = iIn < inLength ? bIn[iIn++] : 'A'; 1027 int i3 = iIn < inLength ? bIn[iIn++] : 'A'; 1028 int b0 = base64m2[i0]; 1029 int b1 = base64m2[i1]; 1030 int b2 = base64m2[i2]; 1031 int b3 = base64m2[i3]; 1032 int o0 = (b0 << 2) | (b1 >>> 4); 1033 int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); 1034 int o2 = ((b2 & 3) << 6) | b3; 1035 out[iOut++] = (byte)o0; 1036 if (iOut < outLength) 1037 out[iOut++] = (byte)o1; 1038 if (iOut < outLength) 1039 out[iOut++] = (byte)o2; 1040 } 1041 return out; 1042 } 1043 1044 /** 1045 * Generated a random UUID with the specified number of characters. 1046 * 1047 * <p> 1048 * Characters are composed of lower-case ASCII letters and numbers only. 1049 * 1050 * <p> 1051 * This method conforms to the restrictions for hostnames as specified in <a class="doclink" href="https://tools.ietf.org/html/rfc952">RFC 952</a> 1052 * Since each character has 36 possible values, the square approximation formula for the number of generated IDs 1053 * that would produce a 50% chance of collision is: 1054 * <code>sqrt(36^N)</code>. 1055 * Dividing this number by 10 gives you an approximation of the number of generated IDs needed to produce a 1056 * <1% chance of collision. 1057 * 1058 * <p> 1059 * For example, given 5 characters, the number of generated IDs need to produce a <1% chance of collision would 1060 * be: 1061 * <code>sqrt(36^5)/10=777</code> 1062 * 1063 * @param numchars The number of characters in the generated UUID. 1064 * @return A new random UUID. 1065 */ 1066 public static String generateUUID(int numchars) { 1067 Random r = new Random(); 1068 StringBuilder sb = new StringBuilder(numchars); 1069 for (int i = 0; i < numchars; i++) { 1070 int c = r.nextInt(36) + 97; 1071 if (c > 'z') 1072 c -= ('z'-'0'+1); 1073 sb.append((char)c); 1074 } 1075 return sb.toString(); 1076 } 1077 1078 /** 1079 * Same as {@link String#trim()} but prevents <code>NullPointerExceptions</code>. 1080 * 1081 * @param s The string to trim. 1082 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1083 */ 1084 public static String trim(String s) { 1085 if (s == null) 1086 return null; 1087 return s.trim(); 1088 } 1089 1090 /** 1091 * Parses an ISO8601 string into a date. 1092 * 1093 * @param date The date string. 1094 * @return The parsed date. 1095 * @throws IllegalArgumentException 1096 */ 1097 public static Date parseISO8601Date(String date) throws IllegalArgumentException { 1098 if (isEmpty(date)) 1099 return null; 1100 date = date.trim().replace(' ', 'T'); // Convert to 'standard' ISO8601 1101 if (date.indexOf(',') != -1) // Trim milliseconds 1102 date = date.substring(0, date.indexOf(',')); 1103 if (date.matches("\\d{4}")) 1104 date += "-01-01T00:00:00"; 1105 else if (date.matches("\\d{4}\\-\\d{2}")) 1106 date += "-01T00:00:00"; 1107 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}")) 1108 date += "T00:00:00"; 1109 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}")) 1110 date += ":00:00"; 1111 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}")) 1112 date += ":00"; 1113 return DatatypeConverter.parseDateTime(date).getTime(); 1114 } 1115 1116 /** 1117 * Simple utility for replacing variables of the form <js>"{key}"</js> with values in the specified map. 1118 * 1119 * <p> 1120 * Nested variables are supported in both the input string and map values. 1121 * 1122 * <p> 1123 * If the map does not contain the specified value, the variable is not replaced. 1124 * 1125 * <p> 1126 * <jk>null</jk> values in the map are treated as blank strings. 1127 * 1128 * @param s The string containing variables to replace. 1129 * @param m The map containing the variable values. 1130 * @return The new string with variables replaced, or the original string if it didn't have variables in it. 1131 */ 1132 public static String replaceVars(String s, Map<String,Object> m) { 1133 1134 if (s == null) 1135 return null; 1136 1137 if (s.indexOf('{') == -1) 1138 return s; 1139 1140 int S1 = 1; // Not in variable, looking for { 1141 int S2 = 2; // Found {, Looking for } 1142 1143 int state = S1; 1144 boolean hasInternalVar = false; 1145 int x = 0; 1146 int depth = 0; 1147 int length = s.length(); 1148 StringBuilder out = new StringBuilder(); 1149 for (int i = 0; i < length; i++) { 1150 char c = s.charAt(i); 1151 if (state == S1) { 1152 if (c == '{') { 1153 state = S2; 1154 x = i; 1155 } else { 1156 out.append(c); 1157 } 1158 } else /* state == S2 */ { 1159 if (c == '{') { 1160 depth++; 1161 hasInternalVar = true; 1162 } else if (c == '}') { 1163 if (depth > 0) { 1164 depth--; 1165 } else { 1166 String key = s.substring(x+1, i); 1167 key = (hasInternalVar ? replaceVars(key, m) : key); 1168 hasInternalVar = false; 1169 if (! m.containsKey(key)) 1170 out.append('{').append(key).append('}'); 1171 else { 1172 Object val = m.get(key); 1173 if (val == null) 1174 val = ""; 1175 String v = val.toString(); 1176 // If the replacement also contains variables, replace them now. 1177 if (v.indexOf('{') != -1) 1178 v = replaceVars(v, m); 1179 out.append(v); 1180 } 1181 state = 1; 1182 } 1183 } 1184 } 1185 } 1186 return out.toString(); 1187 } 1188 1189 /** 1190 * Returns <jk>true</jk> if the specified path string is prefixed with the specified prefix. 1191 * 1192 * <h5 class='section'>Example:</h5> 1193 * <p class='bcode'> 1194 * pathStartsWith(<js>"foo"</js>, <js>"foo"</js>); <jc>// true</jc> 1195 * pathStartsWith(<js>"foo/bar"</js>, <js>"foo"</js>); <jc>// true</jc> 1196 * pathStartsWith(<js>"foo2"</js>, <js>"foo"</js>); <jc>// false</jc> 1197 * pathStartsWith(<js>"foo2"</js>, <js>""</js>); <jc>// false</jc> 1198 * </p> 1199 * 1200 * @param path The path to check. 1201 * @param pathPrefix The prefix. 1202 * @return <jk>true</jk> if the specified path string is prefixed with the specified prefix. 1203 */ 1204 public static boolean pathStartsWith(String path, String pathPrefix) { 1205 if (path == null || pathPrefix == null) 1206 return false; 1207 if (path.startsWith(pathPrefix)) 1208 return path.length() == pathPrefix.length() || path.charAt(pathPrefix.length()) == '/'; 1209 return false; 1210 } 1211 1212 /** 1213 * Same as {@link #pathStartsWith(String, String)} but returns <jk>true</jk> if at least one prefix matches. 1214 * 1215 * @param path The path to check. 1216 * @param pathPrefixes The prefixes. 1217 * @return <jk>true</jk> if the specified path string is prefixed with any of the specified prefixes. 1218 */ 1219 public static boolean pathStartsWith(String path, String[] pathPrefixes) { 1220 for (String p : pathPrefixes) 1221 if (pathStartsWith(path, p)) 1222 return true; 1223 return false; 1224 } 1225 1226 /** 1227 * Replaces <js>"\\uXXXX"</js> character sequences with their unicode characters. 1228 * 1229 * @param s The string to replace unicode sequences in. 1230 * @return A string with unicode sequences replaced. 1231 */ 1232 public static String replaceUnicodeSequences(String s) { 1233 if (s.indexOf('\\') == -1) 1234 return s; 1235 Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})"); 1236 Matcher m = p.matcher(s); 1237 StringBuffer sb = new StringBuffer(s.length()); 1238 while (m.find()) { 1239 String ch = String.valueOf((char) Integer.parseInt(m.group(1), 16)); 1240 m.appendReplacement(sb, Matcher.quoteReplacement(ch)); 1241 } 1242 m.appendTail(sb); 1243 return sb.toString(); 1244 } 1245 1246 /** 1247 * Creates an escaped-unicode sequence (e.g. <js>"\\u1234"</js>) for the specified character. 1248 * 1249 * @param c The character to create a sequence for. 1250 * @return An escaped-unicode sequence. 1251 */ 1252 public static String unicodeSequence(char c) { 1253 StringBuilder sb = new StringBuilder(6); 1254 sb.append('\\').append('u'); 1255 for (char cc : toHex(c)) 1256 sb.append(cc); 1257 return sb.toString(); 1258 } 1259 1260 /** 1261 * Returns the specified field in a delimited string without splitting the string. 1262 * 1263 * <p> 1264 * Equivalent to the following: 1265 * <p class='bcode'> 1266 * String in = <js>"0,1,2"</js>; 1267 * String[] parts = in.split(<js>","</js>); 1268 * String p1 = (parts.<jk>length</jk> > 1 ? parts[1] : <js>""</js>); 1269 * </p> 1270 * 1271 * @param fieldNum The field number. Zero-indexed. 1272 * @param s The input string. 1273 * @param delim The delimiter character. 1274 * @return The field entry in the string, or a blank string if it doesn't exist or the string is null. 1275 */ 1276 public static String getField(int fieldNum, String s, char delim) { 1277 return getField(fieldNum, s, delim, ""); 1278 } 1279 1280 /** 1281 * Same as {@link #getField(int, String, char)} except allows you to specify the default value. 1282 * 1283 * @param fieldNum The field number. Zero-indexed. 1284 * @param s The input string. 1285 * @param delim The delimiter character. 1286 * @param def The default value if the field does not exist. 1287 * @return The field entry in the string, or the default value if it doesn't exist or the string is null. 1288 */ 1289 public static String getField(int fieldNum, String s, char delim, String def) { 1290 if (s == null || fieldNum < 0) 1291 return def; 1292 int start = 0; 1293 for (int i = 0; i < s.length(); i++) { 1294 char c = s.charAt(i); 1295 if (c == delim) { 1296 fieldNum--; 1297 if (fieldNum == 0) 1298 start = i+1; 1299 } 1300 if (fieldNum < 0) 1301 return s.substring(start, i); 1302 } 1303 if (start == 0) 1304 return def; 1305 return s.substring(start); 1306 } 1307 1308 /** 1309 * Calls {@link #toString()} on the specified object if it's not null. 1310 * 1311 * @param o The object to convert to a string. 1312 * @return The object converted to a string, or <jk>null</jk> if the object was null. 1313 */ 1314 public static String asString(Object o) { 1315 return (o == null ? null : o.toString()); 1316 } 1317 1318 /** 1319 * Converts an array of objects to an array of strings. 1320 * 1321 * @param o The array of objects to convert to strings. 1322 * @return A new array of objects converted to strings. 1323 */ 1324 public static String[] asStrings(Object...o) { 1325 if (o == null) 1326 return null; 1327 if (o instanceof String[]) 1328 return (String[])o; 1329 String[] s = new String[o.length]; 1330 for (int i = 0; i < o.length; i++) 1331 s[i] = asString(o[i]); 1332 return s; 1333 } 1334 1335 /** 1336 * Converts a hexadecimal byte stream (e.g. "34A5BC") into a UTF-8 encoded string. 1337 * 1338 * @param hex The hexadecimal string. 1339 * @return The UTF-8 string. 1340 */ 1341 public static String fromHexToUTF8(String hex) { 1342 ByteBuffer buff = ByteBuffer.allocate(hex.length()/2); 1343 for (int i = 0; i < hex.length(); i+=2) 1344 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1345 buff.rewind(); 1346 Charset cs = Charset.forName("UTF-8"); 1347 return cs.decode(buff).toString(); 1348 } 1349 1350 /** 1351 * Converts a space-deliminted hexadecimal byte stream (e.g. "34 A5 BC") into a UTF-8 encoded string. 1352 * 1353 * @param hex The hexadecimal string. 1354 * @return The UTF-8 string. 1355 */ 1356 public static String fromSpacedHexToUTF8(String hex) { 1357 ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3); 1358 for (int i = 0; i < hex.length(); i+=3) 1359 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1360 buff.rewind(); 1361 Charset cs = Charset.forName("UTF-8"); 1362 return cs.decode(buff).toString(); 1363 } 1364 1365 private static final char[] HEX = "0123456789ABCDEF".toCharArray(); 1366 1367 /** 1368 * Converts a byte array into a simple hexadecimal character string. 1369 * 1370 * @param bytes The bytes to convert to hexadecimal. 1371 * @return A new string consisting of hexadecimal characters. 1372 */ 1373 public static String toHex(byte[] bytes) { 1374 StringBuilder sb = new StringBuilder(bytes.length * 2); 1375 for (int j = 0; j < bytes.length; j++) { 1376 int v = bytes[j] & 0xFF; 1377 sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]); 1378 } 1379 return sb.toString(); 1380 } 1381 1382 /** 1383 * Same as {@link #toHex(byte[])} but puts spaces between the byte strings. 1384 * 1385 * @param bytes The bytes to convert to hexadecimal. 1386 * @return A new string consisting of hexadecimal characters. 1387 */ 1388 public static String toSpacedHex(byte[] bytes) { 1389 StringBuilder sb = new StringBuilder(bytes.length * 3); 1390 for (int j = 0; j < bytes.length; j++) { 1391 if (j > 0) 1392 sb.append(' '); 1393 int v = bytes[j] & 0xFF; 1394 sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]); 1395 } 1396 return sb.toString(); 1397 } 1398 1399 /** 1400 * Converts a hexadecimal character string to a byte array. 1401 * 1402 * @param hex The string to convert to a byte array. 1403 * @return A new byte array. 1404 */ 1405 public static byte[] fromHex(String hex) { 1406 ByteBuffer buff = ByteBuffer.allocate(hex.length()/2); 1407 for (int i = 0; i < hex.length(); i+=2) 1408 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1409 buff.rewind(); 1410 return buff.array(); 1411 } 1412 1413 /** 1414 * Same as {@link #fromHex(String)} except expects spaces between the byte strings. 1415 * 1416 * @param hex The string to convert to a byte array. 1417 * @return A new byte array. 1418 */ 1419 public static byte[] fromSpacedHex(String hex) { 1420 ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3); 1421 for (int i = 0; i < hex.length(); i+=3) 1422 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1423 buff.rewind(); 1424 return buff.array(); 1425 } 1426 1427 /** 1428 * Creates a repeated pattern. 1429 * 1430 * @param count The number of times to repeat the pattern. 1431 * @param pattern The pattern to repeat. 1432 * @return A new string consisting of the repeated pattern. 1433 */ 1434 public static String repeat(int count, String pattern) { 1435 StringBuilder sb = new StringBuilder(pattern.length() * count); 1436 for (int i = 0; i < count; i++) 1437 sb.append(pattern); 1438 return sb.toString(); 1439 } 1440 1441 /** 1442 * Trims whitespace characters from the beginning of the specified string. 1443 * 1444 * @param s The string to trim. 1445 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1446 */ 1447 public static String trimStart(String s) { 1448 if (s != null) 1449 while (s.length() > 0 && Character.isWhitespace(s.charAt(0))) 1450 s = s.substring(1); 1451 return s; 1452 } 1453 1454 /** 1455 * Trims whitespace characters from the end of the specified string. 1456 * 1457 * @param s The string to trim. 1458 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1459 */ 1460 public static String trimEnd(String s) { 1461 if (s != null) 1462 while (s.length() > 0 && Character.isWhitespace(s.charAt(s.length()-1))) 1463 s = s.substring(0, s.length()-1); 1464 return s; 1465 } 1466 1467 /** 1468 * Returns <jk>true</jk> if the specified string is one of the specified values. 1469 * 1470 * @param s 1471 * The string to test. 1472 * Can be <jk>null</jk>. 1473 * @param values 1474 * The values to test. 1475 * Can contain <jk>null</jk>. 1476 * @return <jk>true</jk> if the specified string is one of the specified values. 1477 */ 1478 public static boolean isOneOf(String s, String...values) { 1479 for (int i = 0; i < values.length; i++) 1480 if (StringUtils.isEquals(s, values[i])) 1481 return true; 1482 return false; 1483 } 1484 1485 /** 1486 * Trims <js>'/'</js> characters from both the start and end of the specified string. 1487 * 1488 * @param s The string to trim. 1489 * @return A new trimmed string, or the same string if no trimming was necessary. 1490 */ 1491 public static String trimSlashes(String s) { 1492 if (s == null) 1493 return null; 1494 while (endsWith(s, '/')) 1495 s = s.substring(0, s.length()-1); 1496 while (s.length() > 0 && s.charAt(0) == '/') 1497 s = s.substring(1); 1498 return s; 1499 } 1500 1501 /** 1502 * Trims <js>'/'</js> characters from the end of the specified string. 1503 * 1504 * @param s The string to trim. 1505 * @return A new trimmed string, or the same string if no trimming was necessary. 1506 */ 1507 public static String trimTrailingSlashes(String s) { 1508 if (s == null) 1509 return null; 1510 while (endsWith(s, '/')) 1511 s = s.substring(0, s.length()-1); 1512 return s; 1513 } 1514 1515 /** 1516 * Trims <js>'/'</js> characters from the end of the specified string. 1517 * 1518 * @param s The string to trim. 1519 * @return The same string buffer. 1520 */ 1521 public static StringBuffer trimTrailingSlashes(StringBuffer s) { 1522 if (s == null) 1523 return null; 1524 while (s.length() > 0 && s.charAt(s.length()-1) == '/') 1525 s.setLength(s.length()-1); 1526 return s; 1527 } 1528 1529 /** 1530 * Shortcut for calling <code>URLEncoder.<jsm>encode</jsm>(o.toString(), <js>"UTF-8"</js>)</code>. 1531 * 1532 * @param o The object to encode. 1533 * @return The URL encoded string, or <jk>null</jk> if the object was null. 1534 */ 1535 public static String urlEncode(Object o) { 1536 try { 1537 if (o != null) 1538 return URLEncoder.encode(o.toString(), "UTF-8"); 1539 } catch (UnsupportedEncodingException e) {} 1540 return null; 1541 } 1542 1543 private static final AsciiSet URL_ENCODE_PATHINFO_VALIDCHARS = 1544 AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.*/()").build(); 1545 1546 /** 1547 * Similar to {@link #urlEncode(Object)} but doesn't encode <js>"/"</js> characters. 1548 * 1549 * @param o The object to encode. 1550 * @return The URL encoded string, or <jk>null</jk> if the object was null. 1551 */ 1552 public static String urlEncodePath(Object o) { 1553 if (o == null) 1554 return null; 1555 String s = asString(o); 1556 1557 boolean needsEncode = false; 1558 for (int i = 0; i < s.length() && ! needsEncode; i++) 1559 needsEncode = URL_ENCODE_PATHINFO_VALIDCHARS.contains(s.charAt(i)); 1560 if (! needsEncode) 1561 return s; 1562 1563 StringBuilder sb = new StringBuilder(); 1564 CharArrayWriter caw = new CharArrayWriter(); 1565 int caseDiff = ('a' - 'A'); 1566 1567 for (int i = 0; i < s.length();) { 1568 char c = s.charAt(i); 1569 if (URL_ENCODE_PATHINFO_VALIDCHARS.contains(c)) { 1570 sb.append(c); 1571 i++; 1572 } else { 1573 if (c == ' ') { 1574 sb.append('+'); 1575 i++; 1576 } else { 1577 do { 1578 caw.write(c); 1579 if (c >= 0xD800 && c <= 0xDBFF) { 1580 if ( (i+1) < s.length()) { 1581 int d = s.charAt(i+1); 1582 if (d >= 0xDC00 && d <= 0xDFFF) { 1583 caw.write(d); 1584 i++; 1585 } 1586 } 1587 } 1588 i++; 1589 } while (i < s.length() && !URL_ENCODE_PATHINFO_VALIDCHARS.contains((c = s.charAt(i)))); 1590 1591 caw.flush(); 1592 String s2 = new String(caw.toCharArray()); 1593 byte[] ba = s2.getBytes(IOUtils.UTF8); 1594 for (int j = 0; j < ba.length; j++) { 1595 sb.append('%'); 1596 char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); 1597 if (Character.isLetter(ch)) { 1598 ch -= caseDiff; 1599 } 1600 sb.append(ch); 1601 ch = Character.forDigit(ba[j] & 0xF, 16); 1602 if (Character.isLetter(ch)) { 1603 ch -= caseDiff; 1604 } 1605 sb.append(ch); 1606 } 1607 caw.reset(); 1608 } 1609 } 1610 } 1611 return sb.toString(); 1612 } 1613 1614 /** 1615 * Decodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme. 1616 * 1617 * @param s The string to decode. 1618 * @return The decoded string, or <jk>null</jk> if input is <jk>null</jk>. 1619 */ 1620 public static String urlDecode(String s) { 1621 if (s == null) 1622 return s; 1623 boolean needsDecode = false; 1624 for (int i = 0; i < s.length() && ! needsDecode; i++) { 1625 char c = s.charAt(i); 1626 if (c == '+' || c == '%') 1627 needsDecode = true; 1628 } 1629 if (needsDecode) { 1630 try { 1631 return URLDecoder.decode(s, "UTF-8"); 1632 } catch (UnsupportedEncodingException e) {/* Won't happen */} 1633 } 1634 return s; 1635 } 1636 1637 /** 1638 * Encodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme. 1639 * 1640 * @param s The string to encode. 1641 * @return The encoded string, or <jk>null</jk> if input is <jk>null</jk>. 1642 */ 1643 public static String urlEncode(String s) { 1644 if (s == null) 1645 return null; 1646 boolean needsEncode = false; 1647 for (int i = 0; i < s.length() && ! needsEncode; i++) 1648 needsEncode |= (! unencodedChars.contains(s.charAt(i))); 1649 if (needsEncode) { 1650 try { 1651 return URLEncoder.encode(s, "UTF-8"); 1652 } catch (UnsupportedEncodingException e) {/* Won't happen */} 1653 } 1654 return s; 1655 } 1656 1657 /** 1658 * Splits a string into equally-sized parts. 1659 * 1660 * @param s The string to split. 1661 * @param size The token sizes. 1662 * @return The tokens, or <jk>null</jk> if the input was <jk>null</jk>. 1663 */ 1664 public static List<String> splitEqually(String s, int size) { 1665 if (s == null) 1666 return null; 1667 if (size <= 0) 1668 return Collections.singletonList(s); 1669 1670 List<String> l = new ArrayList<>((s.length() + size - 1) / size); 1671 1672 for (int i = 0; i < s.length(); i += size) 1673 l.add(s.substring(i, Math.min(s.length(), i + size))); 1674 1675 return l; 1676 } 1677 1678 /** 1679 * Returns the first non-whitespace character in the string. 1680 * 1681 * @param s The string to check. 1682 * @return 1683 * The first non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed 1684 * of only whitespace. 1685 */ 1686 public static char firstNonWhitespaceChar(String s) { 1687 if (s != null) 1688 for (int i = 0; i < s.length(); i++) 1689 if (! Character.isWhitespace(s.charAt(i))) 1690 return s.charAt(i); 1691 return 0; 1692 } 1693 1694 /** 1695 * Returns the last non-whitespace character in the string. 1696 * 1697 * @param s The string to check. 1698 * @return 1699 * The last non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed 1700 * of only whitespace. 1701 */ 1702 public static char lastNonWhitespaceChar(String s) { 1703 if (s != null) 1704 for (int i = s.length()-1; i >= 0; i--) 1705 if (! Character.isWhitespace(s.charAt(i))) 1706 return s.charAt(i); 1707 return 0; 1708 } 1709 1710 /** 1711 * Returns the character at the specified index in the string without throwing exceptions. 1712 * 1713 * @param s The string. 1714 * @param i The index position. 1715 * @return 1716 * The character at the specified index, or <code>0</code> if the index is out-of-range or the string 1717 * is <jk>null</jk>. 1718 */ 1719 public static char charAt(String s, int i) { 1720 if (s == null) 1721 return 0; 1722 if (i < 0 || i >= s.length()) 1723 return 0; 1724 return s.charAt(i); 1725 } 1726 1727 /** 1728 * Efficiently determines whether a URL is of the pattern "xxx://xxx" 1729 * 1730 * @param s The string to test. 1731 * @return <jk>true</jk> if it's an absolute path. 1732 */ 1733 public static boolean isAbsoluteUri(String s) { 1734 1735 if (isEmpty(s)) 1736 return false; 1737 1738 // Use a state machine for maximum performance. 1739 1740 int S1 = 1; // Looking for http 1741 int S2 = 2; // Found http, looking for : 1742 int S3 = 3; // Found :, looking for / 1743 int S4 = 4; // Found /, looking for / 1744 int S5 = 5; // Found /, looking for x 1745 1746 int state = S1; 1747 for (int i = 0; i < s.length(); i++) { 1748 char c = s.charAt(i); 1749 if (state == S1) { 1750 if (c >= 'a' && c <= 'z') 1751 state = S2; 1752 else 1753 return false; 1754 } else if (state == S2) { 1755 if (c == ':') 1756 state = S3; 1757 else if (c < 'a' || c > 'z') 1758 return false; 1759 } else if (state == S3) { 1760 if (c == '/') 1761 state = S4; 1762 else 1763 return false; 1764 } else if (state == S4) { 1765 if (c == '/') 1766 state = S5; 1767 else 1768 return false; 1769 } else if (state == S5) { 1770 return true; 1771 } 1772 } 1773 return false; 1774 } 1775 1776 /** 1777 * Efficiently determines whether a URL is of the pattern "xxx:/xxx". 1778 * 1779 * <p> 1780 * The pattern matched is: <code>[a-z]{2,}\:\/.*</code> 1781 * 1782 * <p> 1783 * Note that this excludes filesystem paths such as <js>"C:/temp"</js>. 1784 * 1785 * @param s The string to test. 1786 * @return <jk>true</jk> if it's an absolute path. 1787 */ 1788 public static boolean isUri(String s) { 1789 1790 if (isEmpty(s)) 1791 return false; 1792 1793 // Use a state machine for maximum performance. 1794 1795 int S1 = 1; // Looking for protocol char 1 1796 int S2 = 2; // Found protocol char 1, looking for protocol char 2 1797 int S3 = 3; // Found protocol char 2, looking for : 1798 int S4 = 4; // Found :, looking for / 1799 1800 1801 int state = S1; 1802 for (int i = 0; i < s.length(); i++) { 1803 char c = s.charAt(i); 1804 if (state == S1) { 1805 if (c >= 'a' && c <= 'z') 1806 state = S2; 1807 else 1808 return false; 1809 } else if (state == S2) { 1810 if (c >= 'a' && c <= 'z') 1811 state = S3; 1812 else 1813 return false; 1814 } else if (state == S3) { 1815 if (c == ':') 1816 state = S4; 1817 else if (c < 'a' || c > 'z') 1818 return false; 1819 } else if (state == S4) { 1820 if (c == '/') 1821 return true; 1822 return false; 1823 } 1824 } 1825 return false; 1826 } 1827 1828 /** 1829 * Given an absolute URI, returns just the authority portion (e.g. <js>"http://hostname:port"</js>) 1830 * 1831 * @param s The URI string. 1832 * @return Just the authority portion of the URI. 1833 */ 1834 public static String getAuthorityUri(String s) { 1835 1836 // Use a state machine for maximum performance. 1837 1838 int S1 = 1; // Looking for http 1839 int S2 = 2; // Found http, looking for : 1840 int S3 = 3; // Found :, looking for / 1841 int S4 = 4; // Found /, looking for / 1842 int S5 = 5; // Found /, looking for x 1843 int S6 = 6; // Found x, looking for / 1844 1845 int state = S1; 1846 for (int i = 0; i < s.length(); i++) { 1847 char c = s.charAt(i); 1848 if (state == S1) { 1849 if (c >= 'a' && c <= 'z') 1850 state = S2; 1851 else 1852 return s; 1853 } else if (state == S2) { 1854 if (c == ':') 1855 state = S3; 1856 else if (c < 'a' || c > 'z') 1857 return s; 1858 } else if (state == S3) { 1859 if (c == '/') 1860 state = S4; 1861 else 1862 return s; 1863 } else if (state == S4) { 1864 if (c == '/') 1865 state = S5; 1866 else 1867 return s; 1868 } else if (state == S5) { 1869 if (c != '/') 1870 state = S6; 1871 else 1872 return s; 1873 } else if (state == S6) { 1874 if (c == '/') 1875 return s.substring(0, i); 1876 } 1877 } 1878 return s; 1879 } 1880 1881 /** 1882 * Converts the specified object to a URI. 1883 * 1884 * @param o The object to convert to a URI. 1885 * @return A new URI, or the same object if the object was already a URI, or 1886 */ 1887 public static URI toURI(Object o) { 1888 if (o == null || o instanceof URI) 1889 return (URI)o; 1890 try { 1891 return new URI(o.toString()); 1892 } catch (URISyntaxException e) { 1893 throw new RuntimeException(e); 1894 } 1895 } 1896 1897 /** 1898 * Returns the first non-null, non-empty string in the list. 1899 * 1900 * @param s The strings to test. 1901 * @return The first non-empty string in the list, or <jk>null</jk> if they were all <jk>null</jk> or empty. 1902 */ 1903 public static String firstNonEmpty(String...s) { 1904 for (String ss : s) 1905 if (! isEmpty(ss)) 1906 return ss; 1907 return null; 1908 } 1909 1910 /** 1911 * Same as {@link String#indexOf(int)} except allows you to check for multiple characters. 1912 * 1913 * @param s The string to check. 1914 * @param c The characters to check for. 1915 * @return The index into the string that is one of the specified characters. 1916 */ 1917 public static int indexOf(String s, char...c) { 1918 if (s == null) 1919 return -1; 1920 for (int i = 0; i < s.length(); i++) { 1921 char c2 = s.charAt(i); 1922 for (char cc : c) 1923 if (c2 == cc) 1924 return i; 1925 } 1926 return -1; 1927 } 1928 1929 /** 1930 * Similar to {@link MessageFormat#format(String, Object...)} except allows you to specify POJO arguments. 1931 * 1932 * @param pattern The string pattern. 1933 * @param args The arguments. 1934 * @return The formatted string. 1935 */ 1936 public static String format(String pattern, Object...args) { 1937 if (args == null || args.length == 0) 1938 return pattern; 1939 for (int i = 0; i < args.length; i++) 1940 args[i] = convertToReadable(args[i]); 1941 return MessageFormat.format(pattern, args); 1942 } 1943 1944 private static Object convertToReadable(Object o) { 1945 if (o == null) 1946 return null; 1947 if (o instanceof ClassMeta) 1948 return ((ClassMeta<?>)o).getReadableName(); 1949 if (BeanContext.DEFAULT == null) 1950 return o.toString(); 1951 ClassMeta<?> cm = BeanContext.DEFAULT.getClassMetaForObject(o); 1952 if (cm.isMapOrBean() || cm.isCollectionOrArray()) 1953 return JsonSerializer.DEFAULT_LAX.toString(o); 1954 if (cm.isClass()) 1955 return ((Class<?>)o).getName(); 1956 if (cm.isMethod()) 1957 return ClassUtils.toString((Method)o); 1958 return o.toString(); 1959 } 1960 1961 /** 1962 * Converts a string containing a possible multiplier suffix to an integer. 1963 * 1964 * <p> 1965 * The string can contain any of the following multiplier suffixes: 1966 * <ul> 1967 * <li><js>"K"</js> - x 1024 1968 * <li><js>"M"</js> - x 1024*1024 1969 * <li><js>"G"</js> - x 1024*1024*1024 1970 * </ul> 1971 * 1972 * @param s The string to parse. 1973 * @return The parsed value. 1974 */ 1975 public static int parseIntWithSuffix(String s) { 1976 assertFieldNotNull(s, "s"); 1977 int m = 1; 1978 if (s.endsWith("G")) { 1979 m = 1024*1024*1024; 1980 s = s.substring(0, s.length()-1).trim(); 1981 } else if (s.endsWith("M")) { 1982 m = 1024*1024; 1983 s = s.substring(0, s.length()-1).trim(); 1984 } else if (s.endsWith("K")) { 1985 m = 1024; 1986 s = s.substring(0, s.length()-1).trim(); 1987 } 1988 return Integer.decode(s) * m; 1989 } 1990 1991 /** 1992 * Converts a string containing a possible multiplier suffix to a long. 1993 * 1994 * <p> 1995 * The string can contain any of the following multiplier suffixes: 1996 * <ul> 1997 * <li><js>"K"</js> - x 1024 1998 * <li><js>"M"</js> - x 1024*1024 1999 * <li><js>"G"</js> - x 1024*1024*1024 2000 * </ul> 2001 * 2002 * @param s The string to parse. 2003 * @return The parsed value. 2004 */ 2005 public static long parseLongWithSuffix(String s) { 2006 assertFieldNotNull(s, "s"); 2007 int m = 1; 2008 if (s.endsWith("G")) { 2009 m = 1024*1024*1024; 2010 s = s.substring(0, s.length()-1).trim(); 2011 } else if (s.endsWith("M")) { 2012 m = 1024*1024; 2013 s = s.substring(0, s.length()-1).trim(); 2014 } else if (s.endsWith("K")) { 2015 m = 1024; 2016 s = s.substring(0, s.length()-1).trim(); 2017 } 2018 return Long.decode(s) * m; 2019 } 2020 2021 /** 2022 * Same as {@link String#contains(CharSequence)} except returns <jk>null</jk> if the value is null. 2023 * 2024 * @param value The string to check. 2025 * @param substring The value to check for. 2026 * @return <jk>true</jk> if the value contains the specified substring. 2027 */ 2028 public static boolean contains(String value, CharSequence substring) { 2029 return value == null ? false : value.contains(substring); 2030 } 2031 2032 /** 2033 * Returns <jk>true</jk> if the specified string appears to be an JSON array. 2034 * 2035 * @param o The object to test. 2036 * @return <jk>true</jk> if the specified string appears to be a JSON array. 2037 */ 2038 public static boolean isObjectList(Object o) { 2039 if (o instanceof CharSequence) { 2040 String s = o.toString(); 2041 return (s.startsWith("[") && s.endsWith("]") && BeanContext.DEFAULT != null); 2042 } 2043 return false; 2044 } 2045 2046 /** 2047 * Returns <jk>true</jk> if the specified string appears to be a JSON object. 2048 * 2049 * @param o The object to test. 2050 * @return <jk>true</jk> if the specified string appears to be a JSON object. 2051 */ 2052 public static boolean isObjectMap(Object o) { 2053 if (o instanceof CharSequence) { 2054 String s = o.toString(); 2055 return (s.startsWith("{") && s.endsWith("}") && BeanContext.DEFAULT != null); 2056 } 2057 return false; 2058 } 2059}