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

Last change on this file since 6340 was 6313, checked in by Don-vip, 11 years ago

cosmetics in error reporting

  • Property svn:eol-style set to native
File size: 24.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.awt.Color;
5import java.awt.Toolkit;
6import java.awt.datatransfer.Clipboard;
7import java.awt.datatransfer.ClipboardOwner;
8import java.awt.datatransfer.DataFlavor;
9import java.awt.datatransfer.StringSelection;
10import java.awt.datatransfer.Transferable;
11import java.awt.datatransfer.UnsupportedFlavorException;
12import java.io.BufferedReader;
13import java.io.Closeable;
14import java.io.File;
15import java.io.FileInputStream;
16import java.io.FileOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.InputStreamReader;
20import java.io.OutputStream;
21import java.io.UnsupportedEncodingException;
22import java.net.HttpURLConnection;
23import java.net.URL;
24import java.net.URLConnection;
25import java.nio.channels.FileChannel;
26import java.security.MessageDigest;
27import java.security.NoSuchAlgorithmException;
28import java.text.MessageFormat;
29import java.util.AbstractCollection;
30import java.util.AbstractList;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Iterator;
35import java.util.List;
36import java.util.zip.ZipFile;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.data.Version;
40
41/**
42 * Basic utils, that can be useful in different parts of the program.
43 */
44public class Utils {
45
46 public static <T> boolean exists(Iterable<? extends T> collection, Predicate<? super T> predicate) {
47 for (T item : collection) {
48 if (predicate.evaluate(item))
49 return true;
50 }
51 return false;
52 }
53
54 public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) {
55 for (Object item : collection) {
56 if (klass.isInstance(item))
57 return true;
58 }
59 return false;
60 }
61
62 public static <T> T find(Iterable<? extends T> collection, Predicate<? super T> predicate) {
63 for (T item : collection) {
64 if (predicate.evaluate(item))
65 return item;
66 }
67 return null;
68 }
69
70 @SuppressWarnings("unchecked")
71 public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) {
72 for (Object item : collection) {
73 if (klass.isInstance(item))
74 return (T) item;
75 }
76 return null;
77 }
78
79 public static <T> Collection<T> filter(Collection<? extends T> collection, Predicate<? super T> predicate) {
80 return new FilteredCollection<T>(collection, predicate);
81 }
82
83 public static <T> T firstNonNull(T... items) {
84 for (T i : items) {
85 if (i != null) {
86 return i;
87 }
88 }
89 return null;
90 }
91
92 /**
93 * Filter a collection by (sub)class.
94 * This is an efficient read-only implementation.
95 */
96 public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> klass) {
97 return new SubclassFilteredCollection<S, T>(collection, new Predicate<S>() {
98 @Override
99 public boolean evaluate(S o) {
100 return klass.isInstance(o);
101 }
102 });
103 }
104
105 public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) {
106 int i = 0;
107 for (T item : collection) {
108 if (predicate.evaluate(item))
109 return i;
110 i++;
111 }
112 return -1;
113 }
114
115 /**
116 * Get minimum of 3 values
117 */
118 public static int min(int a, int b, int c) {
119 if (b < c) {
120 if (a < b)
121 return a;
122 return b;
123 } else {
124 if (a < c)
125 return a;
126 return c;
127 }
128 }
129
130 public static int max(int a, int b, int c, int d) {
131 return Math.max(Math.max(a, b), Math.max(c, d));
132 }
133
134 /**
135 * for convenience: test whether 2 objects are either both null or a.equals(b)
136 */
137 public static <T> boolean equal(T a, T b) {
138 if (a == b)
139 return true;
140 return (a != null && a.equals(b));
141 }
142
143 public static void ensure(boolean condition, String message, Object...data) {
144 if (!condition)
145 throw new AssertionError(
146 MessageFormat.format(message,data)
147 );
148 }
149
150 /**
151 * return the modulus in the range [0, n)
152 */
153 public static int mod(int a, int n) {
154 if (n <= 0)
155 throw new IllegalArgumentException();
156 int res = a % n;
157 if (res < 0) {
158 res += n;
159 }
160 return res;
161 }
162
163 /**
164 * Joins a list of strings (or objects that can be converted to string via
165 * Object.toString()) into a single string with fields separated by sep.
166 * @param sep the separator
167 * @param values collection of objects, null is converted to the
168 * empty string
169 * @return null if values is null. The joined string otherwise.
170 */
171 public static String join(String sep, Collection<?> values) {
172 if (sep == null)
173 throw new IllegalArgumentException();
174 if (values == null)
175 return null;
176 if (values.isEmpty())
177 return "";
178 StringBuilder s = null;
179 for (Object a : values) {
180 if (a == null) {
181 a = "";
182 }
183 if (s != null) {
184 s.append(sep).append(a.toString());
185 } else {
186 s = new StringBuilder(a.toString());
187 }
188 }
189 return s.toString();
190 }
191
192 public static String joinAsHtmlUnorderedList(Collection<?> values) {
193 StringBuilder sb = new StringBuilder(1024);
194 sb.append("<ul>");
195 for (Object i : values) {
196 sb.append("<li>").append(i).append("</li>");
197 }
198 sb.append("</ul>");
199 return sb.toString();
200 }
201
202 /**
203 * convert Color to String
204 * (Color.toString() omits alpha value)
205 */
206 public static String toString(Color c) {
207 if (c == null)
208 return "null";
209 if (c.getAlpha() == 255)
210 return String.format("#%06x", c.getRGB() & 0x00ffffff);
211 else
212 return String.format("#%06x(alpha=%d)", c.getRGB() & 0x00ffffff, c.getAlpha());
213 }
214
215 /**
216 * convert float range 0 <= x <= 1 to integer range 0..255
217 * when dealing with colors and color alpha value
218 * @return null if val is null, the corresponding int if val is in the
219 * range 0...1. If val is outside that range, return 255
220 */
221 public static Integer color_float2int(Float val) {
222 if (val == null)
223 return null;
224 if (val < 0 || val > 1)
225 return 255;
226 return (int) (255f * val + 0.5f);
227 }
228
229 /**
230 * convert back
231 */
232 public static Float color_int2float(Integer val) {
233 if (val == null)
234 return null;
235 if (val < 0 || val > 255)
236 return 1f;
237 return ((float) val) / 255f;
238 }
239
240 public static Color complement(Color clr) {
241 return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
242 }
243
244 /**
245 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
246 * @param array The array to copy
247 * @return A copy of the original array, or {@code null} if {@code array} is null
248 * @since 6221
249 */
250 public static <T> T[] copyArray(T[] array) {
251 if (array != null) {
252 return Arrays.copyOf(array, array.length);
253 }
254 return null;
255 }
256
257 /**
258 * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
259 * @param array The array to copy
260 * @return A copy of the original array, or {@code null} if {@code array} is null
261 * @since 6222
262 */
263 public static char[] copyArray(char[] array) {
264 if (array != null) {
265 return Arrays.copyOf(array, array.length);
266 }
267 return null;
268 }
269
270 /**
271 * Simple file copy function that will overwrite the target file.<br/>
272 * Taken from <a href="http://www.rgagnon.com/javadetails/java-0064.html">this article</a> (CC-NC-BY-SA)
273 * @param in The source file
274 * @param out The destination file
275 * @throws IOException If any I/O error occurs
276 */
277 public static void copyFile(File in, File out) throws IOException {
278 // TODO: remove this function when we move to Java 7 (use Files.copy instead)
279 FileInputStream inStream = null;
280 FileOutputStream outStream = null;
281 try {
282 inStream = new FileInputStream(in);
283 outStream = new FileOutputStream(out);
284 FileChannel inChannel = inStream.getChannel();
285 inChannel.transferTo(0, inChannel.size(), outStream.getChannel());
286 }
287 catch (IOException e) {
288 throw e;
289 }
290 finally {
291 close(outStream);
292 close(inStream);
293 }
294 }
295
296 public static int copyStream(InputStream source, OutputStream destination) throws IOException {
297 int count = 0;
298 byte[] b = new byte[512];
299 int read;
300 while ((read = source.read(b)) != -1) {
301 count += read;
302 destination.write(b, 0, read);
303 }
304 return count;
305 }
306
307 public static boolean deleteDirectory(File path) {
308 if( path.exists() ) {
309 File[] files = path.listFiles();
310 for (File file : files) {
311 if (file.isDirectory()) {
312 deleteDirectory(file);
313 } else {
314 file.delete();
315 }
316 }
317 }
318 return( path.delete() );
319 }
320
321 /**
322 * <p>Utility method for closing a {@link Closeable} object.</p>
323 *
324 * @param c the closeable object. May be null.
325 */
326 public static void close(Closeable c) {
327 if (c == null) return;
328 try {
329 c.close();
330 } catch (IOException e) {
331 Main.warn(e);
332 }
333 }
334
335 /**
336 * <p>Utility method for closing a {@link ZipFile}.</p>
337 *
338 * @param zip the zip file. May be null.
339 */
340 public static void close(ZipFile zip) {
341 if (zip == null) return;
342 try {
343 zip.close();
344 } catch (IOException e) {
345 Main.warn(e);
346 }
347 }
348
349 private final static double EPSILON = 1e-11;
350
351 /**
352 * Determines if the two given double values are equal (their delta being smaller than a fixed epsilon)
353 * @param a The first double value to compare
354 * @param b The second double value to compare
355 * @return {@code true} if {@code abs(a - b) <= 1e-11}, {@code false} otherwise
356 */
357 public static boolean equalsEpsilon(double a, double b) {
358 return Math.abs(a - b) <= EPSILON;
359 }
360
361 /**
362 * Copies the string {@code s} to system clipboard.
363 * @param s string to be copied to clipboard.
364 * @return true if succeeded, false otherwise.
365 */
366 public static boolean copyToClipboard(String s) {
367 try {
368 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
369
370 @Override
371 public void lostOwnership(Clipboard clpbrd, Transferable t) {
372 }
373 });
374 return true;
375 } catch (IllegalStateException ex) {
376 ex.printStackTrace();
377 return false;
378 }
379 }
380
381 /**
382 * Extracts clipboard content as string.
383 * @return string clipboard contents if available, {@code null} otherwise.
384 */
385 public static String getClipboardContent() {
386 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
387 Transferable t = null;
388 for (int tries = 0; t == null && tries < 10; tries++) {
389 try {
390 t = clipboard.getContents(null);
391 } catch (IllegalStateException e) {
392 // Clipboard currently unavailable. On some platforms, the system clipboard is unavailable while it is accessed by another application.
393 try {
394 Thread.sleep(1);
395 } catch (InterruptedException ex) {
396 Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content");
397 }
398 }
399 }
400 try {
401 if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
402 String text = (String) t.getTransferData(DataFlavor.stringFlavor);
403 return text;
404 }
405 } catch (UnsupportedFlavorException ex) {
406 ex.printStackTrace();
407 return null;
408 } catch (IOException ex) {
409 ex.printStackTrace();
410 return null;
411 }
412 return null;
413 }
414
415 /**
416 * Calculate MD5 hash of a string and output in hexadecimal format.
417 * @param data arbitrary String
418 * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f]
419 */
420 public static String md5Hex(String data) {
421 byte[] byteData = null;
422 try {
423 byteData = data.getBytes("UTF-8");
424 } catch (UnsupportedEncodingException e) {
425 throw new RuntimeException();
426 }
427 MessageDigest md = null;
428 try {
429 md = MessageDigest.getInstance("MD5");
430 } catch (NoSuchAlgorithmException e) {
431 throw new RuntimeException();
432 }
433 byte[] byteDigest = md.digest(byteData);
434 return toHexString(byteDigest);
435 }
436
437 private static final char[] HEX_ARRAY = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
438
439 /**
440 * Converts a byte array to a string of hexadecimal characters.
441 * Preserves leading zeros, so the size of the output string is always twice
442 * the number of input bytes.
443 * @param bytes the byte array
444 * @return hexadecimal representation
445 */
446 public static String toHexString(byte[] bytes) {
447
448 if (bytes == null) {
449 return "";
450 }
451
452 final int len = bytes.length;
453 if (len == 0) {
454 return "";
455 }
456
457 char[] hexChars = new char[len * 2];
458 for (int i = 0, j = 0; i < len; i++) {
459 final int v = bytes[i];
460 hexChars[j++] = HEX_ARRAY[(v & 0xf0) >> 4];
461 hexChars[j++] = HEX_ARRAY[v & 0xf];
462 }
463 return new String(hexChars);
464 }
465
466 /**
467 * Topological sort.
468 *
469 * @param dependencies contains mappings (key -> value). In the final list of sorted objects, the key will come
470 * after the value. (In other words, the key depends on the value(s).)
471 * There must not be cyclic dependencies.
472 * @return the list of sorted objects
473 */
474 public static <T> List<T> topologicalSort(final MultiMap<T,T> dependencies) {
475 MultiMap<T,T> deps = new MultiMap<T,T>();
476 for (T key : dependencies.keySet()) {
477 deps.putVoid(key);
478 for (T val : dependencies.get(key)) {
479 deps.putVoid(val);
480 deps.put(key, val);
481 }
482 }
483
484 int size = deps.size();
485 List<T> sorted = new ArrayList<T>();
486 for (int i=0; i<size; ++i) {
487 T parentless = null;
488 for (T key : deps.keySet()) {
489 if (deps.get(key).isEmpty()) {
490 parentless = key;
491 break;
492 }
493 }
494 if (parentless == null) throw new RuntimeException();
495 sorted.add(parentless);
496 deps.remove(parentless);
497 for (T key : deps.keySet()) {
498 deps.remove(key, parentless);
499 }
500 }
501 if (sorted.size() != size) throw new RuntimeException();
502 return sorted;
503 }
504
505 /**
506 * Represents a function that can be applied to objects of {@code A} and
507 * returns objects of {@code B}.
508 * @param <A> class of input objects
509 * @param <B> class of transformed objects
510 */
511 public static interface Function<A, B> {
512
513 /**
514 * Applies the function on {@code x}.
515 * @param x an object of
516 * @return the transformed object
517 */
518 B apply(A x);
519 }
520
521 /**
522 * Transforms the collection {@code c} into an unmodifiable collection and
523 * applies the {@link Function} {@code f} on each element upon access.
524 * @param <A> class of input collection
525 * @param <B> class of transformed collection
526 * @param c a collection
527 * @param f a function that transforms objects of {@code A} to objects of {@code B}
528 * @return the transformed unmodifiable collection
529 */
530 public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) {
531 return new AbstractCollection<B>() {
532
533 @Override
534 public int size() {
535 return c.size();
536 }
537
538 @Override
539 public Iterator<B> iterator() {
540 return new Iterator<B>() {
541
542 private Iterator<? extends A> it = c.iterator();
543
544 @Override
545 public boolean hasNext() {
546 return it.hasNext();
547 }
548
549 @Override
550 public B next() {
551 return f.apply(it.next());
552 }
553
554 @Override
555 public void remove() {
556 throw new UnsupportedOperationException();
557 }
558 };
559 }
560 };
561 }
562
563 /**
564 * Transforms the list {@code l} into an unmodifiable list and
565 * applies the {@link Function} {@code f} on each element upon access.
566 * @param <A> class of input collection
567 * @param <B> class of transformed collection
568 * @param l a collection
569 * @param f a function that transforms objects of {@code A} to objects of {@code B}
570 * @return the transformed unmodifiable list
571 */
572 public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) {
573 return new AbstractList<B>() {
574
575
576 @Override
577 public int size() {
578 return l.size();
579 }
580
581 @Override
582 public B get(int index) {
583 return f.apply(l.get(index));
584 }
585
586
587 };
588 }
589
590 /**
591 * Convert Hex String to Color.
592 * @param s Must be of the form "#34a300" or "#3f2", otherwise throws Exception.
593 * Upper/lower case does not matter.
594 * @return The corresponding color.
595 */
596 static public Color hexToColor(String s) {
597 String clr = s.substring(1);
598 if (clr.length() == 3) {
599 clr = new String(new char[] {
600 clr.charAt(0), clr.charAt(0), clr.charAt(1), clr.charAt(1), clr.charAt(2), clr.charAt(2)
601 });
602 }
603 if (clr.length() != 6)
604 throw new IllegalArgumentException();
605 return new Color(Integer.parseInt(clr, 16));
606 }
607
608 /**
609 * Opens a HTTP connection to the given URL and sets the User-Agent property to JOSM's one.
610 * @param httpURL The HTTP url to open (must use http:// or https://)
611 * @return An open HTTP connection to the given URL
612 * @throws IOException if an I/O exception occurs.
613 * @since 5587
614 */
615 public static HttpURLConnection openHttpConnection(URL httpURL) throws IOException {
616 if (httpURL == null || !httpURL.getProtocol().matches("https?")) {
617 throw new IllegalArgumentException("Invalid HTTP url");
618 }
619 HttpURLConnection connection = (HttpURLConnection) httpURL.openConnection();
620 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
621 connection.setUseCaches(false);
622 return connection;
623 }
624
625 /**
626 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one.
627 * @param url The url to open
628 * @return An stream for the given URL
629 * @throws IOException if an I/O exception occurs.
630 * @since 5867
631 */
632 public static InputStream openURL(URL url) throws IOException {
633 return setupURLConnection(url.openConnection()).getInputStream();
634 }
635
636 /***
637 * Setups the given URL connection to match JOSM needs by setting its User-Agent and timeout properties.
638 * @param connection The connection to setup
639 * @return {@code connection}, with updated properties
640 * @since 5887
641 */
642 public static URLConnection setupURLConnection(URLConnection connection) {
643 if (connection != null) {
644 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
645 connection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
646 connection.setReadTimeout(Main.pref.getInteger("socket.timeout.read",30)*1000);
647 }
648 return connection;
649 }
650
651 /**
652 * Opens a connection to the given URL and sets the User-Agent property to JOSM's one.
653 * @param url The url to open
654 * @return An buffered stream reader for the given URL (using UTF-8)
655 * @throws IOException if an I/O exception occurs.
656 * @since 5868
657 */
658 public static BufferedReader openURLReader(URL url) throws IOException {
659 return new BufferedReader(new InputStreamReader(openURL(url), "utf-8"));
660 }
661
662 /**
663 * Opens a HTTP connection to the given URL, sets the User-Agent property to JOSM's one and optionnaly disables Keep-Alive.
664 * @param httpURL The HTTP url to open (must use http:// or https://)
665 * @param keepAlive
666 * @return An open HTTP connection to the given URL
667 * @throws IOException if an I/O exception occurs.
668 * @since 5587
669 */
670 public static HttpURLConnection openHttpConnection(URL httpURL, boolean keepAlive) throws IOException {
671 HttpURLConnection connection = openHttpConnection(httpURL);
672 if (!keepAlive) {
673 connection.setRequestProperty("Connection", "close");
674 }
675 return connection;
676 }
677
678 /**
679 * An alternative to {@link String#trim()} to effectively remove all leading and trailing white characters, including Unicode ones.
680 * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java’s String.trim has a strange idea of whitespace</a>
681 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4080617">JDK bug 4080617</a>
682 * @param str The string to strip
683 * @return <code>str</code>, without leading and trailing characters, according to
684 * {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}.
685 * @since 5772
686 */
687 public static String strip(String str) {
688 if (str == null || str.isEmpty()) {
689 return str;
690 }
691 int start = 0, end = str.length();
692 boolean leadingWhite = true;
693 while (leadingWhite && start < end) {
694 char c = str.charAt(start);
695 // '\u200B' (ZERO WIDTH SPACE character) needs to be handled manually because of change in Unicode 6.0 (Java 7, see #8918)
696 leadingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B');
697 if (leadingWhite) {
698 start++;
699 }
700 }
701 boolean trailingWhite = true;
702 while (trailingWhite && end > start+1) {
703 char c = str.charAt(end-1);
704 trailingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\u200B');
705 if (trailingWhite) {
706 end--;
707 }
708 }
709 return str.substring(start, end);
710 }
711
712 /**
713 * Runs an external command and returns the standard output.
714 *
715 * The program is expected to execute fast.
716 *
717 * @param command the command with arguments
718 * @return the output
719 * @throws IOException when there was an error, e.g. command does not exist
720 */
721 public static String execOutput(List<String> command) throws IOException {
722 Process p = new ProcessBuilder(command).start();
723 BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
724 StringBuilder all = null;
725 String line;
726 while ((line = input.readLine()) != null) {
727 if (all == null) {
728 all = new StringBuilder(line);
729 } else {
730 all.append("\n");
731 all.append(line);
732 }
733 }
734 Utils.close(input);
735 return all.toString();
736 }
737
738 /**
739 * Returns the JOSM temp directory.
740 * @return The JOSM temp directory ({@code <java.io.tmpdir>/JOSM}), or {@code null} if {@code java.io.tmpdir} is not defined
741 * @since 6245
742 */
743 public static File getJosmTempDir() {
744 String tmpDir = System.getProperty("java.io.tmpdir");
745 if (tmpDir == null) {
746 return null;
747 }
748 File josmTmpDir = new File(tmpDir, "JOSM");
749 if (!josmTmpDir.exists()) {
750 if (!josmTmpDir.mkdirs()) {
751 Main.warn("Unable to create temp directory "+josmTmpDir);
752 }
753 }
754 return josmTmpDir;
755 }
756}
Note: See TracBrowser for help on using the repository browser.