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

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

see #11390, see #12890 - use Java 8 Predicates

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