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(&quot;post&quot;)               #=&gt; &quot;posts&quot;
095     *   inflector.pluralize(&quot;octopus&quot;)            #=&gt; &quot;octopi&quot;
096     *   inflector.pluralize(&quot;sheep&quot;)              #=&gt; &quot;sheep&quot;
097     *   inflector.pluralize(&quot;words&quot;)              #=&gt; &quot;words&quot;
098     *   inflector.pluralize(&quot;the blue mailman&quot;)   #=&gt; &quot;the blue mailmen&quot;
099     *   inflector.pluralize(&quot;CamelOctopus&quot;)       #=&gt; &quot;CamelOctopi&quot;
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("&lt;");
280                  else if (c == '>')
281                          b.append("&gt;");
282                  else if (c == '&')
283                          b.append("&amp;");
284      else if (c == '"')
285        b.append("&quot;");
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}