[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / tools / Tools.java
blob2a425def75d89b275be7c00086b6c85730ef65fc
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.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.lang.reflect.Array;
33 import java.lang.reflect.InvocationTargetException;
34 import java.lang.reflect.Method;
35 import java.net.URL;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Enumeration;
39 import java.util.Iterator;
40 import java.util.LinkedHashSet;
41 import java.util.logging.Level;
42 import java.util.logging.Logger;
44 /**
46 * @author codistmonk (creation 2010-06-11)
48 public final class Tools {
50 /**
51 * @throws IllegalInstantiationException To prevent instantiation
53 private Tools() {
54 throw new IllegalInstantiationException();
57 public static final int DEBUG_STACK_OFFSET = getDebugStackOffset();
59 /**
60 * Tries to create a temporary file and initialize it using {@code contents}.
61 * {@code contents} is closed at the end of this method.
63 * @param prefix
64 * <br>Not null
65 * @param suffix
66 * <br>Not null
67 * @param contents
68 * <br>Not null
69 * <br>Input-output
70 * @return a temporary file that is deleted when the program exits
71 * <br>Not null
72 * <br>New
73 * @throws RuntimeException if<ul>
74 * <li>the file cannot be created
75 * <li>an I/O error occurs while writing {@code contents} to the file
76 * </ul>
78 public static final File createTemporaryFile(final String prefix, final String suffix,
79 final InputStream contents) {
80 try {
81 final File result = File.createTempFile(prefix, suffix);
83 result.deleteOnExit();
85 if (contents == null) {
86 return result;
89 final OutputStream output = new FileOutputStream(result);
91 try {
92 write(contents, output);
94 return result;
95 } finally {
96 close(output);
98 } catch (final IOException exception) {
99 throw unchecked(exception);
100 } finally {
101 close(contents);
106 * Writes {@code input} to {@code output}; this method does not close the streams when it terminates.
108 * @param input
109 * <br>Not null
110 * <br>Input-output
111 * @param output
112 * <br>Not null
113 * <br>Input-output
114 * @throws RuntimeException if an I/O error occurs
116 public static final void write(final InputStream input, final OutputStream output) {
117 try {
118 final byte[] buffer = new byte[4096];
119 int bytesRead;
121 while ((bytesRead = input.read(buffer)) != -1) {
122 output.write(buffer, 0, bytesRead);
124 } catch (final IOException exception) {
125 throw unchecked(exception);
130 * Tries to close {@code closable} using reflection and without throwing an exception if it fails.
131 * If an exception occurs, it is logged in the caller's logger.
133 * @param closable
134 * <br>Maybe null
136 public static final void close(final Object closable) {
137 try {
138 if (closable != null) {
139 closable.getClass().getMethod("close").invoke(closable);
141 } catch (final Exception exception) {
142 Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName())
143 .log(Level.WARNING, null, exception);
148 * Retrieves the local file associated with the aplication URL (it could be a folder, a compiled
149 * class file or a jar, depending on the packaging).
151 * @return
152 * <br>Not null
153 * <br>New
155 public static final File getApplicationFile() {
156 final URL applicationURL = getCallerClass().getProtectionDomain().getCodeSource().getLocation();
158 return new File(applicationURL.toString().replace("file:", ""));
163 * @param <T> The type of the elements
164 * @param iterable
165 * <br>Not null
166 * @return
167 * <br>Not null
168 * <br>New
170 public static final <T> ArrayList<T> list(final Iterable<T> iterable) {
171 final ArrayList<T> result = new ArrayList<T>();
173 for (final T element : iterable) {
174 result.add(element);
177 return result;
182 * @param <T> The type of the elements
183 * @param enumeration
184 * <br>Not null
185 * <br>Input-output
186 * <br>Shared
187 * @return
188 * <br>Not null
189 * <br>New
191 public static final <T> Iterable<T> iterable(final Enumeration<T> enumeration) {
192 return new Iterable<T>() {
194 @Override
195 public final Iterator<T> iterator() {
196 return new Iterator<T>() {
198 @Override
199 public final boolean hasNext() {
200 return enumeration.hasMoreElements();
203 @Override
204 public final T next() {
205 return enumeration.nextElement();
208 @Override
209 public final void remove() {
210 throw new UnsupportedOperationException();
221 * @param <T> The common type of the elements
222 * @param array
223 * <br>Maybe null
224 * @return
225 * <br>Maybe null
226 * <br>Maybe New
228 public static final <T> T[] array(final T... array) {
229 return array;
234 * @param <T> The common type of the elements
235 * @param elements
236 * <br>Not null
237 * @return
238 * <br>Not null
239 * <br>New
241 public static final <T> LinkedHashSet<T> set(T... elements) {
242 final LinkedHashSet<T> result = new LinkedHashSet<T>();
244 for (final T element : elements) {
245 result.add(element);
248 return result;
253 * @param <T> The common type of the elements
254 * @param array
255 * <br>Not null
256 * @param moreElements
257 * <br>Not null
258 * @return
259 * <br>Not null
260 * <br>New
262 public static final <T> T[] append(final T[] array, final T... moreElements) {
263 @SuppressWarnings("unchecked")
264 final T[] result = (T[]) Array.newInstance(
265 array.getClass().getComponentType(), array.length + moreElements.length);
267 System.arraycopy(array, 0, result, 0, array.length);
268 System.arraycopy(moreElements, 0, result, array.length, moreElements.length);
270 return result;
275 * @param resourcePath
276 * <br>Not null
277 * @return
278 * <br>Maybe null
279 * <br>New
281 public static final InputStream getResourceAsStream(final String resourcePath) {
282 final Class<?> callerClass = getCallerClass();
283 final InputStream candidate = callerClass.getResourceAsStream(resourcePath);
285 return candidate != null ? candidate : getCallerClass().getClassLoader().getResourceAsStream(resourcePath);
289 * Searches for and invokes a method named {@code methodName} that can accept {@code arguments}.
291 * @param <T> The expected return type
292 * @param objectOrClass
293 * <br>Not null
294 * @param methodName
295 * <br>Not null
296 * @param arguments
297 * <br>Not null
298 * @return
299 * <br>Maybe null
300 * @throws RuntimeException if an appropriate method isn't found or if it throws an exception
302 @SuppressWarnings("unchecked")
303 public static final <T> T invoke(final Object objectOrClass,
304 final String methodName, final Object... arguments) {
305 final Object object = objectOrClass instanceof Class<?> ? null : objectOrClass;
306 final Class<?> objectClass = (Class<?>) (objectOrClass instanceof Class<?> ? objectOrClass : objectOrClass.getClass());
308 for (final Method method : Tools.append(objectClass.getMethods(), objectClass.getDeclaredMethods())) {
309 if (method.getName().equals(methodName)) {
310 try {
311 return (T) method.invoke(object, arguments);
312 } catch (final InvocationTargetException exception) {
313 throwUnchecked(exception.getCause());
314 } catch (final Exception exception) {
315 // Ignore
320 throw new RuntimeException(
321 "Method " + methodName + " accepting arguments " + Arrays.toString(arguments) +
322 " was not found for object " + object + " of class " + objectClass);
326 * Tries to find a setter starting with "set" for the specified property of the object.
327 * <br>Eg: {@code getSetter(object, "text", String.class)} tries to find a method {@code setText(String)}
329 * @param object
330 * <br>Should not be null
331 * @param propertyName
332 * <br>Should not be null
333 * @param propertyClass
334 * <br>Should not be null
335 * @return
336 * <br>A non-null value
337 * @throws RuntimeException if an appropriate setter cannot be retrieved
339 public static final Method getSetter(final Object object, final String propertyName, final Class<?> propertyClass) {
340 final String setterName = "set" + toUpperCamelCase(propertyName);
342 try {
343 // Try to retrieve a public setter
344 return object.getClass().getMethod(setterName, propertyClass);
345 } catch (final Exception exception) {
346 // Do nothing
349 try {
350 // Try to retrieve a setter declared in object's class, regardless of its visibility
351 return object.getClass().getDeclaredMethod(setterName, propertyClass);
352 } catch (final Exception exception) {
353 // Do nothing
356 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
360 * Tries to find a getter starting with "get", "is", or "has" (in that order) for the specified property of the object.
361 * <br>Eg: {@code getGetter(object, "empty")} tries to find a method {@code getEmpty()} or {@code isEmpty()} or {@code hasEmpty()}
363 * @param object
364 * <br>Should not be null
365 * @param propertyName the camelCase name of the property
366 * <br>Should not be null
367 * @return
368 * <br>A non-null value
369 * @throws RuntimeException if an appropriate getter cannot be retrieved
371 public static final Method getGetter(final Object object, final String propertyName) {
372 final String upperCamelCase = toUpperCamelCase(propertyName);
374 for (final String prefix : array("get", "is", "has")) {
375 final String getterName = prefix + upperCamelCase;
377 try {
378 // Try to retrieve a public getter
379 return object.getClass().getMethod(getterName);
380 } catch (final Exception exception) {
381 // Do nothing
384 try {
385 // Try to retrieve a getter declared in object's class, regardless of its visibility
386 return object.getClass().getDeclaredMethod(getterName);
387 } catch (final Exception exception) {
388 // Do nothing
392 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
396 * Converts "someName" into "SomeName".
398 * @param lowerCamelCase
399 * <br>Should not be null
400 * @return
401 * <br>A new value
402 * <br>A non-null value
404 public static final String toUpperCamelCase(final String lowerCamelCase) {
405 return Character.toUpperCase(lowerCamelCase.charAt(0)) + lowerCamelCase.substring(1);
409 * Converts {@code null} into "", otherwise returns the parameter untouched.
411 * @param string
412 * <br>Can be null
413 * <br>Shared parameter
414 * @return {@code string} or ""
415 * <br>A non-null value
416 * <br>A shared value
418 public static final String emptyIfNull(final String string) {
419 return string == null ? "" : string;
423 * Returns "package/name/" if the package of {@code cls} is of the form "package.name".
425 * @param cls
426 * <br>Not null
427 * @return
428 * <br>Not null
430 public static final String getPackagePath(final Class<?> cls) {
431 return cls.getPackage().getName().replaceAll("\\.", "/") + "/";
435 * Returns "package/name/" if the package of the caller class is of the form "package.name".
437 * @return
438 * <br>Not null
440 public static final String getThisPackagePath() {
441 return getPackagePath(getCallerClass());
446 * @param cls
447 * <br>Not null
448 * @return the top level class enclosing {@code cls}, or {@code cls} itself if it is a top level class
449 * <br>Not null
451 public static final Class<?> getTopLevelEnclosingClass(final Class<?> cls) {
452 return cls.getEnclosingClass() == null ? cls : getTopLevelEnclosingClass(cls.getEnclosingClass());
456 * If a method {@code A.a()} calls a method {@code B.b()},
457 * then the result of calling this method in {@code b()} will be {@code A.class}.
458 * <br>Warning: this method can only be used directly.
459 * <br>If you want to refactor your code, you can re-implement the functionality
460 * using {@code Thread.currentThread().getStackTrace()}.
462 * @return {@code null} if the caller class cannot be retrieved
463 * <br>Maybe null
465 public static final Class<?> getCallerClass() {
466 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
468 if (stackTrace.length > 3) {
469 try {
470 return Class.forName(stackTrace[3].getClassName());
471 } catch (final ClassNotFoundException exception) {
472 // Do nothing
476 return null;
480 * If a method {@code a()} calls a method {@code b()}, then the result of calling this method in b() will be "a".
481 * <br>Warning: this method can only be used directly.
482 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
484 * @return {@code null} if the caller method cannot be retrieved
485 * <br>A possibly null value
487 public static final String getCallerMethodName() {
488 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
490 return stackTrace.length > 3 ? stackTrace[3].getMethodName() : null;
494 * Calls {@link Logger#getLogger(String)} using the fully qualified name of the calling method.
495 * <br>Warning: this method can only be used directly.
496 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
498 * @return
499 * <br>A non-null value
500 * @throws NullPointerException if the caller class cannot be retrieved
502 public static final Logger getLoggerForThisMethod() {
503 return Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName());
507 * Use this method when you want to propagate a checked exception wrapped in a runtime exception
508 * instead of using the normal checked exception mechanism.
510 * @param <T> the type that the caller is supposed to return
511 * @param cause
512 * <br>Not null
513 * <br>Shared
514 * @return
515 * <br>Does not return
516 * @throws RuntimeException with {@code cause} as cause if it is a checked exception,
517 * otherwise {@code cause} is re-thrown
519 public static final <T> T throwUnchecked(final Throwable cause) {
520 if (cause instanceof Error) {
521 throw (Error) cause;
524 throw unchecked(cause);
528 * Returns an instance of {@link RuntimeException} which is either {@code cause} itself,
529 * if it is already a runtime exception, or a new runtime exception wrapping {@code cause}.
530 * <br>This method can be used as an alternative to {@link #throwUnchecked(java.lang.Throwable)},
531 * <br>with the difference that error types are wrapped.
532 * <br>It is up to the caller to decide what to do with the returned exception.
534 * @param cause
535 * <br>Not null
536 * @return
537 * <br>Not null
538 * <br>Maybe new
540 public static final RuntimeException unchecked(final Throwable cause) {
541 if (cause instanceof RuntimeException) {
542 return (RuntimeException) cause;
545 return new RuntimeException(cause);
549 * Does the same thing as {@link Class#cast(Object)},
550 * but returns {@code null} instead of throwing an exception if the cast cannot be performed.
552 * @param <T> the type into which {@code object} is tentatively being cast
553 * @param cls
554 * <br>Not null
555 * @param object
556 * <br>Maybe null
557 * @return {@code null} if {@code object} is {@code null} or cannot be cast into {@code T},
558 * otherwise {@code object}
559 * <br>Maybe null
561 public static final <T> T cast(final Class<T> cls, final Object object) {
562 if (object == null || !cls.isAssignableFrom(object.getClass())) {
563 return null;
566 return cls.cast(object);
571 * @param <T> the caller type
572 * @param object
573 * <br>Maybe null
574 * @return {@code null} if {@code object} is {@code null} or cannot be cast into the caller type
575 * (obtained using {@link #getCallerClass()}) , otherwise {@code object}
576 * <br>Maybe null
578 @SuppressWarnings("unchecked")
579 public static final <T> T castToCurrentClass(final Object object) {
580 return (T) cast(getCallerClass(), object);
585 * @param object1
586 * <br>Maybe null
587 * @param object2
588 * <br>Maybe null
589 * @return {@code true} if both objects are the same (using {@code ==}) or equal (using {@code equals()})
591 public static final boolean equals(final Object object1, final Object object2) {
592 return object1 == object2 || (object1 != null && object1.equals(object2));
597 * @param object
598 * <br>Maybe null
599 * @return {@code 0} if {@code object is null}, otherwise {@code object.hashcode()}
600 * <br>Range: any integer
602 public static final int hashCode(final Object object) {
603 return object == null ? 0 : object.hashCode();
607 * Concatenates the source location of the call and
608 * the string representations of the parameters separated by spaces.
609 * <br>This is method helps to perform console debugging using System.out or System.err.
611 * @param stackOffset {@link #DEBUG_STACK_OFFSET} is the source of the call,
612 * {@code DEBUG_STACK_OFFSET + 1} is the source of the call's caller, and so forth
613 * <br>Range: {@code [O .. Integer.MAX_VALUE]}
614 * @param objects
615 * <br>Not null
616 * @return
617 * <br>Not null
618 * <br>New
619 * @throws IndexOutOfBoundsException if {@code stackIndex} is invalid
621 public static final String debug(final int stackOffset, final Object... objects) {
622 final StringBuilder builder = new StringBuilder(
623 Thread.currentThread().getStackTrace()[stackOffset + 1].toString());
625 for (final Object object : objects) {
626 builder.append(" ").append(object);
629 return builder.toString();
633 * Prints on the standard output the concatenation of the source location of the call
634 * and the string representations of the parameters separated by spaces.
636 * @param objects
637 * <br>Not null
639 public static final void debugPrint(final Object... objects) {
640 System.out.println(debug(DEBUG_STACK_OFFSET + 1, objects));
645 * @return
646 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
648 public static final int getDebugStackOffset() {
649 int result = 0;
651 for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
652 if (element.getClassName().equals(getCallerClass().getName())) {
653 break;
656 ++ result;
659 return result;