[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / tools / Tools.java
blob2de51f4a548bd0edfb23e811f29e5e09f8f975e2
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.URL;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Enumeration;
42 import java.util.Iterator;
43 import java.util.LinkedHashSet;
44 import java.util.logging.Level;
45 import java.util.logging.Logger;
47 /**
49 * @author codistmonk (creation 2010-06-11)
51 public final class Tools {
53 /**
54 * @throws IllegalInstantiationException To prevent instantiation
56 private Tools() {
57 throw new IllegalInstantiationException();
60 public static final int DEBUG_STACK_OFFSET = getDebugStackOffset();
62 /**
63 * Does nothing, but prevents the IDE from displaying "unused" warning.
65 * @param object
66 * <br>Maybe null
68 public static final void suppressWarningUnused(final Object object) {
69 assert object == null || object != null;
72 /**
73 * Does nothing, but prevents the IDE from displaying "unused" warning.
75 * @param throwable
76 * <br>Maybe null
78 public static final void ignore(final Throwable throwable) {
79 suppressWarningUnused(throwable);
82 /**
83 * Tries to create a temporary file and initialize it using {@code contents}.
84 * {@code contents} is closed at the end of this method.
86 * @param prefix
87 * <br>Not null
88 * @param suffix
89 * <br>Not null
90 * @param contents
91 * <br>Not null
92 * <br>Input-output
93 * @return a temporary file that is deleted when the program exits
94 * <br>Not null
95 * <br>New
96 * @throws RuntimeException if<ul>
97 * <li>the file cannot be created
98 * <li>an I/O error occurs while writing {@code contents} to the file
99 * </ul>
101 public static final File createTemporaryFile(final String prefix, final String suffix,
102 final InputStream contents) {
103 try {
104 final File result = File.createTempFile(prefix, suffix);
106 result.deleteOnExit();
108 if (contents == null) {
109 return result;
112 final OutputStream output = new FileOutputStream(result);
114 try {
115 write(contents, output);
117 return result;
118 } finally {
119 close(output);
121 } catch (final IOException exception) {
122 throw unchecked(exception);
123 } finally {
124 close(contents);
129 * Writes {@code input} to {@code output}; this method does not close the streams when it terminates.
131 * @param input
132 * <br>Not null
133 * <br>Input-output
134 * @param output
135 * <br>Not null
136 * <br>Input-output
137 * @throws RuntimeException if an I/O error occurs
139 public static final void write(final InputStream input, final OutputStream output) {
140 try {
141 final byte[] buffer = new byte[4096];
142 int bytesRead;
144 while ((bytesRead = input.read(buffer)) != -1) {
145 output.write(buffer, 0, bytesRead);
147 } catch (final IOException exception) {
148 throw unchecked(exception);
153 * Tries to close {@code closable} using reflection and without throwing an exception if it fails.
154 * If an exception occurs, it is logged in the caller's logger.
156 * @param closable
157 * <br>Maybe null
159 public static final void close(final Object closable) {
160 try {
161 if (closable != null) {
162 closable.getClass().getMethod("close").invoke(closable);
164 } catch (final Exception exception) {
165 Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName())
166 .log(Level.WARNING, null, exception);
171 * Retrieves the local file associated with the aplication URL (it could be a folder, a compiled
172 * class file or a jar, depending on the packaging).
174 * @return
175 * <br>Not null
176 * <br>New
178 public static final File getApplicationFile() {
179 final URL applicationURL = getCallerClass().getProtectionDomain().getCodeSource().getLocation();
181 return new File(applicationURL.toString().replace("file:", ""));
186 * @param <T> The type of the elements
187 * @param iterable
188 * <br>Not null
189 * @return
190 * <br>Not null
191 * <br>New
193 public static final <T> ArrayList<T> list(final Iterable<T> iterable) {
194 final ArrayList<T> result = new ArrayList<T>();
196 for (final T element : iterable) {
197 result.add(element);
200 return result;
205 * @param <T> The type of the elements
206 * @param enumeration
207 * <br>Not null
208 * <br>Input-output
209 * <br>Shared
210 * @return
211 * <br>Not null
212 * <br>New
214 public static final <T> Iterable<T> iterable(final Enumeration<T> enumeration) {
215 return new Iterable<T>() {
217 @Override
218 public final Iterator<T> iterator() {
219 return new Iterator<T>() {
221 @Override
222 public final boolean hasNext() {
223 return enumeration.hasMoreElements();
226 @Override
227 public final T next() {
228 return enumeration.nextElement();
231 @Override
232 public final void remove() {
233 throw new UnsupportedOperationException();
244 * @param <T> The common type of the elements
245 * @param array
246 * <br>Maybe null
247 * @return
248 * <br>Maybe null
249 * <br>Maybe New
251 public static final <T> T[] array(final T... array) {
252 return array;
257 * @param <T> The common type of the elements
258 * @param elements
259 * <br>Not null
260 * @return
261 * <br>Not null
262 * <br>New
264 public static final <T> LinkedHashSet<T> set(T... elements) {
265 final LinkedHashSet<T> result = new LinkedHashSet<T>();
267 for (final T element : elements) {
268 result.add(element);
271 return result;
276 * @param <T> The common type of the elements
277 * @param array
278 * <br>Not null
279 * @param moreElements
280 * <br>Not null
281 * @return
282 * <br>Not null
283 * <br>New
285 public static final <T> T[] append(final T[] array, final T... moreElements) {
286 @SuppressWarnings("unchecked")
287 final T[] result = (T[]) Array.newInstance(
288 array.getClass().getComponentType(), array.length + moreElements.length);
290 System.arraycopy(array, 0, result, 0, array.length);
291 System.arraycopy(moreElements, 0, result, array.length, moreElements.length);
293 return result;
298 * @param resourcePath
299 * <br>Not null
300 * @return
301 * <br>Maybe null
302 * <br>New
304 public static final InputStream getResourceAsStream(final String resourcePath) {
305 final Class<?> callerClass = getCallerClass();
306 InputStream candidate = callerClass.getResourceAsStream(resourcePath);
308 if (candidate == null) {
309 candidate = getCallerClass().getClassLoader().getResourceAsStream(resourcePath);
312 if (candidate == null) {
313 try {
314 return new FileInputStream(resourcePath);
315 } catch (final FileNotFoundException exception) {
316 ignore(exception);
320 return candidate;
325 * @param resourcePath
326 * <br>Not null
327 * @return
328 * <br>Maybe null
329 * <br>New
331 public static final URL getResourceURL(final String resourcePath) {
332 final Class<?> callerClass = getCallerClass();
333 URL candidate = callerClass.getResource(resourcePath);
335 if (candidate == null) {
336 candidate = getCallerClass().getClassLoader().getResource(resourcePath);
339 if (candidate == null) {
340 try {
341 final File file = new File(resourcePath);
343 candidate = file.exists() ? file.toURI().toURL() : null;
344 } catch (final MalformedURLException exception) {
345 ignore(exception);
349 return candidate;
353 * Searches for and invokes a method named {@code methodName} that can accept {@code arguments}.
355 * @param <T> The expected return type
356 * @param objectOrClass
357 * <br>Not null
358 * @param methodName
359 * <br>Not null
360 * @param arguments
361 * <br>Not null
362 * @return
363 * <br>Maybe null
364 * @throws RuntimeException if an appropriate method isn't found or if it throws an exception
366 @SuppressWarnings("unchecked")
367 public static final <T> T invoke(final Object objectOrClass,
368 final String methodName, final Object... arguments) {
369 final Object object = objectOrClass instanceof Class<?> ? null : objectOrClass;
370 final Class<?> objectClass = (Class<?>) (objectOrClass instanceof Class<?> ? objectOrClass : objectOrClass.getClass());
372 for (final Method method : Tools.append(objectClass.getMethods(), objectClass.getDeclaredMethods())) {
373 if (method.getName().equals(methodName)) {
374 try {
375 return (T) method.invoke(object, arguments);
376 } catch (final InvocationTargetException exception) {
377 throwUnchecked(exception.getCause());
378 } catch (final Exception exception) {
379 // Ignore
384 throw new RuntimeException(
385 "Method " + methodName + " accepting arguments " + Arrays.toString(arguments) +
386 " was not found for object " + object + " of class " + objectClass);
390 * Tries to find a setter starting with "set" for the specified property of the object.
391 * <br>Eg: {@code getSetter(object, "text", String.class)} tries to find a method {@code setText(String)}
393 * @param object
394 * <br>Should not be null
395 * @param propertyName
396 * <br>Should not be null
397 * @param propertyClass
398 * <br>Should not be null
399 * @return
400 * <br>A non-null value
401 * @throws RuntimeException if an appropriate setter cannot be retrieved
403 public static final Method getSetter(final Object object, final String propertyName, final Class<?> propertyClass) {
404 final String setterName = "set" + toUpperCamelCase(propertyName);
406 try {
407 // Try to retrieve a public setter
408 return object.getClass().getMethod(setterName, propertyClass);
409 } catch (final Exception exception) {
410 // Do nothing
413 try {
414 // Try to retrieve a setter declared in object's class, regardless of its visibility
415 return object.getClass().getDeclaredMethod(setterName, propertyClass);
416 } catch (final Exception exception) {
417 // Do nothing
420 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
424 * Tries to find a getter starting with "get", "is", or "has" (in that order) for the specified property of the object.
425 * <br>Eg: {@code getGetter(object, "empty")} tries to find a method {@code getEmpty()} or {@code isEmpty()} or {@code hasEmpty()}
427 * @param object
428 * <br>Should not be null
429 * @param propertyName the camelCase name of the property
430 * <br>Should not be null
431 * @return
432 * <br>A non-null value
433 * @throws RuntimeException if an appropriate getter cannot be retrieved
435 public static final Method getGetter(final Object object, final String propertyName) {
436 final String upperCamelCase = toUpperCamelCase(propertyName);
438 for (final String prefix : array("get", "is", "has")) {
439 final String getterName = prefix + upperCamelCase;
441 try {
442 // Try to retrieve a public getter
443 return object.getClass().getMethod(getterName);
444 } catch (final Exception exception) {
445 // Do nothing
448 try {
449 // Try to retrieve a getter declared in object's class, regardless of its visibility
450 return object.getClass().getDeclaredMethod(getterName);
451 } catch (final Exception exception) {
452 // Do nothing
456 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
460 * Converts "someName" into "SomeName".
462 * @param lowerCamelCase
463 * <br>Should not be null
464 * @return
465 * <br>A new value
466 * <br>A non-null value
468 public static final String toUpperCamelCase(final String lowerCamelCase) {
469 return Character.toUpperCase(lowerCamelCase.charAt(0)) + lowerCamelCase.substring(1);
473 * Converts {@code null} into "", otherwise returns the parameter untouched.
475 * @param string
476 * <br>Can be null
477 * <br>Shared parameter
478 * @return {@code string} or ""
479 * <br>A non-null value
480 * <br>A shared value
482 public static final String emptyIfNull(final String string) {
483 return string == null ? "" : string;
487 * Returns "package/name/" if the package of {@code cls} is of the form "package.name".
489 * @param cls
490 * <br>Not null
491 * @return
492 * <br>Not null
494 public static final String getPackagePath(final Class<?> cls) {
495 return cls.getPackage().getName().replace(".", "/") + "/";
499 * Returns "package/name/" if the package of the caller class is of the form "package.name".
501 * @return
502 * <br>Not null
504 public static final String getThisPackagePath() {
505 return getPackagePath(getCallerClass());
510 * @param cls
511 * <br>Not null
512 * @return the top level class enclosing {@code cls}, or {@code cls} itself if it is a top level class
513 * <br>Not null
515 public static final Class<?> getTopLevelEnclosingClass(final Class<?> cls) {
516 return cls.getEnclosingClass() == null ? cls : getTopLevelEnclosingClass(cls.getEnclosingClass());
520 * If a method {@code A.a()} calls a method {@code B.b()},
521 * then the result of calling this method in {@code b()} will be {@code A.class}.
522 * <br>Warning: this method can only be used directly.
523 * <br>If you want to refactor your code, you can re-implement the functionality
524 * using {@code Thread.currentThread().getStackTrace()}.
526 * @return {@code null} if the caller class cannot be retrieved
527 * <br>Maybe null
529 public static final Class<?> getCallerClass() {
530 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
532 if (stackTrace.length > DEBUG_STACK_OFFSET + 2) {
533 try {
534 return Class.forName(stackTrace[DEBUG_STACK_OFFSET + 2].getClassName());
535 } catch (final ClassNotFoundException exception) {
536 // Do nothing
540 return null;
544 * If a method {@code a()} calls a method {@code b()}, then the result of calling this method in b() will be "a".
545 * <br>Warning: this method can only be used directly.
546 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
548 * @return {@code null} if the caller method cannot be retrieved
549 * <br>A possibly null value
551 public static final String getCallerMethodName() {
552 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
554 return stackTrace.length > 3 ? stackTrace[3].getMethodName() : null;
558 * Calls {@link Logger#getLogger(String)} using the fully qualified name of the calling method.
559 * <br>Warning: this method can only be used directly.
560 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
562 * @return
563 * <br>A non-null value
564 * @throws NullPointerException if the caller class cannot be retrieved
566 public static final Logger getLoggerForThisMethod() {
567 return Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName());
571 * Use this method when you want to propagate a checked exception wrapped in a runtime exception
572 * instead of using the normal checked exception mechanism.
574 * @param <T> the type that the caller is supposed to return
575 * @param cause
576 * <br>Not null
577 * <br>Shared
578 * @return
579 * <br>Does not return
580 * @throws RuntimeException with {@code cause} as cause if it is a checked exception,
581 * otherwise {@code cause} is re-thrown
583 public static final <T> T throwUnchecked(final Throwable cause) {
584 if (cause instanceof Error) {
585 throw (Error) cause;
588 throw unchecked(cause);
592 * Returns an instance of {@link RuntimeException} which is either {@code cause} itself,
593 * if it is already a runtime exception, or a new runtime exception wrapping {@code cause}.
594 * <br>This method can be used as an alternative to {@link #throwUnchecked(java.lang.Throwable)},
595 * <br>with the difference that error types are wrapped.
596 * <br>It is up to the caller to decide what to do with the returned exception.
598 * @param cause
599 * <br>Not null
600 * @return
601 * <br>Not null
602 * <br>Maybe new
604 public static final RuntimeException unchecked(final Throwable cause) {
605 if (cause instanceof RuntimeException) {
606 return (RuntimeException) cause;
609 return new RuntimeException(cause);
613 * Does the same thing as {@link Class#cast(Object)},
614 * but returns {@code null} instead of throwing an exception if the cast cannot be performed.
616 * @param <T> the type into which {@code object} is tentatively being cast
617 * @param cls
618 * <br>Not null
619 * @param object
620 * <br>Maybe null
621 * @return {@code null} if {@code object} is {@code null} or cannot be cast into {@code T},
622 * otherwise {@code object}
623 * <br>Maybe null
625 public static final <T> T cast(final Class<T> cls, final Object object) {
626 if (object == null || !cls.isAssignableFrom(object.getClass())) {
627 return null;
630 return cls.cast(object);
635 * @param <T> the caller type
636 * @param object
637 * <br>Maybe null
638 * @return {@code null} if {@code object} is {@code null} or cannot be cast into the caller type
639 * (obtained using {@link #getCallerClass()}) , otherwise {@code object}
640 * <br>Maybe null
642 @SuppressWarnings("unchecked")
643 public static final <T> T castToCurrentClass(final Object object) {
644 return (T) cast(getCallerClass(), object);
649 * @param object1
650 * <br>Maybe null
651 * @param object2
652 * <br>Maybe null
653 * @return {@code true} if both objects are the same (using {@code ==}) or equal (using {@code equals()})
655 public static final boolean equals(final Object object1, final Object object2) {
656 return object1 == object2 || (object1 != null && object1.equals(object2));
661 * @param object
662 * <br>Maybe null
663 * @return {@code 0} if {@code object is null}, otherwise {@code object.hashcode()}
664 * <br>Range: any integer
666 public static final int hashCode(final Object object) {
667 return object == null ? 0 : object.hashCode();
671 * Concatenates the source location of the call and
672 * the string representations of the parameters separated by spaces.
673 * <br>This is method helps to perform console debugging using System.out or System.err.
675 * @param stackOffset {@link #DEBUG_STACK_OFFSET} is the source of the call,
676 * {@code DEBUG_STACK_OFFSET + 1} is the source of the call's caller, and so forth
677 * <br>Range: {@code [O .. Integer.MAX_VALUE]}
678 * @param objects
679 * <br>Not null
680 * @return
681 * <br>Not null
682 * <br>New
683 * @throws IndexOutOfBoundsException if {@code stackIndex} is invalid
685 public static final String debug(final int stackOffset, final Object... objects) {
686 final StringBuilder builder = new StringBuilder(
687 Thread.currentThread().getStackTrace()[stackOffset + 1].toString());
689 for (final Object object : objects) {
690 builder.append(" ").append(object);
693 return builder.toString();
697 * Prints on the standard output the concatenation of the source location of the call
698 * and the string representations of the parameters separated by spaces.
700 * @param objects
701 * <br>Not null
703 public static final void debugPrint(final Object... objects) {
704 System.out.println(debug(DEBUG_STACK_OFFSET + 1, objects));
709 * @return
710 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
712 private static final int getDebugStackOffset() {
713 int result = 0;
715 for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
716 if (element.getClassName().equals(Tools.class.getName())) {
717 break;
720 ++ result;
723 return result;