[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / tools / Tools.java
blobb51f8ab2d8d0ad806810d5b4be8f630be84b77e2
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.lang.reflect.Array;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.Arrays;
31 import java.util.LinkedHashSet;
32 import java.util.logging.Logger;
34 /**
36 * @author codistmonk (creation 2010-06-11)
38 public final class Tools {
40 /**
41 * Private default constructor to prevent instantiation.
43 private Tools() {
44 // Do nothing
47 /**
49 * @param <T>
50 * @param array
51 * <br>Maybe null
52 * @return
53 * <br>Maybe null
54 * <br>Maybe New
56 public static final <T> T[] array(final T... array) {
57 return array;
60 /**
62 * @param <T> the common type of the elements
63 * @param elements
64 * <br>Not null
65 * @return
66 * <br>Not null
67 * <br>New
69 public static final <T> LinkedHashSet<T> set(T... elements) {
70 final LinkedHashSet<T> result = new LinkedHashSet<T>();
72 for (final T element : elements) {
73 result.add(element);
76 return result;
79 /**
81 * @param <T>
82 * @param array
83 * <br>Not null
84 * @param moreElements
85 * <br>Not null
86 * @return
87 * <br>Not null
88 * <br>New
90 public static final <T> T[] append(final T[] array, final T... moreElements) {
91 @SuppressWarnings("unchecked")
92 final T[] result = (T[]) Array.newInstance(
93 array.getClass().getComponentType(), array.length + moreElements.length);
95 System.arraycopy(array, 0, result, 0, array.length);
96 System.arraycopy(moreElements, 0, result, array.length, moreElements.length);
98 return result;
102 * Searches for and invokes a method named {@code methodName} that can accept {@code arguments}.
104 * @param <T> The expected return type
105 * @param objectOrClass
106 * <br>Not null
107 * @param methodName
108 * <br>Not null
109 * @param arguments
110 * <br>Not null
111 * @return
112 * <br>Maybe null
113 * @throws RuntimeException if an appropriate method isn't found or if it throws an exception
115 @SuppressWarnings("unchecked")
116 public static final <T> T invoke(final Object objectOrClass,
117 final String methodName, final Object... arguments) {
118 final Object object = objectOrClass instanceof Class<?> ? null : objectOrClass;
119 final Class<?> objectClass = (Class<?>) (objectOrClass instanceof Class<?> ? objectOrClass : objectOrClass.getClass());
121 for (final Method method : Tools.append(objectClass.getMethods(), objectClass.getDeclaredMethods())) {
122 if (method.getName().equals(methodName)) {
123 try {
124 return (T) method.invoke(object, arguments);
125 } catch (final InvocationTargetException exception) {
126 throwUnchecked(exception.getCause());
127 } catch (final Exception exception) {
128 // Ignore
133 throw new RuntimeException(
134 "Method " + methodName + " accepting arguments " + Arrays.toString(arguments) +
135 " was not found for object " + object + " of class " + objectClass);
139 * Tries to find a setter starting with "set" for the specified property of the object.
140 * <br>Eg: {@code getSetter(object, "text", String.class)} tries to find a method {@code setText(String)}
142 * @param object
143 * <br>Should not be null
144 * @param propertyName
145 * <br>Should not be null
146 * @param propertyClass
147 * <br>Should not be null
148 * @return
149 * <br>A non-null value
150 * @throws RuntimeException if an appropriate setter cannot be retrieved
152 public static final Method getSetter(final Object object, final String propertyName, final Class<?> propertyClass) {
153 final String setterName = "set" + toUpperCamelCase(propertyName);
155 try {
156 // Try to retrieve a public setter
157 return object.getClass().getMethod(setterName, propertyClass);
158 } catch (final Exception exception) {
159 // Do nothing
162 try {
163 // Try to retrieve a setter declared in object's class, regardless of its visibility
164 return object.getClass().getDeclaredMethod(setterName, propertyClass);
165 } catch (final Exception exception) {
166 // Do nothing
169 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
173 * Tries to find a getter starting with "get", "is", or "has" (in that order) for the specified property of the object.
174 * <br>Eg: {@code getGetter(object, "empty")} tries to find a method {@code getEmpty()} or {@code isEmpty()} or {@code hasEmpty()}
176 * @param object
177 * <br>Should not be null
178 * @param propertyName the camelCase name of the property
179 * <br>Should not be null
180 * @return
181 * <br>A non-null value
182 * @throws RuntimeException if an appropriate getter cannot be retrieved
184 public static final Method getGetter(final Object object, final String propertyName) {
185 final String upperCamelCase = toUpperCamelCase(propertyName);
187 for (final String prefix : array("get", "is", "has")) {
188 final String getterName = prefix + upperCamelCase;
190 try {
191 // Try to retrieve a public getter
192 return object.getClass().getMethod(getterName);
193 } catch (final Exception exception) {
194 // Do nothing
197 try {
198 // Try to retrieve a getter declared in object's class, regardless of its visibility
199 return object.getClass().getDeclaredMethod(getterName);
200 } catch (final Exception exception) {
201 // Do nothing
205 throw new RuntimeException("Unable to retrieve a getter for property " + propertyName);
209 * Converts "someName" into "SomeName".
211 * @param lowerCamelCase
212 * <br>Should not be null
213 * @return
214 * <br>A new value
215 * <br>A non-null value
217 public static final String toUpperCamelCase(final String lowerCamelCase) {
218 return Character.toUpperCase(lowerCamelCase.charAt(0)) + lowerCamelCase.substring(1);
222 * Converts {@code null} into "", otherwise returns the parameter untouched.
224 * @param string
225 * <br>Can be null
226 * <br>Shared parameter
227 * @return {@code string} or ""
228 * <br>A non-null value
229 * <br>A shared value
231 public static final String emptyIfNull(final String string) {
232 return string == null ? "" : string;
236 * Returns "package/name/" if the package of {@code cls} is of the form "package.name".
238 * @param cls
239 * <br>Not null
240 * @return
241 * <br>Not null
243 public static final String getPackagePath(final Class<?> cls) {
244 return cls.getPackage().getName().replaceAll("\\.", "/") + "/";
248 * Returns "package/name/" if the package of the caller class is of the form "package.name".
250 * @return
251 * <br>Not null
253 public static final String getCallerPackagePath() {
254 return getPackagePath(getCallerClass());
259 * @param cls
260 * <br>Not null
261 * @return the top level class enclosing {@code cls}, or {@code cls} itself if it is a top level class
262 * <br>Not null
264 public static final Class<?> getTopLevelEnclosingClass(final Class<?> cls) {
265 return cls.getEnclosingClass() == null ? cls : getTopLevelEnclosingClass(cls.getEnclosingClass());
269 * If a method {@code A.a()} calls a method {@code B.b()},
270 * then the result of calling this method in {@code b()} will be {@code A.class}.
271 * <br>Warning: this method can only be used directly.
272 * <br>If you want to refactor your code, you can re-implement the functionality
273 * using {@code Thread.currentThread().getStackTrace()}.
275 * @return {@code null} if the caller class cannot be retrieved
276 * <br>Maybe null
278 public static final Class<?> getCallerClass() {
279 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
281 if (stackTrace.length > 3) {
282 try {
283 return Class.forName(stackTrace[3].getClassName());
284 } catch (final ClassNotFoundException exception) {
285 // Do nothing
289 return null;
293 * If a method {@code a()} calls a method {@code b()}, then the result of calling this method in b() will be "a".
294 * <br>Warning: this method can only be used directly.
295 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
297 * @return {@code null} if the caller method cannot be retrieved
298 * <br>A possibly null value
300 public static final String getCallerMethodName() {
301 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
303 return stackTrace.length > 3 ? stackTrace[3].getMethodName() : null;
307 * Calls {@link Logger#getLogger(String)} using the fully qualified name of the calling method.
308 * <br>Warning: this method can only be used directly.
309 * <br>If you want to refactor your code, you can re-implement the functionality using {@code Thread.currentThread().getStackTrace()}.
311 * @return
312 * <br>A non-null value
313 * @throws NullPointerException if the caller class cannot be retrieved
315 public static final Logger getLoggerForThisMethod() {
316 return Logger.getLogger(getCallerClass().getName() + "." + getCallerMethodName());
320 * Use this method when you want to propagate a checked exception wrapped in a runtime exception
321 * instead of using the normal checked exception mechanism.
323 * @param <T> the type that the caller is supposed to return
324 * @param cause
325 * <br>Not null
326 * <br>Shared
327 * @return
328 * <br>Does not return
329 * @throws RuntimeException with {@code cause} as cause if it is a checked exception,
330 * otherwise {@code cause} is re-thrown
332 public static final <T> T throwUnchecked(final Throwable cause) {
333 if (cause instanceof Error) {
334 throw (Error) cause;
337 throw unchecked(cause);
341 * Returns an instance of {@link RuntimeException} which is either {@code cause} itself,
342 * if it is already a runtime exception, or a new runtime exception wrapping {@code cause}.
343 * <br>This method can be used as an alternative to {@link #throwUnchecked(java.lang.Throwable)},
344 * <br>with the difference that error types are wrapped.
345 * <br>It is up to the caller to decide what to do with the returned exception.
347 * @param cause
348 * <br>Not null
349 * @return
350 * <br>Not null
351 * <br>Maybe new
353 public static final RuntimeException unchecked(final Throwable cause) {
354 if (cause instanceof RuntimeException) {
355 return (RuntimeException) cause;
358 return new RuntimeException(cause);
362 * Does the same thing as {@link Class#cast(Object)},
363 * but returns {@code null} instead of throwing an exception if the cast cannot be performed.
365 * @param <T> the type into which {@code object} is tentatively being cast
366 * @param cls
367 * <br>Not null
368 * @param object
369 * <br>Maybe null
370 * @return {@code null} if {@code object} is {@code null} or cannot be cast into {@code T},
371 * otherwise {@code object}
372 * <br>Maybe null
374 public static final <T> T cast(final Class<T> cls, final Object object) {
375 if (object == null || !cls.isAssignableFrom(object.getClass())) {
376 return null;
379 return cls.cast(object);
384 * @param <T> the caller type
385 * @param object
386 * <br>Maybe null
387 * @return {@code null} if {@code object} is {@code null} or cannot be cast into the caller type
388 * (obtained using {@link #getCallerClass()}) , otherwise {@code object}
389 * <br>Maybe null
391 @SuppressWarnings("unchecked")
392 public static final <T> T castToCurrentClass(final Object object) {
393 return (T) cast(getCallerClass(), object);
398 * @param object1
399 * <br>Maybe null
400 * @param object2
401 * <br>Maybe null
402 * @return {@code true} if both objects are the same (using {@code ==}) or equal (using {@code equals()})
404 public static final boolean equals(final Object object1, final Object object2) {
405 return object1 == object2 || (object1 != null && object1.equals(object2));
410 * @param object
411 * <br>Maybe null
412 * @return {@code 0} if {@code object is null}, otherwise {@code object.hashcode()}
413 * <br>Range: any integer
415 public static final int hashCode(final Object object) {
416 return object == null ? 0 : object.hashCode();
420 * Concatenates the source location of the call and
421 * the string representations of the parameters separated by spaces.
422 * <br>This is method helps to perform console debugging using System.out or System.err.
424 * @param stackIndex 1 is the source of this method, 2 is the source of the call,
425 * 3 is the source of the call's caller, and so forth
426 * <br>Range: {@code [O .. Integer.MAX_VALUE]}
427 * @param objects
428 * <br>Not null
429 * @return
430 * <br>Not null
431 * <br>New
432 * @throws IndexOutOfBoundsException if {@code stackIndex} is invalid
434 public static final String debug(final int stackIndex, final Object... objects) {
435 final StringBuilder builder = new StringBuilder(
436 Thread.currentThread().getStackTrace()[stackIndex].toString());
438 for (final Object object : objects) {
439 builder.append(" ").append(object);
442 return builder.toString();
446 * Prints on the standard output the concatenation of the source location of the call
447 * and the string representations of the parameters separated by spaces.
449 * @param objects
450 * <br>Not null
452 public static final void debugPrint(final Object... objects) {
453 System.out.println(debug(3, objects));