001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006import static org.openstreetmap.josm.tools.I18n.trn;
007
008import java.awt.Color;
009import java.awt.Font;
010import java.awt.HeadlessException;
011import java.awt.Toolkit;
012import java.awt.datatransfer.Clipboard;
013import java.awt.datatransfer.ClipboardOwner;
014import java.awt.datatransfer.DataFlavor;
015import java.awt.datatransfer.StringSelection;
016import java.awt.datatransfer.Transferable;
017import java.awt.datatransfer.UnsupportedFlavorException;
018import java.awt.font.FontRenderContext;
019import java.awt.font.GlyphVector;
020import java.io.BufferedReader;
021import java.io.ByteArrayOutputStream;
022import java.io.Closeable;
023import java.io.File;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.InputStreamReader;
027import java.io.UnsupportedEncodingException;
028import java.net.MalformedURLException;
029import java.net.URL;
030import java.net.URLDecoder;
031import java.net.URLEncoder;
032import java.nio.charset.StandardCharsets;
033import java.nio.file.Files;
034import java.nio.file.Path;
035import java.nio.file.StandardCopyOption;
036import java.security.MessageDigest;
037import java.security.NoSuchAlgorithmException;
038import java.text.Bidi;
039import java.text.MessageFormat;
040import java.util.AbstractCollection;
041import java.util.AbstractList;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.Collections;
046import java.util.Iterator;
047import java.util.List;
048import java.util.Locale;
049import java.util.Objects;
050import java.util.concurrent.Executor;
051import java.util.concurrent.ForkJoinPool;
052import java.util.concurrent.ForkJoinWorkerThread;
053import java.util.concurrent.ThreadFactory;
054import java.util.concurrent.atomic.AtomicLong;
055import java.util.regex.Matcher;
056import java.util.regex.Pattern;
057import java.util.zip.GZIPInputStream;
058import java.util.zip.ZipEntry;
059import java.util.zip.ZipFile;
060import java.util.zip.ZipInputStream;
061
062import javax.xml.XMLConstants;
063import javax.xml.parsers.ParserConfigurationException;
064import javax.xml.parsers.SAXParser;
065import javax.xml.parsers.SAXParserFactory;
066
067import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
068import org.openstreetmap.josm.Main;
069import org.xml.sax.InputSource;
070import org.xml.sax.SAXException;
071import org.xml.sax.helpers.DefaultHandler;
072
073/**
074 * Basic utils, that can be useful in different parts of the program.
075 */
076public final class Utils {
077
078    /** Pattern matching white spaces */
079    public static final Pattern WHITE_SPACES_PATTERN = Pattern.compile("\\s+");
080
081    private Utils() {
082        // Hide default constructor for utils classes
083    }
084
085    private static final int MILLIS_OF_SECOND = 1000;
086    private static final int MILLIS_OF_MINUTE = 60000;
087    private static final int MILLIS_OF_HOUR = 3600000;
088    private static final int MILLIS_OF_DAY = 86400000;
089
090    public static final String URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%";
091
092    private static final char[] DEFAULT_STRIP = {'\u200B', '\uFEFF'};
093
094    private static final String[] SIZE_UNITS = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
095
096    /**
097     * Tests whether {@code predicate} applies to at least one element from {@code collection}.
098     * @param <T> type of items
099     * @param collection the collection
100     * @param predicate the predicate
101     * @return {@code true} if {@code predicate} applies to at least one element from {@code collection}
102     */
103    public static <T> boolean exists(Iterable<? extends T> collection, Predicate<? super T> predicate) {
104        for (T item : collection) {
105            if (predicate.evaluate(item))
106                return true;
107        }
108        return false;
109    }
110
111    /**
112     * Tests whether {@code predicate} applies to all elements from {@code collection}.
113     * @param <T> type of items
114     * @param collection the collection
115     * @param predicate the predicate
116     * @return {@code true} if {@code predicate} applies to all elements from {@code collection}
117     */
118    public static <T> boolean forAll(Iterable<? extends T> collection, Predicate<? super T> predicate) {
119        return !exists(collection, Predicates.not(predicate));
120    }
121
122    public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) {
123        for (Object item : collection) {
124            if (klass.isInstance(item))
125                return true;
126        }
127        return false;
128    }
129
130    public static <T> T find(Iterable<? extends T> collection, Predicate<? super T> predicate) {
131        for (T item : collection) {
132            if (predicate.evaluate(item))
133                return item;
134        }
135        return null;
136    }
137
138    @SuppressWarnings("unchecked")
139    public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) {
140        for (Object item : collection) {
141            if (klass.isInstance(item))
142                return (T) item;
143        }
144        return null;
145    }
146
147    @SuppressWarnings("unused")
148    public static <T> Collection<T> filter(Collection<? extends T> collection, Predicate<? super T> predicate) {
149        // Diamond operator does not work with Java 9 here
150        return new FilteredCollection<T>(collection, predicate);
151    }
152
153    /**
154     * Returns the first element from {@code items} which is non-null, or null if all elements are null.
155     * @param <T> type of items
156     * @param items the items to look for
157     * @return first non-null item if there is one
158     */
159    @SafeVarargs
160    public static <T> T firstNonNull(T... items) {
161        for (T i : items) {
162            if (i != null) {
163                return i;
164            }
165        }
166        return null;
167    }
168
169    /**
170     * Filter a collection by (sub)class.
171     * This is an efficient read-only implementation.
172     * @param <S> Super type of items
173     * @param <T> type of items
174     * @param collection the collection
175     * @param klass the (sub)class
176     * @return a read-only filtered collection
177     */
178    public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> klass) {
179        return new SubclassFilteredCollection<>(collection, new Predicate<S>() {
180            @Override
181            public boolean evaluate(S o) {
182                return klass.isInstance(o);
183            }
184        });
185    }
186
187    public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) {
188        int i = 0;
189        for (T item : collection) {
190            if (predicate.evaluate(item))
191                return i;
192            i++;
193        }
194        return -1;
195    }
196
197    /**
198     * Returns the minimum of three values.
199     * @param   a   an argument.
200     * @param   b   another argument.
201     * @param   c   another argument.
202     * @return  the smaller of {@code a}, {@code b} and {@code c}.
203     */
204    public static int min(int a, int b, int c) {
205        if (b < c) {
206            if (a < b)
207                return a;
208            return b;
209        } else {
210            if (a < c)
211                return a;
212            return c;
213        }
214    }
215
216    /**
217     * Returns the greater of four {@code int} values. That is, the
218     * result is the argument closer to the value of
219     * {@link Integer#MAX_VALUE}. If the arguments have the same value,
220     * the result is that same value.
221     *
222     * @param   a   an argument.
223     * @param   b   another argument.
224     * @param   c   another argument.
225     * @param   d   another argument.
226     * @return  the larger of {@code a}, {@code b}, {@code c} and {@code d}.
227     */
228    public static int max(int a, int b, int c, int d) {
229        return Math.max(Math.max(a, b), Math.max(c, d));
230    }
231
232    /**
233     * Ensures a logical condition is met. Otherwise throws an assertion error.
234     * @param condition the condition to be met
235     * @param message Formatted error message to raise if condition is not met
236     * @param data Message parameters, optional
237     * @throws AssertionError if the condition is not met
238     */
239    public static void ensure(boolean condition, String message, Object...data) {
240        if (!condition)
241            throw new AssertionError(
242                    MessageFormat.format(message, data)
243            );
244    }
245
246    /**
247     * Return the modulus in the range [0, n)
248     * @param a dividend
249     * @param n divisor
250     * @return modulo (remainder of the Euclidian division of a by n)
251     */
252    public static int mod(int a, int n) {
253        if (n <= 0)
254            throw new IllegalArgumentException("n must be <= 0 but is "+n);
255        int res = a % n;
256        if (res < 0) {
257            res += n;
258        }
259        return res;
260    }
261
262    /**
263     * Joins a list of strings (or objects that can be converted to string via
264     * Object.toString()) into a single string with fields separated by sep.
265     * @param sep the separator
266     * @param values collection of objects, null is converted to the
267     *  empty string
268     * @return null if values is null. The joined string otherwise.
269     */
270    public static String join(String sep, Collection<?> values) {
271        CheckParameterUtil.ensureParameterNotNull(sep, "sep");
272        if (values == null)
273            return null;
274        StringBuilder s = null;
275        for (Object a : values) {
276            if (a == null) {
277                a = "";
278            }
279            if (s != null) {
280                s.append(sep).append(a);
281            } else {
282                s = new StringBuilder(a.toString());
283            }
284        }
285        return s != null ? s.toString() : "";
286    }
287
288    /**
289     * Converts the given iterable collection as an unordered HTML list.
290     * @param values The iterable collection
291     * @return An unordered HTML list
292     */
293    public static String joinAsHtmlUnorderedList(Iterable<?> values) {
294        StringBuilder sb = new StringBuilder(1024);
295        sb.append("<ul>");
296        for (Object i : values) {
297            sb.append("<li>").append(i).append("</li>");
298        }
299        sb.append("</ul>");
300        return sb.toString();
301    }
302
303    /**
304     * convert Color to String
305     * (Color.toString() omits alpha value)
306     * @param c the color
307     * @return the String representation, including alpha
308     */
309    public static String toString(Color c) {
310        if (c == null)
311            return "null";
312        if (c.getAlpha() == 255)
313            return String.format("#%06x", c.getRGB() & 0x00ffffff);
314        else
315            return String.format("#%06x(alpha=%d)", c.getRGB() & 0x00ffffff, c.getAlpha());
316    }
317
318    /**
319     * convert float range 0 &lt;= x &lt;= 1 to integer range 0..255
320     * when dealing with colors and color alpha value
321     * @param val float value between 0 and 1
322     * @return null if val is null, the corresponding int if val is in the
323     *         range 0...1. If val is outside that range, return 255
324     */
325    public static Integer color_float2int(Float val) {
326        if (val == null)
327            return null;
328        if (val < 0 || val > 1)
329            return 255;
330        return (int) (255f * val + 0.5f);
331    }
332
333    /**
334     * convert integer range 0..255 to float range 0 &lt;= x &lt;= 1
335     * when dealing with colors and color alpha value
336     * @param val integer value
337     * @return corresponding float value in range 0 &lt;= x &lt;= 1
338     */
339    public static Float color_int2float(Integer val) {
340        if (val == null)
341            return null;
342        if (val < 0 || val > 255)
343            return 1f;
344        return ((float) val) / 255f;
345    }
346
347    /**
348     * Returns the complementary color of {@code clr}.
349     * @param clr the color to complement
350     * @return the complementary color of {@code clr}
351     */
352    public static Color complement(Color clr) {
353        return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
354    }
355
356    /**
357     * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
358     * @param <T> type of items
359     * @param array The array to copy
360     * @return A copy of the original array, or {@code null} if {@code array} is null
361     * @since 6221
362     */
363    public static <T> T[] copyArray(T[] array) {
364        if (array != null) {
365            return Arrays.copyOf(array, array.length);
366        }
367        return null;
368    }
369
370    /**
371     * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
372     * @param array The array to copy
373     * @return A copy of the original array, or {@code null} if {@code array} is null
374     * @since 6222
375     */
376    public static char[] copyArray(char[] array) {
377        if (array != null) {
378            return Arrays.copyOf(array, array.length);
379        }
380        return null;
381    }
382
383    /**
384     * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
385     * @param array The array to copy
386     * @return A copy of the original array, or {@code null} if {@code array} is null
387     * @since 7436
388     */
389    public static int[] copyArray(int[] array) {
390        if (array != null) {
391            return Arrays.copyOf(array, array.length);
392        }
393        return null;
394    }
395
396    /**
397     * Simple file copy function that will overwrite the target file.
398     * @param in The source file
399     * @param out The destination file
400     * @return the path to the target file
401     * @throws IOException if any I/O error occurs
402     * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
403     * @since 7003
404     */
405    public static Path copyFile(File in, File out) throws IOException {
406        CheckParameterUtil.ensureParameterNotNull(in, "in");
407        CheckParameterUtil.ensureParameterNotNull(out, "out");
408        return Files.copy(in.toPath(), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
409    }
410
411    /**
412     * Recursive directory copy function
413     * @param in The source directory
414     * @param out The destination directory
415     * @throws IOException if any I/O error ooccurs
416     * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
417     * @since 7835
418     */
419    public static void copyDirectory(File in, File out) throws IOException {
420        CheckParameterUtil.ensureParameterNotNull(in, "in");
421        CheckParameterUtil.ensureParameterNotNull(out, "out");
422        if (!out.exists() && !out.mkdirs()) {
423            Main.warn("Unable to create directory "+out.getPath());
424        }
425        File[] files = in.listFiles();
426        if (files != null) {
427            for (File f : files) {
428                File target = new File(out, f.getName());
429                if (f.isDirectory()) {
430                    copyDirectory(f, target);
431                } else {
432                    copyFile(f, target);
433                }
434            }
435        }
436    }
437
438    /**
439     * Deletes a directory recursively.
440     * @param path The directory to delete
441     * @return  <code>true</code> if and only if the file or directory is
442     *          successfully deleted; <code>false</code> otherwise
443     */
444    public static boolean deleteDirectory(File path) {
445        if (path.exists()) {
446            File[] files = path.listFiles();
447            if (files != null) {
448                for (File file : files) {
449                    if (file.isDirectory()) {
450                        deleteDirectory(file);
451                    } else {
452                        deleteFile(file);
453                    }
454                }
455            }
456        }
457        return path.delete();
458    }
459
460    /**
461     * Deletes a file and log a default warning if the deletion fails.
462     * @param file file to delete
463     * @return {@code true} if and only if the file is successfully deleted; {@code false} otherwise
464     * @since 9296
465     */
466    public static boolean deleteFile(File file) {
467        return deleteFile(file, marktr("Unable to delete file {0}"));
468    }
469
470    /**
471     * Deletes a file and log a configurable warning if the deletion fails.
472     * @param file file to delete
473     * @param warnMsg warning message. It will be translated with {@code tr()}
474     * and must contain a single parameter <code>{0}</code> for the file path
475     * @return {@code true} if and only if the file is successfully deleted; {@code false} otherwise
476     * @since 9296
477     */
478    public static boolean deleteFile(File file, String warnMsg) {
479        boolean result = file.delete();
480        if (!result) {
481            Main.warn(tr(warnMsg, file.getPath()));
482        }
483        return result;
484    }
485
486    /**
487     * Creates a directory and log a default warning if the creation fails.
488     * @param dir directory to create
489     * @return {@code true} if and only if the directory is successfully created; {@code false} otherwise
490     * @since 9645
491     */
492    public static boolean mkDirs(File dir) {
493        return mkDirs(dir, marktr("Unable to create directory {0}"));
494    }
495
496    /**
497     * Creates a directory and log a configurable warning if the creation fails.
498     * @param dir directory to create
499     * @param warnMsg warning message. It will be translated with {@code tr()}
500     * and must contain a single parameter <code>{0}</code> for the directory path
501     * @return {@code true} if and only if the directory is successfully created; {@code false} otherwise
502     * @since 9645
503     */
504    public static boolean mkDirs(File dir, String warnMsg) {
505        boolean result = dir.mkdirs();
506        if (!result) {
507            Main.warn(tr(warnMsg, dir.getPath()));
508        }
509        return result;
510    }
511
512    /**
513     * <p>Utility method for closing a {@link java.io.Closeable} object.</p>
514     *
515     * @param c the closeable object. May be null.
516     */
517    public static void close(Closeable c) {
518        if (c == null) return;
519        try {
520            c.close();
521        } catch (IOException e) {
522            Main.warn(e);
523        }
524    }
525
526    /**
527     * <p>Utility method for closing a {@link java.util.zip.ZipFile}.</p>
528     *
529     * @param zip the zip file. May be null.
530     */
531    public static void close(ZipFile zip) {
532        if (zip == null) return;
533        try {
534            zip.close();
535        } catch (IOException e) {
536            Main.warn(e);
537        }
538    }
539
540    /**
541     * Converts the given file to its URL.
542     * @param f The file to get URL from
543     * @return The URL of the given file, or {@code null} if not possible.
544     * @since 6615
545     */
546    public static URL fileToURL(File f) {
547        if (f != null) {
548            try {
549                return f.toURI().toURL();
550            } catch (MalformedURLException ex) {
551                Main.error("Unable to convert filename " + f.getAbsolutePath() + " to URL");
552            }
553        }
554        return null;
555    }
556
557    private static final double EPSILON = 1e-11;
558
559    /**
560     * Determines if the two given double values are equal (their delta being smaller than a fixed epsilon)
561     * @param a The first double value to compare
562     * @param b The second double value to compare
563     * @return {@code true} if {@code abs(a - b) <= 1e-11}, {@code false} otherwise
564     */
565    public static boolean equalsEpsilon(double a, double b) {
566        return Math.abs(a - b) <= EPSILON;
567    }
568
569    /**
570     * Determines if two collections are equal.
571     * @param a first collection
572     * @param b second collection
573     * @return {@code true} if collections are equal, {@code false} otherwise
574     * @since 9217
575     */
576    public static boolean equalCollection(Collection<?> a, Collection<?> b) {
577        if (a == null) return b == null;
578        if (b == null) return false;
579        if (a.size() != b.size()) return false;
580        Iterator<?> itA = a.iterator();
581        Iterator<?> itB = b.iterator();
582        while (itA.hasNext()) {
583            if (!Objects.equals(itA.next(), itB.next()))
584                return false;
585        }
586        return true;
587    }
588
589    /**
590     * Copies the string {@code s} to system clipboard.
591     * @param s string to be copied to clipboard.
592     * @return true if succeeded, false otherwise.
593     */
594    public static boolean copyToClipboard(String s) {
595        try {
596            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
597                @Override
598                public void lostOwnership(Clipboard clpbrd, Transferable t) {
599                    // Do nothing
600                }
601            });
602            return true;
603        } catch (IllegalStateException ex) {
604            Main.error(ex);
605            return false;
606        }
607    }
608
609    /**
610     * Extracts clipboard content as {@code Transferable} object.
611     * @param clipboard clipboard from which contents are retrieved
612     * @return clipboard contents if available, {@code null} otherwise.
613     * @since 8429
614     */
615    public static Transferable getTransferableContent(Clipboard clipboard) {
616        Transferable t = null;
617        for (int tries = 0; t == null && tries < 10; tries++) {
618            try {
619                t = clipboard.getContents(null);
620            } catch (IllegalStateException e) {
621                // Clipboard currently unavailable.
622                // On some platforms, the system clipboard is unavailable while it is accessed by another application.
623                try {
624                    Thread.sleep(1);
625                } catch (InterruptedException ex) {
626                    Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content");
627                }
628            } catch (NullPointerException e) {
629                // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
630                Main.error(e);
631            }
632        }
633        return t;
634    }
635
636    /**
637     * Extracts clipboard content as string.
638     * @return string clipboard contents if available, {@code null} otherwise.
639     */
640    public static String getClipboardContent() {
641        try {
642            Transferable t = getTransferableContent(Toolkit.getDefaultToolkit().getSystemClipboard());
643            if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
644                return (String) t.getTransferData(DataFlavor.stringFlavor);
645            }
646        } catch (UnsupportedFlavorException | IOException | HeadlessException ex) {
647            Main.error(ex);
648            return null;
649        }
650        return null;
651    }
652
653    /**
654     * Calculate MD5 hash of a string and output in hexadecimal format.
655     * @param data arbitrary String
656     * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f]
657     */
658    public static String md5Hex(String data) {
659        MessageDigest md = null;
660        try {
661            md = MessageDigest.getInstance("MD5");
662        } catch (NoSuchAlgorithmException e) {
663            throw new RuntimeException(e);
664        }
665        byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
666        byte[] byteDigest = md.digest(byteData);
667        return toHexString(byteDigest);
668    }
669
670    private static final char[] HEX_ARRAY = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
671
672    /**
673     * Converts a byte array to a string of hexadecimal characters.
674     * Preserves leading zeros, so the size of the output string is always twice
675     * the number of input bytes.
676     * @param bytes the byte array
677     * @return hexadecimal representation
678     */
679    public static String toHexString(byte[] bytes) {
680
681        if (bytes == null) {
682            return "";
683        }
684
685        final int len = bytes.length;
686        if (len == 0) {
687            return "";
688        }
689
690        char[] hexChars = new char[len * 2];
691        for (int i = 0, j = 0; i < len; i++) {
692            final int v = bytes[i];
693            hexChars[j++] = HEX_ARRAY[(v & 0xf0) >> 4];
694            hexChars[j++] = HEX_ARRAY[v & 0xf];
695        }
696        return new String(hexChars);
697    }
698
699    /**
700     * Topological sort.
701     * @param <T> type of items
702     *
703     * @param dependencies contains mappings (key -&gt; value). In the final list of sorted objects, the key will come
704     * after the value. (In other words, the key depends on the value(s).)
705     * There must not be cyclic dependencies.
706     * @return the list of sorted objects
707     */
708    public static <T> List<T> topologicalSort(final MultiMap<T, T> dependencies) {
709        MultiMap<T, T> deps = new MultiMap<>();
710        for (T key : dependencies.keySet()) {
711            deps.putVoid(key);
712            for (T val : dependencies.get(key)) {
713                deps.putVoid(val);
714                deps.put(key, val);
715            }
716        }
717
718        int size = deps.size();
719        List<T> sorted = new ArrayList<>();
720        for (int i = 0; i < size; ++i) {
721            T parentless = null;
722            for (T key : deps.keySet()) {
723                if (deps.get(key).isEmpty()) {
724                    parentless = key;
725                    break;
726                }
727            }
728            if (parentless == null) throw new RuntimeException();
729            sorted.add(parentless);
730            deps.remove(parentless);
731            for (T key : deps.keySet()) {
732                deps.remove(key, parentless);
733            }
734        }
735        if (sorted.size() != size) throw new RuntimeException();
736        return sorted;
737    }
738
739    /**
740     * Replaces some HTML reserved characters (&lt;, &gt; and &amp;) by their equivalent entity (&amp;lt;, &amp;gt; and &amp;amp;);
741     * @param s The unescaped string
742     * @return The escaped string
743     */
744    public static String escapeReservedCharactersHTML(String s) {
745        return s == null ? "" : s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
746    }
747
748    /**
749     * Represents a function that can be applied to objects of {@code A} and
750     * returns objects of {@code B}.
751     * @param <A> class of input objects
752     * @param <B> class of transformed objects
753     */
754    public interface Function<A, B> {
755
756        /**
757         * Applies the function on {@code x}.
758         * @param x an object of
759         * @return the transformed object
760         */
761        B apply(A x);
762    }
763
764    /**
765     * Transforms the collection {@code c} into an unmodifiable collection and
766     * applies the {@link org.openstreetmap.josm.tools.Utils.Function} {@code f} on each element upon access.
767     * @param <A> class of input collection
768     * @param <B> class of transformed collection
769     * @param c a collection
770     * @param f a function that transforms objects of {@code A} to objects of {@code B}
771     * @return the transformed unmodifiable collection
772     */
773    public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) {
774        return new AbstractCollection<B>() {
775
776            @Override
777            public int size() {
778                return c.size();
779            }
780
781            @Override
782            public Iterator<B> iterator() {
783                return new Iterator<B>() {
784
785                    private Iterator<? extends A> it = c.iterator();
786
787                    @Override
788                    public boolean hasNext() {
789                        return it.hasNext();
790                    }
791
792                    @Override
793                    public B next() {
794                        return f.apply(it.next());
795                    }
796
797                    @Override
798                    public void remove() {
799                        throw new UnsupportedOperationException();
800                    }
801                };
802            }
803        };
804    }
805
806    /**
807     * Transforms the list {@code l} into an unmodifiable list and
808     * applies the {@link org.openstreetmap.josm.tools.Utils.Function} {@code f} on each element upon access.
809     * @param <A> class of input collection
810     * @param <B> class of transformed collection
811     * @param l a collection
812     * @param f a function that transforms objects of {@code A} to objects of {@code B}
813     * @return the transformed unmodifiable list
814     */
815    public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) {
816        return new AbstractList<B>() {
817
818            @Override
819            public int size() {
820                return l.size();
821            }
822
823            @Override
824            public B get(int index) {
825                return f.apply(l.get(index));
826            }
827        };
828    }
829
830    /**
831     * Returns a Bzip2 input stream wrapping given input stream.
832     * @param in The raw input stream
833     * @return a Bzip2 input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
834     * @throws IOException if the given input stream does not contain valid BZ2 header
835     * @since 7867
836     */
837    public static BZip2CompressorInputStream getBZip2InputStream(InputStream in) throws IOException {
838        if (in == null) {
839            return null;
840        }
841        return new BZip2CompressorInputStream(in, /* see #9537 */ true);
842    }
843
844    /**
845     * Returns a Gzip input stream wrapping given input stream.
846     * @param in The raw input stream
847     * @return a Gzip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
848     * @throws IOException if an I/O error has occurred
849     * @since 7119
850     */
851    public static GZIPInputStream getGZipInputStream(InputStream in) throws IOException {
852        if (in == null) {
853            return null;
854        }
855        return new GZIPInputStream(in);
856    }
857
858    /**
859     * Returns a Zip input stream wrapping given input stream.
860     * @param in The raw input stream
861     * @return a Zip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
862     * @throws IOException if an I/O error has occurred
863     * @since 7119
864     */
865    public static ZipInputStream getZipInputStream(InputStream in) throws IOException {
866        if (in == null) {
867            return null;
868        }
869        ZipInputStream zis = new ZipInputStream(in, StandardCharsets.UTF_8);
870        // Positions the stream at the beginning of first entry
871        ZipEntry ze = zis.getNextEntry();
872        if (ze != null && Main.isDebugEnabled()) {
873            Main.debug("Zip entry: "+ze.getName());
874        }
875        return zis;
876    }
877
878    /**
879     * An alternative to {@link String#trim()} to effectively remove all leading
880     * and trailing white characters, including Unicode ones.
881     * @param str The string to strip
882     * @return <code>str</code>, without leading and trailing characters, according to
883     *         {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}.
884     * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java’s String.trim has a strange idea of whitespace</a>
885     * @see <a href="https://bugs.openjdk.java.net/browse/JDK-4080617">JDK bug 4080617</a>
886     * @see <a href="https://bugs.openjdk.java.net/browse/JDK-7190385">JDK bug 7190385</a>
887     * @since 5772
888     */
889    public static String strip(final String str) {
890        if (str == null || str.isEmpty()) {
891            return str;
892        }
893        return strip(str, DEFAULT_STRIP);
894    }
895
896    /**
897     * An alternative to {@link String#trim()} to effectively remove all leading
898     * and trailing white characters, including Unicode ones.
899     * @param str The string to strip
900     * @param skipChars additional characters to skip
901     * @return <code>str</code>, without leading and trailing characters, according to
902     *         {@link Character#isWhitespace(char)}, {@link Character#isSpaceChar(char)} and skipChars.
903     * @since 8435
904     */
905    public static String strip(final String str, final String skipChars) {
906        if (str == null || str.isEmpty()) {
907            return str;
908        }
909        return strip(str, stripChars(skipChars));
910    }
911
912    private static String strip(final String str, final char[] skipChars) {
913
914        int start = 0;
915        int end = str.length();
916        boolean leadingSkipChar = true;
917        while (leadingSkipChar && start < end) {
918            char c = str.charAt(start);
919            leadingSkipChar = Character.isWhitespace(c) || Character.isSpaceChar(c) || stripChar(skipChars, c);
920            if (leadingSkipChar) {
921                start++;
922            }
923        }
924        boolean trailingSkipChar = true;
925        while (trailingSkipChar && end > start + 1) {
926            char c = str.charAt(end - 1);
927            trailingSkipChar = Character.isWhitespace(c) || Character.isSpaceChar(c) || stripChar(skipChars, c);
928            if (trailingSkipChar) {
929                end--;
930            }
931        }
932
933        return str.substring(start, end);
934    }
935
936    private static char[] stripChars(final String skipChars) {
937        if (skipChars == null || skipChars.isEmpty()) {
938            return DEFAULT_STRIP;
939        }
940
941        char[] chars = new char[DEFAULT_STRIP.length + skipChars.length()];
942        System.arraycopy(DEFAULT_STRIP, 0, chars, 0, DEFAULT_STRIP.length);
943        skipChars.getChars(0, skipChars.length(), chars, DEFAULT_STRIP.length);
944
945        return chars;
946    }
947
948    private static boolean stripChar(final char[] strip, char c) {
949        for (char s : strip) {
950            if (c == s) {
951                return true;
952            }
953        }
954        return false;
955    }
956
957    /**
958     * Runs an external command and returns the standard output.
959     *
960     * The program is expected to execute fast.
961     *
962     * @param command the command with arguments
963     * @return the output
964     * @throws IOException when there was an error, e.g. command does not exist
965     */
966    public static String execOutput(List<String> command) throws IOException {
967        if (Main.isDebugEnabled()) {
968            Main.debug(join(" ", command));
969        }
970        Process p = new ProcessBuilder(command).start();
971        try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
972            StringBuilder all = null;
973            String line;
974            while ((line = input.readLine()) != null) {
975                if (all == null) {
976                    all = new StringBuilder(line);
977                } else {
978                    all.append('\n');
979                    all.append(line);
980                }
981            }
982            return all != null ? all.toString() : null;
983        }
984    }
985
986    /**
987     * Returns the JOSM temp directory.
988     * @return The JOSM temp directory ({@code <java.io.tmpdir>/JOSM}), or {@code null} if {@code java.io.tmpdir} is not defined
989     * @since 6245
990     */
991    public static File getJosmTempDir() {
992        String tmpDir = System.getProperty("java.io.tmpdir");
993        if (tmpDir == null) {
994            return null;
995        }
996        File josmTmpDir = new File(tmpDir, "JOSM");
997        if (!josmTmpDir.exists() && !josmTmpDir.mkdirs()) {
998            Main.warn("Unable to create temp directory " + josmTmpDir);
999        }
1000        return josmTmpDir;
1001    }
1002
1003    /**
1004     * Returns a simple human readable (hours, minutes, seconds) string for a given duration in milliseconds.
1005     * @param elapsedTime The duration in milliseconds
1006     * @return A human readable string for the given duration
1007     * @throws IllegalArgumentException if elapsedTime is &lt; 0
1008     * @since 6354
1009     */
1010    public static String getDurationString(long elapsedTime) {
1011        if (elapsedTime < 0) {
1012            throw new IllegalArgumentException("elapsedTime must be >= 0");
1013        }
1014        // Is it less than 1 second ?
1015        if (elapsedTime < MILLIS_OF_SECOND) {
1016            return String.format("%d %s", elapsedTime, tr("ms"));
1017        }
1018        // Is it less than 1 minute ?
1019        if (elapsedTime < MILLIS_OF_MINUTE) {
1020            return String.format("%.1f %s", elapsedTime / (double) MILLIS_OF_SECOND, tr("s"));
1021        }
1022        // Is it less than 1 hour ?
1023        if (elapsedTime < MILLIS_OF_HOUR) {
1024            final long min = elapsedTime / MILLIS_OF_MINUTE;
1025            return String.format("%d %s %d %s", min, tr("min"), (elapsedTime - min * MILLIS_OF_MINUTE) / MILLIS_OF_SECOND, tr("s"));
1026        }
1027        // Is it less than 1 day ?
1028        if (elapsedTime < MILLIS_OF_DAY) {
1029            final long hour = elapsedTime / MILLIS_OF_HOUR;
1030            return String.format("%d %s %d %s", hour, tr("h"), (elapsedTime - hour * MILLIS_OF_HOUR) / MILLIS_OF_MINUTE, tr("min"));
1031        }
1032        long days = elapsedTime / MILLIS_OF_DAY;
1033        return String.format("%d %s %d %s", days, trn("day", "days", days), (elapsedTime - days * MILLIS_OF_DAY) / MILLIS_OF_HOUR, tr("h"));
1034    }
1035
1036    /**
1037     * Returns a human readable representation (B, kB, MB, ...) for the given number of byes.
1038     * @param bytes the number of bytes
1039     * @param locale the locale used for formatting
1040     * @return a human readable representation
1041     * @since 9274
1042     */
1043    public static String getSizeString(long bytes, Locale locale) {
1044        if (bytes < 0) {
1045            throw new IllegalArgumentException("bytes must be >= 0");
1046        }
1047        int unitIndex = 0;
1048        double value = bytes;
1049        while (value >= 1024 && unitIndex < SIZE_UNITS.length) {
1050            value /= 1024;
1051            unitIndex++;
1052        }
1053        if (value > 100 || unitIndex == 0) {
1054            return String.format(locale, "%.0f %s", value, SIZE_UNITS[unitIndex]);
1055        } else if (value > 10) {
1056            return String.format(locale, "%.1f %s", value, SIZE_UNITS[unitIndex]);
1057        } else {
1058            return String.format(locale, "%.2f %s", value, SIZE_UNITS[unitIndex]);
1059        }
1060    }
1061
1062    /**
1063     * Returns a human readable representation of a list of positions.
1064     * <p>
1065     * For instance, {@code [1,5,2,6,7} yields "1-2,5-7
1066     * @param positionList a list of positions
1067     * @return a human readable representation
1068     */
1069    public static String getPositionListString(List<Integer> positionList) {
1070        Collections.sort(positionList);
1071        final StringBuilder sb = new StringBuilder(32);
1072        sb.append(positionList.get(0));
1073        int cnt = 0;
1074        int last = positionList.get(0);
1075        for (int i = 1; i < positionList.size(); ++i) {
1076            int cur = positionList.get(i);
1077            if (cur == last + 1) {
1078                ++cnt;
1079            } else if (cnt == 0) {
1080                sb.append(',').append(cur);
1081            } else {
1082                sb.append('-').append(last);
1083                sb.append(',').append(cur);
1084                cnt = 0;
1085            }
1086            last = cur;
1087        }
1088        if (cnt >= 1) {
1089            sb.append('-').append(last);
1090        }
1091        return sb.toString();
1092    }
1093
1094    /**
1095     * Returns a list of capture groups if {@link Matcher#matches()}, or {@code null}.
1096     * The first element (index 0) is the complete match.
1097     * Further elements correspond to the parts in parentheses of the regular expression.
1098     * @param m the matcher
1099     * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
1100     */
1101    public static List<String> getMatches(final Matcher m) {
1102        if (m.matches()) {
1103            List<String> result = new ArrayList<>(m.groupCount() + 1);
1104            for (int i = 0; i <= m.groupCount(); i++) {
1105                result.add(m.group(i));
1106            }
1107            return result;
1108        } else {
1109            return null;
1110        }
1111    }
1112
1113    /**
1114     * Cast an object savely.
1115     * @param <T> the target type
1116     * @param o the object to cast
1117     * @param klass the target class (same as T)
1118     * @return null if <code>o</code> is null or the type <code>o</code> is not
1119     *  a subclass of <code>klass</code>. The casted value otherwise.
1120     */
1121    @SuppressWarnings("unchecked")
1122    public static <T> T cast(Object o, Class<T> klass) {
1123        if (klass.isInstance(o)) {
1124            return (T) o;
1125        }
1126        return null;
1127    }
1128
1129    /**
1130     * Returns the root cause of a throwable object.
1131     * @param t The object to get root cause for
1132     * @return the root cause of {@code t}
1133     * @since 6639
1134     */
1135    public static Throwable getRootCause(Throwable t) {
1136        Throwable result = t;
1137        if (result != null) {
1138            Throwable cause = result.getCause();
1139            while (cause != null && !cause.equals(result)) {
1140                result = cause;
1141                cause = result.getCause();
1142            }
1143        }
1144        return result;
1145    }
1146
1147    /**
1148     * Adds the given item at the end of a new copy of given array.
1149     * @param <T> type of items
1150     * @param array The source array
1151     * @param item The item to add
1152     * @return An extended copy of {@code array} containing {@code item} as additional last element
1153     * @since 6717
1154     */
1155    public static <T> T[] addInArrayCopy(T[] array, T item) {
1156        T[] biggerCopy = Arrays.copyOf(array, array.length + 1);
1157        biggerCopy[array.length] = item;
1158        return biggerCopy;
1159    }
1160
1161    /**
1162     * If the string {@code s} is longer than {@code maxLength}, the string is cut and "..." is appended.
1163     * @param s String to shorten
1164     * @param maxLength maximum number of characters to keep (not including the "...")
1165     * @return the shortened string
1166     */
1167    public static String shortenString(String s, int maxLength) {
1168        if (s != null && s.length() > maxLength) {
1169            return s.substring(0, maxLength - 3) + "...";
1170        } else {
1171            return s;
1172        }
1173    }
1174
1175    /**
1176     * If the string {@code s} is longer than {@code maxLines} lines, the string is cut and a "..." line is appended.
1177     * @param s String to shorten
1178     * @param maxLines maximum number of lines to keep (including including the "..." line)
1179     * @return the shortened string
1180     */
1181    public static String restrictStringLines(String s, int maxLines) {
1182        if (s == null) {
1183            return null;
1184        } else {
1185            return join("\n", limit(Arrays.asList(s.split("\\n")), maxLines, "..."));
1186        }
1187    }
1188
1189    /**
1190     * If the collection {@code elements} is larger than {@code maxElements} elements,
1191     * the collection is shortened and the {@code overflowIndicator} is appended.
1192     * @param <T> type of elements
1193     * @param elements collection to shorten
1194     * @param maxElements maximum number of elements to keep (including including the {@code overflowIndicator})
1195     * @param overflowIndicator the element used to indicate that the collection has been shortened
1196     * @return the shortened collection
1197     */
1198    public static <T> Collection<T> limit(Collection<T> elements, int maxElements, T overflowIndicator) {
1199        if (elements == null) {
1200            return null;
1201        } else {
1202            if (elements.size() > maxElements) {
1203                final Collection<T> r = new ArrayList<>(maxElements);
1204                final Iterator<T> it = elements.iterator();
1205                while (r.size() < maxElements - 1) {
1206                    r.add(it.next());
1207                }
1208                r.add(overflowIndicator);
1209                return r;
1210            } else {
1211                return elements;
1212            }
1213        }
1214    }
1215
1216    /**
1217     * Fixes URL with illegal characters in the query (and fragment) part by
1218     * percent encoding those characters.
1219     *
1220     * special characters like &amp; and # are not encoded
1221     *
1222     * @param url the URL that should be fixed
1223     * @return the repaired URL
1224     */
1225    public static String fixURLQuery(String url) {
1226        if (url == null || url.indexOf('?') == -1)
1227            return url;
1228
1229        String query = url.substring(url.indexOf('?') + 1);
1230
1231        StringBuilder sb = new StringBuilder(url.substring(0, url.indexOf('?') + 1));
1232
1233        for (int i = 0; i < query.length(); i++) {
1234            String c = query.substring(i, i + 1);
1235            if (URL_CHARS.contains(c)) {
1236                sb.append(c);
1237            } else {
1238                sb.append(encodeUrl(c));
1239            }
1240        }
1241        return sb.toString();
1242    }
1243
1244    /**
1245     * Translates a string into <code>application/x-www-form-urlencoded</code>
1246     * format. This method uses UTF-8 encoding scheme to obtain the bytes for unsafe
1247     * characters.
1248     *
1249     * @param   s <code>String</code> to be translated.
1250     * @return  the translated <code>String</code>.
1251     * @see #decodeUrl(String)
1252     * @since 8304
1253     */
1254    public static String encodeUrl(String s) {
1255        final String enc = StandardCharsets.UTF_8.name();
1256        try {
1257            return URLEncoder.encode(s, enc);
1258        } catch (UnsupportedEncodingException e) {
1259            throw new IllegalStateException(e);
1260        }
1261    }
1262
1263    /**
1264     * Decodes a <code>application/x-www-form-urlencoded</code> string.
1265     * UTF-8 encoding is used to determine
1266     * what characters are represented by any consecutive sequences of the
1267     * form "<code>%<i>xy</i></code>".
1268     *
1269     * @param s the <code>String</code> to decode
1270     * @return the newly decoded <code>String</code>
1271     * @see #encodeUrl(String)
1272     * @since 8304
1273     */
1274    public static String decodeUrl(String s) {
1275        final String enc = StandardCharsets.UTF_8.name();
1276        try {
1277            return URLDecoder.decode(s, enc);
1278        } catch (UnsupportedEncodingException e) {
1279            throw new IllegalStateException(e);
1280        }
1281    }
1282
1283    /**
1284     * Determines if the given URL denotes a file on a local filesystem.
1285     * @param url The URL to test
1286     * @return {@code true} if the url points to a local file
1287     * @since 7356
1288     */
1289    public static boolean isLocalUrl(String url) {
1290        if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("resource://"))
1291            return false;
1292        return true;
1293    }
1294
1295    /**
1296     * Creates a new {@link ThreadFactory} which creates threads with names according to {@code nameFormat}.
1297     * @param nameFormat a {@link String#format(String, Object...)} compatible name format; its first argument is a unique thread index
1298     * @param threadPriority the priority of the created threads, see {@link Thread#setPriority(int)}
1299     * @return a new {@link ThreadFactory}
1300     */
1301    public static ThreadFactory newThreadFactory(final String nameFormat, final int threadPriority) {
1302        return new ThreadFactory() {
1303            final AtomicLong count = new AtomicLong(0);
1304            @Override
1305            public Thread newThread(final Runnable runnable) {
1306                final Thread thread = new Thread(runnable, String.format(Locale.ENGLISH, nameFormat, count.getAndIncrement()));
1307                thread.setPriority(threadPriority);
1308                return thread;
1309            }
1310        };
1311    }
1312
1313    /**
1314     * Returns a {@link ForkJoinPool} with the parallelism given by the preference key.
1315     * @param pref The preference key to determine parallelism
1316     * @param nameFormat see {@link #newThreadFactory(String, int)}
1317     * @param threadPriority see {@link #newThreadFactory(String, int)}
1318     * @return a {@link ForkJoinPool}
1319     */
1320    public static ForkJoinPool newForkJoinPool(String pref, final String nameFormat, final int threadPriority) {
1321        int noThreads = Main.pref.getInteger(pref, Runtime.getRuntime().availableProcessors());
1322        return new ForkJoinPool(noThreads, new ForkJoinPool.ForkJoinWorkerThreadFactory() {
1323            final AtomicLong count = new AtomicLong(0);
1324            @Override
1325            public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
1326                final ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
1327                thread.setName(String.format(Locale.ENGLISH, nameFormat, count.getAndIncrement()));
1328                thread.setPriority(threadPriority);
1329                return thread;
1330            }
1331        }, null, true);
1332    }
1333
1334    /**
1335     * Returns an executor which executes commands in the calling thread
1336     * @return an executor
1337     */
1338    public static Executor newDirectExecutor() {
1339        return new Executor() {
1340            @Override
1341            public void execute(Runnable command) {
1342                command.run();
1343            }
1344        };
1345    }
1346
1347    /**
1348     * Updates a given system property.
1349     * @param key The property key
1350     * @param value The property value
1351     * @return the previous value of the system property, or {@code null} if it did not have one.
1352     * @since 7894
1353     */
1354    public static String updateSystemProperty(String key, String value) {
1355        if (value != null) {
1356            String old = System.setProperty(key, value);
1357            if (!key.toLowerCase(Locale.ENGLISH).contains("password")) {
1358                Main.debug("System property '" + key + "' set to '" + value + "'. Old value was '" + old + '\'');
1359            } else {
1360                Main.debug("System property '" + key + "' changed.");
1361            }
1362            return old;
1363        }
1364        return null;
1365    }
1366
1367    /**
1368     * Returns a new secure SAX parser, supporting XML namespaces.
1369     * @return a new secure SAX parser, supporting XML namespaces
1370     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
1371     * @throws SAXException for SAX errors.
1372     * @since 8287
1373     */
1374    public static SAXParser newSafeSAXParser() throws ParserConfigurationException, SAXException {
1375        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
1376        parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
1377        parserFactory.setNamespaceAware(true);
1378        return parserFactory.newSAXParser();
1379    }
1380
1381    /**
1382     * Parse the content given {@link org.xml.sax.InputSource} as XML using the specified {@link org.xml.sax.helpers.DefaultHandler}.
1383     * This method uses a secure SAX parser, supporting XML namespaces.
1384     *
1385     * @param is The InputSource containing the content to be parsed.
1386     * @param dh The SAX DefaultHandler to use.
1387     * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
1388     * @throws SAXException for SAX errors.
1389     * @throws IOException if any IO errors occur.
1390     * @since 8347
1391     */
1392    public static void parseSafeSAX(InputSource is, DefaultHandler dh) throws ParserConfigurationException, SAXException, IOException {
1393        long start = System.currentTimeMillis();
1394        if (Main.isDebugEnabled()) {
1395            Main.debug("Starting SAX parsing of " + is + " using " + dh);
1396        }
1397        newSafeSAXParser().parse(is, dh);
1398        if (Main.isDebugEnabled()) {
1399            Main.debug("SAX parsing done in " + getDurationString(System.currentTimeMillis() - start));
1400        }
1401    }
1402
1403    /**
1404     * Determines if the filename has one of the given extensions, in a robust manner.
1405     * The comparison is case and locale insensitive.
1406     * @param filename The file name
1407     * @param extensions The list of extensions to look for (without dot)
1408     * @return {@code true} if the filename has one of the given extensions
1409     * @since 8404
1410     */
1411    public static boolean hasExtension(String filename, String... extensions) {
1412        String name = filename.toLowerCase(Locale.ENGLISH).replace("?format=raw", "");
1413        for (String ext : extensions) {
1414            if (name.endsWith('.' + ext.toLowerCase(Locale.ENGLISH)))
1415                return true;
1416        }
1417        return false;
1418    }
1419
1420    /**
1421     * Determines if the file's name has one of the given extensions, in a robust manner.
1422     * The comparison is case and locale insensitive.
1423     * @param file The file
1424     * @param extensions The list of extensions to look for (without dot)
1425     * @return {@code true} if the file's name has one of the given extensions
1426     * @since 8404
1427     */
1428    public static boolean hasExtension(File file, String... extensions) {
1429        return hasExtension(file.getName(), extensions);
1430    }
1431
1432    /**
1433     * Reads the input stream and closes the stream at the end of processing (regardless if an exception was thrown)
1434     *
1435     * @param stream input stream
1436     * @return byte array of data in input stream
1437     * @throws IOException if any I/O error occurs
1438     */
1439    public static byte[] readBytesFromStream(InputStream stream) throws IOException {
1440        try {
1441            ByteArrayOutputStream bout = new ByteArrayOutputStream(stream.available());
1442            byte[] buffer = new byte[2048];
1443            boolean finished = false;
1444            do {
1445                int read = stream.read(buffer);
1446                if (read >= 0) {
1447                    bout.write(buffer, 0, read);
1448                } else {
1449                    finished = true;
1450                }
1451            } while (!finished);
1452            if (bout.size() == 0)
1453                return null;
1454            return bout.toByteArray();
1455        } finally {
1456            stream.close();
1457        }
1458    }
1459
1460    /**
1461     * Returns the initial capacity to pass to the HashMap / HashSet constructor
1462     * when it is initialized with a known number of entries.
1463     *
1464     * When a HashMap is filled with entries, the underlying array is copied over
1465     * to a larger one multiple times. To avoid this process when the number of
1466     * entries is known in advance, the initial capacity of the array can be
1467     * given to the HashMap constructor. This method returns a suitable value
1468     * that avoids rehashing but doesn't waste memory.
1469     * @param nEntries the number of entries expected
1470     * @param loadFactor the load factor
1471     * @return the initial capacity for the HashMap constructor
1472     */
1473    public static int hashMapInitialCapacity(int nEntries, float loadFactor) {
1474        return (int) Math.ceil(nEntries / loadFactor);
1475    }
1476
1477    /**
1478     * Returns the initial capacity to pass to the HashMap / HashSet constructor
1479     * when it is initialized with a known number of entries.
1480     *
1481     * When a HashMap is filled with entries, the underlying array is copied over
1482     * to a larger one multiple times. To avoid this process when the number of
1483     * entries is known in advance, the initial capacity of the array can be
1484     * given to the HashMap constructor. This method returns a suitable value
1485     * that avoids rehashing but doesn't waste memory.
1486     *
1487     * Assumes default load factor (0.75).
1488     * @param nEntries the number of entries expected
1489     * @return the initial capacity for the HashMap constructor
1490     */
1491    public static int hashMapInitialCapacity(int nEntries) {
1492        return hashMapInitialCapacity(nEntries, 0.75f);
1493    }
1494
1495    /**
1496     * Utility class to save a string along with its rendering direction
1497     * (left-to-right or right-to-left).
1498     */
1499    private static class DirectionString {
1500        public final int direction;
1501        public final String str;
1502
1503        DirectionString(int direction, String str) {
1504            this.direction = direction;
1505            this.str = str;
1506        }
1507    }
1508
1509    /**
1510     * Convert a string to a list of {@link GlyphVector}s. The string may contain
1511     * bi-directional text. The result will be in correct visual order.
1512     * Each element of the resulting list corresponds to one section of the
1513     * string with consistent writing direction (left-to-right or right-to-left).
1514     *
1515     * @param string the string to render
1516     * @param font the font
1517     * @param frc a FontRenderContext object
1518     * @return a list of GlyphVectors
1519     */
1520    public static List<GlyphVector> getGlyphVectorsBidi(String string, Font font, FontRenderContext frc) {
1521        List<GlyphVector> gvs = new ArrayList<>();
1522        Bidi bidi = new Bidi(string, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
1523        byte[] levels = new byte[bidi.getRunCount()];
1524        DirectionString[] dirStrings = new DirectionString[levels.length];
1525        for (int i = 0; i < levels.length; ++i) {
1526            levels[i] = (byte) bidi.getRunLevel(i);
1527            String substr = string.substring(bidi.getRunStart(i), bidi.getRunLimit(i));
1528            int dir = levels[i] % 2 == 0 ? Bidi.DIRECTION_LEFT_TO_RIGHT : Bidi.DIRECTION_RIGHT_TO_LEFT;
1529            dirStrings[i] = new DirectionString(dir, substr);
1530        }
1531        Bidi.reorderVisually(levels, 0, dirStrings, 0, levels.length);
1532        for (int i = 0; i < dirStrings.length; ++i) {
1533            char[] chars = dirStrings[i].str.toCharArray();
1534            gvs.add(font.layoutGlyphVector(frc, chars, 0, chars.length, dirStrings[i].direction));
1535        }
1536        return gvs;
1537    }
1538
1539}