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

Last change on this file since 6889 was 6889, checked in by Don-vip, 5 years ago

fix some Sonar issues (JLS order)

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