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

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

see #8505 - strip of trailing and leading whitespace characters of input URL (proper solution than String.trim(), see Utils.strip()) + unit test

  • Property svn:eol-style set to native
File size: 19.1 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.File;
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.OutputStream;
16import java.io.Reader;
17import java.io.UnsupportedEncodingException;
18import java.net.HttpURLConnection;
19import java.net.URL;
20import java.security.MessageDigest;
21import java.security.NoSuchAlgorithmException;
22import java.text.MessageFormat;
23import java.util.AbstractCollection;
24import java.util.AbstractList;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Iterator;
28import java.util.List;
29
30import org.openstreetmap.josm.data.Version;
31
32/**
33 * Basic utils, that can be useful in different parts of the program.
34 */
35public class Utils {
36
37 public static <T> boolean exists(Iterable<? extends T> collection, Predicate<? super T> predicate) {
38 for (T item : collection) {
39 if (predicate.evaluate(item))
40 return true;
41 }
42 return false;
43 }
44
45 public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) {
46 for (Object item : collection) {
47 if (klass.isInstance(item))
48 return true;
49 }
50 return false;
51 }
52
53 public static <T> T find(Iterable<? extends T> collection, Predicate<? super T> predicate) {
54 for (T item : collection) {
55 if (predicate.evaluate(item))
56 return item;
57 }
58 return null;
59 }
60
61 @SuppressWarnings("unchecked")
62 public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) {
63 for (Object item : collection) {
64 if (klass.isInstance(item))
65 return (T) item;
66 }
67 return null;
68 }
69
70 public static <T> Collection<T> filter(Collection<? extends T> collection, Predicate<? super T> predicate) {
71 return new FilteredCollection<T>(collection, predicate);
72 }
73
74 public static <T> T firstNonNull(T... items) {
75 for (T i : items) {
76 if (i != null) {
77 return i;
78 }
79 }
80 return null;
81 }
82
83 /**
84 * Filter a collection by (sub)class.
85 * This is an efficient read-only implementation.
86 */
87 public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> klass) {
88 return new SubclassFilteredCollection<S, T>(collection, new Predicate<S>() {
89 @Override
90 public boolean evaluate(S o) {
91 return klass.isInstance(o);
92 }
93 });
94 }
95
96 public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) {
97 int i = 0;
98 for (T item : collection) {
99 if (predicate.evaluate(item))
100 return i;
101 i++;
102 }
103 return -1;
104 }
105
106 /**
107 * Get minimum of 3 values
108 */
109 public static int min(int a, int b, int c) {
110 if (b < c) {
111 if (a < b)
112 return a;
113 return b;
114 } else {
115 if (a < c)
116 return a;
117 return c;
118 }
119 }
120
121 public static int max(int a, int b, int c, int d) {
122 return Math.max(Math.max(a, b), Math.max(c, d));
123 }
124
125 /**
126 * for convenience: test whether 2 objects are either both null or a.equals(b)
127 */
128 public static <T> boolean equal(T a, T b) {
129 if (a == b)
130 return true;
131 return (a != null && a.equals(b));
132 }
133
134 public static void ensure(boolean condition, String message, Object...data) {
135 if (!condition)
136 throw new AssertionError(
137 MessageFormat.format(message,data)
138 );
139 }
140
141 /**
142 * return the modulus in the range [0, n)
143 */
144 public static int mod(int a, int n) {
145 if (n <= 0)
146 throw new IllegalArgumentException();
147 int res = a % n;
148 if (res < 0) {
149 res += n;
150 }
151 return res;
152 }
153
154 /**
155 * Joins a list of strings (or objects that can be converted to string via
156 * Object.toString()) into a single string with fields separated by sep.
157 * @param sep the separator
158 * @param values collection of objects, null is converted to the
159 * empty string
160 * @return null if values is null. The joined string otherwise.
161 */
162 public static String join(String sep, Collection<?> values) {
163 if (sep == null)
164 throw new IllegalArgumentException();
165 if (values == null)
166 return null;
167 if (values.isEmpty())
168 return "";
169 StringBuilder s = null;
170 for (Object a : values) {
171 if (a == null) {
172 a = "";
173 }
174 if (s != null) {
175 s.append(sep).append(a.toString());
176 } else {
177 s = new StringBuilder(a.toString());
178 }
179 }
180 return s.toString();
181 }
182
183 public static String joinAsHtmlUnorderedList(Collection<?> values) {
184 StringBuilder sb = new StringBuilder(1024);
185 sb.append("<ul>");
186 for (Object i : values) {
187 sb.append("<li>").append(i).append("</li>");
188 }
189 sb.append("</ul>");
190 return sb.toString();
191 }
192
193 /**
194 * convert Color to String
195 * (Color.toString() omits alpha value)
196 */
197 public static String toString(Color c) {
198 if (c == null)
199 return "null";
200 if (c.getAlpha() == 255)
201 return String.format("#%06x", c.getRGB() & 0x00ffffff);
202 else
203 return String.format("#%06x(alpha=%d)", c.getRGB() & 0x00ffffff, c.getAlpha());
204 }
205
206 /**
207 * convert float range 0 <= x <= 1 to integer range 0..255
208 * when dealing with colors and color alpha value
209 * @return null if val is null, the corresponding int if val is in the
210 * range 0...1. If val is outside that range, return 255
211 */
212 public static Integer color_float2int(Float val) {
213 if (val == null)
214 return null;
215 if (val < 0 || val > 1)
216 return 255;
217 return (int) (255f * val + 0.5f);
218 }
219
220 /**
221 * convert back
222 */
223 public static Float color_int2float(Integer val) {
224 if (val == null)
225 return null;
226 if (val < 0 || val > 255)
227 return 1f;
228 return ((float) val) / 255f;
229 }
230
231 public static Color complement(Color clr) {
232 return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
233 }
234
235 public static int copyStream(InputStream source, OutputStream destination) throws IOException {
236 int count = 0;
237 byte[] b = new byte[512];
238 int read;
239 while ((read = source.read(b)) != -1) {
240 count += read;
241 destination.write(b, 0, read);
242 }
243 return count;
244 }
245
246 public static boolean deleteDirectory(File path) {
247 if( path.exists() ) {
248 File[] files = path.listFiles();
249 for(int i=0; i<files.length; i++) {
250 if(files[i].isDirectory()) {
251 deleteDirectory(files[i]);
252 }
253 else {
254 files[i].delete();
255 }
256 }
257 }
258 return( path.delete() );
259 }
260
261 /**
262 * <p>Utility method for closing an input stream.</p>
263 *
264 * @param is the input stream. May be null.
265 */
266 public static void close(InputStream is){
267 if (is == null) return;
268 try {
269 is.close();
270 } catch(IOException e){
271 // ignore
272 }
273 }
274
275 /**
276 * <p>Utility method for closing an output stream.</p>
277 *
278 * @param os the output stream. May be null.
279 */
280 public static void close(OutputStream os){
281 if (os == null) return;
282 try {
283 os.close();
284 } catch(IOException e){
285 // ignore
286 }
287 }
288
289 /**
290 * <p>Utility method for closing a reader.</p>
291 *
292 * @param reader the reader. May be null.
293 */
294 public static void close(Reader reader){
295 if (reader == null) return;
296 try {
297 reader.close();
298 } catch(IOException e){
299 // ignore
300 }
301 }
302
303 private final static double EPSILION = 1e-11;
304
305 public static boolean equalsEpsilon(double a, double b) {
306 return Math.abs(a - b) <= EPSILION;
307 }
308
309 /**
310 * Copies the string {@code s} to system clipboard.
311 * @param s string to be copied to clipboard.
312 * @return true if succeeded, false otherwise.
313 */
314 public static boolean copyToClipboard(String s) {
315 try {
316 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
317
318 @Override
319 public void lostOwnership(Clipboard clpbrd, Transferable t) {
320 }
321 });
322 return true;
323 } catch (IllegalStateException ex) {
324 ex.printStackTrace();
325 return false;
326 }
327 }
328
329 /**
330 * Extracts clipboard content as string.
331 * @return string clipboard contents if available, {@code null} otherwise.
332 */
333 public static String getClipboardContent() {
334 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
335 Transferable t = null;
336 for (int tries = 0; t == null && tries < 10; tries++) {
337 try {
338 t = clipboard.getContents(null);
339 } catch (IllegalStateException e) {
340 // Clipboard currently unavailable. On some platforms, the system clipboard is unavailable while it is accessed by another application.
341 try {
342 Thread.sleep(1);
343 } catch (InterruptedException ex) {
344 }
345 }
346 }
347 try {
348 if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
349 String text = (String) t.getTransferData(DataFlavor.stringFlavor);
350 return text;
351 }
352 } catch (UnsupportedFlavorException ex) {
353 ex.printStackTrace();
354 return null;
355 } catch (IOException ex) {
356 ex.printStackTrace();
357 return null;
358 }
359 return null;
360 }
361
362 /**
363 * Calculate MD5 hash of a string and output in hexadecimal format.
364 * @param data arbitrary String
365 * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f]
366 */
367 public static String md5Hex(String data) {
368 byte[] byteData = null;
369 try {
370 byteData = data.getBytes("UTF-8");
371 } catch (UnsupportedEncodingException e) {
372 throw new RuntimeException();
373 }
374 MessageDigest md = null;
375 try {
376 md = MessageDigest.getInstance("MD5");
377 } catch (NoSuchAlgorithmException e) {
378 throw new RuntimeException();
379 }
380 byte[] byteDigest = md.digest(byteData);
381 return toHexString(byteDigest);
382 }
383
384 /**
385 * Converts a byte array to a string of hexadecimal characters.
386 * Preserves leading zeros, so the size of the output string is always twice
387 * the number of input bytes.
388 * @param bytes the byte array
389 * @return hexadecimal representation
390 */
391 public static String toHexString(byte[] bytes) {
392 char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
393 char[] hexChars = new char[bytes.length * 2];
394 for (int j=0; j<bytes.length; j++) {
395 int v = bytes[j] & 0xFF;
396 hexChars[j*2] = hexArray[v/16];
397 hexChars[j*2 + 1] = hexArray[v%16];
398 }
399 return new String(hexChars);
400 }
401
402 /**
403 * Topological sort.
404 *
405 * @param dependencies contains mappings (key -> value). In the final list of sorted objects, the key will come
406 * after the value. (In other words, the key depends on the value(s).)
407 * There must not be cyclic dependencies.
408 * @return the list of sorted objects
409 */
410 public static <T> List<T> topologicalSort(final MultiMap<T,T> dependencies) {
411 MultiMap<T,T> deps = new MultiMap<T,T>();
412 for (T key : dependencies.keySet()) {
413 deps.putVoid(key);
414 for (T val : dependencies.get(key)) {
415 deps.putVoid(val);
416 deps.put(key, val);
417 }
418 }
419
420 int size = deps.size();
421 List<T> sorted = new ArrayList<T>();
422 for (int i=0; i<size; ++i) {
423 T parentless = null;
424 for (T key : deps.keySet()) {
425 if (deps.get(key).size() == 0) {
426 parentless = key;
427 break;
428 }
429 }
430 if (parentless == null) throw new RuntimeException();
431 sorted.add(parentless);
432 deps.remove(parentless);
433 for (T key : deps.keySet()) {
434 deps.remove(key, parentless);
435 }
436 }
437 if (sorted.size() != size) throw new RuntimeException();
438 return sorted;
439 }
440
441 /**
442 * Represents a function that can be applied to objects of {@code A} and
443 * returns objects of {@code B}.
444 * @param <A> class of input objects
445 * @param <B> class of transformed objects
446 */
447 public static interface Function<A, B> {
448
449 /**
450 * Applies the function on {@code x}.
451 * @param x an object of
452 * @return the transformed object
453 */
454 B apply(A x);
455 }
456
457 /**
458 * Transforms the collection {@code c} into an unmodifiable collection and
459 * applies the {@link Function} {@code f} on each element upon access.
460 * @param <A> class of input collection
461 * @param <B> class of transformed collection
462 * @param c a collection
463 * @param f a function that transforms objects of {@code A} to objects of {@code B}
464 * @return the transformed unmodifiable collection
465 */
466 public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) {
467 return new AbstractCollection<B>() {
468
469 @Override
470 public int size() {
471 return c.size();
472 }
473
474 @Override
475 public Iterator<B> iterator() {
476 return new Iterator<B>() {
477
478 private Iterator<? extends A> it = c.iterator();
479
480 @Override
481 public boolean hasNext() {
482 return it.hasNext();
483 }
484
485 @Override
486 public B next() {
487 return f.apply(it.next());
488 }
489
490 @Override
491 public void remove() {
492 throw new UnsupportedOperationException();
493 }
494 };
495 }
496 };
497 }
498
499 /**
500 * Transforms the list {@code l} into an unmodifiable list and
501 * applies the {@link Function} {@code f} on each element upon access.
502 * @param <A> class of input collection
503 * @param <B> class of transformed collection
504 * @param l a collection
505 * @param f a function that transforms objects of {@code A} to objects of {@code B}
506 * @return the transformed unmodifiable list
507 */
508 public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) {
509 return new AbstractList<B>() {
510
511
512 @Override
513 public int size() {
514 return l.size();
515 }
516
517 @Override
518 public B get(int index) {
519 return f.apply(l.get(index));
520 }
521
522
523 };
524 }
525
526 /**
527 * Convert Hex String to Color.
528 * @param s Must be of the form "#34a300" or "#3f2", otherwise throws Exception.
529 * Upper/lower case does not matter.
530 * @return The corresponding color.
531 */
532 static public Color hexToColor(String s) {
533 String clr = s.substring(1);
534 if (clr.length() == 3) {
535 clr = new String(new char[] {
536 clr.charAt(0), clr.charAt(0), clr.charAt(1), clr.charAt(1), clr.charAt(2), clr.charAt(2)
537 });
538 }
539 if (clr.length() != 6)
540 throw new IllegalArgumentException();
541 return new Color(Integer.parseInt(clr, 16));
542 }
543
544 /**
545 * Opens a HTTP connection to the given URL and sets the User-Agent property to JOSM's one.
546 * @param httpURL The HTTP url to open (must use http:// or https://)
547 * @return An open HTTP connection to the given URL
548 * @throws IOException if an I/O exception occurs.
549 * @since 5587
550 */
551 public static HttpURLConnection openHttpConnection(URL httpURL) throws IOException {
552 if (httpURL == null || !httpURL.getProtocol().matches("https?")) {
553 throw new IllegalArgumentException("Invalid HTTP url");
554 }
555 HttpURLConnection connection = (HttpURLConnection) httpURL.openConnection();
556 connection.setRequestProperty("User-Agent", Version.getInstance().getAgentString());
557 return connection;
558 }
559
560 /**
561 * Opens a HTTP connection to the given URL, sets the User-Agent property to JOSM's one and optionnaly disables Keep-Alive.
562 * @param httpURL The HTTP url to open (must use http:// or https://)
563 * @param keepAlive
564 * @return An open HTTP connection to the given URL
565 * @throws IOException if an I/O exception occurs.
566 * @since 5587
567 */
568 public static HttpURLConnection openHttpConnection(URL httpURL, boolean keepAlive) throws IOException {
569 HttpURLConnection connection = openHttpConnection(httpURL);
570 if (!keepAlive) {
571 connection.setRequestProperty("Connection", "close");
572 }
573 return connection;
574 }
575
576 /**
577 * An alternative to {@link String#trim()} to effectively remove all leading and trailing white characters, including Unicode ones.
578 * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java’s String.trim has a strange idea of whitespace</a>
579 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4080617">JDK bug 4080617</a>
580 * @param str The string to strip
581 * @return <code>str</code>, without leading and trailing characters, according to
582 * {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}.
583 * @since 5772
584 */
585 public static String strip(String str) {
586 if (str == null || str.isEmpty()) {
587 return str;
588 }
589 int start = 0, end = str.length();
590 boolean leadingWhite = true;
591 while (leadingWhite && start < end) {
592 char c = str.charAt(start);
593 if (leadingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c))) {
594 start++;
595 }
596 }
597 boolean trailingWhite = true;
598 while (trailingWhite && end > start+1) {
599 char c = str.charAt(end-1);
600 if (trailingWhite = (Character.isWhitespace(c) || Character.isSpaceChar(c))) {
601 end--;
602 }
603 }
604 return str.substring(start, end);
605 }
606}
Note: See TracBrowser for help on using the repository browser.