source: josm/trunk/src/org/openstreetmap/josm/tools/Utils.java@ 6388

Last change on this file since 6388 was 6362, checked in by Don-vip, 10 years ago

Checkstyle:

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