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