001/* 002Copyright (c) 2011+, HL7, Inc 003All rights reserved. 004 005Redistribution and use in source and binary forms, with or without modification, 006are permitted provided that the following conditions are met: 007 008 * Redistributions of source code must retain the above copyright notice, this 009 list of conditions and the following disclaimer. 010 * Redistributions in binary form must reproduce the above copyright notice, 011 this list of conditions and the following disclaimer in the documentation 012 and/or other materials provided with the distribution. 013 * Neither the name of HL7 nor the names of its contributors may be used to 014 endorse or promote products derived from this software without specific 015 prior written permission. 016 017THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 018ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 019WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 020IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 021INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 023PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 024WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 025ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 026POSSIBILITY OF SUCH DAMAGE. 027 028 */ 029package org.hl7.fhir.utilities; 030 031/*- 032 * #%L 033 * org.hl7.fhir.utilities 034 * %% 035 * Copyright (C) 2014 - 2019 Health Level 7 036 * %% 037 * Licensed under the Apache License, Version 2.0 (the "License"); 038 * you may not use this file except in compliance with the License. 039 * You may obtain a copy of the License at 040 * 041 * http://www.apache.org/licenses/LICENSE-2.0 042 * 043 * Unless required by applicable law or agreed to in writing, software 044 * distributed under the License is distributed on an "AS IS" BASIS, 045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 046 * See the License for the specific language governing permissions and 047 * limitations under the License. 048 * #L% 049 */ 050 051 052import java.io.BufferedInputStream; 053import java.io.ByteArrayInputStream; 054import java.io.ByteArrayOutputStream; 055import java.io.File; 056import java.io.FileInputStream; 057import java.io.FileNotFoundException; 058import java.io.FileOutputStream; 059import java.io.FilenameFilter; 060import java.io.IOException; 061import java.io.InputStream; 062import java.io.UnsupportedEncodingException; 063import java.math.BigDecimal; 064import java.net.URLEncoder; 065import java.nio.channels.FileChannel; 066import java.time.Duration; 067import java.util.ArrayList; 068import java.util.Collections; 069import java.util.List; 070import java.util.Map; 071import java.util.Set; 072import java.util.UUID; 073import java.util.concurrent.TimeUnit; 074 075import org.apache.commons.io.FileUtils; 076import org.hl7.fhir.exceptions.FHIRException; 077 078import net.sf.saxon.TransformerFactoryImpl; 079 080import static org.apache.commons.lang3.StringUtils.isBlank; 081 082public class Utilities { 083 084// private static final String TOKEN_REGEX = "^a-z[A-Za-z0-9]*$"; 085 086 private static final String OID_REGEX = "[0-2](\\.(0|[1-9][0-9]*))+"; 087 088 /** 089 * Returns the plural form of the word in the string. 090 * 091 * Examples: 092 * 093 * <pre> 094 * inflector.pluralize("post") #=> "posts" 095 * inflector.pluralize("octopus") #=> "octopi" 096 * inflector.pluralize("sheep") #=> "sheep" 097 * inflector.pluralize("words") #=> "words" 098 * inflector.pluralize("the blue mailman") #=> "the blue mailmen" 099 * inflector.pluralize("CamelOctopus") #=> "CamelOctopi" 100 * </pre> 101 * 102 * 103 * 104 * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too. 105 * 106 * 107 * @param word the word that is to be pluralized. 108 * @return the pluralized form of the word, or the word itself if it could not be pluralized 109 * @see #singularize(Object) 110 */ 111 public static String pluralizeMe( String word ) { 112 Inflector inf = new Inflector(); 113 return inf.pluralize(word); 114 } 115 116 public static String pluralize(String word, int count) { 117 if (count == 1) 118 return word; 119 Inflector inf = new Inflector(); 120 return inf.pluralize(word); 121 } 122 123 124 public static boolean isInteger(String string) { 125 if (isBlank(string)) { 126 return false; 127 } 128 String value = string.startsWith("-") ? string.substring(1) : string; 129 for (char next : value.toCharArray()) { 130 if (!Character.isDigit(next)) { 131 return false; 132 } 133 } 134 // check bounds -2,147,483,648..2,147,483,647 135 if (value.length() > 10) 136 return false; 137 if (string.startsWith("-")) { 138 if (value.length() == 10 && string.compareTo("2147483648") > 0) 139 return false; 140 } else { 141 if (value.length() == 10 && string.compareTo("2147483647") > 0) 142 return false; 143 } 144 return true; 145 } 146 147 public static boolean isLong(String string) { 148 if (isBlank(string)) { 149 return false; 150 } 151 String value = string.startsWith("-") ? string.substring(1) : string; 152 for (char next : value.toCharArray()) { 153 if (!Character.isDigit(next)) { 154 return false; 155 } 156 } 157 // check bounds -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 158 if (value.length() > 20) 159 return false; 160 if (string.startsWith("-")) { 161 if (value.length() == 20 && string.compareTo("9223372036854775808") > 0) 162 return false; 163 } else { 164 if (value.length() == 20 && string.compareTo("9223372036854775807") > 0) 165 return false; 166 } 167 return true; 168 } 169 170 public static boolean isHex(String string) { 171 try { 172 int i = Integer.parseInt(string, 16); 173 return i != i+1; 174 } catch (Exception e) { 175 return false; 176 } 177 } 178 179 public enum DecimalStatus { 180 BLANK, SYNTAX, RANGE, OK 181 } 182 183 public static boolean isDecimal(String value, boolean allowExponent, boolean allowLeadingZero) { 184 DecimalStatus ds = checkDecimal(value, allowExponent, true); 185 return ds == DecimalStatus.OK || ds == DecimalStatus.RANGE; 186 } 187 188 public static boolean isDecimal(String value, boolean allowExponent) { 189 DecimalStatus ds = checkDecimal(value, allowExponent, false); 190 return ds == DecimalStatus.OK || ds == DecimalStatus.RANGE; 191 } 192 193 public static DecimalStatus checkDecimal(String value, boolean allowExponent, boolean allowLeadingZero) { 194 if (isBlank(value)) { 195 return DecimalStatus.BLANK; 196 } 197 198 // check for leading zeros 199 if (!allowLeadingZero) { 200 if (value.startsWith("0") && !"0".equals(value) && !value.startsWith("0.")) 201 return DecimalStatus.SYNTAX; 202 if (value.startsWith("-0") && !"-0".equals(value) && !value.startsWith("-0.")) 203 return DecimalStatus.SYNTAX; 204 if (value.startsWith("+0") && !"+0".equals(value) && !value.startsWith("+0.")) 205 return DecimalStatus.SYNTAX; 206 } 207 208 // check for trailing dot 209 if (value.endsWith(".")) { 210 return DecimalStatus.SYNTAX; 211 } 212 213 boolean havePeriod = false; 214 boolean haveExponent = false; 215 boolean haveSign = false; 216 boolean haveDigits = false; 217 int preDecLength = 0; 218 int postDecLength = 0; 219 int exponentLength = 0; 220 int length = 0; 221 for (char next : value.toCharArray()) { 222 if (next == '.') { 223 if (!haveDigits || havePeriod || haveExponent) 224 return DecimalStatus.SYNTAX; 225 havePeriod = true; 226 preDecLength = length; 227 length = 0; 228 } else if (next == '-' || next == '+' ) { 229 if (haveDigits || haveSign) 230 return DecimalStatus.SYNTAX; 231 haveSign = true; 232 } else if (next == 'e' || next == 'E' ) { 233 if (!haveDigits || haveExponent || !allowExponent) 234 return DecimalStatus.SYNTAX; 235 haveExponent = true; 236 haveSign = false; 237 haveDigits = false; 238 if (havePeriod) 239 postDecLength = length; 240 else 241 preDecLength = length; 242 length = 0; 243 } else if (!Character.isDigit(next)) { 244 return DecimalStatus.SYNTAX; 245 } else { 246 haveDigits = true; 247 length++; 248 } 249 } 250 if (haveExponent && !haveDigits) 251 return DecimalStatus.SYNTAX; 252 if (haveExponent) 253 exponentLength = length; 254 else if (havePeriod) 255 postDecLength = length; 256 else 257 preDecLength = length; 258 259 // now, bounds checking - these are arbitrary 260 if (exponentLength > 4) 261 return DecimalStatus.RANGE; 262 if (preDecLength + postDecLength > 18) 263 return DecimalStatus.RANGE; 264 265 return DecimalStatus.OK; 266 } 267 268 public static String camelCase(String value) { 269 return new Inflector().camelCase(value.trim().replace(" ", "_"), false); 270 } 271 272 public static String escapeXml(String doco) { 273 if (doco == null) 274 return ""; 275 276 StringBuilder b = new StringBuilder(); 277 for (char c : doco.toCharArray()) { 278 if (c == '<') 279 b.append("<"); 280 else if (c == '>') 281 b.append(">"); 282 else if (c == '&') 283 b.append("&"); 284 else if (c == '"') 285 b.append("""); 286 else 287 b.append(c); 288 } 289 return b.toString(); 290 } 291 292 public static String titleize(String s) { 293 StringBuilder b = new StringBuilder(); 294 boolean up = true; 295 for (char c : s.toCharArray()) { 296 if (up) 297 b.append(Character.toUpperCase(c)); 298 else 299 b.append(c); 300 up = c == ' '; 301 } 302 return b.toString(); 303 } 304 305 public static String capitalize(String s) 306 { 307 if( s == null ) return null; 308 if( s.length() == 0 ) return s; 309 if( s.length() == 1 ) return s.toUpperCase(); 310 311 return s.substring(0, 1).toUpperCase() + s.substring(1); 312 } 313 314 public static void copyDirectory(String sourceFolder, String destFolder, FileNotifier notifier) throws IOException, FHIRException { 315 CSFile src = new CSFile(sourceFolder); 316 if (!src.exists()) 317 throw new FHIRException("Folder " +sourceFolder+" not found"); 318 createDirectory(destFolder); 319 320 String[] files = src.list(); 321 for (String f : files) { 322 if (new CSFile(sourceFolder+File.separator+f).isDirectory()) { 323 if (!f.startsWith(".")) // ignore .git files... 324 copyDirectory(sourceFolder+File.separator+f, destFolder+File.separator+f, notifier); 325 } else { 326 if (notifier != null) 327 notifier.copyFile(sourceFolder+File.separator+f, destFolder+File.separator+f); 328 copyFile(new CSFile(sourceFolder+File.separator+f), new CSFile(destFolder+File.separator+f)); 329 } 330 } 331 } 332 333 public static void copyFile(String source, String dest) throws IOException { 334 copyFile(new File(source), new File(dest)); 335 } 336 337 public static void copyFile(File sourceFile, File destFile) throws IOException { 338 if(!destFile.exists()) { 339 if (!new CSFile(destFile.getParent()).exists()) { 340 createDirectory(destFile.getParent()); 341 } 342 destFile.createNewFile(); 343 } 344 345 FileChannel source = null; 346 FileChannel destination = null; 347 348 try { 349 source = new FileInputStream(sourceFile).getChannel(); 350 destination = new FileOutputStream(destFile).getChannel(); 351 destination.transferFrom(source, 0, source.size()); 352 } 353 finally { 354 if(source != null) { 355 source.close(); 356 } 357 if(destination != null) { 358 destination.close(); 359 } 360 } 361 } 362 363 public static boolean checkFolder(String dir, List<String> errors) 364 throws IOException 365 { 366 if (!new CSFile(dir).exists()) { 367 errors.add("Unable to find directory "+dir); 368 return false; 369 } else { 370 return true; 371 } 372 } 373 374 public static boolean checkFile(String purpose, String dir, String file, List<String> errors) 375 throws IOException 376 { 377 if (!new CSFile(dir+file).exists()) { 378 if (errors != null) 379 errors.add("Unable to find "+purpose+" file "+file+" in "+dir); 380 return false; 381 } else { 382 return true; 383 } 384 } 385 386 public static String asCSV(List<String> strings) { 387 StringBuilder s = new StringBuilder(); 388 boolean first = true; 389 for (String n : strings) { 390 if (!first) 391 s.append(","); 392 s.append(n); 393 first = false; 394 } 395 return s.toString(); 396 } 397 398 public static String asHtmlBr(String prefix, List<String> strings) { 399 StringBuilder s = new StringBuilder(); 400 boolean first = true; 401 for (String n : strings) { 402 if (!first) 403 s.append("<br/>"); 404 s.append(prefix); 405 s.append(n); 406 first = false; 407 } 408 return s.toString(); 409 } 410 411 public static void clearDirectory(String folder, String... exemptions) throws IOException { 412 File dir = new File(folder); 413 if (dir.exists()) { 414 if (exemptions.length == 0) 415 FileUtils.cleanDirectory(dir); 416 else { 417 String[] files = new CSFile(folder).list(); 418 if (files != null) { 419 for (String f : files) { 420 if (!existsInList(f, exemptions)) { 421 File fh = new CSFile(folder+File.separatorChar+f); 422 if (fh.isDirectory()) 423 clearDirectory(fh.getAbsolutePath()); 424 fh.delete(); 425 } 426 } 427 } 428 } 429 } 430 } 431 432 public static File createDirectory(String path) throws IOException{ 433 new CSFile(path).mkdirs(); 434 return new File(path); 435 } 436 437 public static String changeFileExt(String name, String ext) { 438 if (name.lastIndexOf('.') > -1) 439 return name.substring(0, name.lastIndexOf('.')) + ext; 440 else 441 return name+ext; 442 } 443 444 public static String cleanupTextString( String contents ) 445 { 446 if( contents == null || contents.trim().equals("") ) 447 return null; 448 else 449 return contents.trim(); 450 } 451 452 453 public static boolean noString(String v) { 454 return v == null || v.equals(""); 455 } 456 457 458 459 460 public static void bytesToFile(byte[] content, String filename) throws IOException { 461 FileOutputStream out = new FileOutputStream(filename); 462 out.write(content); 463 out.close(); 464 465 } 466 467 468 469 public static String appendSlash(String definitions) { 470 return definitions.endsWith(File.separator) ? definitions : definitions+File.separator; 471 } 472 473 public static String appendForwardSlash(String definitions) { 474 return definitions.endsWith("/") ? definitions : definitions+"/"; 475 } 476 477 478 public static String fileTitle(String file) { 479 if (file == null) 480 return null; 481 String s = new File(file).getName(); 482 return s.indexOf(".") == -1? s : s.substring(0, s.indexOf(".")); 483 } 484 485 486 public static String systemEol() 487 { 488 return System.getProperty("line.separator"); 489 } 490 491 public static String normaliseEolns(String value) { 492 return value.replace("\r\n", "\r").replace("\n", "\r").replace("\r", "\r\n"); 493 } 494 495 496 public static String unescapeXml(String xml) throws FHIRException { 497 if (xml == null) 498 return null; 499 500 StringBuilder b = new StringBuilder(); 501 int i = 0; 502 while (i < xml.length()) { 503 if (xml.charAt(i) == '&') { 504 StringBuilder e = new StringBuilder(); 505 i++; 506 while (xml.charAt(i) != ';') { 507 e.append(xml.charAt(i)); 508 i++; 509 } 510 if (e.toString().equals("lt")) 511 b.append("<"); 512 else if (e.toString().equals("gt")) 513 b.append(">"); 514 else if (e.toString().equals("amp")) 515 b.append("&"); 516 else if (e.toString().equals("quot")) 517 b.append("\""); 518 else if (e.toString().equals("mu")) 519 b.append((char)956); 520 else 521 throw new FHIRException("unknown XML entity \""+e.toString()+"\""); 522 } else 523 b.append(xml.charAt(i)); 524 i++; 525 } 526 return b.toString(); 527 } 528 529 530 public static boolean isPlural(String word) { 531 word = word.toLowerCase(); 532 if ("restricts".equals(word) || "contains".equals(word) || "data".equals(word) || "specimen".equals(word) || "replaces".equals(word) || "addresses".equals(word) 533 || "supplementalData".equals(word) || "instantiates".equals(word) || "imports".equals(word)) 534 return false; 535 Inflector inf = new Inflector(); 536 return !inf.singularize(word).equals(word); 537 } 538 539 540 public static String padRight(String src, char c, int len) { 541 StringBuilder s = new StringBuilder(); 542 s.append(src); 543 for (int i = 0; i < len - src.length(); i++) 544 s.append(c); 545 return s.toString(); 546 } 547 548 549 public static String padLeft(String src, char c, int len) { 550 StringBuilder s = new StringBuilder(); 551 for (int i = 0; i < len - src.length(); i++) 552 s.append(c); 553 s.append(src); 554 return s.toString(); 555 } 556 557 558 public static String path(String... args) throws IOException { 559 StringBuilder s = new StringBuilder(); 560 boolean d = false; 561 boolean first = true; 562 for(String arg: args) { 563 if (first && arg == null) 564 continue; 565 first = false; 566 if (!d) 567 d = !noString(arg); 568 else if (!s.toString().endsWith(File.separator)) 569 s.append(File.separator); 570 String a = arg; 571 if ("[tmp]".equals(a)) { 572 if (hasCTempDir()) { 573 a = "c:\\temp"; 574 } else { 575 a = System.getProperty("java.io.tmpdir"); 576 } 577 } 578 a = a.replace("\\", File.separator); 579 a = a.replace("/", File.separator); 580 if (s.length() > 0 && a.startsWith(File.separator)) 581 a = a.substring(File.separator.length()); 582 583 while (a.startsWith(".."+File.separator)) { 584 String p = s.toString().substring(0, s.length()-1); 585 if (!p.contains(File.separator)) { 586 s = new StringBuilder(); 587 } else { 588 s = new StringBuilder(p.substring(0, p.lastIndexOf(File.separator))+File.separator); 589 } 590 a = a.substring(3); 591 } 592 if ("..".equals(a)) { 593 int i = s.substring(0, s.length()-1).lastIndexOf(File.separator); 594 s = new StringBuilder(s.substring(0, i+1)); 595 } else 596 s.append(a); 597 } 598 return s.toString(); 599 } 600 601 private static boolean hasCTempDir() { 602 File tmp = new File("c:\\temp"); 603 return tmp.exists() && tmp.isDirectory(); 604 } 605 606 public static String pathURL(String... args) { 607 StringBuilder s = new StringBuilder(); 608 boolean d = false; 609 for(String arg: args) { 610 if (!d) 611 d = !noString(arg); 612 else if (!s.toString().endsWith("/") && !arg.startsWith("/")) 613 s.append("/"); 614 s.append(arg); 615 } 616 return s.toString(); 617 } 618 619 620 621// public static void checkCase(String filename) { 622// File f = new CSFile(filename); 623// if (!f.getName().equals(filename)) 624// throw new FHIRException("Filename ") 625// 626// } 627 628 public static String nmtokenize(String cs) { 629 if (cs == null) 630 return ""; 631 StringBuilder s = new StringBuilder(); 632 for (int i = 0; i < cs.length(); i++) { 633 char c = cs.charAt(i); 634 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_') 635 s.append(c); 636 else if (c != ' ') 637 s.append("."+Integer.toString(c)); 638 } 639 return s.toString(); 640 } 641 642 643 public static boolean isToken(String tail) { 644 if (tail == null || tail.length() == 0) 645 return false; 646 boolean result = isAlphabetic(tail.charAt(0)); 647 for (int i = 1; i < tail.length(); i++) { 648 result = result && (isAlphabetic(tail.charAt(i)) || isDigit(tail.charAt(i)) || (tail.charAt(i) == '_') || (tail.charAt(i) == '[') || (tail.charAt(i) == ']')); 649 } 650 return result; 651 } 652 653 654 private static boolean isDigit(char c) { 655 return (c >= '0') && (c <= '9'); 656 } 657 658 659 private static boolean isAlphabetic(char c) { 660 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 661 } 662 663 664 public static String getDirectoryForFile(String filepath) { 665 File f = new File(filepath); 666 return f.getParent(); 667 } 668 669 public static String appendPeriod(String s) { 670 if (Utilities.noString(s)) 671 return s; 672 s = s.trim(); 673 if (s.endsWith(".") || s.endsWith("?")) 674 return s; 675 return s+"."; 676 } 677 678 679 public static String removePeriod(String s) { 680 if (Utilities.noString(s)) 681 return s; 682 if (s.endsWith(".")) 683 return s.substring(0, s.length()-1); 684 return s; 685 } 686 687 688 public static String stripBOM(String string) { 689 return string.replace("\uFEFF", ""); 690 } 691 692 693 public static String oidTail(String id) { 694 if (id == null || !id.contains(".")) 695 return id; 696 return id.substring(id.lastIndexOf(".")+1); 697 } 698 699 700 public static String oidRoot(String id) { 701 if (id == null || !id.contains(".")) 702 return id; 703 return id.substring(0, id.indexOf(".")); 704 } 705 706 public static String escapeJava(String doco) { 707 if (doco == null) 708 return ""; 709 710 StringBuilder b = new StringBuilder(); 711 for (char c : doco.toCharArray()) { 712 if (c == '\r') 713 b.append("\\r"); 714 else if (c == '\n') 715 b.append("\\n"); 716 else if (c == '"') 717 b.append("\\\""); 718 else if (c == '\\') 719 b.append("\\\\"); 720 else 721 b.append(c); 722 } 723 return b.toString(); 724 } 725 726 727 public static String[] splitByCamelCase(String name) { 728 List<String> parts = new ArrayList<String>(); 729 StringBuilder b = new StringBuilder(); 730 for (int i = 0; i < name.length(); i++) { 731 if (i > 0 && Character.isUpperCase(name.charAt(i))) { 732 parts.add(b.toString()); 733 b = new StringBuilder(); 734 } 735 b.append(Character.toLowerCase(name.charAt(i))); 736 } 737 parts.add(b.toString()); 738 return parts.toArray(new String[] {} ); 739 } 740 741 742 public static String encodeUri(String v) { 743 return v.replace(" ", "%20").replace("?", "%3F").replace("=", "%3D"); 744 } 745 746 747 748 public static String normalize(String s) { 749 if (noString(s)) 750 return null; 751 StringBuilder b = new StringBuilder(); 752 boolean isWhitespace = false; 753 for (int i = 0; i < s.length(); i++) { 754 char c = s.charAt(i); 755 if (!Character.isWhitespace(c)) { 756 b.append(Character.toLowerCase(c)); 757 isWhitespace = false; 758 } else if (!isWhitespace) { 759 b.append(' '); 760 isWhitespace = true; 761 } 762 } 763 return b.toString().trim(); 764 } 765 766 public static String normalizeSameCase(String s) { 767 if (noString(s)) 768 return null; 769 StringBuilder b = new StringBuilder(); 770 boolean isWhitespace = false; 771 for (int i = 0; i < s.length(); i++) { 772 char c = s.charAt(i); 773 if (!Character.isWhitespace(c)) { 774 b.append(c); 775 isWhitespace = false; 776 } else if (!isWhitespace) { 777 b.append(' '); 778 isWhitespace = true; 779 } 780 } 781 return b.toString().trim(); 782 } 783 784 785 public static void copyFileToDirectory(File source, File destDir) throws IOException { 786 copyFile(source, new File(path(destDir.getAbsolutePath(), source.getName()))); 787 } 788 789 790 public static boolean isWhitespace(String s) { 791 boolean ok = true; 792 for (int i = 0; i < s.length(); i++) 793 ok = ok && Character.isWhitespace(s.charAt(i)); 794 return ok; 795 796 } 797 798 799 public static String URLEncode(String string) { 800 try { 801 return URLEncoder.encode(string, "UTF-8"); 802 } catch (UnsupportedEncodingException e) { 803 throw new Error(e.getMessage()); 804 } 805 } 806 807 808 public static boolean charInSet(char value, char... array) { 809 for (int i : array) 810 if (value == i) 811 return true; 812 return false; 813 } 814 815 816 public static boolean charInRange(char ch, char a, char z) { 817 return ch >= a && ch <= z; 818 } 819 820 public static boolean existsInList(String value, String... array) { 821 if (value == null) 822 return false; 823 for (String s : array) 824 if (value.equals(s)) 825 return true; 826 return false; 827 } 828 829 public static boolean existsInList(int value, int... array) { 830 for (int i : array) 831 if (value == i) 832 return true; 833 return false; 834 } 835 836 public static boolean existsInListNC(String value, String... array) { 837 for (String s : array) 838 if (value.equalsIgnoreCase(s)) 839 return true; 840 return false; 841 } 842 843 844 public static String getFileNameForName(String name) { 845 return name.toLowerCase(); 846 } 847 848 public static void deleteTempFiles() throws IOException { 849 File file = createTempFile("test", "test"); 850 String folder = getDirectoryForFile(file.getAbsolutePath()); 851 String[] list = new File(folder).list(new FilenameFilter() { 852 public boolean accept(File dir, String name) { 853 return name.startsWith("ohfu-"); 854 } 855 }); 856 if (list != null) { 857 for (String n : list) { 858 new File(path(folder, n)).delete(); 859 } 860 } 861 } 862 863 public static File createTempFile(String prefix, String suffix) throws IOException { 864 // this allows use to eaily identify all our dtemp files and delete them, since delete on Exit doesn't really work. 865 File file = File.createTempFile("ohfu-"+prefix, suffix); 866 file.deleteOnExit(); 867 return file; 868 } 869 870 871 public static boolean isAsciiChar(char ch) { 872 return ch >= ' ' && ch <= '~'; 873 } 874 875 876 public static String makeUuidLC() { 877 return UUID.randomUUID().toString().toLowerCase(); 878 } 879 880 public static String makeUuidUrn() { 881 return "urn:uuid:"+UUID.randomUUID().toString().toLowerCase(); 882 } 883 884 public static boolean isURL(String s) { 885 boolean ok = s.matches("^http(s{0,1})://[a-zA-Z0-9_/\\-\\.]+\\.([A-Za-z/]{2,5})[a-zA-Z0-9_/\\&\\?\\=\\-\\.\\~\\%]*"); 886 return ok; 887 } 888 889 890 public static String escapeJson(String value) { 891 if (value == null) 892 return ""; 893 894 StringBuilder b = new StringBuilder(); 895 for (char c : value.toCharArray()) { 896 if (c == '\r') 897 b.append("\\r"); 898 else if (c == '\n') 899 b.append("\\n"); 900 else if (c == '\t') 901 b.append("\\t"); 902 else if (c == '"') 903 b.append("\\\""); 904 else if (c == '\\') 905 b.append("\\\\"); 906 else if (((int) c) < 32) 907 b.append("\\u"+Utilities.padLeft(String.valueOf((int) c), '0', 4)); 908 else 909 b.append(c); 910 } 911 return b.toString(); 912 } 913 914 public static String humanize(String code) { 915 StringBuilder b = new StringBuilder(); 916 boolean lastBreak = true; 917 for (char c : code.toCharArray()) { 918 if (Character.isLetter(c)) { 919 if (lastBreak) 920 b.append(Character.toUpperCase(c)); 921 else { 922 if (Character.isUpperCase(c)) 923 b.append(" "); 924 b.append(c); 925 } 926 lastBreak = false; 927 } else { 928 b.append(" "); 929 lastBreak = true; 930 } 931 } 932 if (b.length() == 0) 933 return code; 934 else 935 return b.toString(); 936 } 937 938 939 public static String uncapitalize(String s) { 940 if( s == null ) return null; 941 if( s.length() == 0 ) return s; 942 if( s.length() == 1 ) return s.toLowerCase(); 943 944 return s.substring(0, 1).toLowerCase() + s.substring(1); 945 } 946 947 948 public static int charCount(String s, char c) { 949 int res = 0; 950 for (char ch : s.toCharArray()) 951 if (ch == c) 952 res++; 953 return res; 954 } 955 956 957 958 959 public static boolean isOid(String cc) { 960 return cc.matches(OID_REGEX) && cc.lastIndexOf('.') >= 5; 961 } 962 963 964 public static boolean equals(String one, String two) { 965 if (one == null && two == null) 966 return true; 967 if (one == null || two == null) 968 return false; 969 return one.equals(two); 970 } 971 972 973 public static void deleteAllFiles(String folder, String type) { 974 File src = new File(folder); 975 String[] files = src.list(); 976 for (String f : files) { 977 if (new File(folder+File.separator+f).isDirectory()) { 978 deleteAllFiles(folder+File.separator+f, type); 979 } else if (f.endsWith(type)) { 980 new File(folder+File.separator+f).delete(); 981 } 982 } 983 984 } 985 986 public static boolean compareIgnoreWhitespace(File f1, File f2) throws IOException { 987 InputStream in1 = null; 988 InputStream in2 = null; 989 try { 990 in1 = new BufferedInputStream(new FileInputStream(f1)); 991 in2 = new BufferedInputStream(new FileInputStream(f2)); 992 993 int expectedByte = in1.read(); 994 while (expectedByte != -1) { 995 boolean w1 = isWhitespace(expectedByte); 996 if (w1) 997 while (isWhitespace(expectedByte)) 998 expectedByte = in1.read(); 999 int foundByte = in2.read(); 1000 if (w1) { 1001 if (!isWhitespace(foundByte)) 1002 return false; 1003 while (isWhitespace(foundByte)) 1004 foundByte = in2.read(); 1005 } 1006 if (expectedByte != foundByte) 1007 return false; 1008 expectedByte = in1.read(); 1009 } 1010 if (in2.read() != -1) { 1011 return false; 1012 } 1013 return true; 1014 } finally { 1015 if (in1 != null) { 1016 try { 1017 in1.close(); 1018 } catch (IOException e) {} 1019 } 1020 if (in2 != null) { 1021 try { 1022 in2.close(); 1023 } catch (IOException e) {} 1024 } 1025 } 1026 } 1027 1028 private static boolean isWhitespace(int b) { 1029 return b == 9 || b == 10 || b == 13 || b == 32; 1030 } 1031 1032 1033 public static boolean compareIgnoreWhitespace(String fn1, String fn2) throws IOException { 1034 return compareIgnoreWhitespace(new File(fn1), new File(fn2)); 1035 } 1036 1037 1038 public static boolean isAbsoluteUrl(String ref) { 1039 return ref != null && (ref.startsWith("http:") || ref.startsWith("https:") || ref.startsWith("urn:uuid:") || ref.startsWith("urn:oid:")) ; 1040 } 1041 1042 1043 public static boolean equivalent(String l, String r) { 1044 if (Utilities.noString(l) && Utilities.noString(r)) 1045 return true; 1046 if (Utilities.noString(l) || Utilities.noString(r)) 1047 return false; 1048 return l.toLowerCase().equals(r.toLowerCase()); 1049 } 1050 1051 1052 public static boolean equivalentNumber(String l, String r) { 1053 if (Utilities.noString(l) && Utilities.noString(r)) 1054 return true; 1055 if (Utilities.noString(l) || Utilities.noString(r)) 1056 return false; 1057 l = l.toLowerCase().trim(); 1058 r = r.toLowerCase().trim(); // not that this should make any difference 1059 return l.startsWith(r) || r.startsWith(l); 1060 } 1061 1062 1063 public static String getFileExtension(String fn) { 1064 return fn.contains(".") ? fn.substring(fn.lastIndexOf(".")+1) : ""; 1065 } 1066 1067 1068 public static String unCamelCase(String name) { 1069 StringBuilder b = new StringBuilder(); 1070 boolean first = true; 1071 for (char c : name.toCharArray()) { 1072 if (Character.isUpperCase(c)) { 1073 if (!first) 1074 b.append(" "); 1075 b.append(Character.toLowerCase(c)); 1076 } else 1077 b.append(c); 1078 first = false; 1079 } 1080 return b.toString(); 1081 } 1082 1083 1084 public static boolean isAbsoluteFileName(String source) { 1085 if (isWindows()) 1086 return (source.length() > 2 && source.charAt(1) == ':') || source.startsWith("\\\\"); 1087 else 1088 return source.startsWith("//"); 1089 } 1090 1091 1092 public static boolean isWindows() { 1093 return System.getProperty("os.name").startsWith("Windows"); 1094 } 1095 1096 1097 public static String splitLineForLength(String line, int prefixLength, int indent, int allowedLength) { 1098 List<String> list = new ArrayList<String>(); 1099 while (prefixLength + line.length() > allowedLength) { 1100 int i = allowedLength - (list.size() == 0 ? prefixLength : indent); 1101 while (i > 0 && line.charAt(i) != ' ') 1102 i--; 1103 if (i == 0) 1104 break; 1105 list.add(line.substring(0, i)); 1106 line = line.substring(i+1); 1107 } 1108 list.add(line); 1109 StringBuilder b = new StringBuilder(); 1110 boolean first = true; 1111 for (String s : list) { 1112 if (first) 1113 first = false; 1114 else 1115 b.append("\r\n"+padLeft("", ' ', indent)); 1116 b.append(s); 1117 } 1118 return b.toString(); 1119 } 1120 1121 1122 public static int countFilesInDirectory(String dirName) { 1123 File dir = new File(dirName); 1124 if (dir.exists() == false) { 1125 return 0; 1126 } 1127 int i = 0; 1128 for (File f : dir.listFiles()) 1129 if (!f.isDirectory()) 1130 i++; 1131 return i; 1132 } 1133 1134 public static String makeId(String name) { 1135 StringBuilder b = new StringBuilder(); 1136 for (char ch : name.toCharArray()) { 1137 if (ch >= 'a' && ch <= 'z') 1138 b.append(ch); 1139 else if (ch >= 'A' && ch <= 'Z') 1140 b.append(ch); 1141 else if (ch >= '0' && ch <= '9') 1142 b.append(ch); 1143 else if (ch == '-' || ch == '.') 1144 b.append(ch); 1145 } 1146 return b.toString(); 1147 } 1148 1149 public interface FileVisitor { 1150 void visitFile(File file) throws FileNotFoundException, IOException; 1151 } 1152 1153 public static void visitFiles(String folder, String extension, FileVisitor visitor) throws FileNotFoundException, IOException { 1154 visitFiles(new File(folder), extension, visitor); 1155 } 1156 1157 public static void visitFiles(File folder, String extension, FileVisitor visitor) throws FileNotFoundException, IOException { 1158 for (File file : folder.listFiles()) { 1159 if (file.isDirectory()) 1160 visitFiles(file, extension, visitor); 1161 else if (extension == null || file.getName().endsWith(extension)) 1162 visitor.visitFile(file); 1163 } 1164 } 1165 1166 public static String extractBaseUrl(String url) { 1167 if (url == null) 1168 return null; 1169 else if (url.contains("/")) 1170 return url.substring(0, url.lastIndexOf("/")); 1171 else 1172 return url; 1173 } 1174 1175 public static String listCanonicalUrls(Set<String> keys) { 1176 return keys.toString(); 1177 } 1178 1179 public static boolean isValidId(String id) { 1180 return id.matches("[A-Za-z0-9\\-\\.]{1,64}"); 1181 } 1182 1183 public static List<String> sorted(Set<String> set) { 1184 List<String> list = new ArrayList<>(); 1185 list.addAll(set); 1186 Collections.sort(list); 1187 return list; 1188 } 1189 1190 public static void analyseStringDiffs(Set<String> source, Set<String> target, Set<String> missed, Set<String> extra) { 1191 for (String s : source) 1192 if (!target.contains(s)) 1193 missed.add(s); 1194 for (String s : target) 1195 if (!source.contains(s)) 1196 extra.add(s); 1197 1198 } 1199 1200 /** 1201 * Only handles simple FHIRPath expressions of the type produced by the validator 1202 * 1203 * @param path 1204 * @return 1205 */ 1206 public static String fhirPathToXPath(String path) { 1207 String[] p = path.split("\\."); 1208 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 1209 int i = 0; 1210 while (i < p.length) { 1211 String s = p[i]; 1212 if (s.contains("[")) { 1213 String si = s.substring(s.indexOf("[")+1, s.length()-1); 1214 if (!Utilities.isInteger(si)) 1215 throw new FHIRException("The FHIRPath expression '"+path+"' is not valid"); 1216 s = s.substring(0, s.indexOf("["))+"["+Integer.toString(Integer.parseInt(si)+1)+"]"; 1217 } 1218 if (i < p.length - 1 && p[i+1].startsWith(".ofType(")) { 1219 i++; 1220 s = s + capitalize(p[i].substring(8, p.length-1)); 1221 } 1222 b.append(s); 1223 i++; 1224 } 1225 return b.toString(); 1226 } 1227 1228 public static String describeDuration(Duration d) { 1229 if (d.toDays() > 2) { 1230 return String.format("%s days", d.toDays()); 1231 } else if (d.toHours() > 2) { 1232 return String.format("%s hours", d.toHours()); 1233 } else if (d.toMinutes() > 2) { 1234 return String.format("%s mins", d.toMinutes()); 1235 } else { 1236 return String.format("%s ms", d.toMillis()); 1237 } 1238 } 1239 1240 1241 1242}