View Javadoc

1   /***
2    *     Ambient - A music player for the Android platform
3    Copyright (C) 2007 Martin Vysny
4    
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9    
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14  
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  package sk.baka.ambient.commons;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.Closeable;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.ObjectInputStream;
28  import java.io.ObjectOutputStream;
29  import java.io.ObjectStreamClass;
30  import java.io.OutputStream;
31  import java.io.PrintWriter;
32  import java.io.Serializable;
33  import java.io.StringWriter;
34  import java.net.ServerSocket;
35  import java.net.Socket;
36  import java.text.ParseException;
37  import java.text.SimpleDateFormat;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Date;
41  import java.util.Formatter;
42  import java.util.List;
43  import java.util.Locale;
44  
45  import sk.baka.ambient.AmbientApplication;
46  import android.util.Log;
47  
48  /***
49   * Miscellaneous utility methods.
50   * 
51   * @author Martin Vysny
52   */
53  public final class MiscUtils {
54  	private MiscUtils() {
55  		throw new Error();
56  	}
57  
58  	/***
59  	 * Compares two objects. Handles <code>null</code>s correctly. For return
60  	 * value please see the {@link Comparable#compareTo(Object)} contract.
61  	 * 
62  	 * @param <T>
63  	 *            the object type
64  	 * @param o1
65  	 *            first object
66  	 * @param o2
67  	 *            second object
68  	 * @param nullFirst
69  	 *            if <code>true</code> then <code>null</code> is less than any
70  	 *            non-<code>null</code> object. If <code>false</code> then
71  	 *            <code>null</code> is greater than any non-null object.
72  	 * @return see {@link Comparable#compareTo(Object)} for details.
73  	 */
74  	public static <T extends Comparable<? super T>> int nullCompare(final T o1,
75  			final T o2, final boolean nullFirst) {
76  		final int nullCompareToObject = nullFirst ? -1 : 1;
77  		return o1 == null ? (o2 == null ? 0 : nullCompareToObject)
78  				: (o2 == null ? -nullCompareToObject : o1.compareTo(o2));
79  	}
80  
81  	/***
82  	 * Returns next enum constant. If there is no next constant then returns the
83  	 * first one.
84  	 * 
85  	 * @param <T>
86  	 *            the enum type
87  	 * @param constant
88  	 *            the constant, must not be <code>null</code>.
89  	 * @param next
90  	 *            if <code>true</code> then next constant is returned, if
91  	 *            <code>false</code> then simply given constant is returned.
92  	 * @return the next constant.
93  	 */
94  	public static <T extends Enum<T>> T nextOrThis(final T constant,
95  			final boolean next) {
96  		if (!next)
97  			return constant;
98  		final Class<T> clazz = constant.getDeclaringClass();
99  		final T[] values = clazz.getEnumConstants();
100 		int ordinal = constant.ordinal() + 1;
101 		if (ordinal >= values.length)
102 			ordinal = 0;
103 		return values[ordinal];
104 	}
105 
106 	/***
107 	 * Returns all interfaces that given class and all superclasses implements.
108 	 * 
109 	 * @param clazz
110 	 *            the class to analyze
111 	 * @return interface list that the class is castable to.
112 	 */
113 	public static List<Class<?>> getInterfaces(final Class<?> clazz) {
114 		Class<?> cl = clazz;
115 		final List<Class<?>> result = new ArrayList<Class<?>>();
116 		for (; cl != Object.class; cl = cl.getSuperclass()) {
117 			final List<Class<?>> interfaces = Arrays.<Class<?>>asList(cl.getInterfaces());
118 			result.addAll(interfaces);
119 		}
120 		return result;
121 	}
122 
123 	/***
124 	 * If given string is empty, contains spaces only or is <code>null</code>
125 	 * then <code>null</code> is returned.
126 	 * 
127 	 * @param str
128 	 *            the string.
129 	 * @return string or <code>null</code> if one of the above applies.
130 	 */
131 	public static String nullIfEmptyOrWhitespace(final String str) {
132 		if (str == null)
133 			return null;
134 		if (str.trim().length() == 0)
135 			return null;
136 		return str;
137 	}
138 
139 	/***
140 	 * Returns given string. If given string is <code>null</code> then an empty
141 	 * string is returned.
142 	 * 
143 	 * @param str
144 	 *            the string.
145 	 * @return non-<code>null</code> string.
146 	 */
147 	public static String emptyIfNull(final String str) {
148 		return str == null ? "" : str;
149 	}
150 
151 	/***
152 	 * Checks if given string is empty or consists of whitespace characters
153 	 * only.
154 	 * 
155 	 * @param str
156 	 *            the string to check.
157 	 * @return <code>true</code> if the string is empty, <code>false</code>
158 	 *         otherwise.
159 	 */
160 	public static boolean isEmptyOrWhitespace(final String str) {
161 		return null == nullIfEmptyOrWhitespace(str);
162 	}
163 
164 	/***
165 	 * Checks if given string is empty (<code>null</code> or zero-sized).
166 	 * 
167 	 * @param str
168 	 *            the string to check.
169 	 * @return <code>true</code> if the string is empty, <code>false</code>
170 	 *         otherwise.
171 	 */
172 	public static boolean isEmpty(final String str) {
173 		return (str == null) || (str.length() == 0);
174 	}
175 
176 	/***
177 	 * Formats given string. Uses {@link Formatter}.
178 	 * 
179 	 * @param resId
180 	 *            formatting string id
181 	 * @param args
182 	 *            parameters
183 	 * @return formatted string
184 	 */
185 	public static String format(final int resId, Object... args) {
186 		final String formatStr = AmbientApplication.getInstance().getString(
187 				resId);
188 		return new Formatter().format(formatStr, args).toString();
189 	}
190 
191 	/***
192 	 * Similar to {@link #nullCompare(Comparable, Comparable, boolean)}, but
193 	 * uses {@link #equals(Object)} instead.
194 	 * 
195 	 * @param <T>
196 	 *            the object type
197 	 * @param o1
198 	 *            first object
199 	 * @param o2
200 	 *            second object
201 	 * @return <code>true</code> if either o1 is equal to o2, or both are
202 	 *         <code>null</code>s; <code>false</code> otherwise.
203 	 */
204 	public static <T> boolean nullEquals(final T o1, final T o2) {
205 		return o1 == null ? o2 == null : o1.equals(o2);
206 	}
207 
208 	/***
209 	 * Deserializes an object from contents of given file.
210 	 * 
211 	 * @param file
212 	 *            the file to read
213 	 * @return deserialized object.
214 	 * @throws IOException
215 	 */
216 	public static Serializable deserializeFromFile(final File file)
217 			throws IOException {
218 		return deserialize(new FileInputStream(file));
219 	}
220 
221 	/***
222 	 * Deserializes an object from an input stream. The stream is closed.
223 	 * 
224 	 * @param stream
225 	 *            the stream to read
226 	 * @return deserialized object.
227 	 * @throws IOException
228 	 */
229 	public static Serializable deserialize(final InputStream stream)
230 			throws IOException {
231 		if (stream == null)
232 			return null;
233 		try {
234 			final ObjectInputStream in = new ObjectInputStream(stream) {
235 				@Override
236 				protected Class<?> resolveClass(
237 						ObjectStreamClass objectStreamClass)
238 						throws ClassNotFoundException {
239 					return Class.forName(objectStreamClass.getName(), true,
240 							MiscUtils.class.getClassLoader());
241 				}
242 			};
243 			try {
244 				return (Serializable) in.readObject();
245 			} catch (ClassNotFoundException e) {
246 				throw new RuntimeException(e);
247 			}
248 		} finally {
249 			closeQuietly(stream);
250 		}
251 	}
252 
253 	/***
254 	 * Serializes given object to the output stream. The stream is closed.
255 	 * 
256 	 * @param object
257 	 *            the object to serialize
258 	 * @param out
259 	 *            serialize here
260 	 * @throws IOException
261 	 */
262 	public static void serialize(final Serializable object,
263 			final OutputStream out) throws IOException {
264 		try {
265 			final ObjectOutputStream oout = new ObjectOutputStream(out);
266 			oout.writeObject(object);
267 			closeQuietly(oout);
268 		} finally {
269 			closeQuietly(out);
270 		}
271 	}
272 
273 	/***
274 	 * Serializes given object to the output stream. The stream is closed.
275 	 * 
276 	 * @param object
277 	 *            the object to serialize
278 	 * @return serialized form
279 	 */
280 	public static byte[] serializeToBytes(final Serializable object) {
281 		if (object == null)
282 			return null;
283 		final ByteArrayOutputStream out = new ByteArrayOutputStream();
284 		try {
285 			serialize(object, out);
286 		} catch (IOException e) {
287 			throw new RuntimeException(e);
288 		}
289 		return out.toByteArray();
290 	}
291 
292 	/***
293 	 * Deserializes an object from a byte array.
294 	 * 
295 	 * @param serialized
296 	 *            the serialized object form
297 	 * @return deserialized object.
298 	 */
299 	public static Serializable deserialize(final byte[] serialized) {
300 		if (serialized == null)
301 			return null;
302 		try {
303 			return deserialize(new ByteArrayInputStream(serialized));
304 		} catch (IOException e) {
305 			// shouldn't happen unless the stream is corrupted
306 			throw new RuntimeException(e);
307 		}
308 	}
309 
310 	/***
311 	 * Closes the closeable object. Catches any exception.
312 	 * 
313 	 * @param c
314 	 *            close this closeable. Does nothing if the stream is
315 	 *            <code>null</code>.
316 	 */
317 	public static void closeQuietly(final Closeable c) {
318 		if (c == null) {
319 			return;
320 		}
321 		try {
322 			c.close();
323 		} catch (Exception e) {
324 			Log.w(MiscUtils.class.getSimpleName(), "Failed to close object", e);
325 		}
326 	}
327 
328 	/***
329 	 * Closes a socket quietly.
330 	 * 
331 	 * @param socket
332 	 *            the socket to close.
333 	 */
334 	public static void closeQuietly(final Socket socket) {
335 		closeQuietly(new Closeable() {
336 			public void close() throws IOException {
337 				socket.close();
338 			}
339 		});
340 	}
341 
342 	/***
343 	 * Closes a server socket quietly.
344 	 * 
345 	 * @param socket
346 	 *            the socket to close.
347 	 */
348 	public static void closeQuietly(final ServerSocket socket) {
349 		closeQuietly(new Closeable() {
350 			public void close() throws IOException {
351 				socket.close();
352 			}
353 		});
354 	}
355 
356 	/***
357 	 * Iterates over given iterable and returns its items as a list.
358 	 * 
359 	 * @param <T>
360 	 *            the item type
361 	 * @param i
362 	 *            the iterable
363 	 * @return list, never <code>null</code>.
364 	 */
365 	public static <T> List<T> iterableToList(final Iterable<? extends T> i) {
366 		final List<T> result = new ArrayList<T>();
367 		for (final T t : i) {
368 			result.add(t);
369 		}
370 		return result;
371 	}
372 
373 	/***
374 	 * SI prefixes.
375 	 */
376 	private static final char[] PREFIXES = new char[] { 'k', 'M', 'G', 'T', 'P' };
377 
378 	/***
379 	 * Formats the length and returns it.
380 	 * 
381 	 * @param length
382 	 *            in bytes
383 	 * @return xxKb/xxMb/etc
384 	 */
385 	public static String formatByteLength(final long length) {
386 		long len = length * 10; // show a single-digit decimal fraction
387 		int prefix = -1;
388 		while ((len > 10240) && (prefix < PREFIXES.length)) {
389 			len /= 1024;
390 			prefix++;
391 		}
392 		final StringBuilder result = new StringBuilder();
393 		if (prefix < 0) {
394 			result.append(length);
395 			result.append('b');
396 			return result.toString();
397 		}
398 		result.append(len);
399 		result.insert(result.length() - 1, '.');
400 		if (result.charAt(result.length() - 1) == '0') {
401 			result.delete(result.length() - 2, result.length());
402 		}
403 		result.append(PREFIXES[prefix]);
404 		result.append('b');
405 		return result.toString();
406 	}
407 
408 	/***
409 	 * Moves the leading "the" word to the end of given name.
410 	 * 
411 	 * @param artistAlbum
412 	 *            an artist/album name
413 	 * @return fixed name
414 	 */
415 	public static String fixArtistAlbumName(final String artistAlbum) {
416 		if (artistAlbum == null) {
417 			return null;
418 		}
419 		final String name = artistAlbum.trim();
420 		if (name.length() < 5) {
421 			return name;
422 		}
423 		if (!name.substring(0, 3).equalsIgnoreCase("the")
424 				|| !Character.isWhitespace(name.charAt(3))) {
425 			return name;
426 		}
427 		int i = 4;
428 		for (; (i < name.length()) && Character.isWhitespace(name.charAt(i)); i++) {
429 		}
430 		return name.substring(i) + ", " + name.substring(0, 3);
431 	}
432 
433 	/***
434 	 * May be used in maps and/or sets which does not support <code>null</code>.
435 	 */
436 	public static final Object NULL = new Object();
437 
438 	/***
439 	 * Wraps given object to a reference. The purpose of this reference is to
440 	 * enforce reference-equality in place of object-equality. In other words,
441 	 * two references returned by this method are considered equal if and only
442 	 * if <code>r1.get() == r2.get()</code>.
443 	 * 
444 	 * @param <T>
445 	 *            the object type.
446 	 * @param object
447 	 *            the object instance. <code>null</code>s are handled correctly,
448 	 *            i.e. <code>null</code> is only equal to <code>null</code>.
449 	 * @return a reference to given object.
450 	 */
451 	public static <T> Ref<T> getIdentity(final T object) {
452 		return new Ref<T>() {
453 			public T get() {
454 				return object;
455 			}
456 
457 			@Override
458 			public boolean equals(Object o) {
459 				if (o == null)
460 					return false;
461 				if (o == this)
462 					return true;
463 				if (o.getClass() != getClass())
464 					return false;
465 				return ((Ref<?>) o).get() == object;
466 			}
467 
468 			@Override
469 			public int hashCode() {
470 				return object == null ? 0 : System.identityHashCode(object);
471 			}
472 		};
473 	}
474 
475 	/***
476 	 * Returns object hash code. Returns 0 if <code>null</code> was given.
477 	 * 
478 	 * @param o
479 	 *            the object to compute hash code for.
480 	 * @return the hash code.
481 	 */
482 	public static int hashCode(final Object o) {
483 		return o == null ? 0 : o.hashCode();
484 	}
485 
486 	/***
487 	 * Parses RFC 2822 date.
488 	 * 
489 	 * @param date
490 	 *            the date to parse.
491 	 * @return parsed date.
492 	 * @throws ParseException
493 	 *             if parse fails.
494 	 */
495 	public static Date parseRFC2822Date(final String date)
496 			throws ParseException {
497 		final String pattern = "EEE, d MMM yyyy HH:mm:ss Z";
498 		final SimpleDateFormat sdf = new SimpleDateFormat(pattern,
499 				Locale.ENGLISH);
500 		return sdf.parse(date);
501 	}
502 
503 	/***
504 	 * Formats given date as a RFC 2822 date.
505 	 * 
506 	 * @param date
507 	 *            the date to format.
508 	 * @return formatted date.
509 	 */
510 	public static String getRFC2822Date(final Date date) {
511 		final String pattern = "EEE, d MMM yyyy HH:mm:ss Z";
512 		final SimpleDateFormat sdf = new SimpleDateFormat(pattern,
513 				Locale.ENGLISH);
514 		return sdf.format(date);
515 	}
516 
517 	private static final char[] HEX = new char[] { '0', '1', '2', '3', '4',
518 			'5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
519 
520 	/***
521 	 * Formats each byte in the array in its hexadecimal form and returns it.
522 	 * 
523 	 * @param array
524 	 *            the array to print
525 	 * @return hexadecimal format
526 	 */
527 	public static String toHexa(final byte[] array) {
528 		if (array == null) {
529 			return "null";
530 		}
531 		final StringBuilder b = new StringBuilder(array.length * 2);
532 		for (final byte by : array) {
533 			int bval = by;
534 			if (bval < 0) {
535 				bval += 256;
536 			}
537 			b.append(HEX[bval >> 4]);
538 			b.append(HEX[bval & 15]);
539 		}
540 		return b.toString();
541 	}
542 
543 	/***
544 	 * Converts hexadecimal string returned by {@link #toHexa(byte[])} back to
545 	 * the original array.
546 	 * 
547 	 * @param hexaArray
548 	 *            the array returned by {@link #toHexa(byte[])}.
549 	 * @return the array.
550 	 */
551 	public static byte[] fromHexa(final String hexaArray) {
552 		if (hexaArray == null || hexaArray.equals("null")) {
553 			return null;
554 		}
555 		final int length = hexaArray.length() / 2;
556 		final byte[] result = new byte[length];
557 		for (int i = 0; i < length; i++) {
558 			final int pos = i * 2;
559 			result[i] = (byte) (getHexa(hexaArray.charAt(pos)) * 16 + getHexa(hexaArray
560 					.charAt(pos + 1)));
561 		}
562 		return result;
563 	}
564 
565 	private static byte getHexa(final char hexa) {
566 		final int result = Arrays.binarySearch(HEX, hexa);
567 		if (result < 0) {
568 			throw new IllegalArgumentException("Not a hexa character: " + hexa);
569 		}
570 		return (byte) result;
571 	}
572 
573 	/***
574 	 * Returns given exception (including its stack trace) as a string.
575 	 * 
576 	 * @param throwable
577 	 *            the throwable.
578 	 * @return string representation of the throwable.
579 	 */
580 	public static String getStackTrace(final Throwable throwable) {
581 		final StringWriter writer = new StringWriter();
582 		final PrintWriter pw = new PrintWriter(writer);
583 		throwable.printStackTrace(pw);
584 		pw.flush();
585 		return writer.toString();
586 	}
587 
588 	/***
589 	 * Sysout for the fucking Android.
590 	 * 
591 	 * @param string
592 	 *            the fucking string.
593 	 */
594 	public static void sysout(String string) {
595 		Log.e("Sysout", "sysout: " + string);
596 	}
597 }