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

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

fixed #15139 - raster-image filter settings are Not Stored when saving session (color Saturation, Brightness, Sharpness)

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