4 * Copyright 2010 Codist Monk.
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
25 package net
.sourceforge
.aprog
.tools
;
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
;
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
;
50 * @author codistmonk (creation 2010-06-11)
52 public final class Tools
{
55 * @throws IllegalInstantiationException To prevent instantiation
58 throw new IllegalInstantiationException();
61 public static final int DEBUG_STACK_OFFSET
= getDebugStackOffset();
64 * Does nothing, but prevents the IDE from displaying "unused" warning.
69 public static final void suppressWarningUnused(final Object object
) {
70 assert object
== null || object
!= null;
74 * Does nothing, but prevents the IDE from displaying "unused" warning.
79 public static final void ignore(final Throwable throwable
) {
80 suppressWarningUnused(throwable
);
84 * Tries to create a temporary file and initialize it using {@code contents}.
85 * {@code contents} is closed at the end of this method.
94 * @return a temporary file that is deleted when the program exits
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
102 public static final File
createTemporaryFile(final String prefix
, final String suffix
,
103 final InputStream contents
) {
105 final File result
= File
.createTempFile(prefix
, suffix
);
107 result
.deleteOnExit();
109 if (contents
== null) {
113 final OutputStream output
= new FileOutputStream(result
);
116 write(contents
, output
);
122 } catch (final IOException exception
) {
123 throw unchecked(exception
);
130 * Writes {@code input} to {@code output}; this method does not close the streams when it terminates.
138 * @throws RuntimeException if an I/O error occurs
140 public static final void write(final InputStream input
, final OutputStream output
) {
142 final byte[] buffer
= new byte[4096];
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.
160 public static final void close(final Object closable
) {
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).
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).
191 public static final File
getApplicationFile() {
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
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
) {
220 * @param <T> The type of the elements
229 public static final <T
> Iterable
<T
> iterable(final Enumeration
<T
> enumeration
) {
230 return new Iterable
<T
>() {
233 public final Iterator
<T
> iterator() {
234 return new Iterator
<T
>() {
237 public final boolean hasNext() {
238 return enumeration
.hasMoreElements();
242 public final T
next() {
243 return enumeration
.nextElement();
247 public final void remove() {
248 throw new UnsupportedOperationException();
259 * @param <T> The common type of the elements
266 public static final <T
> T
[] array(final T
... array
) {
272 * @param <T> The common type of the elements
279 public static final <T
> LinkedHashSet
<T
> set(T
... elements
) {
280 final LinkedHashSet
<T
> result
= new LinkedHashSet
<T
>();
282 for (final T element
: elements
) {
291 * @param <T> The common type of the elements
294 * @param moreElements
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
);
313 * @param resourcePath
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) {
329 return new FileInputStream(resourcePath
);
330 } catch (final FileNotFoundException exception
) {
340 * @param resourcePath
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) {
356 final File file
= new File(resourcePath
);
358 candidate
= file
.exists() ? file
.toURI().toURL() : null;
359 } catch (final MalformedURLException exception
) {
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
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
)) {
390 return (T
) method
.invoke(object
, arguments
);
391 } catch (final InvocationTargetException exception
) {
392 throwUnchecked(exception
.getCause());
393 } catch (final Exception exception
) {
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)}
409 * <br>Should not be null
410 * @param propertyName
411 * <br>Should not be null
412 * @param propertyClass
413 * <br>Should not be null
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
);
422 // Try to retrieve a public setter
423 return object
.getClass().getMethod(setterName
, propertyClass
);
424 } catch (final Exception exception
) {
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
) {
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()}
443 * <br>Should not be null
444 * @param propertyName the camelCase name of the property
445 * <br>Should not be null
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
;
457 // Try to retrieve a public getter
458 return object
.getClass().getMethod(getterName
);
459 } catch (final Exception exception
) {
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
) {
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
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.
492 * <br>Shared parameter
493 * @return {@code string} or ""
494 * <br>A non-null 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".
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".
519 public static final String
getThisPackagePath() {
520 return getPackagePath(getCallerClass());
527 * @return the top level class enclosing {@code cls}, or {@code cls} itself if it is a top level class
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
544 public static final Class
<?
> getCallerClass() {
545 final StackTraceElement
[] stackTrace
= Thread
.currentThread().getStackTrace();
547 if (stackTrace
.length
> DEBUG_STACK_OFFSET
+ 2) {
549 return Class
.forName(stackTrace
[DEBUG_STACK_OFFSET
+ 2].getClassName());
550 } catch (final ClassNotFoundException exception
) {
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()}.
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
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
) {
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.
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
636 * @return {@code null} if {@code object} is {@code null} or cannot be cast into {@code T},
637 * otherwise {@code object}
640 public static final <T
> T
cast(final Class
<T
> cls
, final Object object
) {
641 if (object
== null || !cls
.isAssignableFrom(object
.getClass())) {
645 return cls
.cast(object
);
650 * @param <T> the caller type
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}
657 @SuppressWarnings("unchecked")
658 public static final <T
> T
castToCurrentClass(final Object object
) {
659 return (T
) cast(getCallerClass(), object
);
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
));
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]}
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.
718 public static final void debugPrint(final Object
... objects
) {
719 System
.out
.println(debug(DEBUG_STACK_OFFSET
+ 1, objects
));
725 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
727 private static final int getDebugStackOffset() {
730 for (final StackTraceElement element
: Thread
.currentThread().getStackTrace()) {
731 if (element
.getClassName().equals(Tools
.class.getName())) {