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

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

remote control: add more unit tests, robustness

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