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

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

Sonar - fix various issues

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