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.*; 026import java.util.concurrent.atomic.*; 027import java.util.regex.*; 028 029import javax.xml.bind.*; 030 031import org.apache.juneau.*; 032import org.apache.juneau.json.*; 033import org.apache.juneau.parser.*; 034import org.apache.juneau.parser.ParseException; 035 036/** 037 * Reusable string utility methods. 038 */ 039public final class StringUtils { 040 041 private static final AsciiSet numberChars = AsciiSet.create("-xX.+-#pP0123456789abcdefABCDEF"); 042 private static final AsciiSet firstNumberChars =AsciiSet.create("+-.#0123456789"); 043 private static final AsciiSet octChars = AsciiSet.create("01234567"); 044 private static final AsciiSet decChars = AsciiSet.create("0123456789"); 045 private static final AsciiSet hexChars = AsciiSet.create("0123456789abcdefABCDEF"); 046 047 // Maps 6-bit nibbles to BASE64 characters. 048 private static final char[] base64m1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 049 050 // Characters that do not need to be URL-encoded 051 private static final AsciiSet unencodedChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.!~*'()\\").build(); 052 053 // Characters that really do not need to be URL-encoded 054 private static final AsciiSet unencodedCharsLax = unencodedChars.copy() 055 .chars(":@$,") // reserved, but can't be confused in a query parameter. 056 .chars("{}|\\^[]`") // unwise characters. 057 .build(); 058 059 // Maps BASE64 characters to 6-bit nibbles. 060 private static final byte[] base64m2 = new byte[128]; 061 static { 062 for (int i = 0; i < 64; i++) 063 base64m2[base64m1[i]] = (byte)i; 064 } 065 066 /** 067 * Parses a number from the specified reader stream. 068 * 069 * @param r The reader to parse the string from. 070 * @param type 071 * The number type to created. 072 * Can be any of the following: 073 * <ul> 074 * <li> Integer 075 * <li> Double 076 * <li> Float 077 * <li> Long 078 * <li> Short 079 * <li> Byte 080 * <li> BigInteger 081 * <li> BigDecimal 082 * </ul> 083 * If <jk>null</jk>, uses the best guess. 084 * @throws IOException If a problem occurred trying to read from the reader. 085 * @return The parsed number. 086 * @throws Exception 087 */ 088 public static Number parseNumber(ParserReader r, Class<? extends Number> type) throws Exception { 089 return parseNumber(parseNumberString(r), type); 090 } 091 092 /** 093 * Reads a numeric string from the specified reader. 094 * 095 * @param r The reader to read form. 096 * @return The parsed number string. 097 * @throws Exception 098 */ 099 public static String parseNumberString(ParserReader r) throws Exception { 100 r.mark(); 101 int c = 0; 102 while (true) { 103 c = r.read(); 104 if (c == -1) 105 break; 106 if (! numberChars.contains((char)c)) { 107 r.unread(); 108 break; 109 } 110 } 111 return r.getMarked(); 112 } 113 114 /** 115 * Parses a number from the specified string. 116 * 117 * @param s The string to parse the number from. 118 * @param type 119 * The number type to created. 120 * Can be any of the following: 121 * <ul> 122 * <li> Integer 123 * <li> Double 124 * <li> Float 125 * <li> Long 126 * <li> Short 127 * <li> Byte 128 * <li> BigInteger 129 * <li> BigDecimal 130 * </ul> 131 * If <jk>null</jk> or <code>Number</code>, uses the best guess. 132 * @return The parsed number, or <jk>null</jk> if the string was null. 133 * @throws ParseException 134 */ 135 public static Number parseNumber(String s, Class<? extends Number> type) throws ParseException { 136 if (s == null) 137 return null; 138 if (s.isEmpty()) 139 s = "0"; 140 if (type == null) 141 type = Number.class; 142 143 try { 144 // Determine the data type if it wasn't specified. 145 boolean isAutoDetect = (type == Number.class); 146 boolean isDecimal = false; 147 if (isAutoDetect) { 148 // If we're auto-detecting, then we use either an Integer, Long, or Double depending on how 149 // long the string is. 150 // An integer range is -2,147,483,648 to 2,147,483,647 151 // An long range is -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 152 isDecimal = isDecimal(s); 153 if (isDecimal) { 154 if (s.length() > 20) 155 type = Double.class; 156 else if (s.length() >= 10) 157 type = Long.class; 158 else 159 type = Integer.class; 160 } 161 else if (isFloat(s)) 162 type = Double.class; 163 else 164 throw new NumberFormatException(s); 165 } 166 167 if (type == Double.class || type == Double.TYPE) { 168 Double d = Double.valueOf(s); 169 Float f = Float.valueOf(s); 170 if (isAutoDetect && (!isDecimal) && d.toString().equals(f.toString())) 171 return f; 172 return d; 173 } 174 if (type == Float.class || type == Float.TYPE) 175 return Float.valueOf(s); 176 if (type == BigDecimal.class) 177 return new BigDecimal(s); 178 if (type == Long.class || type == Long.TYPE || type == AtomicLong.class) { 179 try { 180 Long l = Long.decode(s); 181 if (type == AtomicLong.class) 182 return new AtomicLong(l); 183 if (isAutoDetect && l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { 184 // This occurs if the string is 10 characters long but is still a valid integer value. 185 return l.intValue(); 186 } 187 return l; 188 } catch (NumberFormatException e) { 189 if (isAutoDetect) { 190 // This occurs if the string is 20 characters long but still falls outside the range of a valid long. 191 return Double.valueOf(s); 192 } 193 throw e; 194 } 195 } 196 if (type == Integer.class || type == Integer.TYPE) 197 return Integer.decode(s); 198 if (type == Short.class || type == Short.TYPE) 199 return Short.decode(s); 200 if (type == Byte.class || type == Byte.TYPE) 201 return Byte.decode(s); 202 if (type == BigInteger.class) 203 return new BigInteger(s); 204 if (type == AtomicInteger.class) 205 return new AtomicInteger(Integer.decode(s)); 206 throw new ParseException("Unsupported Number type: {0}", type.getName()); 207 } catch (NumberFormatException e) { 208 throw new ParseException(e, "Invalid number: ''{0}'', class=''{1}''", s, type.getSimpleName()); 209 } 210 } 211 212 private static final Pattern fpRegex = Pattern.compile( 213 "[+-]?(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]*" 214 ); 215 216 /** 217 * Converts a <code>String</code> to a <code>Character</code> 218 * 219 * @param o The string to convert. 220 * @return The first character of the string if the string is of length 0, or <jk>null</jk> if the string is <jk>null</jk> or empty. 221 * @throws ParseException If string has a length greater than 1. 222 */ 223 public static Character parseCharacter(Object o) throws ParseException { 224 if (o == null) 225 return null; 226 String s = o.toString(); 227 if (s.length() == 0) 228 return null; 229 if (s.length() == 1) 230 return s.charAt(0); 231 throw new ParseException("Invalid character: ''{0}''", s); 232 } 233 234 /** 235 * Returns <jk>true</jk> if this string can be parsed by {@link #parseNumber(String, Class)}. 236 * 237 * @param s The string to check. 238 * @return <jk>true</jk> if this string can be parsed without causing an exception. 239 */ 240 public static boolean isNumeric(String s) { 241 if (s == null || s.isEmpty()) 242 return false; 243 if (! isFirstNumberChar(s.charAt(0))) 244 return false; 245 return isDecimal(s) || isFloat(s); 246 } 247 248 /** 249 * Returns <jk>true</jk> if the specified character is a valid first character for a number. 250 * 251 * @param c The character to test. 252 * @return <jk>true</jk> if the specified character is a valid first character for a number. 253 */ 254 public static boolean isFirstNumberChar(char c) { 255 return firstNumberChars.contains(c); 256 } 257 258 /** 259 * Returns <jk>true</jk> if the specified string is a floating point number. 260 * 261 * @param s The string to check. 262 * @return <jk>true</jk> if the specified string is a floating point number. 263 */ 264 public static boolean isFloat(String s) { 265 if (s == null || s.isEmpty()) 266 return false; 267 if (! firstNumberChars.contains(s.charAt(0))) 268 return (s.equals("NaN") || s.equals("Infinity")); 269 int i = 0; 270 int length = s.length(); 271 char c = s.charAt(0); 272 if (c == '+' || c == '-') 273 i++; 274 if (i == length) 275 return false; 276 c = s.charAt(i++); 277 if (c == '.' || decChars.contains(c)) { 278 return fpRegex.matcher(s).matches(); 279 } 280 return false; 281 } 282 283 /** 284 * Returns <jk>true</jk> if the specified string is numeric. 285 * 286 * @param s The string to check. 287 * @return <jk>true</jk> if the specified string is numeric. 288 */ 289 public static boolean isDecimal(String s) { 290 if (s == null || s.isEmpty()) 291 return false; 292 if (! firstNumberChars.contains(s.charAt(0))) 293 return false; 294 int i = 0; 295 int length = s.length(); 296 char c = s.charAt(0); 297 boolean isPrefixed = false; 298 if (c == '+' || c == '-') { 299 isPrefixed = true; 300 i++; 301 } 302 if (i == length) 303 return false; 304 c = s.charAt(i++); 305 if (c == '0' && length > (isPrefixed ? 2 : 1)) { 306 c = s.charAt(i++); 307 if (c == 'x' || c == 'X') { 308 for (int j = i; j < length; j++) { 309 if (! hexChars.contains(s.charAt(j))) 310 return false; 311 } 312 } else if (octChars.contains(c)) { 313 for (int j = i; j < length; j++) 314 if (! octChars.contains(s.charAt(j))) 315 return false; 316 } else { 317 return false; 318 } 319 } else if (c == '#') { 320 for (int j = i; j < length; j++) { 321 if (! hexChars.contains(s.charAt(j))) 322 return false; 323 } 324 } else if (decChars.contains(c)) { 325 for (int j = i; j < length; j++) 326 if (! decChars.contains(s.charAt(j))) 327 return false; 328 } else { 329 return false; 330 } 331 return true; 332 } 333 334 /** 335 * Convenience method for getting a stack trace as a string. 336 * 337 * @param t The throwable to get the stack trace from. 338 * @return The same content that would normally be rendered via <code>t.printStackTrace()</code> 339 */ 340 public static String getStackTrace(Throwable t) { 341 StringWriter sw = new StringWriter(); 342 try (PrintWriter pw = new PrintWriter(sw)) { 343 t.printStackTrace(pw); 344 } 345 return sw.toString(); 346 } 347 348 /** 349 * Join the specified tokens into a delimited string. 350 * 351 * @param tokens The tokens to join. 352 * @param separator The delimiter. 353 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 354 */ 355 public static String join(Object[] tokens, String separator) { 356 if (tokens == null) 357 return null; 358 StringBuilder sb = new StringBuilder(); 359 for (int i = 0; i < tokens.length; i++) { 360 if (i > 0) 361 sb.append(separator); 362 sb.append(tokens[i]); 363 } 364 return sb.toString(); 365 } 366 367 /** 368 * Join the specified tokens into a delimited string. 369 * 370 * @param tokens The tokens to join. 371 * @param d The delimiter. 372 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 373 */ 374 public static String join(int[] tokens, String d) { 375 if (tokens == null) 376 return null; 377 StringBuilder sb = new StringBuilder(); 378 for (int i = 0; i < tokens.length; i++) { 379 if (i > 0) 380 sb.append(d); 381 sb.append(tokens[i]); 382 } 383 return sb.toString(); 384 } 385 386 /** 387 * Join the specified tokens into a delimited string. 388 * 389 * @param tokens The tokens to join. 390 * @param d The delimiter. 391 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 392 */ 393 public static String join(Collection<?> tokens, String d) { 394 if (tokens == null) 395 return null; 396 return join(tokens, d, new StringBuilder()).toString(); 397 } 398 399 /** 400 * Joins the specified tokens into a delimited string and writes the output to the specified string builder. 401 * 402 * @param tokens The tokens to join. 403 * @param d The delimiter. 404 * @param sb The string builder to append the response to. 405 * @return The same string builder passed in as <code>sb</code>. 406 */ 407 public static StringBuilder join(Collection<?> tokens, String d, StringBuilder sb) { 408 if (tokens == null) 409 return sb; 410 for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) { 411 sb.append(iter.next()); 412 if (iter.hasNext()) 413 sb.append(d); 414 } 415 return sb; 416 } 417 418 /** 419 * Joins the specified tokens into a delimited string. 420 * 421 * @param tokens The tokens to join. 422 * @param d The delimiter. 423 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 424 */ 425 public static String join(Object[] tokens, char d) { 426 if (tokens == null) 427 return null; 428 return join(tokens, d, new StringBuilder()).toString(); 429 } 430 431 /** 432 * Same as {@link #join(Object[], char)} except escapes the delimiter character if found in the tokens. 433 * 434 * @param tokens The tokens to join. 435 * @param d The delimiter. 436 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 437 */ 438 public static String joine(Object[] tokens, char d) { 439 if (tokens == null) 440 return null; 441 return joine(tokens, d, new StringBuilder()).toString(); 442 } 443 444 private static AsciiSet getEscapeSet(char c) { 445 AsciiSet s = ESCAPE_SETS.get(c); 446 if (s == null) { 447 s = AsciiSet.create().chars(c, '\\').build(); 448 ESCAPE_SETS.put(c, s); 449 } 450 return s; 451 } 452 static Map<Character,AsciiSet> ESCAPE_SETS = new ConcurrentHashMap<>(); 453 454 /** 455 * Join the specified tokens into a delimited string and writes the output to the specified string builder. 456 * 457 * @param tokens The tokens to join. 458 * @param d The delimiter. 459 * @param sb The string builder to append the response to. 460 * @return The same string builder passed in as <code>sb</code>. 461 */ 462 public static StringBuilder join(Object[] tokens, char d, StringBuilder sb) { 463 if (tokens == null) 464 return sb; 465 for (int i = 0; i < tokens.length; i++) { 466 if (i > 0) 467 sb.append(d); 468 sb.append(tokens[i]); 469 } 470 return sb; 471 } 472 473 /** 474 * Same as {@link #join(Object[], char, StringBuilder)} but escapes the delimiter character if found in the tokens. 475 * 476 * @param tokens The tokens to join. 477 * @param d The delimiter. 478 * @param sb The string builder to append the response to. 479 * @return The same string builder passed in as <code>sb</code>. 480 */ 481 public static StringBuilder joine(Object[] tokens, char d, StringBuilder sb) { 482 if (tokens == null) 483 return sb; 484 AsciiSet as = getEscapeSet(d); 485 for (int i = 0; i < tokens.length; i++) { 486 if (i > 0) 487 sb.append(d); 488 sb.append(escapeChars(asString(tokens[i]), as)); 489 } 490 return sb; 491 } 492 493 /** 494 * Join the specified tokens into a delimited string. 495 * 496 * @param tokens The tokens to join. 497 * @param d The delimiter. 498 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 499 */ 500 public static String join(int[] tokens, char d) { 501 if (tokens == null) 502 return null; 503 StringBuilder sb = new StringBuilder(); 504 for (int i = 0; i < tokens.length; i++) { 505 if (i > 0) 506 sb.append(d); 507 sb.append(tokens[i]); 508 } 509 return sb.toString(); 510 } 511 512 /** 513 * Join the specified tokens into a delimited string. 514 * 515 * @param tokens The tokens to join. 516 * @param d The delimiter. 517 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 518 */ 519 public static String join(Collection<?> tokens, char d) { 520 if (tokens == null) 521 return null; 522 StringBuilder sb = new StringBuilder(); 523 for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) { 524 sb.append(iter.next()); 525 if (iter.hasNext()) 526 sb.append(d); 527 } 528 return sb.toString(); 529 } 530 531 /** 532 * Same as {@link #join(Collection, char)} but escapes the delimiter if found in the tokens. 533 * 534 * @param tokens The tokens to join. 535 * @param d The delimiter. 536 * @return The delimited string. If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>. 537 */ 538 public static String joine(Collection<?> tokens, char d) { 539 if (tokens == null) 540 return null; 541 AsciiSet as = getEscapeSet(d); 542 StringBuilder sb = new StringBuilder(); 543 for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) { 544 sb.append(escapeChars(asString(iter.next()), as)); 545 if (iter.hasNext()) 546 sb.append(d); 547 } 548 return sb.toString(); 549 } 550 551 /** 552 * Joins tokens with newlines. 553 * 554 * @param tokens The tokens to concatenate. 555 * @return A string with the specified tokens contatenated with newlines. 556 */ 557 public static String joinnl(Object[] tokens) { 558 return join(tokens, '\n'); 559 } 560 561 /** 562 * Shortcut for calling <code>split(s, <js>','</js>)</code> 563 * 564 * @param s The string to split. Can be <jk>null</jk>. 565 * @return The tokens, or <jk>null</jk> if the string was null. 566 */ 567 public static String[] split(String s) { 568 return split(s, ','); 569 } 570 571 /** 572 * Splits a character-delimited string into a string array. 573 * 574 * <p> 575 * Does not split on escaped-delimiters (e.g. "\,"); 576 * Resulting tokens are trimmed of whitespace. 577 * 578 * <p> 579 * <b>NOTE:</b> This behavior is different than the Jakarta equivalent. 580 * split("a,b,c",',') -> {"a","b","c"} 581 * split("a, b ,c ",',') -> {"a","b","c"} 582 * split("a,,c",',') -> {"a","","c"} 583 * split(",,",',') -> {"","",""} 584 * split("",',') -> {} 585 * split(null,',') -> null 586 * split("a,b\,c,d", ',', false) -> {"a","b\,c","d"} 587 * split("a,b\\,c,d", ',', false) -> {"a","b\","c","d"} 588 * split("a,b\,c,d", ',', true) -> {"a","b,c","d"} 589 * 590 * @param s The string to split. Can be <jk>null</jk>. 591 * @param c The character to split on. 592 * @return The tokens, or <jk>null</jk> if the string was null. 593 */ 594 public static String[] split(String s, char c) { 595 return split(s, c, Integer.MAX_VALUE); 596 } 597 598 /** 599 * Same as {@link #split(String, char)} but limits the number of tokens returned. 600 * 601 * @param s The string to split. Can be <jk>null</jk>. 602 * @param c The character to split on. 603 * @param limit The maximum number of tokens to return. 604 * @return The tokens, or <jk>null</jk> if the string was null. 605 */ 606 public static String[] split(String s, char c, int limit) { 607 608 AsciiSet escapeChars = getEscapeSet(c); 609 610 if (s == null) 611 return null; 612 if (isEmpty(s)) 613 return new String[0]; 614 if (s.indexOf(c) == -1) 615 return new String[]{s}; 616 617 List<String> l = new LinkedList<>(); 618 char[] sArray = s.toCharArray(); 619 int x1 = 0, escapeCount = 0; 620 limit--; 621 for (int i = 0; i < sArray.length && limit > 0; i++) { 622 if (sArray[i] == '\\') escapeCount++; 623 else if (sArray[i]==c && escapeCount % 2 == 0) { 624 String s2 = new String(sArray, x1, i-x1); 625 String s3 = unEscapeChars(s2, escapeChars); 626 l.add(s3.trim()); 627 limit--; 628 x1 = i+1; 629 } 630 if (sArray[i] != '\\') escapeCount = 0; 631 } 632 String s2 = new String(sArray, x1, sArray.length-x1); 633 String s3 = unEscapeChars(s2, escapeChars); 634 l.add(s3.trim()); 635 636 return l.toArray(new String[l.size()]); 637 } 638 639 /** 640 * Same as {@link #split(String, char)} except splits all strings in the input and returns a single result. 641 * 642 * @param s The string to split. Can be <jk>null</jk>. 643 * @param c The character to split on. 644 * @return The tokens. 645 */ 646 public static String[] split(String[] s, char c) { 647 if (s == null) 648 return null; 649 List<String> l = new LinkedList<>(); 650 for (String ss : s) { 651 if (ss == null || ss.indexOf(c) == -1) 652 l.add(ss); 653 else 654 l.addAll(Arrays.asList(split(ss, c))); 655 } 656 return l.toArray(new String[l.size()]); 657 } 658 659 /** 660 * Splits a list of key-value pairs into an ordered map. 661 * 662 * <p> 663 * Example: 664 * <p class='bcode w800'> 665 * String in = <js>"foo=1;bar=2"</js>; 666 * Map m = StringUtils.<jsm>splitMap</jsm>(in, <js>';'</js>, <js>'='</js>, <jk>true</jk>); 667 * </p> 668 * 669 * @param s The string to split. 670 * @param trim Trim strings after parsing. 671 * @return The parsed map. Never <jk>null</jk>. 672 */ 673 public static Map<String,String> splitMap(String s, boolean trim) { 674 675 if (s == null) 676 return null; 677 if (isEmpty(s)) 678 return Collections.EMPTY_MAP; 679 680 Map<String,String> m = new LinkedHashMap<>(); 681 682 int 683 S1 = 1, // Found start of key, looking for equals. 684 S2 = 2; // Found equals, looking for delimiter (or end). 685 686 int state = S1; 687 688 char[] sArray = s.toCharArray(); 689 int x1 = 0, escapeCount = 0; 690 String key = null; 691 for (int i = 0; i < sArray.length + 1; i++) { 692 char c = i == sArray.length ? ',' : sArray[i]; 693 if (c == '\\') 694 escapeCount++; 695 if (escapeCount % 2 == 0) { 696 if (state == S1) { 697 if (c == '=') { 698 key = s.substring(x1, i); 699 if (trim) 700 key = trim(key); 701 key = unEscapeChars(key, MAP_ESCAPE_SET); 702 state = S2; 703 x1 = i+1; 704 } else if (c == ',') { 705 key = s.substring(x1, i); 706 if (trim) 707 key = trim(key); 708 key = unEscapeChars(key, MAP_ESCAPE_SET); 709 m.put(key, ""); 710 state = S1; 711 x1 = i+1; 712 } 713 } else if (state == S2) { 714 if (c == ',') { 715 String val = s.substring(x1, i); 716 if (trim) 717 val = trim(val); 718 val = unEscapeChars(val, MAP_ESCAPE_SET); 719 m.put(key, val); 720 key = null; 721 x1 = i+1; 722 state = S1; 723 } 724 } 725 } 726 if (c != '\\') escapeCount = 0; 727 } 728 729 return m; 730 } 731 732 private static final AsciiSet MAP_ESCAPE_SET = AsciiSet.create(",=\\"); 733 734 /** 735 * Returns <jk>true</jk> if the specified string contains any of the specified characters. 736 * 737 * @param s The string to test. 738 * @param chars The characters to look for. 739 * @return 740 * <jk>true</jk> if the specified string contains any of the specified characters. 741 * <br><jk>false</jk> if the string is <jk>null</jk>. 742 */ 743 public static boolean containsAny(String s, char...chars) { 744 if (s == null) 745 return false; 746 for (int i = 0; i < s.length(); i++) { 747 char c = s.charAt(i); 748 for (char c2 : chars) 749 if (c == c2) 750 return true; 751 } 752 return false; 753 } 754 755 /** 756 * Splits a space-delimited string with optionally quoted arguments. 757 * 758 * <p> 759 * Examples: 760 * <ul> 761 * <li><js>"foo"</js> => <code>["foo"]</code> 762 * <li><js>" foo "</js> => <code>["foo"]</code> 763 * <li><js>"foo bar baz"</js> => <code>["foo","bar","baz"]</code> 764 * <li><js>"foo 'bar baz'"</js> => <code>["foo","bar baz"]</code> 765 * <li><js>"foo \"bar baz\""</js> => <code>["foo","bar baz"]</code> 766 * <li><js>"foo 'bar\'baz'"</js> => <code>["foo","bar'baz"]</code> 767 * </ul> 768 * 769 * @param s The input string. 770 * @return 771 * The results, or <jk>null</jk> if the input was <jk>null</jk>. 772 * <br>An empty string results in an empty array. 773 */ 774 public static String[] splitQuoted(String s) { 775 776 if (s == null) 777 return null; 778 779 s = s.trim(); 780 781 if (isEmpty(s)) 782 return new String[0]; 783 784 if (! containsAny(s, ' ', '\t', '\'', '"')) 785 return new String[]{s}; 786 787 int 788 S1 = 1, // Looking for start of token. 789 S2 = 2, // Found ', looking for end ' 790 S3 = 3, // Found ", looking for end " 791 S4 = 4; // Found non-whitespace, looking for end whitespace. 792 793 int state = S1; 794 795 boolean isInEscape = false, needsUnescape = false; 796 int mark = 0; 797 798 List<String> l = new ArrayList<>(); 799 for (int i = 0; i < s.length(); i++) { 800 char c = s.charAt(i); 801 802 if (state == S1) { 803 if (c == '\'') { 804 state = S2; 805 mark = i+1; 806 } else if (c == '"') { 807 state = S3; 808 mark = i+1; 809 } else if (c != ' ' && c != '\t') { 810 state = S4; 811 mark = i; 812 } 813 } else if (state == S2 || state == S3) { 814 if (c == '\\') { 815 isInEscape = ! isInEscape; 816 needsUnescape = true; 817 } else if (! isInEscape) { 818 if (c == (state == S2 ? '\'' : '"')) { 819 String s2 = s.substring(mark, i); 820 if (needsUnescape) 821 s2 = unEscapeChars(s2, QUOTE_ESCAPE_SET); 822 l.add(s2); 823 state = S1; 824 isInEscape = needsUnescape = false; 825 } 826 } else { 827 isInEscape = false; 828 } 829 } else if (state == S4) { 830 if (c == ' ' || c == '\t') { 831 l.add(s.substring(mark, i)); 832 state = S1; 833 } 834 } 835 } 836 if (state == S4) 837 l.add(s.substring(mark)); 838 else if (state == S2 || state == S3) 839 throw new RuntimeException("Unmatched string quotes: " + s); 840 return l.toArray(new String[l.size()]); 841 } 842 843 private static final AsciiSet QUOTE_ESCAPE_SET = AsciiSet.create("\"'\\"); 844 845 /** 846 * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty. 847 * 848 * @param s The string to check. 849 * @return <jk>true</jk> if specified string is <jk>null</jk> or empty. 850 */ 851 public static boolean isEmpty(String s) { 852 return s == null || s.isEmpty(); 853 } 854 855 /** 856 * Returns <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty 857 * string. 858 * 859 * @param s The string to check. 860 * @return 861 * <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty string. 862 */ 863 public static boolean isEmpty(Object s) { 864 return s == null || s.toString().isEmpty(); 865 } 866 867 /** 868 * Returns <jk>true</jk> if specified string is not <jk>null</jk> or empty. 869 * 870 * @param s The string to check. 871 * @return <jk>true</jk> if specified string is not <jk>null</jk> or empty. 872 */ 873 public static boolean isNotEmpty(String s) { 874 return ! isEmpty(s); 875 } 876 877 /** 878 * Returns <jk>true</jk> if specified string is not <jk>null</jk> or it's {@link #toString()} method doesn't return an empty 879 * string. 880 * 881 * @param s The string to check. 882 * @return 883 * <jk>true</jk> if specified string is not <jk>null</jk> or it's {@link #toString()} method doesn't return an empty string. 884 */ 885 public static boolean isNotEmpty(Object s) { 886 return ! isEmpty(s); 887 } 888 889 /** 890 * Returns <jk>null</jk> if the specified string is <jk>null</jk> or empty. 891 * 892 * @param s The string to check. 893 * @return <jk>null</jk> if the specified string is <jk>null</jk> or empty, or the same string if not. 894 */ 895 public static String nullIfEmpty(String s) { 896 if (s == null || s.isEmpty()) 897 return null; 898 return s; 899 } 900 901 /** 902 * Returns an empty string if the specified string is <jk>null</jk>. 903 * 904 * @param s The string to check. 905 * @return An empty string if the specified string is <jk>null</jk>, or the same string otherwise. 906 */ 907 public static String emptyIfNull(String s) { 908 if (s == null) 909 return ""; 910 return s; 911 } 912 913 /** 914 * Returns an empty string if the specified object is <jk>null</jk>. 915 * 916 * @param o The object to check. 917 * @return An empty string if the specified object is <jk>null</jk>, or the object converted to a string using {@link String#toString()}. 918 */ 919 public static String emptyIfNull(Object o) { 920 if (o == null) 921 return ""; 922 return o.toString(); 923 } 924 925 926 /** 927 * Removes escape characters from the specified characters. 928 * 929 * @param s The string to remove escape characters from. 930 * @param escaped The characters escaped. 931 * @return A new string if characters were removed, or the same string if not or if the input was <jk>null</jk>. 932 */ 933 public static String unEscapeChars(String s, AsciiSet escaped) { 934 if (s == null || s.length() == 0) 935 return s; 936 int count = 0; 937 for (int i = 0; i < s.length(); i++) 938 if (escaped.contains(s.charAt(i))) 939 count++; 940 if (count == 0) 941 return s; 942 StringBuffer sb = new StringBuffer(s.length()-count); 943 for (int i = 0; i < s.length(); i++) { 944 char c = s.charAt(i); 945 946 if (c == '\\') { 947 if (i+1 != s.length()) { 948 char c2 = s.charAt(i+1); 949 if (escaped.contains(c2)) { 950 i++; 951 } else if (c2 == '\\') { 952 sb.append('\\'); 953 i++; 954 } 955 } 956 } 957 sb.append(s.charAt(i)); 958 } 959 return sb.toString(); 960 } 961 962 /** 963 * Escapes the specified characters in the string. 964 * 965 * @param s The string with characters to escape. 966 * @param escaped The characters to escape. 967 * @return The string with characters escaped, or the same string if no escapable characters were found. 968 */ 969 public static String escapeChars(String s, AsciiSet escaped) { 970 if (s == null || s.length() == 0) 971 return s; 972 973 int count = 0; 974 for (int i = 0; i < s.length(); i++) 975 if (escaped.contains(s.charAt(i))) 976 count++; 977 if (count == 0) 978 return s; 979 980 StringBuffer sb = new StringBuffer(s.length() + count); 981 for (int i = 0; i < s.length(); i++) { 982 char c = s.charAt(i); 983 if (escaped.contains(c)) 984 sb.append('\\'); 985 sb.append(c); 986 } 987 return sb.toString(); 988 } 989 990 /** 991 * Debug method for rendering non-ASCII character sequences. 992 * 993 * @param s The string to decode. 994 * @return A string with non-ASCII characters converted to <js>"[hex]"</js> sequences. 995 */ 996 public static String decodeHex(String s) { 997 if (s == null) 998 return null; 999 StringBuilder sb = new StringBuilder(); 1000 for (char c : s.toCharArray()) { 1001 if (c < ' ' || c > '~') 1002 sb.append("["+Integer.toHexString(c)+"]"); 1003 else 1004 sb.append(c); 1005 } 1006 return sb.toString(); 1007 } 1008 1009 /** 1010 * An efficient method for checking if a string starts with a character. 1011 * 1012 * @param s The string to check. Can be <jk>null</jk>. 1013 * @param c The character to check for. 1014 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and starts with the specified character. 1015 */ 1016 public static boolean startsWith(String s, char c) { 1017 if (s != null) { 1018 int i = s.length(); 1019 if (i > 0) 1020 return s.charAt(0) == c; 1021 } 1022 return false; 1023 } 1024 1025 /** 1026 * An efficient method for checking if a string ends with a character. 1027 * 1028 * @param s The string to check. Can be <jk>null</jk>. 1029 * @param c The character to check for. 1030 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character. 1031 */ 1032 public static boolean endsWith(String s, char c) { 1033 if (s != null) { 1034 int i = s.length(); 1035 if (i > 0) 1036 return s.charAt(i-1) == c; 1037 } 1038 return false; 1039 } 1040 1041 /** 1042 * Same as {@link #endsWith(String, char)} except check for multiple characters. 1043 * 1044 * @param s The string to check. Can be <jk>null</jk>. 1045 * @param c The characters to check for. 1046 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character. 1047 */ 1048 public static boolean endsWith(String s, char...c) { 1049 if (s != null) { 1050 int i = s.length(); 1051 if (i > 0) { 1052 char c2 = s.charAt(i-1); 1053 for (char cc : c) 1054 if (c2 == cc) 1055 return true; 1056 } 1057 } 1058 return false; 1059 } 1060 1061 /** 1062 * Converts the specified number into a 2 hexadecimal characters. 1063 * 1064 * @param num The number to convert to hex. 1065 * @return A <code><jk>char</jk>[2]</code> containing the specified characters. 1066 */ 1067 public static final char[] toHex2(int num) { 1068 if (num < 0 || num > 255) 1069 throw new NumberFormatException("toHex2 can only be used on numbers between 0 and 255"); 1070 char[] n = new char[2]; 1071 int a = num%16; 1072 n[1] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1073 a = (num/16)%16; 1074 n[0] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1075 return n; 1076 } 1077 1078 /** 1079 * Converts the specified number into a 4 hexadecimal characters. 1080 * 1081 * @param num The number to convert to hex. 1082 * @return A <code><jk>char</jk>[4]</code> containing the specified characters. 1083 */ 1084 public static final char[] toHex4(int num) { 1085 char[] n = new char[4]; 1086 int a = num%16; 1087 n[3] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1088 int base = 16; 1089 for (int i = 1; i < 4; i++) { 1090 a = (num/base)%16; 1091 base <<= 4; 1092 n[3-i] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1093 } 1094 return n; 1095 } 1096 1097 /** 1098 * Converts the specified number into a 8 hexadecimal characters. 1099 * 1100 * @param num The number to convert to hex. 1101 * @return A <code><jk>char</jk>[8]</code> containing the specified characters. 1102 */ 1103 public static final char[] toHex8(long num) { 1104 char[] n = new char[8]; 1105 long a = num%16; 1106 n[7] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1107 int base = 16; 1108 for (int i = 1; i < 8; i++) { 1109 a = (num/base)%16; 1110 base <<= 4; 1111 n[7-i] = (char)(a > 9 ? 'A'+a-10 : '0'+a); 1112 } 1113 return n; 1114 } 1115 1116 /** 1117 * Tests two strings for equality, but gracefully handles nulls. 1118 * 1119 * @param s1 String 1. 1120 * @param s2 String 2. 1121 * @return <jk>true</jk> if the strings are equal. 1122 */ 1123 public static boolean isEquals(String s1, String s2) { 1124 if (s1 == null) 1125 return s2 == null; 1126 if (s2 == null) 1127 return false; 1128 return s1.equals(s2); 1129 } 1130 1131 /** 1132 * Tests two strings for non-equality, but gracefully handles nulls. 1133 * 1134 * @param s1 String 1. 1135 * @param s2 String 2. 1136 * @return <jk>true</jk> if the strings are not equal. 1137 */ 1138 public static boolean isNotEquals(String s1, String s2) { 1139 return ! isEquals(s1, s2); 1140 } 1141 1142 /** 1143 * Shortcut for calling <code>base64Encode(in.getBytes(<js>"UTF-8"</js>))</code> 1144 * 1145 * @param in The input string to convert. 1146 * @return The string converted to BASE-64 encoding. 1147 */ 1148 public static String base64EncodeToString(String in) { 1149 if (in == null) 1150 return null; 1151 return base64Encode(in.getBytes(IOUtils.UTF8)); 1152 } 1153 1154 /** 1155 * BASE64-encodes the specified byte array. 1156 * 1157 * @param in The input byte array to convert. 1158 * @return The byte array converted to a BASE-64 encoded string. 1159 */ 1160 public static String base64Encode(byte[] in) { 1161 int outLength = (in.length * 4 + 2) / 3; // Output length without padding 1162 char[] out = new char[((in.length + 2) / 3) * 4]; // Length includes padding. 1163 int iIn = 0; 1164 int iOut = 0; 1165 while (iIn < in.length) { 1166 int i0 = in[iIn++] & 0xff; 1167 int i1 = iIn < in.length ? in[iIn++] & 0xff : 0; 1168 int i2 = iIn < in.length ? in[iIn++] & 0xff : 0; 1169 int o0 = i0 >>> 2; 1170 int o1 = ((i0 & 3) << 4) | (i1 >>> 4); 1171 int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); 1172 int o3 = i2 & 0x3F; 1173 out[iOut++] = base64m1[o0]; 1174 out[iOut++] = base64m1[o1]; 1175 out[iOut] = iOut < outLength ? base64m1[o2] : '='; 1176 iOut++; 1177 out[iOut] = iOut < outLength ? base64m1[o3] : '='; 1178 iOut++; 1179 } 1180 return new String(out); 1181 } 1182 1183 /** 1184 * Shortcut for calling <code>base64Decode(String)</code> and converting the result to a UTF-8 encoded string. 1185 * 1186 * @param in The BASE-64 encoded string to decode. 1187 * @return The decoded string. 1188 */ 1189 public static String base64DecodeToString(String in) { 1190 byte[] b = base64Decode(in); 1191 if (b == null) 1192 return null; 1193 return new String(b, IOUtils.UTF8); 1194 } 1195 1196 /** 1197 * BASE64-decodes the specified string. 1198 * 1199 * @param in The BASE-64 encoded string. 1200 * @return The decoded byte array. 1201 */ 1202 public static byte[] base64Decode(String in) { 1203 if (in == null) 1204 return null; 1205 1206 byte bIn[] = in.getBytes(IOUtils.UTF8); 1207 1208 if (bIn.length % 4 != 0) 1209 illegalArg("Invalid BASE64 string length. Must be multiple of 4."); 1210 1211 // Strip out any trailing '=' filler characters. 1212 int inLength = bIn.length; 1213 while (inLength > 0 && bIn[inLength - 1] == '=') 1214 inLength--; 1215 1216 int outLength = (inLength * 3) / 4; 1217 byte[] out = new byte[outLength]; 1218 int iIn = 0; 1219 int iOut = 0; 1220 while (iIn < inLength) { 1221 int i0 = bIn[iIn++]; 1222 int i1 = bIn[iIn++]; 1223 int i2 = iIn < inLength ? bIn[iIn++] : 'A'; 1224 int i3 = iIn < inLength ? bIn[iIn++] : 'A'; 1225 int b0 = base64m2[i0]; 1226 int b1 = base64m2[i1]; 1227 int b2 = base64m2[i2]; 1228 int b3 = base64m2[i3]; 1229 int o0 = (b0 << 2) | (b1 >>> 4); 1230 int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); 1231 int o2 = ((b2 & 3) << 6) | b3; 1232 out[iOut++] = (byte)o0; 1233 if (iOut < outLength) 1234 out[iOut++] = (byte)o1; 1235 if (iOut < outLength) 1236 out[iOut++] = (byte)o2; 1237 } 1238 return out; 1239 } 1240 1241 /** 1242 * Generated a random UUID with the specified number of characters. 1243 * 1244 * <p> 1245 * Characters are composed of lower-case ASCII letters and numbers only. 1246 * 1247 * <p> 1248 * This method conforms to the restrictions for hostnames as specified in {@doc https://tools.ietf.org/html/rfc952 RFC 952} 1249 * Since each character has 36 possible values, the square approximation formula for the number of generated IDs 1250 * that would produce a 50% chance of collision is: 1251 * <code>sqrt(36^N)</code>. 1252 * Dividing this number by 10 gives you an approximation of the number of generated IDs needed to produce a 1253 * <1% chance of collision. 1254 * 1255 * <p> 1256 * For example, given 5 characters, the number of generated IDs need to produce a <1% chance of collision would 1257 * be: 1258 * <code>sqrt(36^5)/10=777</code> 1259 * 1260 * @param numchars The number of characters in the generated UUID. 1261 * @return A new random UUID. 1262 */ 1263 public static String generateUUID(int numchars) { 1264 Random r = new Random(); 1265 StringBuilder sb = new StringBuilder(numchars); 1266 for (int i = 0; i < numchars; i++) { 1267 int c = r.nextInt(36) + 97; 1268 if (c > 'z') 1269 c -= ('z'-'0'+1); 1270 sb.append((char)c); 1271 } 1272 return sb.toString(); 1273 } 1274 1275 /** 1276 * Same as {@link String#trim()} but prevents <code>NullPointerExceptions</code>. 1277 * 1278 * @param s The string to trim. 1279 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1280 */ 1281 public static String trim(String s) { 1282 if (s == null) 1283 return null; 1284 return s.trim(); 1285 } 1286 1287 /** 1288 * Parses an ISO8601 string into a date. 1289 * 1290 * <p> 1291 * Supports any of the following formats: 1292 * <br><code>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</code> 1293 * 1294 * @param date The date string. 1295 * @return The parsed date. 1296 * @throws IllegalArgumentException 1297 */ 1298 public static Date parseIsoDate(String date) throws IllegalArgumentException { 1299 if (isEmpty(date)) 1300 return null; 1301 return parseIsoCalendar(date).getTime(); 1302 } 1303 1304 /** 1305 * Parses an ISO8601 string into a calendar. 1306 * 1307 * <p> 1308 * Supports any of the following formats: 1309 * <br><code>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</code> 1310 * 1311 * @param date The date string. 1312 * @return The parsed calendar. 1313 * @throws IllegalArgumentException 1314 */ 1315 public static Calendar parseIsoCalendar(String date) throws IllegalArgumentException { 1316 if (isEmpty(date)) 1317 return null; 1318 date = date.trim().replace(' ', 'T'); // Convert to 'standard' ISO8601 1319 if (date.indexOf(',') != -1) // Trim milliseconds 1320 date = date.substring(0, date.indexOf(',')); 1321 if (date.matches("\\d{4}")) 1322 date += "-01-01T00:00:00"; 1323 else if (date.matches("\\d{4}\\-\\d{2}")) 1324 date += "-01T00:00:00"; 1325 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}")) 1326 date += "T00:00:00"; 1327 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}")) 1328 date += ":00:00"; 1329 else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}")) 1330 date += ":00"; 1331 return DatatypeConverter.parseDateTime(date); 1332 } 1333 1334 /** 1335 * Converts the specified object to an ISO8601 date string. 1336 * 1337 * @param c The object to convert. 1338 * @return The converted object. 1339 */ 1340 public static String toIsoDate(Calendar c) { 1341 return DatatypeConverter.printDate(c); 1342 } 1343 1344 /** 1345 * Converts the specified object to an ISO8601 date-time string. 1346 * 1347 * @param c The object to convert. 1348 * @return The converted object. 1349 */ 1350 public static String toIsoDateTime(Calendar c) { 1351 return DatatypeConverter.printDateTime(c); 1352 } 1353 1354 /** 1355 * Simple utility for replacing variables of the form <js>"{key}"</js> with values in the specified map. 1356 * 1357 * <p> 1358 * Nested variables are supported in both the input string and map values. 1359 * 1360 * <p> 1361 * If the map does not contain the specified value, the variable is not replaced. 1362 * 1363 * <p> 1364 * <jk>null</jk> values in the map are treated as blank strings. 1365 * 1366 * @param s The string containing variables to replace. 1367 * @param m The map containing the variable values. 1368 * @return The new string with variables replaced, or the original string if it didn't have variables in it. 1369 */ 1370 public static String replaceVars(String s, Map<String,Object> m) { 1371 1372 if (s == null) 1373 return null; 1374 1375 if (s.indexOf('{') == -1) 1376 return s; 1377 1378 int S1 = 1; // Not in variable, looking for { 1379 int S2 = 2; // Found {, Looking for } 1380 1381 int state = S1; 1382 boolean hasInternalVar = false; 1383 int x = 0; 1384 int depth = 0; 1385 int length = s.length(); 1386 StringBuilder out = new StringBuilder(); 1387 for (int i = 0; i < length; i++) { 1388 char c = s.charAt(i); 1389 if (state == S1) { 1390 if (c == '{') { 1391 state = S2; 1392 x = i; 1393 } else { 1394 out.append(c); 1395 } 1396 } else /* state == S2 */ { 1397 if (c == '{') { 1398 depth++; 1399 hasInternalVar = true; 1400 } else if (c == '}') { 1401 if (depth > 0) { 1402 depth--; 1403 } else { 1404 String key = s.substring(x+1, i); 1405 key = (hasInternalVar ? replaceVars(key, m) : key); 1406 hasInternalVar = false; 1407 if (! m.containsKey(key)) 1408 out.append('{').append(key).append('}'); 1409 else { 1410 Object val = m.get(key); 1411 if (val == null) 1412 val = ""; 1413 String v = val.toString(); 1414 // If the replacement also contains variables, replace them now. 1415 if (v.indexOf('{') != -1) 1416 v = replaceVars(v, m); 1417 out.append(v); 1418 } 1419 state = 1; 1420 } 1421 } 1422 } 1423 } 1424 return out.toString(); 1425 } 1426 1427 /** 1428 * Returns <jk>true</jk> if the specified path string is prefixed with the specified prefix. 1429 * 1430 * <h5 class='section'>Example:</h5> 1431 * <p class='bcode w800'> 1432 * pathStartsWith(<js>"foo"</js>, <js>"foo"</js>); <jc>// true</jc> 1433 * pathStartsWith(<js>"foo/bar"</js>, <js>"foo"</js>); <jc>// true</jc> 1434 * pathStartsWith(<js>"foo2"</js>, <js>"foo"</js>); <jc>// false</jc> 1435 * pathStartsWith(<js>"foo2"</js>, <js>""</js>); <jc>// false</jc> 1436 * </p> 1437 * 1438 * @param path The path to check. 1439 * @param pathPrefix The prefix. 1440 * @return <jk>true</jk> if the specified path string is prefixed with the specified prefix. 1441 */ 1442 public static boolean pathStartsWith(String path, String pathPrefix) { 1443 if (path == null || pathPrefix == null) 1444 return false; 1445 if (path.startsWith(pathPrefix)) 1446 return path.length() == pathPrefix.length() || path.charAt(pathPrefix.length()) == '/'; 1447 return false; 1448 } 1449 1450 /** 1451 * Same as {@link #pathStartsWith(String, String)} but returns <jk>true</jk> if at least one prefix matches. 1452 * 1453 * @param path The path to check. 1454 * @param pathPrefixes The prefixes. 1455 * @return <jk>true</jk> if the specified path string is prefixed with any of the specified prefixes. 1456 */ 1457 public static boolean pathStartsWith(String path, String[] pathPrefixes) { 1458 for (String p : pathPrefixes) 1459 if (pathStartsWith(path, p)) 1460 return true; 1461 return false; 1462 } 1463 1464 /** 1465 * Replaces <js>"\\uXXXX"</js> character sequences with their unicode characters. 1466 * 1467 * @param s The string to replace unicode sequences in. 1468 * @return A string with unicode sequences replaced. 1469 */ 1470 public static String replaceUnicodeSequences(String s) { 1471 if (s.indexOf('\\') == -1) 1472 return s; 1473 Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})"); 1474 Matcher m = p.matcher(s); 1475 StringBuffer sb = new StringBuffer(s.length()); 1476 while (m.find()) { 1477 String ch = String.valueOf((char) Integer.parseInt(m.group(1), 16)); 1478 m.appendReplacement(sb, Matcher.quoteReplacement(ch)); 1479 } 1480 m.appendTail(sb); 1481 return sb.toString(); 1482 } 1483 1484 /** 1485 * Creates an escaped-unicode sequence (e.g. <js>"\\u1234"</js>) for the specified character. 1486 * 1487 * @param c The character to create a sequence for. 1488 * @return An escaped-unicode sequence. 1489 */ 1490 public static String unicodeSequence(char c) { 1491 StringBuilder sb = new StringBuilder(6); 1492 sb.append('\\').append('u'); 1493 for (char cc : toHex4(c)) 1494 sb.append(cc); 1495 return sb.toString(); 1496 } 1497 1498 /** 1499 * Returns the specified field in a delimited string without splitting the string. 1500 * 1501 * <p> 1502 * Equivalent to the following: 1503 * <p class='bcode w800'> 1504 * String in = <js>"0,1,2"</js>; 1505 * String[] parts = in.split(<js>","</js>); 1506 * String p1 = (parts.<jk>length</jk> > 1 ? parts[1] : <js>""</js>); 1507 * </p> 1508 * 1509 * @param fieldNum The field number. Zero-indexed. 1510 * @param s The input string. 1511 * @param delim The delimiter character. 1512 * @return The field entry in the string, or a blank string if it doesn't exist or the string is null. 1513 */ 1514 public static String getField(int fieldNum, String s, char delim) { 1515 return getField(fieldNum, s, delim, ""); 1516 } 1517 1518 /** 1519 * Same as {@link #getField(int, String, char)} except allows you to specify the default value. 1520 * 1521 * @param fieldNum The field number. Zero-indexed. 1522 * @param s The input string. 1523 * @param delim The delimiter character. 1524 * @param def The default value if the field does not exist. 1525 * @return The field entry in the string, or the default value if it doesn't exist or the string is null. 1526 */ 1527 public static String getField(int fieldNum, String s, char delim, String def) { 1528 if (s == null || fieldNum < 0) 1529 return def; 1530 int start = 0; 1531 for (int i = 0; i < s.length(); i++) { 1532 char c = s.charAt(i); 1533 if (c == delim) { 1534 fieldNum--; 1535 if (fieldNum == 0) 1536 start = i+1; 1537 } 1538 if (fieldNum < 0) 1539 return s.substring(start, i); 1540 } 1541 if (start == 0) 1542 return def; 1543 return s.substring(start); 1544 } 1545 1546 /** 1547 * Calls {@link #toString()} on the specified object if it's not null. 1548 * 1549 * @param o The object to convert to a string. 1550 * @return The object converted to a string, or <jk>null</jk> if the object was null. 1551 */ 1552 public static String asString(Object o) { 1553 return (o == null ? null : o.toString()); 1554 } 1555 1556 /** 1557 * Converts an array of objects to an array of strings. 1558 * 1559 * @param o The array of objects to convert to strings. 1560 * @return A new array of objects converted to strings. 1561 */ 1562 public static String[] asStrings(Object...o) { 1563 if (o == null) 1564 return null; 1565 if (o instanceof String[]) 1566 return (String[])o; 1567 String[] s = new String[o.length]; 1568 for (int i = 0; i < o.length; i++) 1569 s[i] = asString(o[i]); 1570 return s; 1571 } 1572 1573 /** 1574 * Converts a hexadecimal byte stream (e.g. "34A5BC") into a UTF-8 encoded string. 1575 * 1576 * @param hex The hexadecimal string. 1577 * @return The UTF-8 string. 1578 */ 1579 public static String fromHexToUTF8(String hex) { 1580 ByteBuffer buff = ByteBuffer.allocate(hex.length()/2); 1581 for (int i = 0; i < hex.length(); i+=2) 1582 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1583 buff.rewind(); 1584 Charset cs = Charset.forName("UTF-8"); 1585 return cs.decode(buff).toString(); 1586 } 1587 1588 /** 1589 * Converts a space-deliminted hexadecimal byte stream (e.g. "34 A5 BC") into a UTF-8 encoded string. 1590 * 1591 * @param hex The hexadecimal string. 1592 * @return The UTF-8 string. 1593 */ 1594 public static String fromSpacedHexToUTF8(String hex) { 1595 ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3); 1596 for (int i = 0; i < hex.length(); i+=3) 1597 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1598 buff.rewind(); 1599 Charset cs = Charset.forName("UTF-8"); 1600 return cs.decode(buff).toString(); 1601 } 1602 1603 private static final char[] HEX = "0123456789ABCDEF".toCharArray(); 1604 1605 /** 1606 * Converts a byte array into a simple hexadecimal character string. 1607 * 1608 * @param bytes The bytes to convert to hexadecimal. 1609 * @return A new string consisting of hexadecimal characters. 1610 */ 1611 public static String toHex(byte[] bytes) { 1612 StringBuilder sb = new StringBuilder(bytes.length * 2); 1613 for (int j = 0; j < bytes.length; j++) { 1614 int v = bytes[j] & 0xFF; 1615 sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]); 1616 } 1617 return sb.toString(); 1618 } 1619 1620 /** 1621 * Same as {@link #toHex(byte[])} but puts spaces between the byte strings. 1622 * 1623 * @param bytes The bytes to convert to hexadecimal. 1624 * @return A new string consisting of hexadecimal characters. 1625 */ 1626 public static String toSpacedHex(byte[] bytes) { 1627 StringBuilder sb = new StringBuilder(bytes.length * 3); 1628 for (int j = 0; j < bytes.length; j++) { 1629 if (j > 0) 1630 sb.append(' '); 1631 int v = bytes[j] & 0xFF; 1632 sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]); 1633 } 1634 return sb.toString(); 1635 } 1636 1637 /** 1638 * Converts a hexadecimal character string to a byte array. 1639 * 1640 * @param hex The string to convert to a byte array. 1641 * @return A new byte array. 1642 */ 1643 public static byte[] fromHex(String hex) { 1644 ByteBuffer buff = ByteBuffer.allocate(hex.length()/2); 1645 for (int i = 0; i < hex.length(); i+=2) 1646 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1647 buff.rewind(); 1648 return buff.array(); 1649 } 1650 1651 /** 1652 * Same as {@link #fromHex(String)} except expects spaces between the byte strings. 1653 * 1654 * @param hex The string to convert to a byte array. 1655 * @return A new byte array. 1656 */ 1657 public static byte[] fromSpacedHex(String hex) { 1658 ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3); 1659 for (int i = 0; i < hex.length(); i+=3) 1660 buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16)); 1661 buff.rewind(); 1662 return buff.array(); 1663 } 1664 1665 /** 1666 * Creates a repeated pattern. 1667 * 1668 * @param count The number of times to repeat the pattern. 1669 * @param pattern The pattern to repeat. 1670 * @return A new string consisting of the repeated pattern. 1671 */ 1672 public static String repeat(int count, String pattern) { 1673 StringBuilder sb = new StringBuilder(pattern.length() * count); 1674 for (int i = 0; i < count; i++) 1675 sb.append(pattern); 1676 return sb.toString(); 1677 } 1678 1679 /** 1680 * Trims whitespace characters from the beginning of the specified string. 1681 * 1682 * @param s The string to trim. 1683 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1684 */ 1685 public static String trimStart(String s) { 1686 if (s != null) 1687 while (s.length() > 0 && Character.isWhitespace(s.charAt(0))) 1688 s = s.substring(1); 1689 return s; 1690 } 1691 1692 /** 1693 * Trims whitespace characters from the end of the specified string. 1694 * 1695 * @param s The string to trim. 1696 * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>. 1697 */ 1698 public static String trimEnd(String s) { 1699 if (s != null) 1700 while (s.length() > 0 && Character.isWhitespace(s.charAt(s.length()-1))) 1701 s = s.substring(0, s.length()-1); 1702 return s; 1703 } 1704 1705 /** 1706 * Returns <jk>true</jk> if the specified string is one of the specified values. 1707 * 1708 * @param s 1709 * The string to test. 1710 * Can be <jk>null</jk>. 1711 * @param values 1712 * The values to test. 1713 * Can contain <jk>null</jk>. 1714 * @return <jk>true</jk> if the specified string is one of the specified values. 1715 */ 1716 public static boolean isOneOf(String s, String...values) { 1717 for (int i = 0; i < values.length; i++) 1718 if (StringUtils.isEquals(s, values[i])) 1719 return true; 1720 return false; 1721 } 1722 1723 /** 1724 * Trims <js>'/'</js> characters from both the start and end of the specified string. 1725 * 1726 * @param s The string to trim. 1727 * @return A new trimmed string, or the same string if no trimming was necessary. 1728 */ 1729 public static String trimSlashes(String s) { 1730 if (s == null) 1731 return null; 1732 while (endsWith(s, '/')) 1733 s = s.substring(0, s.length()-1); 1734 while (s.length() > 0 && s.charAt(0) == '/') 1735 s = s.substring(1); 1736 return s; 1737 } 1738 1739 /** 1740 * Trims <js>'/'</js> characters from the end of the specified string. 1741 * 1742 * @param s The string to trim. 1743 * @return A new trimmed string, or the same string if no trimming was necessary. 1744 */ 1745 public static String trimTrailingSlashes(String s) { 1746 if (s == null) 1747 return null; 1748 while (endsWith(s, '/')) 1749 s = s.substring(0, s.length()-1); 1750 return s; 1751 } 1752 1753 /** 1754 * Trims <js>'/'</js> characters from the end of the specified string. 1755 * 1756 * @param s The string to trim. 1757 * @return The same string buffer. 1758 */ 1759 public static StringBuffer trimTrailingSlashes(StringBuffer s) { 1760 if (s == null) 1761 return null; 1762 while (s.length() > 0 && s.charAt(s.length()-1) == '/') 1763 s.setLength(s.length()-1); 1764 return s; 1765 } 1766 1767 /** 1768 * Shortcut for calling <code>URLEncoder.<jsm>encode</jsm>(o.toString(), <js>"UTF-8"</js>)</code>. 1769 * 1770 * @param o The object to encode. 1771 * @return The URL encoded string, or <jk>null</jk> if the object was null. 1772 */ 1773 public static String urlEncode(Object o) { 1774 try { 1775 if (o != null) 1776 return URLEncoder.encode(o.toString(), "UTF-8"); 1777 } catch (UnsupportedEncodingException e) {} 1778 return null; 1779 } 1780 1781 private static final AsciiSet URL_ENCODE_PATHINFO_VALIDCHARS = 1782 AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.*/()").build(); 1783 1784 /** 1785 * Similar to {@link #urlEncode(Object)} but doesn't encode <js>"/"</js> characters. 1786 * 1787 * @param o The object to encode. 1788 * @return The URL encoded string, or <jk>null</jk> if the object was null. 1789 */ 1790 public static String urlEncodePath(Object o) { 1791 if (o == null) 1792 return null; 1793 String s = asString(o); 1794 1795 boolean needsEncode = false; 1796 for (int i = 0; i < s.length() && ! needsEncode; i++) 1797 needsEncode = URL_ENCODE_PATHINFO_VALIDCHARS.contains(s.charAt(i)); 1798 if (! needsEncode) 1799 return s; 1800 1801 StringBuilder sb = new StringBuilder(); 1802 CharArrayWriter caw = new CharArrayWriter(); 1803 int caseDiff = ('a' - 'A'); 1804 1805 for (int i = 0; i < s.length();) { 1806 char c = s.charAt(i); 1807 if (URL_ENCODE_PATHINFO_VALIDCHARS.contains(c)) { 1808 sb.append(c); 1809 i++; 1810 } else { 1811 if (c == ' ') { 1812 sb.append('+'); 1813 i++; 1814 } else { 1815 do { 1816 caw.write(c); 1817 if (c >= 0xD800 && c <= 0xDBFF) { 1818 if ( (i+1) < s.length()) { 1819 int d = s.charAt(i+1); 1820 if (d >= 0xDC00 && d <= 0xDFFF) { 1821 caw.write(d); 1822 i++; 1823 } 1824 } 1825 } 1826 i++; 1827 } while (i < s.length() && !URL_ENCODE_PATHINFO_VALIDCHARS.contains((c = s.charAt(i)))); 1828 1829 caw.flush(); 1830 String s2 = new String(caw.toCharArray()); 1831 byte[] ba = s2.getBytes(IOUtils.UTF8); 1832 for (int j = 0; j < ba.length; j++) { 1833 sb.append('%'); 1834 char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); 1835 if (Character.isLetter(ch)) { 1836 ch -= caseDiff; 1837 } 1838 sb.append(ch); 1839 ch = Character.forDigit(ba[j] & 0xF, 16); 1840 if (Character.isLetter(ch)) { 1841 ch -= caseDiff; 1842 } 1843 sb.append(ch); 1844 } 1845 caw.reset(); 1846 } 1847 } 1848 } 1849 return sb.toString(); 1850 } 1851 1852 /** 1853 * Decodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme. 1854 * 1855 * @param s The string to decode. 1856 * @return The decoded string, or <jk>null</jk> if input is <jk>null</jk>. 1857 */ 1858 public static String urlDecode(String s) { 1859 if (s == null) 1860 return s; 1861 boolean needsDecode = false; 1862 for (int i = 0; i < s.length() && ! needsDecode; i++) { 1863 char c = s.charAt(i); 1864 if (c == '+' || c == '%') 1865 needsDecode = true; 1866 } 1867 if (needsDecode) { 1868 try { 1869 return URLDecoder.decode(s, "UTF-8"); 1870 } catch (UnsupportedEncodingException e) {/* Won't happen */} 1871 } 1872 return s; 1873 } 1874 1875 /** 1876 * Encodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme. 1877 * 1878 * @param s The string to encode. 1879 * @return The encoded string, or <jk>null</jk> if input is <jk>null</jk>. 1880 */ 1881 public static String urlEncode(String s) { 1882 if (s == null) 1883 return null; 1884 boolean needsEncode = false; 1885 for (int i = 0; i < s.length() && ! needsEncode; i++) 1886 needsEncode |= (! unencodedChars.contains(s.charAt(i))); 1887 if (needsEncode) { 1888 try { 1889 return URLEncoder.encode(s, "UTF-8"); 1890 } catch (UnsupportedEncodingException e) {/* Won't happen */} 1891 } 1892 return s; 1893 } 1894 1895 /** 1896 * Same as {@link #urlEncode(String)} except only escapes characters that absolutely need to be escaped. 1897 * 1898 * @param s The string to escape. 1899 * @return The encoded string, or <jk>null</jk> if input is <jk>null</jk>. 1900 */ 1901 public static String urlEncodeLax(String s) { 1902 if (s == null) 1903 return null; 1904 boolean needsEncode = false; 1905 for (int i = 0; i < s.length() && ! needsEncode; i++) 1906 needsEncode |= (! unencodedCharsLax.contains(s.charAt(i))); 1907 if (needsEncode) { 1908 StringBuilder sb = new StringBuilder(s.length()*2); 1909 for (int i = 0; i < s.length(); i++) { 1910 char c = s.charAt(i); 1911 if (unencodedCharsLax.contains(c)) 1912 sb.append(c); 1913 else if (c == ' ') 1914 sb.append("+"); 1915 else if (c <= 127) 1916 sb.append('%').append(toHex2(c)); 1917 else 1918 try { 1919 sb.append(URLEncoder.encode(""+c, "UTF-8")); // Yuck. 1920 } catch (UnsupportedEncodingException e) { 1921 // Not possible. 1922 } 1923 } 1924 s = sb.toString(); 1925 } 1926 return s; 1927 } 1928 1929 /** 1930 * Splits a string into equally-sized parts. 1931 * 1932 * @param s The string to split. 1933 * @param size The token sizes. 1934 * @return The tokens, or <jk>null</jk> if the input was <jk>null</jk>. 1935 */ 1936 public static List<String> splitEqually(String s, int size) { 1937 if (s == null) 1938 return null; 1939 if (size <= 0) 1940 return Collections.singletonList(s); 1941 1942 List<String> l = new ArrayList<>((s.length() + size - 1) / size); 1943 1944 for (int i = 0; i < s.length(); i += size) 1945 l.add(s.substring(i, Math.min(s.length(), i + size))); 1946 1947 return l; 1948 } 1949 1950 /** 1951 * Returns the first non-whitespace character in the string. 1952 * 1953 * @param s The string to check. 1954 * @return 1955 * The first non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed 1956 * of only whitespace. 1957 */ 1958 public static char firstNonWhitespaceChar(String s) { 1959 if (s != null) 1960 for (int i = 0; i < s.length(); i++) 1961 if (! Character.isWhitespace(s.charAt(i))) 1962 return s.charAt(i); 1963 return 0; 1964 } 1965 1966 /** 1967 * Returns the last non-whitespace character in the string. 1968 * 1969 * @param s The string to check. 1970 * @return 1971 * The last non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed 1972 * of only whitespace. 1973 */ 1974 public static char lastNonWhitespaceChar(String s) { 1975 if (s != null) 1976 for (int i = s.length()-1; i >= 0; i--) 1977 if (! Character.isWhitespace(s.charAt(i))) 1978 return s.charAt(i); 1979 return 0; 1980 } 1981 1982 /** 1983 * Returns the character at the specified index in the string without throwing exceptions. 1984 * 1985 * @param s The string. 1986 * @param i The index position. 1987 * @return 1988 * The character at the specified index, or <code>0</code> if the index is out-of-range or the string 1989 * is <jk>null</jk>. 1990 */ 1991 public static char charAt(String s, int i) { 1992 if (s == null) 1993 return 0; 1994 if (i < 0 || i >= s.length()) 1995 return 0; 1996 return s.charAt(i); 1997 } 1998 1999 /** 2000 * Efficiently determines whether a URL is of the pattern "xxx://xxx" 2001 * 2002 * @param s The string to test. 2003 * @return <jk>true</jk> if it's an absolute path. 2004 */ 2005 public static boolean isAbsoluteUri(String s) { 2006 2007 if (isEmpty(s)) 2008 return false; 2009 2010 // Use a state machine for maximum performance. 2011 2012 int S1 = 1; // Looking for http 2013 int S2 = 2; // Found http, looking for : 2014 int S3 = 3; // Found :, looking for / 2015 int S4 = 4; // Found /, looking for / 2016 int S5 = 5; // Found /, looking for x 2017 2018 int state = S1; 2019 for (int i = 0; i < s.length(); i++) { 2020 char c = s.charAt(i); 2021 if (state == S1) { 2022 if (c >= 'a' && c <= 'z') 2023 state = S2; 2024 else 2025 return false; 2026 } else if (state == S2) { 2027 if (c == ':') 2028 state = S3; 2029 else if (c < 'a' || c > 'z') 2030 return false; 2031 } else if (state == S3) { 2032 if (c == '/') 2033 state = S4; 2034 else 2035 return false; 2036 } else if (state == S4) { 2037 if (c == '/') 2038 state = S5; 2039 else 2040 return false; 2041 } else if (state == S5) { 2042 return true; 2043 } 2044 } 2045 return false; 2046 } 2047 2048 /** 2049 * Efficiently determines whether a URL is of the pattern "xxx:/xxx". 2050 * 2051 * <p> 2052 * The pattern matched is: <code>[a-z]{2,}\:\/.*</code> 2053 * 2054 * <p> 2055 * Note that this excludes filesystem paths such as <js>"C:/temp"</js>. 2056 * 2057 * @param s The string to test. 2058 * @return <jk>true</jk> if it's an absolute path. 2059 */ 2060 public static boolean isUri(String s) { 2061 2062 if (isEmpty(s)) 2063 return false; 2064 2065 // Use a state machine for maximum performance. 2066 2067 int S1 = 1; // Looking for protocol char 1 2068 int S2 = 2; // Found protocol char 1, looking for protocol char 2 2069 int S3 = 3; // Found protocol char 2, looking for : 2070 int S4 = 4; // Found :, looking for / 2071 2072 2073 int state = S1; 2074 for (int i = 0; i < s.length(); i++) { 2075 char c = s.charAt(i); 2076 if (state == S1) { 2077 if (c >= 'a' && c <= 'z') 2078 state = S2; 2079 else 2080 return false; 2081 } else if (state == S2) { 2082 if (c >= 'a' && c <= 'z') 2083 state = S3; 2084 else 2085 return false; 2086 } else if (state == S3) { 2087 if (c == ':') 2088 state = S4; 2089 else if (c < 'a' || c > 'z') 2090 return false; 2091 } else if (state == S4) { 2092 if (c == '/') 2093 return true; 2094 return false; 2095 } 2096 } 2097 return false; 2098 } 2099 2100 /** 2101 * Given an absolute URI, returns just the authority portion (e.g. <js>"http://hostname:port"</js>) 2102 * 2103 * @param s The URI string. 2104 * @return Just the authority portion of the URI. 2105 */ 2106 public static String getAuthorityUri(String s) { 2107 2108 // Use a state machine for maximum performance. 2109 2110 int S1 = 1; // Looking for http 2111 int S2 = 2; // Found http, looking for : 2112 int S3 = 3; // Found :, looking for / 2113 int S4 = 4; // Found /, looking for / 2114 int S5 = 5; // Found /, looking for x 2115 int S6 = 6; // Found x, looking for / 2116 2117 int state = S1; 2118 for (int i = 0; i < s.length(); i++) { 2119 char c = s.charAt(i); 2120 if (state == S1) { 2121 if (c >= 'a' && c <= 'z') 2122 state = S2; 2123 else 2124 return s; 2125 } else if (state == S2) { 2126 if (c == ':') 2127 state = S3; 2128 else if (c < 'a' || c > 'z') 2129 return s; 2130 } else if (state == S3) { 2131 if (c == '/') 2132 state = S4; 2133 else 2134 return s; 2135 } else if (state == S4) { 2136 if (c == '/') 2137 state = S5; 2138 else 2139 return s; 2140 } else if (state == S5) { 2141 if (c != '/') 2142 state = S6; 2143 else 2144 return s; 2145 } else if (state == S6) { 2146 if (c == '/') 2147 return s.substring(0, i); 2148 } 2149 } 2150 return s; 2151 } 2152 2153 /** 2154 * Converts the specified object to a URI. 2155 * 2156 * @param o The object to convert to a URI. 2157 * @return A new URI, or the same object if the object was already a URI, or 2158 */ 2159 public static URI toURI(Object o) { 2160 if (o == null || o instanceof URI) 2161 return (URI)o; 2162 try { 2163 return new URI(o.toString()); 2164 } catch (URISyntaxException e) { 2165 throw new RuntimeException(e); 2166 } 2167 } 2168 2169 /** 2170 * Returns the first non-null, non-empty string in the list. 2171 * 2172 * @param s The strings to test. 2173 * @return The first non-empty string in the list, or <jk>null</jk> if they were all <jk>null</jk> or empty. 2174 */ 2175 public static String firstNonEmpty(String...s) { 2176 for (String ss : s) 2177 if (isNotEmpty(ss)) 2178 return ss; 2179 return null; 2180 } 2181 2182 /** 2183 * Same as {@link String#indexOf(int)} except allows you to check for multiple characters. 2184 * 2185 * @param s The string to check. 2186 * @param c The characters to check for. 2187 * @return The index into the string that is one of the specified characters. 2188 */ 2189 public static int indexOf(String s, char...c) { 2190 if (s == null) 2191 return -1; 2192 for (int i = 0; i < s.length(); i++) { 2193 char c2 = s.charAt(i); 2194 for (char cc : c) 2195 if (c2 == cc) 2196 return i; 2197 } 2198 return -1; 2199 } 2200 2201 /** 2202 * Similar to {@link MessageFormat#format(String, Object...)} except allows you to specify POJO arguments. 2203 * 2204 * @param pattern The string pattern. 2205 * @param args The arguments. 2206 * @return The formatted string. 2207 */ 2208 public static String format(String pattern, Object...args) { 2209 if (args == null || args.length == 0) 2210 return pattern; 2211 for (int i = 0; i < args.length; i++) 2212 args[i] = convertToReadable(args[i]); 2213 return MessageFormat.format(pattern, args); 2214 } 2215 2216 private static Object convertToReadable(Object o) { 2217 if (o == null) 2218 return null; 2219 if (o instanceof ClassMeta) 2220 return ((ClassMeta<?>)o).getReadableName(); 2221 if (BeanContext.DEFAULT == null) 2222 return o.toString(); 2223 ClassMeta<?> cm = BeanContext.DEFAULT.getClassMetaForObject(o); 2224 if (cm.isMapOrBean() || cm.isCollectionOrArray()) 2225 return SimpleJsonSerializer.DEFAULT.toString(o); 2226 if (cm.isClass()) 2227 return ((Class<?>)o).getName(); 2228 if (cm.isMethod()) 2229 return ClassUtils.toString((Method)o); 2230 return o.toString(); 2231 } 2232 2233 /** 2234 * Converts a string containing a possible multiplier suffix to an integer. 2235 * 2236 * <p> 2237 * The string can contain any of the following multiplier suffixes: 2238 * <ul> 2239 * <li><js>"K"</js> - x 1024 2240 * <li><js>"M"</js> - x 1024*1024 2241 * <li><js>"G"</js> - x 1024*1024*1024 2242 * </ul> 2243 * 2244 * @param s The string to parse. 2245 * @return The parsed value. 2246 */ 2247 public static int parseIntWithSuffix(String s) { 2248 assertFieldNotNull(s, "s"); 2249 int m = 1; 2250 if (s.endsWith("G")) { 2251 m = 1024*1024*1024; 2252 s = s.substring(0, s.length()-1).trim(); 2253 } else if (s.endsWith("M")) { 2254 m = 1024*1024; 2255 s = s.substring(0, s.length()-1).trim(); 2256 } else if (s.endsWith("K")) { 2257 m = 1024; 2258 s = s.substring(0, s.length()-1).trim(); 2259 } 2260 return Integer.decode(s) * m; 2261 } 2262 2263 /** 2264 * Converts a string containing a possible multiplier suffix to a long. 2265 * 2266 * <p> 2267 * The string can contain any of the following multiplier suffixes: 2268 * <ul> 2269 * <li><js>"K"</js> - x 1024 2270 * <li><js>"M"</js> - x 1024*1024 2271 * <li><js>"G"</js> - x 1024*1024*1024 2272 * </ul> 2273 * 2274 * @param s The string to parse. 2275 * @return The parsed value. 2276 */ 2277 public static long parseLongWithSuffix(String s) { 2278 assertFieldNotNull(s, "s"); 2279 int m = 1; 2280 if (s.endsWith("G")) { 2281 m = 1024*1024*1024; 2282 s = s.substring(0, s.length()-1).trim(); 2283 } else if (s.endsWith("M")) { 2284 m = 1024*1024; 2285 s = s.substring(0, s.length()-1).trim(); 2286 } else if (s.endsWith("K")) { 2287 m = 1024; 2288 s = s.substring(0, s.length()-1).trim(); 2289 } 2290 return Long.decode(s) * m; 2291 } 2292 2293 /** 2294 * Same as {@link String#contains(CharSequence)} except returns <jk>null</jk> if the value is null. 2295 * 2296 * @param value The string to check. 2297 * @param substring The value to check for. 2298 * @return <jk>true</jk> if the value contains the specified substring. 2299 */ 2300 public static boolean contains(String value, CharSequence substring) { 2301 return value == null ? false : value.contains(substring); 2302 } 2303 2304 /** 2305 * Returns <jk>true</jk> if the specified string appears to be an JSON array. 2306 * 2307 * @param o The object to test. 2308 * @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and trailing whitespace and comments will be ignored. 2309 * @return <jk>true</jk> if the specified string appears to be a JSON array. 2310 */ 2311 public static boolean isObjectList(Object o, boolean ignoreWhitespaceAndComments) { 2312 if (o instanceof CharSequence) { 2313 String s = o.toString(); 2314 if (! ignoreWhitespaceAndComments) 2315 return (s.startsWith("[") && s.endsWith("]")); 2316 if (firstRealCharacter(s) != '[') 2317 return false; 2318 int i = s.lastIndexOf(']'); 2319 if (i == -1) 2320 return false; 2321 s = s.substring(i+1); 2322 if (firstRealCharacter(s) != -1) 2323 return false; 2324 return true; 2325 } 2326 return false; 2327 } 2328 2329 /** 2330 * Parses a string that can consist of either a JSON array or comma-delimited list. 2331 * 2332 * <p> 2333 * The type of string is auto-detected. 2334 * 2335 * @param s The string to parse. 2336 * @return The parsed string. 2337 * @throws ParseException 2338 */ 2339 public static ObjectList parseListOrCdl(String s) throws ParseException { 2340 if (isEmpty(s)) 2341 return null; 2342 if (! isObjectList(s, true)) 2343 return new ObjectList(Arrays.asList(StringUtils.split(s.trim(), ','))); 2344 return new ObjectList(s); 2345 } 2346 2347 /** 2348 * Returns <jk>true</jk> if the specified string is valid JSON. 2349 * 2350 * <p> 2351 * Leading and trailing spaces are ignored. 2352 * <br>Leading and trailing comments are not allowed. 2353 * 2354 * @param s The string to test. 2355 * @return <jk>true</jk> if the specified string is valid JSON. 2356 */ 2357 public static boolean isJson(String s) { 2358 if (s == null) 2359 return false; 2360 char c1 = StringUtils.firstNonWhitespaceChar(s), c2 = StringUtils.lastNonWhitespaceChar(s); 2361 if (c1 == '{' && c2 == '}' || c1 == '[' && c2 == ']' || c1 == '\'' && c2 == '\'') 2362 return true; 2363 if (StringUtils.isOneOf(s, "true","false","null")) 2364 return true; 2365 if (StringUtils.isNumeric(s)) 2366 return true; 2367 return false; 2368 } 2369 2370 /** 2371 * Returns <jk>true</jk> if the specified string appears to be a JSON object. 2372 * 2373 * @param o The object to test. 2374 * @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and trailing whitespace and comments will be ignored. 2375 * @return <jk>true</jk> if the specified string appears to be a JSON object. 2376 */ 2377 public static boolean isObjectMap(Object o, boolean ignoreWhitespaceAndComments) { 2378 if (o instanceof CharSequence) { 2379 String s = o.toString(); 2380 if (! ignoreWhitespaceAndComments) 2381 return (s.startsWith("{") && s.endsWith("}")); 2382 if (firstRealCharacter(s) != '{') 2383 return false; 2384 int i = s.lastIndexOf('}'); 2385 if (i == -1) 2386 return false; 2387 s = s.substring(i+1); 2388 if (firstRealCharacter(s) != -1) 2389 return false; 2390 return true; 2391 } 2392 return false; 2393 } 2394 2395 private static int firstRealCharacter(String s) { 2396 try (StringReader r = new StringReader(s)) { 2397 int c = 0; 2398 while ((c = r.read()) != -1) { 2399 if (! Character.isWhitespace(c)) { 2400 if (c == '/') { 2401 skipComments(r); 2402 } else { 2403 return c; 2404 } 2405 } 2406 } 2407 return -1; 2408 } catch (Exception e) { 2409 throw new RuntimeException(e); 2410 } 2411 } 2412 private static void skipComments(StringReader r) throws IOException { 2413 int c = r.read(); 2414 // "/* */" style comments 2415 if (c == '*') { 2416 while (c != -1) 2417 if ((c = r.read()) == '*') 2418 if ((c = r.read()) == '/') 2419 return; 2420 // "//" style comments 2421 } else if (c == '/') { 2422 while (c != -1) { 2423 c = r.read(); 2424 if (c == -1 || c == '\n') 2425 return; 2426 } 2427 } 2428 } 2429 2430 /** 2431 * Takes in a string, splits it by lines, and then prepends each line with line numbers. 2432 * 2433 * @param s The string. 2434 * @return The string with line numbers added. 2435 */ 2436 public static String getNumberedLines(String s) { 2437 return getNumberedLines(s, 1, Integer.MAX_VALUE); 2438 } 2439 2440 /** 2441 * Same as {@link #getNumberedLines(String)} except only returns the specified lines. 2442 * 2443 * <p> 2444 * Out-of-bounds values are allowed and fixed. 2445 * 2446 * @param s The string. 2447 * @param start The starting line (1-indexed). 2448 * @param end The ending line (1-indexed). 2449 * @return The string with line numbers added. 2450 */ 2451 public static String getNumberedLines(String s, int start, int end) { 2452 if (s == null) 2453 return null; 2454 String[] lines = s.split("[\r\n]+"); 2455 final int digits = String.valueOf(lines.length).length(); 2456 if (start < 1) 2457 start = 1; 2458 if (end > lines.length) 2459 end = lines.length; 2460 StringBuilder sb = new StringBuilder(); 2461 for (String l : Arrays.asList(lines).subList(start-1, end)) 2462 sb.append(String.format("%0"+digits+"d", start++)).append(": ").append(l).append("\n"); 2463 return sb.toString(); 2464 } 2465 2466 /** 2467 * Compares two strings, but gracefully handles <jk>nulls</jk>. 2468 * 2469 * @param s1 The first string. 2470 * @param s2 The second string. 2471 * @return The same as {@link String#compareTo(String)}. 2472 */ 2473 public static int compare(String s1, String s2) { 2474 if (s1 == null && s2 == null) 2475 return 0; 2476 if (s1 == null) 2477 return Integer.MIN_VALUE; 2478 if (s2 == null) 2479 return Integer.MAX_VALUE; 2480 return s1.compareTo(s2); 2481 } 2482 2483 /** 2484 * Returns the first character in the specified string. 2485 * 2486 * @param s The string to check. 2487 * @return The first character in the string, or <code>0</code> if the string is <jk>null</jk> or empty. 2488 */ 2489 public static char firstChar(String s) { 2490 if (s == null || s.length() == 0) 2491 return 0; 2492 return s.charAt(0); 2493 } 2494 2495 /** 2496 * Converts a string containing <js>"*"</js> meta characters with a regular expression pattern. 2497 * 2498 * @param s The string to create a pattern from. 2499 * @return A regular expression pattern. 2500 */ 2501 public static Pattern getMatchPattern(String s) { 2502 if (s == null) 2503 return null; 2504 StringBuilder sb = new StringBuilder(); 2505 sb.append("\\Q"); 2506 for (int i = 0; i < s.length(); i++) { 2507 char c = s.charAt(i); 2508 if (c == '*') 2509 sb.append("\\E").append(".*").append("\\Q"); 2510 else 2511 sb.append(c); 2512 } 2513 sb.append("\\E"); 2514 return Pattern.compile(sb.toString()); 2515 } 2516 2517 /** 2518 * Null-safe {@link String#toLowerCase()}. 2519 * 2520 * @param s The string to convert to lower case. 2521 * @return The string converted to lower case, or <jk>null</jk> if the string was null. 2522 */ 2523 public static String toLowerCase(String s) { 2524 return s == null ? null : s.toLowerCase(); 2525 } 2526 2527 /** 2528 * Parses a duration string. 2529 * 2530 * <p> 2531 * Examples: 2532 * <ul> 2533 * <li><js>"1000"</js> - 1000 milliseconds. 2534 * <li><js>"10s"</js> - 10 seconds. 2535 * <li><js>"10 sec"</js> - 10 seconds. 2536 * <li><js>"10 seconds"</js> - 10 seconds. 2537 * </ul> 2538 * 2539 * <p> 2540 * Use any of the following suffixes: 2541 * <ul> 2542 * <li>None (time in milliseconds). 2543 * <li><js>"s"</js>/<js>"sec"</js>/<js>"second"</js>/<js>"seconds"</js> 2544 * <li><js>"m"</js>/<js>"min"</js>/<js>"minutes"</js>/<js>"seconds"</js> 2545 * <li><js>"h"</js>/<js>"hour"</js>/<js>"hours"</js> 2546 * <li><js>"d"</js>/<js>"day"</js>/<js>"days"</js> 2547 * <li><js>"w"</js>/<js>"week"</js>/<js>"weeks"</js> 2548 * </ul> 2549 * 2550 * <p> 2551 * Suffixes are case-insensitive. 2552 * <br>Whitespace is ignored. 2553 * 2554 * @param s The string to parse. 2555 * @return 2556 * The time in milliseconds, or <code>-1</code> if the string is empty or <jk>null</jk>. 2557 */ 2558 public static long getDuration(String s) { 2559 s = trim(s); 2560 if (isEmpty(s)) 2561 return -1; 2562 int i; 2563 for (i = 0; i < s.length(); i++) { 2564 char c = s.charAt(i); 2565 if (c < '0' || c > '9') 2566 break; 2567 } 2568 long l; 2569 if (i == s.length()) 2570 l = Long.parseLong(s); 2571 else { 2572 l = Long.parseLong(s.substring(0, i).trim()); 2573 String r = s.substring(i).trim().toLowerCase(); 2574 if (r.startsWith("s")) 2575 l *= 1000; 2576 else if (r.startsWith("m")) 2577 l *= 1000 * 60; 2578 else if (r.startsWith("h")) 2579 l *= 1000 * 60 * 60; 2580 else if (r.startsWith("d")) 2581 l *= 1000 * 60 * 60 * 24; 2582 else if (r.startsWith("w")) 2583 l *= 1000 * 60 * 60 * 24 * 7; 2584 } 2585 return l; 2586 } 2587}