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

Last change on this file since 10246 was 10223, checked in by Don-vip, 8 years ago

findbugs: DP_DO_INSIDE_DO_PRIVILEGED + UWF_UNWRITTEN_FIELD + RC_REF_COMPARISON + OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE

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