001package org.hl7.fhir.utilities; 002 003import static org.apache.commons.lang3.StringUtils.isBlank; 004 005import java.io.BufferedInputStream; 006import java.io.File; 007import java.io.FileInputStream; 008import java.io.FileNotFoundException; 009import java.io.FileOutputStream; 010import java.io.FilenameFilter; 011import java.io.IOException; 012import java.io.InputStream; 013import java.io.UnsupportedEncodingException; 014import java.math.BigDecimal; 015import java.math.RoundingMode; 016import java.net.URLDecoder; 017import java.net.URLEncoder; 018import java.nio.file.Files; 019import java.nio.file.Path; 020import java.nio.file.Paths; 021import java.nio.file.StandardCopyOption; 022import java.time.Duration; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Set; 030import java.util.UUID; 031import java.util.concurrent.TimeUnit; 032import java.util.zip.ZipEntry; 033import java.util.zip.ZipInputStream; 034 035/* 036 Copyright (c) 2011+, HL7, Inc. 037 All rights reserved. 038 039 Redistribution and use in source and binary forms, with or without modification, 040 are permitted provided that the following conditions are met: 041 042 * Redistributions of source code must retain the above copyright notice, this 043 list of conditions and the following disclaimer. 044 * Redistributions in binary form must reproduce the above copyright notice, 045 this list of conditions and the following disclaimer in the documentation 046 and/or other materials provided with the distribution. 047 * Neither the name of HL7 nor the names of its contributors may be used to 048 endorse or promote products derived from this software without specific 049 prior written permission. 050 051 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 052 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 053 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 054 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 055 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 056 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 057 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 058 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 059 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 060 POSSIBILITY OF SUCH DAMAGE. 061 062 */ 063 064 065import org.apache.commons.io.FileUtils; 066import org.hl7.fhir.exceptions.FHIRException; 067 068public class Utilities { 069 070 private static final String UUID_REGEX = "[0-9a-f]{8}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{12}"; 071 private static final String OID_REGEX = "[0-2](\\.(0|[1-9][0-9]*))+"; 072 073 /** 074 * Returns the plural form of the word in the string. 075 * <p> 076 * Examples: 077 * 078 * <pre> 079 * inflector.pluralize("post") #=> "posts" 080 * inflector.pluralize("octopus") #=> "octopi" 081 * inflector.pluralize("sheep") #=> "sheep" 082 * inflector.pluralize("words") #=> "words" 083 * inflector.pluralize("the blue mailman") #=> "the blue mailmen" 084 * inflector.pluralize("CamelOctopus") #=> "CamelOctopi" 085 * </pre> 086 * <p> 087 * <p> 088 * <p> 089 * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too. 090 * 091 * @param word the word that is to be pluralized. 092 * @return the pluralized form of the word, or the word itself if it could not be pluralized 093 * @see #singularize(Object) 094 */ 095 public static String pluralizeMe(String word) { 096 Inflector inf = new Inflector(); 097 return inf.pluralize(word); 098 } 099 100 public static String pluralize(String word, int count) { 101 if (count == 1) 102 return word; 103 Inflector inf = new Inflector(); 104 return inf.pluralize(word); 105 } 106 107 108 public static boolean isInteger(String string) { 109 if (isBlank(string)) { 110 return false; 111 } 112 String value = string.startsWith("-") ? string.substring(1) : string; 113 for (char next : value.toCharArray()) { 114 if (!Character.isDigit(next)) { 115 return false; 116 } 117 } 118 // check bounds -2,147,483,648..2,147,483,647 119 if (value.length() > 10) 120 return false; 121 if (string.startsWith("-")) { 122 if (value.length() == 10 && string.compareTo("2147483648") > 0) 123 return false; 124 } else { 125 if (value.length() == 10 && string.compareTo("2147483647") > 0) 126 return false; 127 } 128 return true; 129 } 130 131 public static boolean isLong(String string) { 132 if (isBlank(string)) { 133 return false; 134 } 135 String value = string.startsWith("-") ? string.substring(1) : string; 136 for (char next : value.toCharArray()) { 137 if (!Character.isDigit(next)) { 138 return false; 139 } 140 } 141 // check bounds -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 142 if (value.length() > 20) 143 return false; 144 if (string.startsWith("-")) { 145 if (value.length() == 20 && string.compareTo("9223372036854775808") > 0) 146 return false; 147 } else { 148 if (value.length() == 20 && string.compareTo("9223372036854775807") > 0) 149 return false; 150 } 151 return true; 152 } 153 154 public static boolean isHex(String string) { 155 try { 156 int i = Integer.parseInt(string, 16); 157 return i != i + 1; 158 } catch (Exception e) { 159 return false; 160 } 161 } 162 163 public enum DecimalStatus { 164 BLANK, SYNTAX, RANGE, OK 165 } 166 167 public static boolean isDecimal(String value, boolean allowExponent, boolean allowLeadingZero) { 168 DecimalStatus ds = checkDecimal(value, allowExponent, true); 169 return ds == DecimalStatus.OK || ds == DecimalStatus.RANGE; 170 } 171 172 public static boolean isDecimal(String value, boolean allowExponent) { 173 DecimalStatus ds = checkDecimal(value, allowExponent, false); 174 return ds == DecimalStatus.OK || ds == DecimalStatus.RANGE; 175 } 176 177 public static DecimalStatus checkDecimal(String value, boolean allowExponent, boolean allowLeadingZero) { 178 if (isBlank(value)) { 179 return DecimalStatus.BLANK; 180 } 181 182 // check for leading zeros 183 if (!allowLeadingZero) { 184 if (value.startsWith("0") && !"0".equals(value) && !value.startsWith("0.")) 185 return DecimalStatus.SYNTAX; 186 if (value.startsWith("-0") && !"-0".equals(value) && !value.startsWith("-0.")) 187 return DecimalStatus.SYNTAX; 188 if (value.startsWith("+0") && !"+0".equals(value) && !value.startsWith("+0.")) 189 return DecimalStatus.SYNTAX; 190 } 191 192 // check for trailing dot 193 if (value.endsWith(".")) { 194 return DecimalStatus.SYNTAX; 195 } 196 197 boolean havePeriod = false; 198 boolean haveExponent = false; 199 boolean haveSign = false; 200 boolean haveDigits = false; 201 int preDecLength = 0; 202 int postDecLength = 0; 203 int exponentLength = 0; 204 int length = 0; 205 for (char next : value.toCharArray()) { 206 if (next == '.') { 207 if (!haveDigits || havePeriod || haveExponent) 208 return DecimalStatus.SYNTAX; 209 havePeriod = true; 210 preDecLength = length; 211 length = 0; 212 } else if (next == '-' || next == '+') { 213 if (haveDigits || haveSign) 214 return DecimalStatus.SYNTAX; 215 haveSign = true; 216 } else if (next == 'e' || next == 'E') { 217 if (!haveDigits || haveExponent || !allowExponent) 218 return DecimalStatus.SYNTAX; 219 haveExponent = true; 220 haveSign = false; 221 haveDigits = false; 222 if (havePeriod) 223 postDecLength = length; 224 else 225 preDecLength = length; 226 length = 0; 227 } else if (!Character.isDigit(next)) { 228 return DecimalStatus.SYNTAX; 229 } else { 230 haveDigits = true; 231 length++; 232 } 233 } 234 if (haveExponent && !haveDigits) 235 return DecimalStatus.SYNTAX; 236 if (haveExponent) 237 exponentLength = length; 238 else if (havePeriod) 239 postDecLength = length; 240 else 241 preDecLength = length; 242 243 // now, bounds checking - these are arbitrary 244 if (exponentLength > 4) 245 return DecimalStatus.RANGE; 246 if (preDecLength + postDecLength > 18) 247 return DecimalStatus.RANGE; 248 249 return DecimalStatus.OK; 250 } 251 252 public static String camelCase(String value) { 253 return new Inflector().camelCase(value.trim().replace(" ", "_"), false); 254 } 255 256 public static String escapeXml(String doco) { 257 if (doco == null) 258 return ""; 259 260 StringBuilder b = new StringBuilder(); 261 for (char c : doco.toCharArray()) { 262 if (c == '<') 263 b.append("<"); 264 else if (c == '>') 265 b.append(">"); 266 else if (c == '&') 267 b.append("&"); 268 else if (c == '"') 269 b.append("""); 270 else 271 b.append(c); 272 } 273 return b.toString(); 274 } 275 276 public static String titleize(String s) { 277 StringBuilder b = new StringBuilder(); 278 boolean up = true; 279 for (char c : s.toCharArray()) { 280 if (up) 281 b.append(Character.toUpperCase(c)); 282 else 283 b.append(c); 284 up = c == ' '; 285 } 286 return b.toString(); 287 } 288 289 public static String capitalize(String s) { 290 if (s == null) return null; 291 if (s.length() == 0) return s; 292 if (s.length() == 1) return s.toUpperCase(); 293 294 return s.substring(0, 1).toUpperCase() + s.substring(1); 295 } 296 297 public static void copyDirectory(String sourceFolder, String destFolder, FileNotifier notifier) throws IOException, FHIRException { 298 CSFile src = new CSFile(sourceFolder); 299 if (!src.exists()) 300 throw new FHIRException("Folder " + sourceFolder + " not found"); 301 createDirectory(destFolder); 302 303 String[] files = src.list(); 304 for (String f : files) { 305 if (new CSFile(sourceFolder + File.separator + f).isDirectory()) { 306 if (!f.startsWith(".")) // ignore .git files... 307 copyDirectory(sourceFolder + File.separator + f, destFolder + File.separator + f, notifier); 308 } else { 309 if (notifier != null) 310 notifier.copyFile(sourceFolder + File.separator + f, destFolder + File.separator + f); 311 copyFile(new CSFile(sourceFolder + File.separator + f), new CSFile(destFolder + File.separator + f)); 312 } 313 } 314 } 315 316 public static void copyFile(String source, String dest) throws IOException { 317 copyFile(new File(source), new File(dest)); 318 } 319 320 public static void copyFile(File sourceFile, File destFile) throws IOException { 321 if (!destFile.exists()) { 322 if (!new CSFile(destFile.getParent()).exists()) { 323 createDirectory(destFile.getParent()); 324 } 325 destFile.createNewFile(); 326 } 327 328 FileInputStream source = null; 329 FileOutputStream destination = null; 330 331 try { 332 source = new FileInputStream(sourceFile); 333 destination = new FileOutputStream(destFile); 334 destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size()); 335 } finally { 336 if (source != null) { 337 source.close(); 338 } 339 if (destination != null) { 340 destination.close(); 341 } 342 } 343 } 344 345 public static boolean checkFolder(String dir, List<String> errors) 346 throws IOException { 347 if (!new CSFile(dir).exists()) { 348 errors.add("Unable to find directory " + dir); 349 return false; 350 } else { 351 return true; 352 } 353 } 354 355 public static boolean checkFile(String purpose, String dir, String file, List<String> errors) 356 throws IOException { 357 if (!new CSFile(dir + file).exists()) { 358 if (errors != null) 359 errors.add("Unable to find " + purpose + " file " + file + " in " + dir); 360 return false; 361 } else { 362 return true; 363 } 364 } 365 366 public static String asCSV(List<String> strings) { 367 StringBuilder s = new StringBuilder(); 368 boolean first = true; 369 for (String n : strings) { 370 if (!first) 371 s.append(","); 372 s.append(n); 373 first = false; 374 } 375 return s.toString(); 376 } 377 378 public static String asHtmlBr(String prefix, List<String> strings) { 379 StringBuilder s = new StringBuilder(); 380 boolean first = true; 381 for (String n : strings) { 382 if (!first) 383 s.append("<br/>"); 384 s.append(prefix); 385 s.append(n); 386 first = false; 387 } 388 return s.toString(); 389 } 390 391 public static void clearDirectory(String folder, String... exemptions) throws IOException { 392 File dir = new File(folder); 393 if (dir.exists()) { 394 if (exemptions.length == 0) 395 FileUtils.cleanDirectory(dir); 396 else { 397 String[] files = new CSFile(folder).list(); 398 if (files != null) { 399 for (String f : files) { 400 if (!existsInList(f, exemptions)) { 401 File fh = new CSFile(folder + File.separatorChar + f); 402 if (fh.isDirectory()) 403 clearDirectory(fh.getAbsolutePath()); 404 fh.delete(); 405 } 406 } 407 } 408 } 409 } 410 } 411 412 public static File createDirectory(String path) throws IOException { 413 new CSFile(path).mkdirs(); 414 return new File(path); 415 } 416 417 public static String changeFileExt(String name, String ext) { 418 if (name.lastIndexOf('.') > -1) 419 return name.substring(0, name.lastIndexOf('.')) + ext; 420 else 421 return name + ext; 422 } 423 424 public static String cleanupTextString(String contents) { 425 if (contents == null || contents.trim().equals("")) 426 return null; 427 else 428 return contents.trim(); 429 } 430 431 432 public static boolean noString(String v) { 433 return v == null || v.equals(""); 434 } 435 436 437 public static void bytesToFile(byte[] content, String filename) throws IOException { 438 FileOutputStream out = new FileOutputStream(filename); 439 out.write(content); 440 out.close(); 441 442 } 443 444 445 public static String appendSlash(String definitions) { 446 return definitions.endsWith(File.separator) ? definitions : definitions + File.separator; 447 } 448 449 public static String appendForwardSlash(String definitions) { 450 return definitions.endsWith("/") ? definitions : definitions + "/"; 451 } 452 453 454 public static String fileTitle(String file) { 455 if (file == null) 456 return null; 457 String s = new File(file).getName(); 458 return s.indexOf(".") == -1 ? s : s.substring(0, s.indexOf(".")); 459 } 460 461 462 public static String systemEol() { 463 return System.getProperty("line.separator"); 464 } 465 466 public static String normaliseEolns(String value) { 467 return value.replace("\r\n", "\r").replace("\n", "\r").replace("\r", "\r\n"); 468 } 469 470 471 public static String unescapeXml(String xml) throws FHIRException { 472 if (xml == null) 473 return null; 474 475 StringBuilder b = new StringBuilder(); 476 int i = 0; 477 while (i < xml.length()) { 478 if (xml.charAt(i) == '&') { 479 StringBuilder e = new StringBuilder(); 480 i++; 481 while (xml.charAt(i) != ';') { 482 e.append(xml.charAt(i)); 483 i++; 484 } 485 if (e.toString().equals("lt")) 486 b.append("<"); 487 else if (e.toString().equals("gt")) 488 b.append(">"); 489 else if (e.toString().equals("amp")) 490 b.append("&"); 491 else if (e.toString().equals("quot")) 492 b.append("\""); 493 else if (e.toString().equals("mu")) 494 b.append((char) 956); 495 else 496 throw new FHIRException("unknown XML entity \"" + e.toString() + "\""); 497 } else 498 b.append(xml.charAt(i)); 499 i++; 500 } 501 return b.toString(); 502 } 503 504 public static String unescapeJson(String json) throws FHIRException { 505 if (json == null) 506 return null; 507 508 StringBuilder b = new StringBuilder(); 509 int i = 0; 510 while (i < json.length()) { 511 if (json.charAt(i) == '\\') { 512 i++; 513 char ch = json.charAt(i); 514 switch (ch) { 515 case '"': 516 b.append('b'); 517 break; 518 case '\\': 519 b.append('\\'); 520 break; 521 case '/': 522 b.append('/'); 523 break; 524 case 'b': 525 b.append('\b'); 526 break; 527 case 'f': 528 b.append('\f'); 529 break; 530 case 'n': 531 b.append('\n'); 532 break; 533 case 'r': 534 b.append('\r'); 535 break; 536 case 't': 537 b.append('\t'); 538 break; 539 case 'u': 540 String hex = json.substring(i + 1, i + 5); 541 b.append((char) Integer.parseInt(hex, 16)); 542 break; 543 default: 544 throw new FHIRException("Unknown JSON escape \\" + ch); 545 } 546 } else 547 b.append(json.charAt(i)); 548 i++; 549 } 550 return b.toString(); 551 } 552 553 554 public static boolean isPlural(String word) { 555 word = word.toLowerCase(); 556 if ("restricts".equals(word) || "contains".equals(word) || "data".equals(word) || "specimen".equals(word) || "replaces".equals(word) || "addresses".equals(word) 557 || "supplementalData".equals(word) || "instantiates".equals(word) || "imports".equals(word)) 558 return false; 559 Inflector inf = new Inflector(); 560 return !inf.singularize(word).equals(word); 561 } 562 563 564 public static String padRight(String src, char c, int len) { 565 StringBuilder s = new StringBuilder(); 566 s.append(src); 567 for (int i = 0; i < len - src.length(); i++) 568 s.append(c); 569 return s.toString(); 570 } 571 572 573 public static String padLeft(String src, char c, int len) { 574 StringBuilder s = new StringBuilder(); 575 for (int i = 0; i < len - src.length(); i++) 576 s.append(c); 577 s.append(src); 578 return s.toString(); 579 } 580 581 582 public static String path(String... args) throws IOException { 583 StringBuilder s = new StringBuilder(); 584 boolean d = false; 585 boolean first = true; 586 for (String arg : args) { 587 if (first && arg == null) 588 continue; 589 first = false; 590 if (!d) 591 d = !noString(arg); 592 else if (!s.toString().endsWith(File.separator)) 593 s.append(File.separator); 594 String a = arg; 595 if (s.length() == 0) { 596 if ("[tmp]".equals(a)) { 597 if (hasCTempDir()) { 598 a = "c:\\temp"; 599 } else { 600 a = System.getProperty("java.io.tmpdir"); 601 } 602 } else if ("[user]".equals(a)) { 603 a = System.getProperty("user.home"); 604 } else if (a.startsWith("[") && a.endsWith("]")) { 605 String ev = System.getenv(a.replace("[", "").replace("]", "")); 606 if (ev != null) { 607 a = ev; 608 } else { 609 a = "null"; 610 } 611 } 612 } 613 a = a.replace("\\", File.separator); 614 a = a.replace("/", File.separator); 615 if (s.length() > 0 && a.startsWith(File.separator)) 616 a = a.substring(File.separator.length()); 617 618 while (a.startsWith(".." + File.separator)) { 619 if (s.length() == 0) { 620 s = new StringBuilder(Paths.get(".").toAbsolutePath().normalize().toString()); 621 } else { 622 String p = s.toString().substring(0, s.length() - 1); 623 if (!p.contains(File.separator)) { 624 s = new StringBuilder(); 625 } else { 626 s = new StringBuilder(p.substring(0, p.lastIndexOf(File.separator)) + File.separator); 627 } 628 } 629 a = a.substring(3); 630 } 631 if ("..".equals(a)) { 632 int i = s.substring(0, s.length() - 1).lastIndexOf(File.separator); 633 s = new StringBuilder(s.substring(0, i + 1)); 634 } else 635 s.append(a); 636 } 637 return s.toString(); 638 } 639 640 private static boolean hasCTempDir() { 641 if (!System.getProperty("os.name").toLowerCase().contains("win")) { 642 return false; 643 } 644 File tmp = new File("c:\\temp"); 645 return tmp.exists() && tmp.isDirectory() && tmp.canWrite(); 646 } 647 648 public static String pathURL(String... args) { 649 StringBuilder s = new StringBuilder(); 650 boolean d = false; 651 for (String arg : args) { 652 if (arg != null) { 653 if (!d) 654 d = !noString(arg); 655 else if (s.toString() != null && !s.toString().endsWith("/") && !arg.startsWith("/")) 656 s.append("/"); 657 s.append(arg); 658 } 659 } 660 return s.toString(); 661 } 662 663 public static String nmtokenize(String cs) { 664 if (cs == null) 665 return ""; 666 StringBuilder s = new StringBuilder(); 667 for (int i = 0; i < cs.length(); i++) { 668 char c = cs.charAt(i); 669 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_') 670 s.append(c); 671 else if (c != ' ') 672 s.append("." + Integer.toString(c)); 673 } 674 return s.toString(); 675 } 676 677 678 public static boolean isToken(String tail) { 679 if (tail == null || tail.length() == 0) 680 return false; 681 boolean result = isAlphabetic(tail.charAt(0)); 682 for (int i = 1; i < tail.length(); i++) { 683 result = result && (isAlphabetic(tail.charAt(i)) || isDigit(tail.charAt(i)) || (tail.charAt(i) == '_') || (tail.charAt(i) == '[') || (tail.charAt(i) == ']')); 684 } 685 return result; 686 } 687 688 689 public static boolean isDigit(char c) { 690 return (c >= '0') && (c <= '9'); 691 } 692 693 694 public static boolean isAlphabetic(char c) { 695 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 696 } 697 698 699 public static String getDirectoryForFile(String filepath) { 700 File f = new File(filepath); 701 return f.getParent(); 702 } 703 704 public static String appendPeriod(String s) { 705 if (Utilities.noString(s)) 706 return s; 707 s = s.trim(); 708 if (s.endsWith(".") || s.endsWith("?")) 709 return s; 710 return s + "."; 711 } 712 713 714 public static String removePeriod(String s) { 715 if (Utilities.noString(s)) 716 return s; 717 if (s.endsWith(".")) 718 return s.substring(0, s.length() - 1); 719 return s; 720 } 721 722 723 public static String stripBOM(String string) { 724 return string.replace("\uFEFF", ""); 725 } 726 727 728 public static String oidTail(String id) { 729 if (id == null || !id.contains(".")) 730 return id; 731 return id.substring(id.lastIndexOf(".") + 1); 732 } 733 734 735 public static String oidRoot(String id) { 736 if (id == null || !id.contains(".")) 737 return id; 738 return id.substring(0, id.indexOf(".")); 739 } 740 741 public static String escapeJava(String doco) { 742 if (doco == null) 743 return ""; 744 745 StringBuilder b = new StringBuilder(); 746 for (char c : doco.toCharArray()) { 747 if (c == '\r') 748 b.append("\\r"); 749 else if (c == '\n') 750 b.append("\\n"); 751 else if (c == '"') 752 b.append("\\\""); 753 else if (c == '\\') 754 b.append("\\\\"); 755 else 756 b.append(c); 757 } 758 return b.toString(); 759 } 760 761 762 public static String[] splitByCamelCase(String name) { 763 List<String> parts = new ArrayList<String>(); 764 StringBuilder b = new StringBuilder(); 765 for (int i = 0; i < name.length(); i++) { 766 if (i > 0 && Character.isUpperCase(name.charAt(i))) { 767 parts.add(b.toString()); 768 b = new StringBuilder(); 769 } 770 b.append(Character.toLowerCase(name.charAt(i))); 771 } 772 parts.add(b.toString()); 773 return parts.toArray(new String[]{}); 774 } 775 776 777 public static String encodeUri(String v) { 778 return v.replace(" ", "%20").replace("?", "%3F").replace("=", "%3D").replace("|", "%7C"); 779 } 780 781 782 public static String normalize(String s) { 783 if (noString(s)) 784 return null; 785 StringBuilder b = new StringBuilder(); 786 boolean isWhitespace = false; 787 for (int i = 0; i < s.length(); i++) { 788 char c = s.charAt(i); 789 if (!Character.isWhitespace(c)) { 790 b.append(Character.toLowerCase(c)); 791 isWhitespace = false; 792 } else if (!isWhitespace) { 793 b.append(' '); 794 isWhitespace = true; 795 } 796 } 797 return b.toString().trim(); 798 } 799 800 public static String normalizeSameCase(String s) { 801 if (noString(s)) 802 return null; 803 StringBuilder b = new StringBuilder(); 804 boolean isWhitespace = false; 805 for (int i = 0; i < s.length(); i++) { 806 char c = s.charAt(i); 807 if (!Character.isWhitespace(c)) { 808 b.append(c); 809 isWhitespace = false; 810 } else if (!isWhitespace) { 811 b.append(' '); 812 isWhitespace = true; 813 } 814 } 815 return b.toString().trim(); 816 } 817 818 819 public static void copyFileToDirectory(File source, File destDir) throws IOException { 820 copyFile(source, new File(path(destDir.getAbsolutePath(), source.getName()))); 821 } 822 823 824 public static boolean isWhitespace(String s) { 825 boolean ok = true; 826 for (int i = 0; i < s.length(); i++) 827 ok = ok && Character.isWhitespace(s.charAt(i)); 828 return ok; 829 830 } 831 832 833 public static String URLEncode(String string) { 834 try { 835 return URLEncoder.encode(string, "UTF-8"); 836 } catch (UnsupportedEncodingException e) { 837 throw new Error(e.getMessage()); 838 } 839 } 840 841 842 public static String URLDecode(String ref) { 843 try { 844 return URLDecoder.decode(ref, "UTF-8"); 845 } catch (UnsupportedEncodingException e) { 846 throw new Error(e.getMessage()); 847 } 848 } 849 850 public static boolean charInSet(char value, char... array) { 851 for (int i : array) 852 if (value == i) 853 return true; 854 return false; 855 } 856 857 858 public static boolean charInRange(char ch, char a, char z) { 859 return ch >= a && ch <= z; 860 } 861 862 public static boolean existsInList(String value, List<String> array) { 863 if (value == null) 864 return false; 865 for (String s : array) 866 if (value.equals(s)) 867 return true; 868 return false; 869 } 870 871 public static boolean existsInList(String value, String... array) { 872 if (value == null) 873 return false; 874 for (String s : array) 875 if (value.equals(s)) 876 return true; 877 return false; 878 } 879 880 public static boolean existsInList(int value, int... array) { 881 for (int i : array) 882 if (value == i) 883 return true; 884 return false; 885 } 886 887 public static boolean existsInListNC(String value, String... array) { 888 for (String s : array) 889 if (value.equalsIgnoreCase(s)) 890 return true; 891 return false; 892 } 893 894 895 public static String getFileNameForName(String name) { 896 return name.toLowerCase(); 897 } 898 899 public static void deleteTempFiles() throws IOException { 900 File file = createTempFile("test", "test"); 901 String folder = getDirectoryForFile(file.getAbsolutePath()); 902 String[] list = new File(folder).list(new FilenameFilter() { 903 public boolean accept(File dir, String name) { 904 return name.startsWith("ohfu-"); 905 } 906 }); 907 if (list != null) { 908 for (String n : list) { 909 new File(path(folder, n)).delete(); 910 } 911 } 912 } 913 914 public static File createTempFile(String prefix, String suffix) throws IOException { 915 // this allows use to eaily identify all our dtemp files and delete them, since delete on Exit doesn't really work. 916 File file = File.createTempFile("ohfu-" + prefix, suffix); 917 file.deleteOnExit(); 918 return file; 919 } 920 921 922 public static boolean isAsciiChar(char ch) { 923 return ch >= ' ' && ch <= '~'; 924 } 925 926 927 public static String makeUuidLC() { 928 return UUID.randomUUID().toString().toLowerCase(); 929 } 930 931 public static String makeUuidUrn() { 932 return "urn:uuid:" + UUID.randomUUID().toString().toLowerCase(); 933 } 934 935 public static boolean isURL(String s) { 936 boolean ok = s.matches("^http(s{0,1})://[a-zA-Z0-9_/\\-\\.]+\\.([A-Za-z/]{2,5})[a-zA-Z0-9_/\\&\\?\\=\\-\\.\\~\\%]*"); 937 return ok; 938 } 939 940 941 public static String escapeJson(String value) { 942 if (value == null) 943 return ""; 944 945 StringBuilder b = new StringBuilder(); 946 for (char c : value.toCharArray()) { 947 if (c == '\r') 948 b.append("\\r"); 949 else if (c == '\n') 950 b.append("\\n"); 951 else if (c == '\t') 952 b.append("\\t"); 953 else if (c == '"') 954 b.append("\\\""); 955 else if (c == '\\') 956 b.append("\\\\"); 957 else if (((int) c) < 32) 958 b.append("\\u" + Utilities.padLeft(String.valueOf((int) c), '0', 4)); 959 else 960 b.append(c); 961 } 962 return b.toString(); 963 } 964 965 public static String humanize(String code) { 966 StringBuilder b = new StringBuilder(); 967 boolean lastBreak = true; 968 for (char c : code.toCharArray()) { 969 if (Character.isLetter(c)) { 970 if (lastBreak) 971 b.append(Character.toUpperCase(c)); 972 else { 973 if (Character.isUpperCase(c)) 974 b.append(" "); 975 b.append(c); 976 } 977 lastBreak = false; 978 } else { 979 b.append(" "); 980 lastBreak = true; 981 } 982 } 983 if (b.length() == 0) 984 return code; 985 else 986 return b.toString(); 987 } 988 989 990 public static String uncapitalize(String s) { 991 if (s == null) return null; 992 if (s.length() == 0) return s; 993 if (s.length() == 1) return s.toLowerCase(); 994 995 return s.substring(0, 1).toLowerCase() + s.substring(1); 996 } 997 998 999 public static int charCount(String s, char c) { 1000 int res = 0; 1001 for (char ch : s.toCharArray()) 1002 if (ch == c) 1003 res++; 1004 return res; 1005 } 1006 1007 1008 public static boolean isOid(String cc) { 1009 return cc.matches(OID_REGEX) && cc.lastIndexOf('.') >= 5; 1010 } 1011 1012 1013 public static boolean equals(String one, String two) { 1014 if (one == null && two == null) 1015 return true; 1016 if (one == null || two == null) 1017 return false; 1018 return one.equals(two); 1019 } 1020 1021 1022 public static void deleteAllFiles(String folder, String type) { 1023 File src = new File(folder); 1024 String[] files = src.list(); 1025 for (String f : files) { 1026 if (new File(folder + File.separator + f).isDirectory()) { 1027 deleteAllFiles(folder + File.separator + f, type); 1028 } else if (f.endsWith(type)) { 1029 new File(folder + File.separator + f).delete(); 1030 } 1031 } 1032 1033 } 1034 1035 public static boolean compareIgnoreWhitespace(File f1, File f2) throws IOException { 1036 InputStream in1 = null; 1037 InputStream in2 = null; 1038 try { 1039 in1 = new BufferedInputStream(new FileInputStream(f1)); 1040 in2 = new BufferedInputStream(new FileInputStream(f2)); 1041 1042 int expectedByte = in1.read(); 1043 while (expectedByte != -1) { 1044 boolean w1 = isWhitespace(expectedByte); 1045 if (w1) 1046 while (isWhitespace(expectedByte)) 1047 expectedByte = in1.read(); 1048 int foundByte = in2.read(); 1049 if (w1) { 1050 if (!isWhitespace(foundByte)) 1051 return false; 1052 while (isWhitespace(foundByte)) 1053 foundByte = in2.read(); 1054 } 1055 if (expectedByte != foundByte) 1056 return false; 1057 expectedByte = in1.read(); 1058 } 1059 if (in2.read() != -1) { 1060 return false; 1061 } 1062 return true; 1063 } finally { 1064 if (in1 != null) { 1065 try { 1066 in1.close(); 1067 } catch (IOException e) { 1068 } 1069 } 1070 if (in2 != null) { 1071 try { 1072 in2.close(); 1073 } catch (IOException e) { 1074 } 1075 } 1076 } 1077 } 1078 1079 private static boolean isWhitespace(int b) { 1080 return b == 9 || b == 10 || b == 13 || b == 32; 1081 } 1082 1083 1084 public static boolean compareIgnoreWhitespace(String fn1, String fn2) throws IOException { 1085 return compareIgnoreWhitespace(new File(fn1), new File(fn2)); 1086 } 1087 1088 1089 public static boolean isAbsoluteUrl(String ref) { 1090 if (ref != null && ref.contains(":")) { 1091 String scheme = ref.substring(0, ref.indexOf(":")); 1092 String details = ref.substring(ref.indexOf(":")+1); 1093 return (existsInList(scheme, "http", "https", "urn") || (isToken(scheme) && scheme.equals(scheme.toLowerCase())) || Utilities.startsWithInList(ref, "urn:iso:", "urn:iso-iec:", "urn:iso-cie:", "urn:iso-astm:", "urn:iso-ieee:", "urn:iec:")) 1094 && details != null && details.length() > 0 && !details.contains(" "); // rfc5141 1095 } 1096 return false; 1097 } 1098 1099 public static boolean isAbsoluteUrlLinkable(String ref) { 1100 if (ref != null && ref.contains(":")) { 1101 String scheme = ref.substring(0, ref.indexOf(":")); 1102 String details = ref.substring(ref.indexOf(":")+1); 1103 return (existsInList(scheme, "http", "https", "ftp")) 1104 && details != null && details.length() > 0 && !details.contains(" "); // rfc5141 1105 } 1106 return false; 1107 } 1108 1109 public static boolean equivalent(String l, String r) { 1110 if (Utilities.noString(l) && Utilities.noString(r)) 1111 return true; 1112 if (Utilities.noString(l) || Utilities.noString(r)) 1113 return false; 1114 return l.toLowerCase().equals(r.toLowerCase()); 1115 } 1116 1117 1118 public static boolean equivalentNumber(String l, String r) { 1119 if (Utilities.noString(l) && Utilities.noString(r)) 1120 return true; 1121 if (Utilities.noString(l) || Utilities.noString(r)) 1122 return false; 1123 if (!Utilities.isDecimal(l, true) || !Utilities.isDecimal(r, true)) 1124 return false; 1125 BigDecimal dl = new BigDecimal(l); 1126 BigDecimal dr = new BigDecimal(r); 1127 if (dl.scale() < dr.scale()) { 1128 dr = dr.setScale(dl.scale(), RoundingMode.HALF_UP); 1129 } else if (dl.scale() > dr.scale()) { 1130 dl = dl.setScale(dr.scale(), RoundingMode.HALF_UP); 1131 } 1132 return dl.equals(dr); 1133 } 1134 1135 public static String getFileExtension(String fn) { 1136 return fn.contains(".") ? fn.substring(fn.lastIndexOf(".") + 1) : ""; 1137 } 1138 1139 1140 public static String unCamelCase(String name) { 1141 StringBuilder b = new StringBuilder(); 1142 boolean first = true; 1143 for (char c : name.toCharArray()) { 1144 if (Character.isUpperCase(c)) { 1145 if (!first) 1146 b.append(" "); 1147 b.append(Character.toLowerCase(c)); 1148 } else 1149 b.append(c); 1150 first = false; 1151 } 1152 return b.toString(); 1153 } 1154 1155 1156 public static boolean isAbsoluteFileName(String source) { 1157 if (isWindows()) 1158 return (source.length() > 2 && source.charAt(1) == ':') || source.startsWith("\\\\"); 1159 else 1160 return source.startsWith("//"); 1161 } 1162 1163 1164 public static boolean isWindows() { 1165 return System.getProperty("os.name").startsWith("Windows"); 1166 } 1167 1168 1169 public static String splitLineForLength(String line, int prefixLength, int indent, int allowedLength) { 1170 List<String> list = new ArrayList<String>(); 1171 while (prefixLength + line.length() > allowedLength) { 1172 int i = allowedLength - (list.size() == 0 ? prefixLength : indent); 1173 while (i > 0 && line.charAt(i) != ' ') 1174 i--; 1175 if (i == 0) 1176 break; 1177 list.add(line.substring(0, i)); 1178 line = line.substring(i + 1); 1179 } 1180 list.add(line); 1181 StringBuilder b = new StringBuilder(); 1182 boolean first = true; 1183 for (String s : list) { 1184 if (first) 1185 first = false; 1186 else 1187 b.append("\r\n" + padLeft("", ' ', indent)); 1188 b.append(s); 1189 } 1190 return b.toString(); 1191 } 1192 1193 1194 public static int countFilesInDirectory(String dirName) { 1195 File dir = new File(dirName); 1196 if (dir.exists() == false) { 1197 return 0; 1198 } 1199 int i = 0; 1200 for (File f : dir.listFiles()) 1201 if (!f.isDirectory()) 1202 i++; 1203 return i; 1204 } 1205 1206 public static String makeId(String name) { 1207 StringBuilder b = new StringBuilder(); 1208 for (char ch : name.toCharArray()) { 1209 if (ch >= 'a' && ch <= 'z') 1210 b.append(ch); 1211 else if (ch >= 'A' && ch <= 'Z') 1212 b.append(ch); 1213 else if (ch >= '0' && ch <= '9') 1214 b.append(ch); 1215 else if (ch == '-' || ch == '.') 1216 b.append(ch); 1217 } 1218 return b.toString(); 1219 } 1220 1221 public interface FileVisitor { 1222 void visitFile(File file) throws FileNotFoundException, IOException; 1223 } 1224 1225 public static void visitFiles(String folder, String extension, FileVisitor visitor) throws FileNotFoundException, IOException { 1226 visitFiles(new File(folder), extension, visitor); 1227 } 1228 1229 public static void visitFiles(File folder, String extension, FileVisitor visitor) throws FileNotFoundException, IOException { 1230 for (File file : folder.listFiles()) { 1231 if (file.isDirectory()) 1232 visitFiles(file, extension, visitor); 1233 else if (extension == null || file.getName().endsWith(extension)) 1234 visitor.visitFile(file); 1235 } 1236 } 1237 1238 public static String extractBaseUrl(String url) { 1239 if (url == null) 1240 return null; 1241 else if (url.contains("/")) 1242 return url.substring(0, url.lastIndexOf("/")); 1243 else 1244 return url; 1245 } 1246 1247 public static String listCanonicalUrls(Set<String> keys) { 1248 return keys.toString(); 1249 } 1250 1251 public static boolean isValidId(String id) { 1252 return id.matches("[A-Za-z0-9\\-\\.]{1,64}"); 1253 } 1254 1255 public static List<String> sorted(Set<String> set) { 1256 List<String> list = new ArrayList<>(); 1257 list.addAll(set); 1258 Collections.sort(list); 1259 return list; 1260 } 1261 1262 public static void analyseStringDiffs(Set<String> source, Set<String> target, Set<String> missed, Set<String> extra) { 1263 for (String s : source) 1264 if (!target.contains(s)) 1265 missed.add(s); 1266 for (String s : target) 1267 if (!source.contains(s)) 1268 extra.add(s); 1269 1270 } 1271 1272 /** 1273 * Only handles simple FHIRPath expressions of the type produced by the validator 1274 * 1275 * @param path 1276 * @return 1277 */ 1278 public static String fhirPathToXPath(String path) { 1279 String[] p = path.split("\\."); 1280 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 1281 int i = 0; 1282 while (i < p.length) { 1283 String s = p[i]; 1284 if (s.contains("[")) { 1285 String si = s.substring(s.indexOf("[") + 1, s.length() - 1); 1286 if (!Utilities.isInteger(si)) 1287 throw new FHIRException("The FHIRPath expression '" + path + "' is not valid"); 1288 s = s.substring(0, s.indexOf("[")) + "[" + Integer.toString(Integer.parseInt(si) + 1) + "]"; 1289 } 1290 if (i < p.length - 1 && p[i + 1].startsWith(".ofType(")) { 1291 i++; 1292 s = s + capitalize(p[i].substring(8, p.length - 1)); 1293 } 1294 b.append(s); 1295 i++; 1296 } 1297 return b.toString(); 1298 } 1299 1300 public static String describeDuration(Duration d) { 1301 if (d.toDays() > 2) { 1302 return String.format("%s days", d.toDays()); 1303 } else if (d.toHours() > 2) { 1304 return String.format("%s hours", d.toHours()); 1305 } else if (d.toMinutes() > 2) { 1306 return String.format("%s mins", d.toMinutes()); 1307 } else { 1308 return String.format("%s ms", d.toMillis()); 1309 } 1310 } 1311 1312 public static boolean startsWithInList(String s, String... list) { 1313 if (s == null) { 1314 return false; 1315 } 1316 for (String l : list) { 1317 if (s.startsWith(l)) { 1318 return true; 1319 } 1320 } 1321 return false; 1322 } 1323 1324 public static boolean startsWithInList(String s, Collection<String> list) { 1325 if (s == null) { 1326 return false; 1327 } 1328 for (String l : list) { 1329 if (s.startsWith(l)) { 1330 return true; 1331 } 1332 } 1333 return false; 1334 } 1335 1336 public static final int ONE_MB = 1024; 1337 public static final String GB = "Gb"; 1338 public static final String MB = "Mb"; 1339 public static final String KB = "Kb"; 1340 public static final String BT = "b"; 1341 1342 public static String describeSize(int length) { 1343 if (length < 0) throw new IllegalArgumentException("File length of < 0 passed in..."); 1344 1345 if (length > Math.pow(ONE_MB, 3)) { 1346 return length / ((long) Math.pow(ONE_MB, 3)) + GB; 1347 } 1348 if (length > Math.pow(ONE_MB, 2)) { 1349 return length / ((long) Math.pow(ONE_MB, 2)) + MB; 1350 } 1351 if (length > ONE_MB) { 1352 return length / (ONE_MB) + KB; 1353 } 1354 return length + BT; 1355 } 1356 1357 public static String describeSize(long length) { 1358 if (length < 0) throw new IllegalArgumentException("File length of < 0 passed in..."); 1359 1360 if (length > Math.pow(ONE_MB, 3)) { 1361 return length / ((long) Math.pow(ONE_MB, 3)) + GB; 1362 } 1363 if (length > Math.pow(ONE_MB, 2)) { 1364 return length / ((long) Math.pow(ONE_MB, 2)) + MB; 1365 } 1366 if (length > ONE_MB) { 1367 return length / (ONE_MB) + KB; 1368 } 1369 return length + BT; 1370 } 1371 1372 public static List<byte[]> splitBytes(byte[] array, byte[] delimiter) { 1373 List<byte[]> byteArrays = new LinkedList<byte[]>(); 1374 if (delimiter.length == 0) 1375 { 1376 return byteArrays; 1377 } 1378 int begin = 0; 1379 1380 outer: for (int i = 0; i < array.length - delimiter.length + 1; i++) 1381 { 1382 for (int j = 0; j < delimiter.length; j++) 1383 { 1384 if (array[i + j] != delimiter[j]) 1385 { 1386 continue outer; 1387 } 1388 } 1389 1390 // If delimiter is at the beginning then there will not be any data. 1391 if (begin < i) 1392 byteArrays.add(Arrays.copyOfRange(array, begin, i)); 1393 begin = i + delimiter.length; 1394 } 1395 1396 // delimiter at the very end with no data following? 1397 if (begin != array.length) 1398 byteArrays.add(Arrays.copyOfRange(array, begin, array.length)); 1399 1400 return byteArrays; 1401 } 1402 1403 public static String presentDuration(long duration) { 1404 duration = duration / 1000000; 1405 String res = ""; // ; 1406 long days = TimeUnit.MILLISECONDS.toDays(duration); 1407 long hours = TimeUnit.MILLISECONDS.toHours(duration) - 1408 TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(duration)); 1409 long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) - 1410 TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(duration)); 1411 long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) - 1412 TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)); 1413 long millis = TimeUnit.MILLISECONDS.toMillis(duration) - 1414 TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(duration)); 1415 1416 if (days > 0) 1417 res = String.format("%dd %02d:%02d:%02d.%04d", days, hours, minutes, seconds, millis); 1418 else if (hours > 0) 1419 res = String.format("%02d:%02d:%02d.%04d", hours, minutes, seconds, millis); 1420 else // 1421 res = String.format("%02d:%02d.%04d", minutes, seconds, millis); 1422// else 1423// res = String.format("%02d.%04d", seconds, millis); 1424 return res; 1425 } 1426 1427 public static void unzip(InputStream zip, Path target) throws IOException { 1428 try (ZipInputStream zis = new ZipInputStream(zip)) { 1429 ZipEntry zipEntry = zis.getNextEntry(); 1430 while (zipEntry != null) { 1431 boolean isDirectory = false; 1432 if (zipEntry.getName().endsWith("/") || zipEntry.getName().endsWith("\\")) { 1433 isDirectory = true; 1434 } 1435 Path newPath = zipSlipProtect(zipEntry, target); 1436 if (isDirectory) { 1437 Files.createDirectories(newPath); 1438 } else { 1439 if (newPath.getParent() != null) { 1440 if (Files.notExists(newPath.getParent())) { 1441 Files.createDirectories(newPath.getParent()); 1442 } 1443 } 1444 Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING); 1445 } 1446 zipEntry = zis.getNextEntry(); 1447 } 1448 zis.closeEntry(); 1449 } 1450 } 1451 1452 private static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir) 1453 throws IOException { 1454 1455 // test zip slip vulnerability 1456 // Path targetDirResolved = targetDir.resolve("../../" + zipEntry.getName()); 1457 1458 Path targetDirResolved = targetDir.resolve(zipEntry.getName()); 1459 1460 // make sure normalized file still has targetDir as its prefix 1461 // else throws exception 1462 Path normalizePath = targetDirResolved.normalize(); 1463 if (!normalizePath.startsWith(targetDir)) { 1464 throw new IOException("Bad zip entry: " + zipEntry.getName()); 1465 } 1466 1467 return normalizePath; 1468 } 1469 1470 final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; 1471 1472 static { 1473 Arrays.sort(illegalChars); 1474 } 1475 1476 public static String cleanFileName(String badFileName) { 1477 StringBuilder cleanName = new StringBuilder(); 1478 int len = badFileName.codePointCount(0, badFileName.length()); 1479 for (int i=0; i<len; i++) { 1480 int c = badFileName.codePointAt(i); 1481 if (Arrays.binarySearch(illegalChars, c) < 0) { 1482 cleanName.appendCodePoint(c); 1483 } 1484 } 1485 return cleanName.toString(); 1486 } 1487 1488 public static boolean isValidUUID(String uuid) { 1489 return uuid.matches(UUID_REGEX); 1490 } 1491 1492 public static boolean isValidOID(String oid) { 1493 return oid.matches(OID_REGEX); 1494 } 1495 1496 public static int findinList(String[] list, String val) { 1497 for (int i = 0; i < list.length; i++) { 1498 if (val.equals(list[i])) { 1499 return i; 1500 } 1501 } 1502 return -1; 1503 } 1504 1505 public static String toString(String[] expected) { 1506 return "['"+String.join("' | '", expected)+"']"; 1507 } 1508 1509 1510}