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

Last change on this file since 12870 was 12854, checked in by bastiK, 7 years ago

see #15229 - fix checkstyle warnings

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