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

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

see #11390, fix #12908 - Java 8: Move to java Predicates (patch by michael2402) - gsoc-core

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