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

Last change on this file since 7253 was 7119, checked in by Don-vip, 10 years ago

code refactoring/cleanup/javadoc + fix bug in preset text comparator in menu

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