[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / tools / Tools.java
blobae773cf8e16cfc15e200855d9f28ec79db42bfad
1 /*
2 * The MIT License
3 *
4 * Copyright 2010 Codist Monk.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 package net.sourceforge.aprog.tools;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.lang.reflect.Array;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.net.MalformedURLException;
38 import java.net.URISyntaxException;
39 import java.net.URL;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Enumeration;
43 import java.util.Iterator;
44 import java.util.LinkedHashSet;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
48 /**
50 * @author codistmonk (creation 2010-06-11)
52 public final class Tools {
54 /**
55 * @throws IllegalInstantiationException To prevent instantiation
57 private Tools() {
58 throw new IllegalInstantiationException();
61 public static final int DEBUG_STACK_OFFSET = getDebugStackOffset();
63 /**
64 * Does nothing, but prevents the IDE from displaying "unused" warning.
66 * @param object
67 * <br>Maybe null
69 public static final void suppressWarningUnused(final Object object) {
70 assert object == null || object != null;
73 /**
74 * Does nothing, but prevents the IDE from displaying "unused" warning.
76 * @param throwable
77 * <br>Maybe null
79 public static final void ignore(final Throwable throwable) {
80 suppressWarningUnused(throwable);
83 /**
84 * Tries to create a temporary file and initialize it using {@code contents}.
85 * {@code contents} is closed at the end of this method.
87 * @param prefix
88 * <br>Not null
89 * @param suffix
90 * <br>Not null
91 * @param contents
92 * <br>Not null
93 * <br>Input-output
94 * @return a temporary file that is deleted when the program exits
95 * <br>Maybe null
96 * <br>New
97 * @throws RuntimeException if<ul>
98 * <li>the file cannot be created
99 * <li>an I/O error occurs while writing {@code contents} to the file
100 * </ul>
102 public static final File createTemporaryFile(final String prefix, final String suffix,
103 final InputStream contents) {
104 try {
105 final File result = File.createTempFile(prefix, suffix);
107 result.deleteOnExit();
109 if (contents == null) {
110 return result;
113 final OutputStream output = new FileOutputStream(result);
115 try {
116 write(contents, output);
118 return result;
119 } finally {
120 close(output);
122 } catch (final IOException exception) {
123 throw unchecked(exception);
124 } finally {
125 close(contents);
130 * Writes {@code input} to {@code output}; this method does not close the streams when it terminates.
132 * @param input
133 * <br>Not null
134 * <br>Input-output
135 * @param output
136 * <br>Not null
137 * <br>Input-output
138 * @throws RuntimeException if an I/O error occurs
140 public static final void write(final InputStream input, final OutputStream output) {
141 try {
142 final byte[] buffer = new byte[4096];
143 int bytesRead;
145 while ((bytesRead = input.read(buffer)) != -1) {
146 output.write(buffer, 0, bytesRead);
148 } catch (final IOException exception) {
149 throw unchecked(exception);
154 * Tries to close {@code closable} using reflection and without throwing an exception if it fails.
155 * If an exception occurs, it is logged in the caller's logger.
157 * @param closable
158 * <br>Maybe null
160 public static final void close(final Object closable) {
161 try {
162 if (closable != null) {
163 closable.getClass().getMethod("close").invoke(closable);
165 } catch (final Exception exception) {
166 Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName())
167 .log(Level.WARNING, null, exception);
172 * Retrieves the application URL (it could be a folder, a compiled
173 * class file or a jar, depending on the packaging).
175 * @return
176 * <br>Not null
177 * <br>New
179 public static final URL getApplicationURL() {
180 return getCallerClass().getProtectionDomain().getCodeSource().getLocation();
184 * Retrieves the local file associated with the application URL (it could be a folder, a compiled
185 * class file or a jar, depending on the packaging).
187 * @return
188 * <br>Not null
189 * <br>New
191 public static final File getApplicationFile() {
192 try {
193 return new File(getCallerClass().getProtectionDomain().getCodeSource().getLocation().toURI());
194 } catch (final URISyntaxException exception) {
195 throw unchecked(exception);
201 * @param <T> The type of the elements
202 * @param iterable
203 * <br>Not null
204 * @return
205 * <br>Not null
206 * <br>New
208 public static final <T> ArrayList<T> list(final Iterable<T> iterable) {
209 final ArrayList<T> result = new ArrayList<T>();
211 for (final T element : iterable) {
212 result.add(element);
215 return result;
220 * @param <T> The type of the elements
221 * @param enumeration
222 * <br>Not null
223 * <br>Input-output
224 * <br>Shared
225 * @return
226 * <br>Not null
227 * <br>New
229 public static final <T> Iterable<T> iterable(final Enumeration<T> enumeration) {
230 return new Iterable<T>() {
232 @Override
233 public final Iterator<T> iterator() {
234 return new Iterator<T>() {
236 @Override
237 public final boolean hasNext() {
238 return enumeration.hasMoreElements();
241 @Override
242 public final T next() {
243 return enumeration.nextElement();
246 @Override
247 public final void remove() {
248 throw new UnsupportedOperationException();
259 * @param <T> The common type of the elements
260 * @param array
261 * <br>Maybe null
262 * @return
263 * <br>Maybe null
264 * <br>Maybe New
266 public static final <T> T[] array(final T... array) {
267 return array;
272 * @param <T> The common type of the elements
273 * @param elements
274 * <br>Not null
275 * @return
276 * <br>Not null
277 * <br>New
279 public static final <T> LinkedHashSet<T> set(T... elements) {
280 final LinkedHashSet<T> result = new LinkedHashSet<T>();
282 for (final T element : elements) {
283 result.add(element);
286 return result;
291 * @param <T> The common type of the elements
292 * @param array
293 * <br>Not null
294 * @param moreElements
295 * <br>Not null
296 * @return
297 * <br>Not null
298 * <br>New
300 public static final <T> T[] append(final T[] array, final T... moreElements) {
301 @SuppressWarnings("unchecked")
302 final T[] result = (T[]) Array.newInstance(
303 array.getClass().getComponentType(), array.length + moreElements.length);
305 System.arraycopy(array, 0, result, 0, array.length);
306 System.arraycopy(moreElements, 0, result, array.length, moreElements.length);
308 return result;
313 * @param resourcePath
314 * <br>Not null
315 * @return
316 * <br>Maybe null
317 * <br>New
319 public static final InputStream getResourceAsStream(final String resourcePath) {
320 final Class<?> callerClass = getCallerClass();
321 InputStream candidate = callerClass.getResourceAsStream(resourcePath);
323 if (candidate == null) {
324 candidate = getCallerClass().getClassLoader().getResourceAsStream(resourcePath);
327 if (candidate == null) {
328 try {
329 return new FileInputStream(resourcePath);
330 } catch (final FileNotFoundException exception) {
331 ignore(exception);
335 return candidate;
340 * @param resourcePath
341 * <br>Not null
342 * @return
343 * <br>Maybe null
344 * <br>New
346 public static final URL getResourceURL(final String resourcePath) {
347 final Class<?> callerClass = getCallerClass();
348 URL candidate = callerClass.getResource(resourcePath);
350 if (candidate == null) {
351 candidate = getCallerClass().getClassLoader().getResource(resourcePath);
354 if (candidate == null) {
355 try {
356 final File file = new File(resourcePath);
358 candidate = file.exists() ? file.toURI().toURL() : null;
359 } catch (final MalformedURLException exception) {
360 ignore(exception);
364 return candidate;
368 * Searches for and invokes a method named {@code methodName} that can accept {@code arguments}.
370 * @param <T> The expected return type
371 * @param objectOrClass
372 * <br>Not null
373 * @param methodName
374 * <br>Not null
375 * @param arguments
376 * <br>Not null
377 * @return
378 * <br>Maybe null
379 * @throws RuntimeException if an appropriate method isn't found or if it throws an exception
381 @SuppressWarnings("unchecked")
382 public static final <T> T invoke(final Object objectOrClass,
383 final String methodName, final Object... arguments) {
384 final Object object = objectOrClass instanceof Class<?> ? null : objectOrClass;
385 final Class<?> objectClass = (Class<?>) (objectOrClass instanceof Class<?> ? objectOrClass : objectOrClass.getClass());
387 for (final Method method : Tools.append(objectClass.getMethods(), objectClass.getDeclaredMethods())) {
388 if (method.getName().equals(methodName)) {
389 try {
390 return (T) method.invoke(object, arguments);
391 } catch (final InvocationTargetException exception) {
392 throwUnchecked(exception.getCause());
393 } catch (final Exception exception) {
394 // Ignore
399 throw new RuntimeException(
400 "Method " + methodName + " accepting arguments " + Arrays.toString(arguments) +
401 " was not found for object " + object + " of class " + objectClass);
405 * Tries to find a setter starting with "set" for the specified property of the object.
406 * <br>Eg: {@code getSetter(object, "text", String.class)} tries to find a method {@code setText(String)}
408 * @param object
409 * <br>Should not be null
410 * @param propertyName
411 * <br>Should not be null
412 * @param propertyClass
413 * <br>Should not be null
414 * @return
415 * <br>A non-null value
416 * @throws RuntimeException if an appropriate setter cannot be retrieved
418 public static final Method getSetter(final Object object, final String propertyName, final Class<?> propertyClass) {
419 final String setterName = "set" + toUpperCamelCase(propertyName);
421 try {
422 // Try to retrieve a public setter
423 return object.getClass().getMethod(setterName, propertyClass);
424 } catch (final Exception exception) {
425 // Do nothing
428 try {
429 // Try to retrieve a setter declared in object's class, regardless of its visibility
430 return object.getClass().getDeclaredMethod(setterName, propertyClass);
431 } catch (final Exception exception) {
432 // Do nothing
435 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
439 * Tries to find a getter starting with "get", "is", or "has" (in that order) for the specified property of the object.
440 * <br>Eg: {@code getGetter(object, "empty")} tries to find a method {@code getEmpty()} or {@code isEmpty()} or {@code hasEmpty()}
442 * @param object
443 * <br>Should not be null
444 * @param propertyName the camelCase name of the property
445 * <br>Should not be null
446 * @return
447 * <br>A non-null value
448 * @throws RuntimeException if an appropriate getter cannot be retrieved
450 public static final Method getGetter(final Object object, final String propertyName) {
451 final String upperCamelCase = toUpperCamelCase(propertyName);
453 for (final String prefix : array("get", "is", "has")) {
454 final String getterName = prefix + upperCamelCase;
456 try {
457 // Try to retrieve a public getter
458 return object.getClass().getMethod(getterName);
459 } catch (final Exception exception) {
460 // Do nothing
463 try {
464 // Try to retrieve a getter declared in object's class, regardless of its visibility
465 return object.getClass().getDeclaredMethod(getterName);
466 } catch (final Exception exception) {
467 // Do nothing
471 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
475 * Converts "someName" into "SomeName".
477 * @param lowerCamelCase
478 * <br>Should not be null
479 * @return
480 * <br>A new value
481 * <br>A non-null value
483 public static final String toUpperCamelCase(final String lowerCamelCase) {
484 return Character.toUpperCase(lowerCamelCase.charAt(0)) + lowerCamelCase.substring(1);
488 * Converts {@code null} into "", otherwise returns the parameter untouched.
490 * @param string
491 * <br>Can be null
492 * <br>Shared parameter
493 * @return {@code string} or ""
494 * <br>A non-null value
495 * <br>A shared value
497 public static final String emptyIfNull(final String string) {
498 return string == null ? "" : string;
502 * Returns "package/name/" if the package of {@code cls} is of the form "package.name".
504 * @param cls
505 * <br>Not null
506 * @return
507 * <br>Not null
509 public static final String getPackagePath(final Class<?> cls) {
510 return cls.getPackage().getName().replace(".", "/") + "/";
514 * Returns "package/name/" if the package of the caller class is of the form "package.name".
516 * @return
517 * <br>Not null
519 public static final String getThisPackagePath() {
520 return getPackagePath(getCallerClass());
525 * @param cls
526 * <br>Not null
527 * @return the top level class enclosing {@code cls}, or {@code cls} itself if it is a top level class
528 * <br>Not null
530 public static final Class<?> getTopLevelEnclosingClass(final Class<?> cls) {
531 return cls.getEnclosingClass() == null ? cls : getTopLevelEnclosingClass(cls.getEnclosingClass());
535 * If a method {@code A.a()} calls a method {@code B.b()},
536 * then the result of calling this method in {@code b()} will be {@code A.class}.
537 * <br>Warning: this method can only be used directly.
538 * <br>If you want to refactor your code, you can re-implement the functionality
539 * using {@code Thread.currentThread().getStackTrace()}.
541 * @return {@code null} if the caller class cannot be retrieved
542 * <br>Maybe null
544 public static final Class<?> getCallerClass() {
545 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
547 if (stackTrace.length > DEBUG_STACK_OFFSET + 2) {
548 try {
549 return Class.forName(stackTrace[DEBUG_STACK_OFFSET + 2].getClassName());
550 } catch (final ClassNotFoundException exception) {
551 // Do nothing
555 return null;
559 * If a method {@code a()} calls a method {@code b()}, then the result of calling this method in b() will be "a".
560 * <br>Warning: this method can only be used directly.
561 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
563 * @return {@code null} if the caller method cannot be retrieved
564 * <br>A possibly null value
566 public static final String getCallerMethodName() {
567 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
569 return stackTrace.length > 3 ? stackTrace[3].getMethodName() : null;
573 * Calls {@link Logger#getLogger(String)} using the fully qualified name of the calling method.
574 * <br>Warning: this method can only be used directly.
575 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
577 * @return
578 * <br>A non-null value
579 * @throws NullPointerException if the caller class cannot be retrieved
581 public static final Logger getLoggerForThisMethod() {
582 return Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName());
586 * Use this method when you want to propagate a checked exception wrapped in a runtime exception
587 * instead of using the normal checked exception mechanism.
589 * @param <T> the type that the caller is supposed to return
590 * @param cause
591 * <br>Not null
592 * <br>Shared
593 * @return
594 * <br>Does not return
595 * @throws RuntimeException with {@code cause} as cause if it is a checked exception,
596 * otherwise {@code cause} is re-thrown
598 public static final <T> T throwUnchecked(final Throwable cause) {
599 if (cause instanceof Error) {
600 throw (Error) cause;
603 throw unchecked(cause);
607 * Returns an instance of {@link RuntimeException} which is either {@code cause} itself,
608 * if it is already a runtime exception, or a new runtime exception wrapping {@code cause}.
609 * <br>This method can be used as an alternative to {@link #throwUnchecked(java.lang.Throwable)},
610 * <br>with the difference that error types are wrapped.
611 * <br>It is up to the caller to decide what to do with the returned exception.
613 * @param cause
614 * <br>Not null
615 * @return
616 * <br>Not null
617 * <br>Maybe new
619 public static final RuntimeException unchecked(final Throwable cause) {
620 if (cause instanceof RuntimeException) {
621 return (RuntimeException) cause;
624 return new RuntimeException(cause);
628 * Does the same thing as {@link Class#cast(Object)},
629 * but returns {@code null} instead of throwing an exception if the cast cannot be performed.
631 * @param <T> the type into which {@code object} is tentatively being cast
632 * @param cls
633 * <br>Not null
634 * @param object
635 * <br>Maybe null
636 * @return {@code null} if {@code object} is {@code null} or cannot be cast into {@code T},
637 * otherwise {@code object}
638 * <br>Maybe null
640 public static final <T> T cast(final Class<T> cls, final Object object) {
641 if (object == null || !cls.isAssignableFrom(object.getClass())) {
642 return null;
645 return cls.cast(object);
650 * @param <T> the caller type
651 * @param object
652 * <br>Maybe null
653 * @return {@code null} if {@code object} is {@code null} or cannot be cast into the caller type
654 * (obtained using {@link #getCallerClass()}) , otherwise {@code object}
655 * <br>Maybe null
657 @SuppressWarnings("unchecked")
658 public static final <T> T castToCurrentClass(final Object object) {
659 return (T) cast(getCallerClass(), object);
664 * @param object1
665 * <br>Maybe null
666 * @param object2
667 * <br>Maybe null
668 * @return {@code true} if both objects are the same (using {@code ==}) or equal (using {@code equals()})
670 public static final boolean equals(final Object object1, final Object object2) {
671 return object1 == object2 || (object1 != null && object1.equals(object2));
676 * @param object
677 * <br>Maybe null
678 * @return {@code 0} if {@code object is null}, otherwise {@code object.hashcode()}
679 * <br>Range: any integer
681 public static final int hashCode(final Object object) {
682 return object == null ? 0 : object.hashCode();
686 * Concatenates the source location of the call and
687 * the string representations of the parameters separated by spaces.
688 * <br>This is method helps to perform console debugging using System.out or System.err.
690 * @param stackOffset {@link #DEBUG_STACK_OFFSET} is the source of the call,
691 * {@code DEBUG_STACK_OFFSET + 1} is the source of the call's caller, and so forth
692 * <br>Range: {@code [O .. Integer.MAX_VALUE]}
693 * @param objects
694 * <br>Not null
695 * @return
696 * <br>Not null
697 * <br>New
698 * @throws IndexOutOfBoundsException if {@code stackIndex} is invalid
700 public static final String debug(final int stackOffset, final Object... objects) {
701 final StringBuilder builder = new StringBuilder(
702 Thread.currentThread().getStackTrace()[stackOffset + 1].toString());
704 for (final Object object : objects) {
705 builder.append(" ").append(object);
708 return builder.toString();
712 * Prints on the standard output the concatenation of the source location of the call
713 * and the string representations of the parameters separated by spaces.
715 * @param objects
716 * <br>Not null
718 public static final void debugPrint(final Object... objects) {
719 System.out.println(debug(DEBUG_STACK_OFFSET + 1, objects));
724 * @return
725 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
727 private static final int getDebugStackOffset() {
728 int result = 0;
730 for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
731 if (element.getClassName().equals(Tools.class.getName())) {
732 break;
735 ++ result;
738 return result;