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

Last change on this file since 8308 was 8308, checked in by Don-vip, 9 years ago

fix potential NPEs and Sonar issues related to serialization

  • Property svn:eol-style set to native
File size: 43.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Color;
8import java.awt.Toolkit;
9import java.awt.datatransfer.Clipboard;
10import java.awt.datatransfer.ClipboardOwner;
11import java.awt.datatransfer.DataFlavor;
12import java.awt.datatransfer.StringSelection;
13import java.awt.datatransfer.Transferable;
14import java.awt.datatransfer.UnsupportedFlavorException;
15import java.io.BufferedReader;
16import java.io.Closeable;
17import java.io.File;
18import java.io.IOException;
19import java.io.InputStream;
20import java.io.InputStreamReader;
21import java.io.OutputStream;
22import java.io.UnsupportedEncodingException;
23import java.net.HttpURLConnection;
24import java.net.MalformedURLException;
25import java.net.URL;
26import java.net.URLConnection;
27import java.net.URLDecoder;
28import java.net.URLEncoder;
29import java.nio.charset.StandardCharsets;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.StandardCopyOption;
33import java.security.MessageDigest;
34import java.security.NoSuchAlgorithmException;
35import java.text.MessageFormat;
36import java.util.AbstractCollection;
37import java.util.AbstractList;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Collection;
41import java.util.Collections;
42import java.util.Iterator;
43import java.util.List;
44import java.util.concurrent.ExecutorService;
45import java.util.concurrent.Executors;
46import java.util.regex.Matcher;
47import java.util.regex.Pattern;
48import java.util.zip.GZIPInputStream;
49import java.util.zip.ZipEntry;
50import java.util.zip.ZipFile;
51import java.util.zip.ZipInputStream;
52
53import javax.xml.XMLConstants;
54import javax.xml.parsers.ParserConfigurationException;
55import javax.xml.parsers.SAXParser;
56import javax.xml.parsers.SAXParserFactory;
57
58import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
59import org.openstreetmap.josm.Main;
60import org.openstreetmap.josm.data.Version;
61import org.xml.sax.SAXException;
62
63/**
64 * Basic utils, that can be useful in different parts of the program.
65 */
66public final class Utils {
67
68 public static final Pattern WHITE_SPACES_PATTERN = Pattern.compile("\\s+");
69
70 private Utils() {
71 // Hide default constructor for utils classes
72 }
73
74 private static final int MILLIS_OF_SECOND = 1000;
75 private static final int MILLIS_OF_MINUTE = 60000;
76 private static final int MILLIS_OF_HOUR = 3600000;
77 private static final int MILLIS_OF_DAY = 86400000;
78
79 public static final String URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%";
80
81 /**
82 * Tests whether {@code predicate} applies to at least one elements from {@code collection}.
83 */
84 public static <T> boolean exists(Iterable<? extends T> collection, Predicate<? super T> predicate) {
85 for (T item : collection) {
86 if (predicate.evaluate(item))
87 return true;
88 }
89 return false;
90 }
91
92 /**
93 * Tests whether {@code predicate} applies to all elements from {@code collection}.
94 */
95 public static <T> boolean forAll(Iterable<? extends T> collection, Predicate<? super T> predicate) {
96 return !exists(collection, Predicates.not(predicate));
97 }
98
99 public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) {
100 for (Object item : collection) {
101 if (klass.isInstance(item))
102 return true;
103 }
104 return false;
105 }
106
107 public static <T> T find(Iterable<? extends T> collection, Predicate<? super T> predicate) {
108 for (T item : collection) {
109 if (predicate.evaluate(item))
110 return item;
111 }
112 return null;
113 }
114
115 @SuppressWarnings("unchecked")
116 public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) {
117 for (Object item : collection) {
118 if (klass.isInstance(item))
119 return (T) item;
120 }
121 return null;
122 }
123
124 public static <T> Collection<T> filter(Collection<? extends T> collection, Predicate<? super T> predicate) {
125 return new FilteredCollection<>(collection, predicate);
126 }
127
128 /**
129 * Returns the first element from {@code items} which is non-null, or null if all elements are null.
130 * @param items the items to look for
131 * @return first non-null item if there is one
132 */
133 @SafeVarargs
134 public static <T> T firstNonNull(T... items) {
135 for (T i : items) {
136 if (i != null) {
137 return i;
138 }
139 }
140 return null;
141 }
142
143 /**
144 * Filter a collection by (sub)class.
145 * This is an efficient read-only implementation.
146 */
147 public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> klass) {
148 return new SubclassFilteredCollection<>(collection, new Predicate<S>() {
149 @Override
150 public boolean evaluate(S o) {
151 return klass.isInstance(o);
152 }
153 });
154 }
155
156 public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) {
157 int i = 0;
158 for (T item : collection) {
159 if (predicate.evaluate(item))
160 return i;
161 i++;
162 }
163 return -1;
164 }
165
166 /**
167 * Returns the minimum of three values.
168 * @param a an argument.
169 * @param b another argument.
170 * @param c another argument.
171 * @return the smaller of {@code a}, {@code b} and {@code c}.
172 */
173 public static int min(int a, int b, int c) {
174 if (b < c) {
175 if (a < b)
176 return a;
177 return b;
178 } else {
179 if (a < c)
180 return a;
181 return c;
182 }
183 }
184
185 /**
186 * Returns the greater of four {@code int} values. That is, the
187 * result is the argument closer to the value of
188 * {@link Integer#MAX_VALUE}. If the arguments have the same value,
189 * the result is that same value.
190 *
191 * @param a an argument.
192 * @param b another argument.
193 * @param c another argument.
194 * @param d another argument.
195 * @return the larger of {@code a}, {@code b}, {@code c} and {@code d}.
196 */
197 public static int max(int a, int b, int c, int d) {
198 return Math.max(Math.max(a, b), Math.max(c, d));
199 }
200
201 /**
202 * Ensures a logical condition is met. Otherwise throws an assertion error.
203 * @param condition the condition to be met
204 * @param message Formatted error message to raise if condition is not met
205 * @param data Message parameters, optional
206 * @throws AssertionError if the condition is not met
207 */
208 public static void ensure(boolean condition, String message, Object...data) {
209 if (!condition)
210 throw new AssertionError(
211 MessageFormat.format(message,data)
212 );
213 }
214
215 /**
216 * return the modulus in the range [0, n)
217 */
218 public static int mod(int a, int n) {
219 if (n <= 0)
220 throw new IllegalArgumentException("n must be <= 0 but is "+n);
221 int res = a % n;
222 if (res < 0) {
223 res += n;
224 }
225 return res;
226 }
227
228 /**
229 * Joins a list of strings (or objects that can be converted to string via
230 * Object.toString()) into a single string with fields separated by sep.
231 * @param sep the separator
232 * @param values collection of objects, null is converted to the
233 * empty string
234 * @return null if values is null. The joined string otherwise.
235 */
236 public static String join(String sep, Collection<?> values) {
237 CheckParameterUtil.ensureParameterNotNull(sep, "sep");
238 if (values == null)
239 return null;
240 StringBuilder s = null;
241 for (Object a : values) {
242 if (a == null) {
243 a = "";
244 }
245 if (s != null) {
246 s.append(sep).append(a.toString());
247 } else {
248 s = new StringBuilder(a.toString());
249 }
250 }
251 return s != null ? s.toString() : "";
252 }
253
254 /**
255 * Converts the given iterable collection as an unordered HTML list.
256 * @param values The iterable collection
257 * @return An unordered HTML list
258 */
259 public static String joinAsHtmlUnorderedList(Iterable<?> values) {
260 StringBuilder sb = new StringBuilder(1024);
261 sb.append("<ul>");
262 for (Object i : values) {
263 sb.append("<li>").append(i).append("</li>");
264 }
265 sb.append("</ul>");
266 return sb.toString();
267 }
268
269 /**
270 * convert Color to String
271 * (Color.toString() omits alpha value)
272 */
273 public static String toString(Color c) {
274 if (c == null)
275 return "null";
276 if (c.getAlpha() == 255)
277 return String.format("#%06x", c.getRGB() & 0x00ffffff);
278 else
279 return String.format("#%06x(alpha=%d)", c.getRGB() & 0x00ffffff, c.getAlpha());
280 }
281
282 /**
283 * convert float range 0 &lt;= x &lt;= 1 to integer range 0..255
284 * when dealing with colors and color alpha value
285 * @return null if val is null, the corresponding int if val is in the
286 * range 0...1. If val is outside that range, return 255
287 */
288 public static Integer color_float2int(Float val) {
289 if (val == null)
290 return null;
291 if (val < 0 || val > 1)
292 return 255;
293 return (int) (255f * val + 0.5f);
294 }
295
296 /**
297 * convert integer range 0..255 to float range 0 &lt;= x &lt;= 1
298 * when dealing with colors and color alpha value
299 */
300 public static Float color_int2float(Integer val) {
301 if (val == null)
302 return null;
303 if (val < 0 || val > 255)
304 return 1f;
305 return ((float) val) / 255f;
306 }
307
308 public static Color complement(Color clr) {
309 return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
310 }
311
312 /**
313 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
314 * @param array The array to copy
315 * @return A copy of the original array, or {@code null} if {@code array} is null
316 * @since 6221
317 */
318 public static <T> T[] copyArray(T[] array) {
319 if (array != null) {
320 return Arrays.copyOf(array, array.length);
321 }
322 return null;
323 }
324
325 /**
326 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
327 * @param array The array to copy
328 * @return A copy of the original array, or {@code null} if {@code array} is null
329 * @since 6222
330 */
331 public static char[] copyArray(char[] array) {
332 if (array != null) {
333 return Arrays.copyOf(array, array.length);
334 }
335 return null;
336 }
337
338 /**
339 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
340 * @param array The array to copy
341 * @return A copy of the original array, or {@code null} if {@code array} is null
342 * @since 7436
343 */
344 public static int[] copyArray(int[] array) {
345 if (array != null) {
346 return Arrays.copyOf(array, array.length);
347 }
348 return null;
349 }
350
351 /**
352 * Simple file copy function that will overwrite the target file.
353 * @param in The source file
354 * @param out The destination file
355 * @return the path to the target file
356 * @throws IOException if any I/O error occurs
357 * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
358 * @since 7003
359 */
360 public static Path copyFile(File in, File out) throws IOException {
361 CheckParameterUtil.ensureParameterNotNull(in, "in");
362 CheckParameterUtil.ensureParameterNotNull(out, "out");
363 return Files.copy(in.toPath(), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
364 }
365
366 /**
367 * Recursive directory copy function
368 * @param in The source directory
369 * @param out The destination directory
370 * @throws IOException if any I/O error ooccurs
371 * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
372 * @since 7835
373 */
374 public static void copyDirectory(File in, File out) throws IOException {
375 CheckParameterUtil.ensureParameterNotNull(in, "in");
376 CheckParameterUtil.ensureParameterNotNull(out, "out");
377 if (!out.exists() && !out.mkdirs()) {
378 Main.warn("Unable to create directory "+out.getPath());
379 }
380 File[] files = in.listFiles();
381 if (files != null) {
382 for (File f : files) {
383 File target = new File(out, f.getName());
384 if (f.isDirectory()) {
385 copyDirectory(f, target);
386 } else {
387 copyFile(f, target);
388 }
389 }
390 }
391 }
392
393 /**
394 * Copy data from source stream to output stream.
395 * @param source source stream
396 * @param destination target stream
397 * @return number of bytes copied
398 * @throws IOException if any I/O error occurs
399 */
400 public static int copyStream(InputStream source, OutputStream destination) throws IOException {
401 int count = 0;
402 byte[] b = new byte[512];
403 int read;
404 while ((read = source.read(b)) != -1) {
405 count += read;
406 destination.write(b, 0, read);
407 }
408 return count;
409 }
410
411 /**
412 * Deletes a directory recursively.
413 * @param path The directory to delete
414 * @return <code>true</code> if and only if the file or directory is
415 * successfully deleted; <code>false</code> otherwise
416 */
417 public static boolean deleteDirectory(File path) {
418 if( path.exists() ) {
419 File[] files = path.listFiles();
420 if (files != null) {
421 for (File file : files) {
422 if (file.isDirectory()) {
423 deleteDirectory(file);
424 } else if (!file.delete()) {
425 Main.warn("Unable to delete file: "+file.getPath());
426 }
427 }
428 }
429 }
430 return path.delete();
431 }
432
433 /**
434 * <p>Utility method for closing a {@link java.io.Closeable} object.</p>
435 *
436 * @param c the closeable object. May be null.
437 */
438 public static void close(Closeable c) {
439 if (c == null) return;
440 try {
441 c.close();
442 } catch (IOException e) {
443 Main.warn(e);
444 }
445 }
446
447 /**
448 * <p>Utility method for closing a {@link java.util.zip.ZipFile}.</p>
449 *
450 * @param zip the zip file. May be null.
451 */
452 public static void close(ZipFile zip) {
453 if (zip == null) return;
454 try {
455 zip.close();
456 } catch (IOException e) {
457 Main.warn(e);
458 }
459 }
460
461 /**
462 * Converts the given file to its URL.
463 * @param f The file to get URL from
464 * @return The URL of the given file, or {@code null} if not possible.
465 * @since 6615
466 */
467 public static URL fileToURL(File f) {
468 if (f != null) {
469 try {
470 return f.toURI().toURL();
471 } catch (MalformedURLException ex) {
472 Main.error("Unable to convert filename " + f.getAbsolutePath() + " to URL");
473 }
474 }
475 return null;
476 }
477
478 private static final double EPSILON = 1e-11;
479
480 /**
481 * Determines if the two given double values are equal (their delta being smaller than a fixed epsilon)
482 * @param a The first double value to compare
483 * @param b The second double value to compare
484 * @return {@code true} if {@code abs(a - b) <= 1e-11}, {@code false} otherwise
485 */
486 public static boolean equalsEpsilon(double a, double b) {
487 return Math.abs(a - b) <= EPSILON;
488 }
489
490 /**
491 * Copies the string {@code s} to system clipboard.
492 * @param s string to be copied to clipboard.
493 * @return true if succeeded, false otherwise.
494 */
495 public static boolean copyToClipboard(String s) {
496 try {
497 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
498
499 @Override
500 public void lostOwnership(Clipboard clpbrd, Transferable t) {
501 }
502 });
503 return true;
504 } catch (IllegalStateException ex) {
505 Main.error(ex);
506 return false;
507 }
508 }
509
510 /**
511 * Extracts clipboard content as string.
512 * @return string clipboard contents if available, {@code null} otherwise.
513 */
514 public static String getClipboardContent() {
515 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
516 Transferable t = null;
517 for (int tries = 0; t == null && tries < 10; tries++) {
518 try {
519 t = clipboard.getContents(null);
520 } catch (IllegalStateException e) {
521 // Clipboard currently unavailable. On some platforms, the system clipboard is unavailable while it is accessed by another application.
522 try {
523 Thread.sleep(1);
524 } catch (InterruptedException ex) {
525 Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content");
526 }
527 }
528 }
529 try {
530 if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
531 return (String) t.getTransferData(DataFlavor.stringFlavor);
532 }
533 } catch (UnsupportedFlavorException | IOException ex) {
534 Main.error(ex);
535 return null;
536 }
537 return null;
538 }
539
540 /**
541 * Calculate MD5 hash of a string and output in hexadecimal format.
542 * @param data arbitrary String
543 * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f]
544 */
545 public static String md5Hex(String data) {
546 byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
547 MessageDigest md = null;
548 try {
549 md = MessageDigest.getInstance("MD5");
550 } catch (NoSuchAlgorithmException e) {
551 throw new RuntimeException(e);
552 }
553 byte[] byteDigest = md.digest(byteData);
554 return toHexString(byteDigest);
555 }
556
557 private static final char[] HEX_ARRAY = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
558
559 /**
560 * Converts a byte array to a string of hexadecimal characters.
561 * Preserves leading zeros, so the size of the output string is always twice
562 * the number of input bytes.
563 * @param bytes the byte array
564 * @return hexadecimal representation
565 */
566 public static String toHexString(byte[] bytes) {
567
568 if (bytes == null) {
569 return "";
570 }
571
572 final int len = bytes.length;
573 if (len == 0) {
574 return "";
575 }
576
577 char[] hexChars = new char[len * 2];
578 for (int i = 0, j = 0; i < len; i++) {
579 final int v = bytes[i];
580 hexChars[j++] = HEX_ARRAY[(v & 0xf0) >> 4];
581 hexChars[j++] = HEX_ARRAY[v & 0xf];
582 }
583 return new String(hexChars);
584 }
585
586 /**
587 * Topological sort.
588 *
589 * @param dependencies contains mappings (key -&gt; value). In the final list of sorted objects, the key will come
590 * after the value. (In other words, the key depends on the value(s).)
591 * There must not be cyclic dependencies.
592 * @return the list of sorted objects
593 */
594 public static <T> List<T> topologicalSort(final MultiMap<T,T> dependencies) {
595 MultiMap<T,T> deps = new MultiMap<>();
596 for (T key : dependencies.keySet()) {
597 deps.putVoid(key);
598 for (T val : dependencies.get(key)) {
599 deps.putVoid(val);
600 deps.put(key, val);
601 }
602 }
603
604 int size = deps.size();
605 List<T> sorted = new ArrayList<>();
606 for (int i=0; i<size; ++i) {
607 T parentless = null;
608 for (T key : deps.keySet()) {
609 if (deps.get(key).isEmpty()) {
610 parentless = key;
611 break;
612 }
613 }
614 if (parentless == null) throw new RuntimeException();
615 sorted.add(parentless);
616 deps.remove(parentless);
617 for (T key : deps.keySet()) {
618 deps.remove(key, parentless);
619 }
620 }
621 if (sorted.size() != size) throw new RuntimeException();
622 return sorted;
623 }
624
625 /**
626 * Represents a function that can be applied to objects of {@code A} and
627 * returns objects of {@code B}.
628 * @param <A> class of input objects
629 * @param <B> class of transformed objects
630 */
631 public static interface Function<A, B> {
632
633 /**
634 * Applies the function on {@code x}.
635 * @param x an object of
636 * @return the transformed object
637 */
638 B apply(A x);
639 }
640
641 /**
642 * Transforms the collection {@code c} into an unmodifiable collection and
643 * applies the {@link org.openstreetmap.josm.tools.Utils.Function} {@code f} on each element upon access.
644 * @param <A> class of input collection
645 * @param <B> class of transformed collection
646 * @param c a collection
647 * @param f a function that transforms objects of {@code A} to objects of {@code B}
648 * @return the transformed unmodifiable collection
649 */
650 public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) {
651 return new AbstractCollection<B>() {
652
653 @Override
654 public int size() {
655 return c.size();
656 }
657
658 @Override
659 public Iterator<B> iterator() {
660 return new Iterator<B>() {
661
662 private Iterator<? extends A> it = c.iterator();
663
664 @Override
665 public boolean hasNext() {
666 return it.hasNext();
667 }
668
669 @Override
670 public B next() {
671 return f.apply(it.next());
672 }
673
674 @Override
675 public void remove() {
676 throw new UnsupportedOperationException();
677 }
678 };
679 }
680 };
681 }
682
683 /**
684 * Transforms the list {@code l} into an unmodifiable list and
685 * applies the {@link org.openstreetmap.josm.tools.Utils.Function} {@code f} on each element upon access.
686 * @param <A> class of input collection
687 * @param <B> class of transformed collection
688 * @param l a collection
689 * @param f a function that transforms objects of {@code A} to objects of {@code B}
690 * @return the transformed unmodifiable list
691 */
692 public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) {
693 return new AbstractList<B>() {
694
695 @Override
696 public int size() {
697 return l.size();
698 }
699
700 @Override
701 public B get(int index) {
702 return f.apply(l.get(index));
703 }
704 };
705 }
706
707 private static final Pattern HTTP_PREFFIX_PATTERN = Pattern.compile("https?");
708
709 /**
710 * Opens a HTTP connection to the given URL and sets the User-Agent property to JOSM's one.
711 * @param httpURL The HTTP url to open (must use http:// or https://)
712 * @return An open HTTP connection to the given URL
713 * @throws java.io.IOException if an I/O exception occurs.
714 * @since 5587
715 */
716 public static HttpURLConnection openHttpConnection(URL httpURL) throws IOException {
717 if (httpURL == null || !HTTP_PREFFIX_PATTERN.matcher(httpURL.getProtocol()).matches()) {
718 throw new IllegalArgumentException("Invalid HTTP url");
719 }
720 if (Main.isDebugEnabled()) {
721 Main.debug("Opening HTTP connection to "+httpURL.toExternalForm());
722 }
723 HttpURLConnection connection = (HttpURLConnection) httpURL.openConnection();
724 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
725 connection.setUseCaches(false);
726 return connection;
727 }
728
729 /**
730 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one.
731 * @param url The url to open
732 * @return An stream for the given URL
733 * @throws java.io.IOException if an I/O exception occurs.
734 * @since 5867
735 */
736 public static InputStream openURL(URL url) throws IOException {
737 return openURLAndDecompress(url, false);
738 }
739
740 /**
741 * Opens a connection to the given URL, sets the User-Agent property to JOSM's one, and decompresses stream if necessary.
742 * @param url The url to open
743 * @param decompress whether to wrap steam in a {@link GZIPInputStream} or {@link BZip2CompressorInputStream}
744 * if the {@code Content-Type} header is set accordingly.
745 * @return An stream for the given URL
746 * @throws IOException if an I/O exception occurs.
747 * @since 6421
748 */
749 public static InputStream openURLAndDecompress(final URL url, final boolean decompress) throws IOException {
750 final URLConnection connection = setupURLConnection(url.openConnection());
751 final InputStream in = connection.getInputStream();
752 if (decompress) {
753 switch (connection.getHeaderField("Content-Type")) {
754 case "application/zip":
755 return getZipInputStream(in);
756 case "application/x-gzip":
757 return getGZipInputStream(in);
758 case "application/x-bzip2":
759 return getBZip2InputStream(in);
760 }
761 }
762 return in;
763 }
764
765 /**
766 * Returns a Bzip2 input stream wrapping given input stream.
767 * @param in The raw input stream
768 * @return a Bzip2 input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
769 * @throws IOException if the given input stream does not contain valid BZ2 header
770 * @since 7867
771 */
772 public static BZip2CompressorInputStream getBZip2InputStream(InputStream in) throws IOException {
773 if (in == null) {
774 return null;
775 }
776 return new BZip2CompressorInputStream(in, /* see #9537 */ true);
777 }
778
779 /**
780 * Returns a Gzip input stream wrapping given input stream.
781 * @param in The raw input stream
782 * @return a Gzip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
783 * @throws IOException if an I/O error has occurred
784 * @since 7119
785 */
786 public static GZIPInputStream getGZipInputStream(InputStream in) throws IOException {
787 if (in == null) {
788 return null;
789 }
790 return new GZIPInputStream(in);
791 }
792
793 /**
794 * Returns a Zip input stream wrapping given input stream.
795 * @param in The raw input stream
796 * @return a Zip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
797 * @throws IOException if an I/O error has occurred
798 * @since 7119
799 */
800 public static ZipInputStream getZipInputStream(InputStream in) throws IOException {
801 if (in == null) {
802 return null;
803 }
804 ZipInputStream zis = new ZipInputStream(in, StandardCharsets.UTF_8);
805 // Positions the stream at the beginning of first entry
806 ZipEntry ze = zis.getNextEntry();
807 if (ze != null && Main.isDebugEnabled()) {
808 Main.debug("Zip entry: "+ze.getName());
809 }
810 return zis;
811 }
812
813 /***
814 * Setups the given URL connection to match JOSM needs by setting its User-Agent and timeout properties.
815 * @param connection The connection to setup
816 * @return {@code connection}, with updated properties
817 * @since 5887
818 */
819 public static URLConnection setupURLConnection(URLConnection connection) {
820 if (connection != null) {
821 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
822 connection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
823 connection.setReadTimeout(Main.pref.getInteger("socket.timeout.read",30)*1000);
824 }
825 return connection;
826 }
827
828 /**
829 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one.
830 * @param url The url to open
831 * @return An buffered stream reader for the given URL (using UTF-8)
832 * @throws java.io.IOException if an I/O exception occurs.
833 * @since 5868
834 */
835 public static BufferedReader openURLReader(URL url) throws IOException {
836 return openURLReaderAndDecompress(url, false);
837 }
838
839 /**
840 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one.
841 * @param url The url to open
842 * @param decompress whether to wrap steam in a {@link GZIPInputStream} or {@link BZip2CompressorInputStream}
843 * if the {@code Content-Type} header is set accordingly.
844 * @return An buffered stream reader for the given URL (using UTF-8)
845 * @throws IOException if an I/O exception occurs.
846 * @since 6421
847 */
848 public static BufferedReader openURLReaderAndDecompress(final URL url, final boolean decompress) throws IOException {
849 return new BufferedReader(new InputStreamReader(openURLAndDecompress(url, decompress), StandardCharsets.UTF_8));
850 }
851
852 /**
853 * Opens a HTTP connection to the given URL, sets the User-Agent property to JOSM's one and optionnaly disables Keep-Alive.
854 * @param httpURL The HTTP url to open (must use http:// or https://)
855 * @param keepAlive whether not to set header {@code Connection=close}
856 * @return An open HTTP connection to the given URL
857 * @throws java.io.IOException if an I/O exception occurs.
858 * @since 5587
859 */
860 public static HttpURLConnection openHttpConnection(URL httpURL, boolean keepAlive) throws IOException {
861 HttpURLConnection connection = openHttpConnection(httpURL);
862 if (!keepAlive) {
863 connection.setRequestProperty("Connection", "close");
864 }
865 if (Main.isDebugEnabled()) {
866 try {
867 Main.debug("REQUEST: "+ connection.getRequestProperties());
868 } catch (IllegalStateException e) {
869 Main.warn(e);
870 }
871 }
872 return connection;
873 }
874
875 /**
876 * An alternative to {@link String#trim()} to effectively remove all leading and trailing white characters, including Unicode ones.
877 * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java’s String.trim has a strange idea of whitespace</a>
878 * @see <a href="https://bugs.openjdk.java.net/browse/JDK-4080617">JDK bug 4080617</a>
879 * @param str The string to strip
880 * @return <code>str</code>, without leading and trailing characters, according to
881 * {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}.
882 * @since 5772
883 */
884 public static String strip(String str) {
885 if (str == null || str.isEmpty()) {
886 return str;
887 }
888 int start = 0, end = str.length();
889 boolean leadingWhite = true;
890 while (leadingWhite && start < end) {
891 char c = str.charAt(start);
892 // '\u200B' (ZERO WIDTH SPACE character) needs to be handled manually because of change in Unicode 6.0 (Java 7, see #8918)
893 // same for '\uFEFF' (ZERO WIDTH NO-BREAK SPACE)
894 leadingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B' || c == '\uFEFF');
895 if (leadingWhite) {
896 start++;
897 }
898 }
899 boolean trailingWhite = true;
900 while (trailingWhite && end > start+1) {
901 char c = str.charAt(end-1);
902 trailingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B' || c == '\uFEFF');
903 if (trailingWhite) {
904 end--;
905 }
906 }
907 return str.substring(start, end);
908 }
909
910 /**
911 * Runs an external command and returns the standard output.
912 *
913 * The program is expected to execute fast.
914 *
915 * @param command the command with arguments
916 * @return the output
917 * @throws IOException when there was an error, e.g. command does not exist
918 */
919 public static String execOutput(List<String> command) throws IOException {
920 if (Main.isDebugEnabled()) {
921 Main.debug(join(" ", command));
922 }
923 Process p = new ProcessBuilder(command).start();
924 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
925 StringBuilder all = null;
926 String line;
927 while ((line = input.readLine()) != null) {
928 if (all == null) {
929 all = new StringBuilder(line);
930 } else {
931 all.append("\n");
932 all.append(line);
933 }
934 }
935 return all != null ? all.toString() : null;
936 }
937 }
938
939 /**
940 * Returns the JOSM temp directory.
941 * @return The JOSM temp directory ({@code <java.io.tmpdir>/JOSM}), or {@code null} if {@code java.io.tmpdir} is not defined
942 * @since 6245
943 */
944 public static File getJosmTempDir() {
945 String tmpDir = System.getProperty("java.io.tmpdir");
946 if (tmpDir == null) {
947 return null;
948 }
949 File josmTmpDir = new File(tmpDir, "JOSM");
950 if (!josmTmpDir.exists() && !josmTmpDir.mkdirs()) {
951 Main.warn("Unable to create temp directory "+josmTmpDir);
952 }
953 return josmTmpDir;
954 }
955
956 /**
957 * Returns a simple human readable (hours, minutes, seconds) string for a given duration in milliseconds.
958 * @param elapsedTime The duration in milliseconds
959 * @return A human readable string for the given duration
960 * @throws IllegalArgumentException if elapsedTime is &lt; 0
961 * @since 6354
962 */
963 public static String getDurationString(long elapsedTime) {
964 if (elapsedTime < 0) {
965 throw new IllegalArgumentException("elapsedTime must be >= 0");
966 }
967 // Is it less than 1 second ?
968 if (elapsedTime < MILLIS_OF_SECOND) {
969 return String.format("%d %s", elapsedTime, tr("ms"));
970 }
971 // Is it less than 1 minute ?
972 if (elapsedTime < MILLIS_OF_MINUTE) {
973 return String.format("%.1f %s", elapsedTime / (float) MILLIS_OF_SECOND, tr("s"));
974 }
975 // Is it less than 1 hour ?
976 if (elapsedTime < MILLIS_OF_HOUR) {
977 final long min = elapsedTime / MILLIS_OF_MINUTE;
978 return String.format("%d %s %d %s", min, tr("min"), (elapsedTime - min * MILLIS_OF_MINUTE) / MILLIS_OF_SECOND, tr("s"));
979 }
980 // Is it less than 1 day ?
981 if (elapsedTime < MILLIS_OF_DAY) {
982 final long hour = elapsedTime / MILLIS_OF_HOUR;
983 return String.format("%d %s %d %s", hour, tr("h"), (elapsedTime - hour * MILLIS_OF_HOUR) / MILLIS_OF_MINUTE, tr("min"));
984 }
985 long days = elapsedTime / MILLIS_OF_DAY;
986 return String.format("%d %s %d %s", days, trn("day", "days", days), (elapsedTime - days * MILLIS_OF_DAY) / MILLIS_OF_HOUR, tr("h"));
987 }
988
989 /**
990 * Returns a human readable representation of a list of positions.
991 * <p>
992 * For instance, {@code [1,5,2,6,7} yields "1-2,5-7
993 * @param positionList a list of positions
994 * @return a human readable representation
995 */
996 public static String getPositionListString(List<Integer> positionList) {
997 Collections.sort(positionList);
998 final StringBuilder sb = new StringBuilder(32);
999 sb.append(positionList.get(0));
1000 int cnt = 0;
1001 int last = positionList.get(0);
1002 for (int i = 1; i < positionList.size(); ++i) {
1003 int cur = positionList.get(i);
1004 if (cur == last + 1) {
1005 ++cnt;
1006 } else if (cnt == 0) {
1007 sb.append(",").append(cur);
1008 } else {
1009 sb.append("-").append(last);
1010 sb.append(",").append(cur);
1011 cnt = 0;
1012 }
1013 last = cur;
1014 }
1015 if (cnt >= 1) {
1016 sb.append("-").append(last);
1017 }
1018 return sb.toString();
1019 }
1020
1021
1022 /**
1023 * Returns a list of capture groups if {@link Matcher#matches()}, or {@code null}.
1024 * The first element (index 0) is the complete match.
1025 * Further elements correspond to the parts in parentheses of the regular expression.
1026 * @param m the matcher
1027 * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
1028 */
1029 public static List<String> getMatches(final Matcher m) {
1030 if (m.matches()) {
1031 List<String> result = new ArrayList<>(m.groupCount() + 1);
1032 for (int i = 0; i <= m.groupCount(); i++) {
1033 result.add(m.group(i));
1034 }
1035 return result;
1036 } else {
1037 return null;
1038 }
1039 }
1040
1041 /**
1042 * Cast an object savely.
1043 * @param <T> the target type
1044 * @param o the object to cast
1045 * @param klass the target class (same as T)
1046 * @return null if <code>o</code> is null or the type <code>o</code> is not
1047 * a subclass of <code>klass</code>. The casted value otherwise.
1048 */
1049 @SuppressWarnings("unchecked")
1050 public static <T> T cast(Object o, Class<T> klass) {
1051 if (klass.isInstance(o)) {
1052 return (T) o;
1053 }
1054 return null;
1055 }
1056
1057 /**
1058 * Returns the root cause of a throwable object.
1059 * @param t The object to get root cause for
1060 * @return the root cause of {@code t}
1061 * @since 6639
1062 */
1063 public static Throwable getRootCause(Throwable t) {
1064 Throwable result = t;
1065 if (result != null) {
1066 Throwable cause = result.getCause();
1067 while (cause != null && !cause.equals(result)) {
1068 result = cause;
1069 cause = result.getCause();
1070 }
1071 }
1072 return result;
1073 }
1074
1075 /**
1076 * Adds the given item at the end of a new copy of given array.
1077 * @param array The source array
1078 * @param item The item to add
1079 * @return An extended copy of {@code array} containing {@code item} as additional last element
1080 * @since 6717
1081 */
1082 public static <T> T[] addInArrayCopy(T[] array, T item) {
1083 T[] biggerCopy = Arrays.copyOf(array, array.length + 1);
1084 biggerCopy[array.length] = item;
1085 return biggerCopy;
1086 }
1087
1088 /**
1089 * If the string {@code s} is longer than {@code maxLength}, the string is cut and "..." is appended.
1090 * @param s String to shorten
1091 * @param maxLength maximum number of characters to keep (not including the "...")
1092 * @return the shortened string
1093 */
1094 public static String shortenString(String s, int maxLength) {
1095 if (s != null && s.length() > maxLength) {
1096 return s.substring(0, maxLength - 3) + "...";
1097 } else {
1098 return s;
1099 }
1100 }
1101
1102 /**
1103 * Fixes URL with illegal characters in the query (and fragment) part by
1104 * percent encoding those characters.
1105 *
1106 * special characters like &amp; and # are not encoded
1107 *
1108 * @param url the URL that should be fixed
1109 * @return the repaired URL
1110 */
1111 public static String fixURLQuery(String url) {
1112 if (url.indexOf('?') == -1)
1113 return url;
1114
1115 String query = url.substring(url.indexOf('?') + 1);
1116
1117 StringBuilder sb = new StringBuilder(url.substring(0, url.indexOf('?') + 1));
1118
1119 for (int i=0; i<query.length(); i++) {
1120 String c = query.substring(i, i+1);
1121 if (URL_CHARS.contains(c)) {
1122 sb.append(c);
1123 } else {
1124 sb.append(encodeUrl(c));
1125 }
1126 }
1127 return sb.toString();
1128 }
1129
1130 /**
1131 * Translates a string into <code>application/x-www-form-urlencoded</code>
1132 * format. This method uses UTF-8 encoding scheme to obtain the bytes for unsafe
1133 * characters.
1134 *
1135 * @param s <code>String</code> to be translated.
1136 * @return the translated <code>String</code>.
1137 * @see #decodeUrl(String)
1138 * @since 8304
1139 */
1140 public static String encodeUrl(String s) {
1141 final String enc = StandardCharsets.UTF_8.name();
1142 try {
1143 return URLEncoder.encode(s, enc);
1144 } catch (UnsupportedEncodingException e) {
1145 Main.error(e);
1146 return null;
1147 }
1148 }
1149
1150 /**
1151 * Decodes a <code>application/x-www-form-urlencoded</code> string.
1152 * UTF-8 encoding is used to determine
1153 * what characters are represented by any consecutive sequences of the
1154 * form "<code>%<i>xy</i></code>".
1155 *
1156 * @param s the <code>String</code> to decode
1157 * @return the newly decoded <code>String</code>
1158 * @see #encodeUrl(String)
1159 * @since 8304
1160 */
1161 public static String decodeUrl(String s) {
1162 final String enc = StandardCharsets.UTF_8.name();
1163 try {
1164 return URLDecoder.decode(s, enc);
1165 } catch (UnsupportedEncodingException e) {
1166 Main.error(e);
1167 return null;
1168 }
1169 }
1170
1171 /**
1172 * Determines if the given URL denotes a file on a local filesystem.
1173 * @param url The URL to test
1174 * @return {@code true} if the url points to a local file
1175 * @since 7356
1176 */
1177 public static boolean isLocalUrl(String url) {
1178 if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("resource://"))
1179 return false;
1180 return true;
1181 }
1182
1183 /**
1184 * Returns a pair containing the number of threads (n), and a thread pool (if n > 1) to perform
1185 * multi-thread computation in the context of the given preference key.
1186 * @param pref The preference key
1187 * @return a pair containing the number of threads (n), and a thread pool (if n > 1, null otherwise)
1188 * @since 7423
1189 */
1190 public static Pair<Integer, ExecutorService> newThreadPool(String pref) {
1191 int noThreads = Main.pref.getInteger(pref, Runtime.getRuntime().availableProcessors());
1192 ExecutorService pool = noThreads <= 1 ? null : Executors.newFixedThreadPool(noThreads);
1193 return new Pair<>(noThreads, pool);
1194 }
1195
1196 /**
1197 * Updates a given system property.
1198 * @param key The property key
1199 * @param value The property value
1200 * @return the previous value of the system property, or {@code null} if it did not have one.
1201 * @since 7894
1202 */
1203 public static String updateSystemProperty(String key, String value) {
1204 if (value != null) {
1205 String old = System.setProperty(key, value);
1206 if (!key.toLowerCase().contains("password")) {
1207 Main.debug("System property '"+key+"' set to '"+value+"'. Old value was '"+old+"'");
1208 } else {
1209 Main.debug("System property '"+key+"' changed.");
1210 }
1211 return old;
1212 }
1213 return null;
1214 }
1215
1216 /**
1217 * Returns a new secure SAX parser, supporting XML namespaces.
1218 * @return a new secure SAX parser, supporting XML namespaces
1219 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
1220 * @throws SAXException for SAX errors.
1221 * @since 8287
1222 */
1223 public static SAXParser newSafeSAXParser() throws ParserConfigurationException, SAXException {
1224 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
1225 parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
1226 parserFactory.setNamespaceAware(true);
1227 return parserFactory.newSAXParser();
1228 }
1229}
Note: See TracBrowser for help on using the repository browser.