001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.Color; 008import java.awt.Toolkit; 009import java.awt.datatransfer.Clipboard; 010import java.awt.datatransfer.ClipboardOwner; 011import java.awt.datatransfer.DataFlavor; 012import java.awt.datatransfer.StringSelection; 013import java.awt.datatransfer.Transferable; 014import java.awt.datatransfer.UnsupportedFlavorException; 015import java.io.BufferedInputStream; 016import java.io.BufferedReader; 017import java.io.Closeable; 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.io.OutputStream; 025import java.io.UnsupportedEncodingException; 026import java.net.HttpURLConnection; 027import java.net.URL; 028import java.net.URLConnection; 029import java.nio.channels.FileChannel; 030import java.security.MessageDigest; 031import java.security.NoSuchAlgorithmException; 032import java.text.MessageFormat; 033import java.util.AbstractCollection; 034import java.util.AbstractList; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collection; 038import java.util.Iterator; 039import java.util.List; 040import java.util.zip.GZIPInputStream; 041import java.util.zip.ZipFile; 042 043import org.apache.tools.bzip2.CBZip2InputStream; 044import org.openstreetmap.josm.Main; 045import org.openstreetmap.josm.data.Version; 046import org.openstreetmap.josm.io.FileImporter; 047 048/** 049 * Basic utils, that can be useful in different parts of the program. 050 */ 051public final class Utils { 052 053 private Utils() { 054 // Hide default constructor for utils classes 055 } 056 057 public static <T> boolean exists(Iterable<? extends T> collection, Predicate<? super T> predicate) { 058 for (T item : collection) { 059 if (predicate.evaluate(item)) 060 return true; 061 } 062 return false; 063 } 064 065 public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) { 066 for (Object item : collection) { 067 if (klass.isInstance(item)) 068 return true; 069 } 070 return false; 071 } 072 073 public static <T> T find(Iterable<? extends T> collection, Predicate<? super T> predicate) { 074 for (T item : collection) { 075 if (predicate.evaluate(item)) 076 return item; 077 } 078 return null; 079 } 080 081 @SuppressWarnings("unchecked") 082 public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) { 083 for (Object item : collection) { 084 if (klass.isInstance(item)) 085 return (T) item; 086 } 087 return null; 088 } 089 090 public static <T> Collection<T> filter(Collection<? extends T> collection, Predicate<? super T> predicate) { 091 return new FilteredCollection<T>(collection, predicate); 092 } 093 094 public static <T> T firstNonNull(T... items) { 095 for (T i : items) { 096 if (i != null) { 097 return i; 098 } 099 } 100 return null; 101 } 102 103 /** 104 * Filter a collection by (sub)class. 105 * This is an efficient read-only implementation. 106 */ 107 public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> klass) { 108 return new SubclassFilteredCollection<S, T>(collection, new Predicate<S>() { 109 @Override 110 public boolean evaluate(S o) { 111 return klass.isInstance(o); 112 } 113 }); 114 } 115 116 public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) { 117 int i = 0; 118 for (T item : collection) { 119 if (predicate.evaluate(item)) 120 return i; 121 i++; 122 } 123 return -1; 124 } 125 126 /** 127 * Get minimum of 3 values 128 */ 129 public static int min(int a, int b, int c) { 130 if (b < c) { 131 if (a < b) 132 return a; 133 return b; 134 } else { 135 if (a < c) 136 return a; 137 return c; 138 } 139 } 140 141 public static int max(int a, int b, int c, int d) { 142 return Math.max(Math.max(a, b), Math.max(c, d)); 143 } 144 145 /** 146 * for convenience: test whether 2 objects are either both null or a.equals(b) 147 */ 148 public static <T> boolean equal(T a, T b) { 149 if (a == b) 150 return true; 151 return (a != null && a.equals(b)); 152 } 153 154 public static void ensure(boolean condition, String message, Object...data) { 155 if (!condition) 156 throw new AssertionError( 157 MessageFormat.format(message,data) 158 ); 159 } 160 161 /** 162 * return the modulus in the range [0, n) 163 */ 164 public static int mod(int a, int n) { 165 if (n <= 0) 166 throw new IllegalArgumentException(); 167 int res = a % n; 168 if (res < 0) { 169 res += n; 170 } 171 return res; 172 } 173 174 /** 175 * Joins a list of strings (or objects that can be converted to string via 176 * Object.toString()) into a single string with fields separated by sep. 177 * @param sep the separator 178 * @param values collection of objects, null is converted to the 179 * empty string 180 * @return null if values is null. The joined string otherwise. 181 */ 182 public static String join(String sep, Collection<?> values) { 183 if (sep == null) 184 throw new IllegalArgumentException(); 185 if (values == null) 186 return null; 187 if (values.isEmpty()) 188 return ""; 189 StringBuilder s = null; 190 for (Object a : values) { 191 if (a == null) { 192 a = ""; 193 } 194 if (s != null) { 195 s.append(sep).append(a.toString()); 196 } else { 197 s = new StringBuilder(a.toString()); 198 } 199 } 200 return s.toString(); 201 } 202 203 public static String joinAsHtmlUnorderedList(Collection<?> values) { 204 StringBuilder sb = new StringBuilder(1024); 205 sb.append("<ul>"); 206 for (Object i : values) { 207 sb.append("<li>").append(i).append("</li>"); 208 } 209 sb.append("</ul>"); 210 return sb.toString(); 211 } 212 213 /** 214 * convert Color to String 215 * (Color.toString() omits alpha value) 216 */ 217 public static String toString(Color c) { 218 if (c == null) 219 return "null"; 220 if (c.getAlpha() == 255) 221 return String.format("#%06x", c.getRGB() & 0x00ffffff); 222 else 223 return String.format("#%06x(alpha=%d)", c.getRGB() & 0x00ffffff, c.getAlpha()); 224 } 225 226 /** 227 * convert float range 0 <= x <= 1 to integer range 0..255 228 * when dealing with colors and color alpha value 229 * @return null if val is null, the corresponding int if val is in the 230 * range 0...1. If val is outside that range, return 255 231 */ 232 public static Integer color_float2int(Float val) { 233 if (val == null) 234 return null; 235 if (val < 0 || val > 1) 236 return 255; 237 return (int) (255f * val + 0.5f); 238 } 239 240 /** 241 * convert back 242 */ 243 public static Float color_int2float(Integer val) { 244 if (val == null) 245 return null; 246 if (val < 0 || val > 255) 247 return 1f; 248 return ((float) val) / 255f; 249 } 250 251 public static Color complement(Color clr) { 252 return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha()); 253 } 254 255 /** 256 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe. 257 * @param array The array to copy 258 * @return A copy of the original array, or {@code null} if {@code array} is null 259 * @since 6221 260 */ 261 public static <T> T[] copyArray(T[] array) { 262 if (array != null) { 263 return Arrays.copyOf(array, array.length); 264 } 265 return null; 266 } 267 268 /** 269 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe. 270 * @param array The array to copy 271 * @return A copy of the original array, or {@code null} if {@code array} is null 272 * @since 6222 273 */ 274 public static char[] copyArray(char[] array) { 275 if (array != null) { 276 return Arrays.copyOf(array, array.length); 277 } 278 return null; 279 } 280 281 /** 282 * Simple file copy function that will overwrite the target file.<br/> 283 * Taken from <a href="http://www.rgagnon.com/javadetails/java-0064.html">this article</a> (CC-NC-BY-SA) 284 * @param in The source file 285 * @param out The destination file 286 * @throws IOException If any I/O error occurs 287 */ 288 public static void copyFile(File in, File out) throws IOException { 289 // TODO: remove this function when we move to Java 7 (use Files.copy instead) 290 FileInputStream inStream = null; 291 FileOutputStream outStream = null; 292 try { 293 inStream = new FileInputStream(in); 294 outStream = new FileOutputStream(out); 295 FileChannel inChannel = inStream.getChannel(); 296 inChannel.transferTo(0, inChannel.size(), outStream.getChannel()); 297 } 298 catch (IOException e) { 299 throw e; 300 } 301 finally { 302 close(outStream); 303 close(inStream); 304 } 305 } 306 307 public static int copyStream(InputStream source, OutputStream destination) throws IOException { 308 int count = 0; 309 byte[] b = new byte[512]; 310 int read; 311 while ((read = source.read(b)) != -1) { 312 count += read; 313 destination.write(b, 0, read); 314 } 315 return count; 316 } 317 318 public static boolean deleteDirectory(File path) { 319 if( path.exists() ) { 320 File[] files = path.listFiles(); 321 for (File file : files) { 322 if (file.isDirectory()) { 323 deleteDirectory(file); 324 } else { 325 file.delete(); 326 } 327 } 328 } 329 return( path.delete() ); 330 } 331 332 /** 333 * <p>Utility method for closing a {@link Closeable} object.</p> 334 * 335 * @param c the closeable object. May be null. 336 */ 337 public static void close(Closeable c) { 338 if (c == null) return; 339 try { 340 c.close(); 341 } catch (IOException e) { 342 Main.warn(e); 343 } 344 } 345 346 /** 347 * <p>Utility method for closing a {@link ZipFile}.</p> 348 * 349 * @param zip the zip file. May be null. 350 */ 351 public static void close(ZipFile zip) { 352 if (zip == null) return; 353 try { 354 zip.close(); 355 } catch (IOException e) { 356 Main.warn(e); 357 } 358 } 359 360 private final static double EPSILON = 1e-11; 361 362 /** 363 * Determines if the two given double values are equal (their delta being smaller than a fixed epsilon) 364 * @param a The first double value to compare 365 * @param b The second double value to compare 366 * @return {@code true} if {@code abs(a - b) <= 1e-11}, {@code false} otherwise 367 */ 368 public static boolean equalsEpsilon(double a, double b) { 369 return Math.abs(a - b) <= EPSILON; 370 } 371 372 /** 373 * Copies the string {@code s} to system clipboard. 374 * @param s string to be copied to clipboard. 375 * @return true if succeeded, false otherwise. 376 */ 377 public static boolean copyToClipboard(String s) { 378 try { 379 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() { 380 381 @Override 382 public void lostOwnership(Clipboard clpbrd, Transferable t) { 383 } 384 }); 385 return true; 386 } catch (IllegalStateException ex) { 387 ex.printStackTrace(); 388 return false; 389 } 390 } 391 392 /** 393 * Extracts clipboard content as string. 394 * @return string clipboard contents if available, {@code null} otherwise. 395 */ 396 public static String getClipboardContent() { 397 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 398 Transferable t = null; 399 for (int tries = 0; t == null && tries < 10; tries++) { 400 try { 401 t = clipboard.getContents(null); 402 } catch (IllegalStateException e) { 403 // Clipboard currently unavailable. On some platforms, the system clipboard is unavailable while it is accessed by another application. 404 try { 405 Thread.sleep(1); 406 } catch (InterruptedException ex) { 407 Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content"); 408 } 409 } 410 } 411 try { 412 if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) { 413 String text = (String) t.getTransferData(DataFlavor.stringFlavor); 414 return text; 415 } 416 } catch (UnsupportedFlavorException ex) { 417 ex.printStackTrace(); 418 return null; 419 } catch (IOException ex) { 420 ex.printStackTrace(); 421 return null; 422 } 423 return null; 424 } 425 426 /** 427 * Calculate MD5 hash of a string and output in hexadecimal format. 428 * @param data arbitrary String 429 * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f] 430 */ 431 public static String md5Hex(String data) { 432 byte[] byteData = null; 433 try { 434 byteData = data.getBytes("UTF-8"); 435 } catch (UnsupportedEncodingException e) { 436 throw new RuntimeException(); 437 } 438 MessageDigest md = null; 439 try { 440 md = MessageDigest.getInstance("MD5"); 441 } catch (NoSuchAlgorithmException e) { 442 throw new RuntimeException(); 443 } 444 byte[] byteDigest = md.digest(byteData); 445 return toHexString(byteDigest); 446 } 447 448 private static final char[] HEX_ARRAY = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; 449 450 /** 451 * Converts a byte array to a string of hexadecimal characters. 452 * Preserves leading zeros, so the size of the output string is always twice 453 * the number of input bytes. 454 * @param bytes the byte array 455 * @return hexadecimal representation 456 */ 457 public static String toHexString(byte[] bytes) { 458 459 if (bytes == null) { 460 return ""; 461 } 462 463 final int len = bytes.length; 464 if (len == 0) { 465 return ""; 466 } 467 468 char[] hexChars = new char[len * 2]; 469 for (int i = 0, j = 0; i < len; i++) { 470 final int v = bytes[i]; 471 hexChars[j++] = HEX_ARRAY[(v & 0xf0) >> 4]; 472 hexChars[j++] = HEX_ARRAY[v & 0xf]; 473 } 474 return new String(hexChars); 475 } 476 477 /** 478 * Topological sort. 479 * 480 * @param dependencies contains mappings (key -> value). In the final list of sorted objects, the key will come 481 * after the value. (In other words, the key depends on the value(s).) 482 * There must not be cyclic dependencies. 483 * @return the list of sorted objects 484 */ 485 public static <T> List<T> topologicalSort(final MultiMap<T,T> dependencies) { 486 MultiMap<T,T> deps = new MultiMap<T,T>(); 487 for (T key : dependencies.keySet()) { 488 deps.putVoid(key); 489 for (T val : dependencies.get(key)) { 490 deps.putVoid(val); 491 deps.put(key, val); 492 } 493 } 494 495 int size = deps.size(); 496 List<T> sorted = new ArrayList<T>(); 497 for (int i=0; i<size; ++i) { 498 T parentless = null; 499 for (T key : deps.keySet()) { 500 if (deps.get(key).isEmpty()) { 501 parentless = key; 502 break; 503 } 504 } 505 if (parentless == null) throw new RuntimeException(); 506 sorted.add(parentless); 507 deps.remove(parentless); 508 for (T key : deps.keySet()) { 509 deps.remove(key, parentless); 510 } 511 } 512 if (sorted.size() != size) throw new RuntimeException(); 513 return sorted; 514 } 515 516 /** 517 * Represents a function that can be applied to objects of {@code A} and 518 * returns objects of {@code B}. 519 * @param <A> class of input objects 520 * @param <B> class of transformed objects 521 */ 522 public static interface Function<A, B> { 523 524 /** 525 * Applies the function on {@code x}. 526 * @param x an object of 527 * @return the transformed object 528 */ 529 B apply(A x); 530 } 531 532 /** 533 * Transforms the collection {@code c} into an unmodifiable collection and 534 * applies the {@link Function} {@code f} on each element upon access. 535 * @param <A> class of input collection 536 * @param <B> class of transformed collection 537 * @param c a collection 538 * @param f a function that transforms objects of {@code A} to objects of {@code B} 539 * @return the transformed unmodifiable collection 540 */ 541 public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) { 542 return new AbstractCollection<B>() { 543 544 @Override 545 public int size() { 546 return c.size(); 547 } 548 549 @Override 550 public Iterator<B> iterator() { 551 return new Iterator<B>() { 552 553 private Iterator<? extends A> it = c.iterator(); 554 555 @Override 556 public boolean hasNext() { 557 return it.hasNext(); 558 } 559 560 @Override 561 public B next() { 562 return f.apply(it.next()); 563 } 564 565 @Override 566 public void remove() { 567 throw new UnsupportedOperationException(); 568 } 569 }; 570 } 571 }; 572 } 573 574 /** 575 * Transforms the list {@code l} into an unmodifiable list and 576 * applies the {@link Function} {@code f} on each element upon access. 577 * @param <A> class of input collection 578 * @param <B> class of transformed collection 579 * @param l a collection 580 * @param f a function that transforms objects of {@code A} to objects of {@code B} 581 * @return the transformed unmodifiable list 582 */ 583 public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) { 584 return new AbstractList<B>() { 585 586 587 @Override 588 public int size() { 589 return l.size(); 590 } 591 592 @Override 593 public B get(int index) { 594 return f.apply(l.get(index)); 595 } 596 597 598 }; 599 } 600 601 /** 602 * Convert Hex String to Color. 603 * @param s Must be of the form "#34a300" or "#3f2", otherwise throws Exception. 604 * Upper/lower case does not matter. 605 * @return The corresponding color. 606 */ 607 static public Color hexToColor(String s) { 608 String clr = s.substring(1); 609 if (clr.length() == 3) { 610 clr = new String(new char[] { 611 clr.charAt(0), clr.charAt(0), clr.charAt(1), clr.charAt(1), clr.charAt(2), clr.charAt(2) 612 }); 613 } 614 if (clr.length() != 6) 615 throw new IllegalArgumentException(); 616 return new Color(Integer.parseInt(clr, 16)); 617 } 618 619 /** 620 * Opens a HTTP connection to the given URL and sets the User-Agent property to JOSM's one. 621 * @param httpURL The HTTP url to open (must use http:// or https://) 622 * @return An open HTTP connection to the given URL 623 * @throws IOException if an I/O exception occurs. 624 * @since 5587 625 */ 626 public static HttpURLConnection openHttpConnection(URL httpURL) throws IOException { 627 if (httpURL == null || !httpURL.getProtocol().matches("https?")) { 628 throw new IllegalArgumentException("Invalid HTTP url"); 629 } 630 HttpURLConnection connection = (HttpURLConnection) httpURL.openConnection(); 631 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString()); 632 connection.setUseCaches(false); 633 return connection; 634 } 635 636 /** 637 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one. 638 * @param url The url to open 639 * @return An stream for the given URL 640 * @throws IOException if an I/O exception occurs. 641 * @since 5867 642 */ 643 public static InputStream openURL(URL url) throws IOException { 644 return openURLAndDecompress(url, false); 645 } 646 647 /** 648 * Opens a connection to the given URL, sets the User-Agent property to JOSM's one, and decompresses stream if necessary. 649 * @param url The url to open 650 * @param decompress whether to wrap steam in a {@link GZIPInputStream} or {@link CBZip2InputStream} 651 * if the {@code Content-Type} header is set accordingly. 652 * @return An stream for the given URL 653 * @throws IOException if an I/O exception occurs. 654 * @since 6421 655 */ 656 public static InputStream openURLAndDecompress(final URL url, final boolean decompress) throws IOException { 657 final URLConnection connection = setupURLConnection(url.openConnection()); 658 if (decompress && "application/x-gzip".equals(connection.getHeaderField("Content-Type"))) { 659 return new GZIPInputStream(connection.getInputStream()); 660 } else if (decompress && "application/x-bzip2".equals(connection.getHeaderField("Content-Type"))) { 661 return FileImporter.getBZip2InputStream(new BufferedInputStream(connection.getInputStream())); 662 } else { 663 return connection.getInputStream(); 664 } 665 } 666 667 /*** 668 * Setups the given URL connection to match JOSM needs by setting its User-Agent and timeout properties. 669 * @param connection The connection to setup 670 * @return {@code connection}, with updated properties 671 * @since 5887 672 */ 673 public static URLConnection setupURLConnection(URLConnection connection) { 674 if (connection != null) { 675 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString()); 676 connection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000); 677 connection.setReadTimeout(Main.pref.getInteger("socket.timeout.read",30)*1000); 678 } 679 return connection; 680 } 681 682 /** 683 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one. 684 * @param url The url to open 685 * @return An buffered stream reader for the given URL (using UTF-8) 686 * @throws IOException if an I/O exception occurs. 687 * @since 5868 688 */ 689 public static BufferedReader openURLReader(URL url) throws IOException { 690 return openURLReaderAndDecompress(url, false); 691 } 692 693 /** 694 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one. 695 * @param url The url to open 696 * @param decompress whether to wrap steam in a {@link GZIPInputStream} or {@link CBZip2InputStream} 697 * if the {@code Content-Type} header is set accordingly. 698 * @return An buffered stream reader for the given URL (using UTF-8) 699 * @throws IOException if an I/O exception occurs. 700 * @since 6421 701 */ 702 public static BufferedReader openURLReaderAndDecompress(final URL url, final boolean decompress) throws IOException { 703 return new BufferedReader(new InputStreamReader(openURLAndDecompress(url, decompress), "utf-8")); 704 } 705 706 /** 707 * Opens a HTTP connection to the given URL, sets the User-Agent property to JOSM's one and optionnaly disables Keep-Alive. 708 * @param httpURL The HTTP url to open (must use http:// or https://) 709 * @param keepAlive whether not to set header {@code Connection=close} 710 * @return An open HTTP connection to the given URL 711 * @throws IOException if an I/O exception occurs. 712 * @since 5587 713 */ 714 public static HttpURLConnection openHttpConnection(URL httpURL, boolean keepAlive) throws IOException { 715 HttpURLConnection connection = openHttpConnection(httpURL); 716 if (!keepAlive) { 717 connection.setRequestProperty("Connection", "close"); 718 } 719 return connection; 720 } 721 722 /** 723 * An alternative to {@link String#trim()} to effectively remove all leading and trailing white characters, including Unicode ones. 724 * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java’s String.trim has a strange idea of whitespace</a> 725 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4080617">JDK bug 4080617</a> 726 * @param str The string to strip 727 * @return <code>str</code>, without leading and trailing characters, according to 728 * {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}. 729 * @since 5772 730 */ 731 public static String strip(String str) { 732 if (str == null || str.isEmpty()) { 733 return str; 734 } 735 int start = 0, end = str.length(); 736 boolean leadingWhite = true; 737 while (leadingWhite && start < end) { 738 char c = str.charAt(start); 739 // '\u200B' (ZERO WIDTH SPACE character) needs to be handled manually because of change in Unicode 6.0 (Java 7, see #8918) 740 // same for '\uFEFF' (ZERO WIDTH NO-BREAK SPACE) 741 leadingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B' || c == '\uFEFF'); 742 if (leadingWhite) { 743 start++; 744 } 745 } 746 boolean trailingWhite = true; 747 while (trailingWhite && end > start+1) { 748 char c = str.charAt(end-1); 749 trailingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B' || c == '\uFEFF'); 750 if (trailingWhite) { 751 end--; 752 } 753 } 754 return str.substring(start, end); 755 } 756 757 /** 758 * Runs an external command and returns the standard output. 759 * 760 * The program is expected to execute fast. 761 * 762 * @param command the command with arguments 763 * @return the output 764 * @throws IOException when there was an error, e.g. command does not exist 765 */ 766 public static String execOutput(List<String> command) throws IOException { 767 Process p = new ProcessBuilder(command).start(); 768 BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); 769 StringBuilder all = null; 770 String line; 771 while ((line = input.readLine()) != null) { 772 if (all == null) { 773 all = new StringBuilder(line); 774 } else { 775 all.append("\n"); 776 all.append(line); 777 } 778 } 779 Utils.close(input); 780 return all.toString(); 781 } 782 783 /** 784 * Returns the JOSM temp directory. 785 * @return The JOSM temp directory ({@code <java.io.tmpdir>/JOSM}), or {@code null} if {@code java.io.tmpdir} is not defined 786 * @since 6245 787 */ 788 public static File getJosmTempDir() { 789 String tmpDir = System.getProperty("java.io.tmpdir"); 790 if (tmpDir == null) { 791 return null; 792 } 793 File josmTmpDir = new File(tmpDir, "JOSM"); 794 if (!josmTmpDir.exists()) { 795 if (!josmTmpDir.mkdirs()) { 796 Main.warn("Unable to create temp directory "+josmTmpDir); 797 } 798 } 799 return josmTmpDir; 800 } 801 802 /** 803 * Returns a simple human readable (hours, minutes, seconds) string for a given duration in milliseconds. 804 * @param elapsedTime The duration in milliseconds 805 * @return A human redable string for the given duration 806 * @throws IllegalArgumentException if elapsedTime is < 0 807 * @since 6354 808 */ 809 public static String getDurationString(long elapsedTime) throws IllegalArgumentException { 810 if (elapsedTime < 0) { 811 throw new IllegalArgumentException("elapsedTime must be > 0"); 812 } 813 // Is it less than 1 second ? 814 if (elapsedTime < 1000) { 815 return String.format("%d %s", elapsedTime, tr("ms")); 816 } 817 // Is it less than 1 minute ? 818 if (elapsedTime < 60*1000) { 819 return String.format("%.1f %s", elapsedTime/1000f, tr("s")); 820 } 821 // Is it less than 1 hour ? 822 if (elapsedTime < 60*60*1000) { 823 return String.format("%d %s %d %s", elapsedTime/60000, tr("min"), elapsedTime/1000, tr("s")); 824 } 825 // Is it less than 1 day ? 826 if (elapsedTime < 24*60*60*1000) { 827 return String.format("%d %s %d %s", elapsedTime/3600000, tr("h"), elapsedTime/60000, tr("min")); 828 } 829 long days = elapsedTime/86400000; 830 return String.format("%d %s %d %s", days, trn("day", "days", days), elapsedTime/3600000, tr("h")); 831 } 832}