/*license*\ XBN-Java: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) This software is dual-licensed under the: - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; - Apache Software License (ASL) version 2.0. Either license may be applied at your discretion. More information may be found at - http://en.wikipedia.org/wiki/Multi-licensing. The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt \*license*/ package com.github.xbn.lang.reflect; import com.github.xbn.io.NewTextAppenterFor; import com.github.xbn.io.TextAppenter; import com.github.xbn.lang.CrashIfObject; import com.github.xbn.lang.RTExceptionInInitializerError; import com.github.xbn.lang.RTIllegalAccessException; import com.github.xbn.lang.RTInstantiationException; import com.github.xbn.text.CrashIfString; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Objects; import static com.github.xbn.lang.CrashIfBase.*; /** <p>Reflection-related utilities with runtime errors only. The original exception is accessible via {@code getCause()}.</p> * @since 0.1.0 * @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://xbnjava.aliteralmind.com">{@code http://xbnjava.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/xbnjava">{@code https://github.com/aliteralmind/xbnjava}</a> **/ public class ReflectRtxUtil { /** <p>Is one class assignable from another?.</p> * @param class_forPreDot May not be {@code null}. * @param class_forParam May not be {@code null}. * @return <code>class_forPreDot.{@link java.lang.Class#isAssignableFrom(Class) isAssignableFrom}(class_forParam)</code> * @see #crashIfNotAssignableFrom(Class, Class) crashIfNotAssignableFrom(c,c) */ public static final boolean isClassAssignableFrom(Class<?> class_forPreDot, Class<?> class_forParam) { try { return class_forPreDot.isAssignableFrom(class_forParam); } catch(RuntimeException rx) { Objects.requireNonNull(class_forPreDot, "class_forPreDot"); throw CrashIfObject.nullOrReturnCause(class_forParam, "class_forParam", null, rx); } } /** <p>If one class is not assignable from another, crash. Otherwise, do nothing.</p> * @exception ClassCastException If <br/>     <code>{@link #isClassAssignableFrom(Class, Class) isClassAssignableFrom}(class_forPreDot, class_forParam)</code> <br/>is {@code false} */ public static final void crashIfNotAssignableFrom(Class<?> class_forPreDot, Class<?> class_forParam) { if(!isClassAssignableFrom(class_forPreDot, class_forParam)) { throw new ClassCastException("class_forPreDot expected to be of type " + class_forParam + ". class_forPreDot.getName()=\"" + class_forPreDot.getName() + "\""); } } /** <p>Invoke a non-{@code void} method, and get any {@code RuntimeException} thrown from it. If no exception is thrown, this returns {@code null}.</p> * @param obj_methodInvokedFrom If non-{@code null}, the method is being invoked from this class. If {@code null}, the method must be static. * @param method May not be {@code null}. * @param param_objArr May not be {@code null}. * @return <code>method.{@link java.lang.reflect.Method#invoke(Object, Object...) invoke}(obj_methodInvokedFrom, param_objArr)</code> * @exception RuntimeException If any error occurs. The original exception is accessible with {@link java.lang.RuntimeException#getCause() getCause}{@code ()}. * @see #invokeVoidMethodGetRtx(Object, Method, Object, Object...) invokeVoidMethodGetRtx * @see InvokeMethodWithRtx */ public static final Object invokeMethodWithRtxGetReturnValue(Object obj_methodInvokedFrom, Method method, Object xtra_errInfo, Object... param_objArr) { Throwable tbl = null; try { return method.invoke(obj_methodInvokedFrom, param_objArr); } catch(Exception x) { Objects.requireNonNull(method, "method"); tbl = x; } throw newRtxForInvokeAttemptOrNullIfNullThrbl(obj_methodInvokedFrom, method, xtra_errInfo, tbl); } private static final RuntimeException newRtxForInvokeAttemptOrNullIfNullThrbl(Object obj_methodInvokedFrom, Method method, Object xtra_errInfo, Throwable cause) { if(cause == null) { return null; } String sClsNm = getClassNameWithNullDefault("from object ", obj_methodInvokedFrom, null, "(the class from which the method is invoked is null, meaning the method is supposed to be static)"); return new RuntimeException(getXMsg("Attempting to invoke " + ((method == null) ? "[method is null]" : method.getName()) + " " + sClsNm, xtra_errInfo), cause); } /** <p>If a fully qualified name represents an actually-existing Java class, get its class object.</p> * @return <code>{@link #getClassIfExistsOrNull(String, Class) getClassIfExistsOrNull}(fully_qualifiedName, null)</code> */ public static final Class<?> getClassIfExistsOrNull(String fully_qualifiedName) { return getClassIfExistsOrNull(fully_qualifiedName, null); } /** <p>If a fully qualified name represents an actually-existing Java class, get its class object and optionally verify that it's assingable from a specific type. If the class doesn't exist, this returns {@code null}.</p> <p><i>This uses a {@code ClassNotFoundException} as its {@code false} logic. <a href="http://stackoverflow.com/questions/19809640/check-if-class-exists-without-running-into-classnotfoundexception">It seems not</a>.</i></p> * @param fully_qualifiedName <i>Should</i> not be {@code null} or empty. * @param rqdType_ifNonNull If non-{@code null}, this is the type the class must be {@linkplain java.lang.Class#isAssignableFrom(Class) assignable from}. * @see #getClassIfExistsOrNull(String) * @exception ClassCastException if {@code rqdType_ifNonNull} is non-{@code null} and the class is not assignable from it. */ public static final <O> Class<O> getClassIfExistsOrNull(String fully_qualifiedName, Class<O> rqdType_ifNonNull) { Class<O> cls = null; try { @SuppressWarnings("unchecked") Class<O> cls2 = (Class<O>)Class.forName(fully_qualifiedName); cls = cls2; } catch(ClassNotFoundException cnfx) { //Class does not exist. //Exception for "false" logic :( Is there a non-exception way? return null; } if(rqdType_ifNonNull != null) { crashIfNotAssignableFrom(rqdType_ifNonNull, cls); } return cls; } /** <p>Call a void function and get the exception thrown from it <i>which is wrapped in a {@code RuntimeException}</i>. If no exception is thrown, this returns {@code null}.</p> <p>This calls <br/>     <code>method.{@link java.lang.reflect.Method#invoke(Object, Object...) invoke}(obj_methodInvokedFrom, dddo_params)</code></p> * @param obj_methodInvokedFrom The object the underlying method is invoked from. If the method is {@code static}, set this to {@code null}. * @param method May not be {@code null}. * @return <b>{@code null}</b> If the function returned normally. Otherwise, a <b>{@code RuntimeException}</b> whose {@code getCause()} contains the original exception. * @see #invokeMethodWithRtxGetReturnValue(Object, Method, Object, Object...) invokeMethodWithRtxGetReturnValue(o,m,o,o...) * @see InvokeMethodWithRtx */ public static final RuntimeException invokeVoidMethodGetRtx(Object obj_methodInvokedFrom, Method method, Object xtra_errInfo, Object... dddo_params) { Throwable tbl = null; try { method.invoke(obj_methodInvokedFrom, (Object[])dddo_params); } catch(Exception x) { Objects.requireNonNull(method, "method"); tbl = x; } return newRtxForInvokeAttemptOrNullIfNullThrbl(obj_methodInvokedFrom, method, xtra_errInfo, tbl); } /** <p>Get an object`s class-name with a prefix and postfix string, and an if-null default.</p> * @param prefix If non-{@code null}, this precedes the class-name. If {@code null}, nothing precedes it. When {@code object} is {@code null}, this and {@code postfix} are ignored. * @param object The object whose class-name is needed. * @param postfix If non-{@code null}, this follows the class-name. If {@code null}, nothing follows it. * @param if_nonNull What to return if {@code object} is {@code null}. * @return If {@code object} is<ul> <li>{@code null}: {@code if_nonNull}</li> <li>non-{@code null}: <code>prefix + object.{@link java.lang.Object#getClass getClass}().{@link java.lang.Class#getName() getName}() + prefix</code></li> </ul> */ private static final String getClassNameWithNullDefault(String prefix, Object object, String postfix, String if_nonNull) { if(object == null) { return ((if_nonNull == null) ? "" : if_nonNull); } return ((prefix == null) ? "" : prefix) + object.getClass().getName() + ((postfix == null) ? "" : postfix); } /** <p>Get a new instance of a class from its no-parameter constructor, given its fully-qualified name.</p> * @param class_name May not be {@code null}, and must represent an existing class, and one that has a publicly accessible zero-parameter constructor. * @param rqdType_ifNonNull If non-{@code null}, this is the type that {@code class_name} must be {@linkplain java.lang.Class#isAssignableFrom(Class) assignable from}. * @param debugDest_ifNonNull If non-{@code null}, debugging output is printed. Using this <i>should</i> not result in any errors. * @exception RTClassNotFoundException If {@code class_name} is non-{@code null}, non-empty, but does not represent an actually-existing class. * @exception ClassCastException if {@code rqdType_ifNonNull} is non-{@code null} and the class is not assignable from it. */ public static final <O> O getNewInstanceFromNoParamCnstr(String class_name, Class<O> rqdType_ifNonNull, Appendable debugDest_ifNonNull) { TextAppenter dbgAptr = NewTextAppenterFor.appendableUnusableIfNull(debugDest_ifNonNull); if(dbgAptr.isUseable()) { dbgAptr.appentln(" Checking class (class_name=\"" + class_name + "\") exists and is of the rqdType_ifNonNull (" + rqdType_ifNonNull.getName() + ")..."); } Class<O> oClass = ReflectRtxUtil.<O>getClassIfExistsOrNull(class_name, rqdType_ifNonNull); if(oClass == null) { throw new RTClassNotFoundException("class_name=\"" + class_name + "\""); } if(dbgAptr.isUseable()) { dbgAptr.appentln(" ...success. Instantiating..."); } O oInstance = null; try { oInstance = oClass.newInstance(); } catch(ExceptionInInitializerError xir) { throw new RTExceptionInInitializerError("class_name=\"" + class_name + "\"", xir); } catch(InstantiationException ix) { throw new RTInstantiationException("class_name=\"" + class_name + "\"", ix); } catch(IllegalAccessException icx) { throw new RTIllegalAccessException("class_name=\"" + class_name + "\"", icx); } catch(RuntimeException rx) { throw new RuntimeException("Attempting to instantiate the class (class_name=" + class_name + ") with its no-parameter constructor. oInstance=" + oInstance, rx); } if(dbgAptr.isUseable()) { dbgAptr.appentln(" ...success"); } return oInstance; } /* public static final RuntimeException invokeMainMethodGetRtx(Object obj_withMain, Object xtra_errInfo, String... command_lineParams) { Class<?> cls = null; try { cls = obj_withMain.getClass(); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(obj_withMain, "obj_withMain", null, rx); } return invokeMainMethodGetRtx(cls, xtra_errInfo, command_lineParams); } public static final RuntimeException invokeMainMethodGetRtx(String fq_className, Object xtra_errInfo, String... command_lineParams) { Method m = getDeclaredMethod(fq_className, "main", xtra_errInfo, String[].class); return invokeVoidMethodGetRtx(null, m, xtra_errInfo, (Object)command_lineParams); } public static final RuntimeException invokeMainMethodGetRtx(Class cls_withMain, Object xtra_errInfo, String... command_lineParams) { Method m = getDeclaredMethod(cls_withMain, xtra_errInfo, String[].class); return invokeVoidMethodGetRtx(null, m, xtra_errInfo, (Object)command_lineParams); } */ /** <p>Get a method for a class whose fully-qualified name is provided.</p> <p>This<ol> <li>Gets the object's <code>Class</code> with <br/>     <code>{@link #getClassForName(String, String) getClassForName}(fq_className, "fq_className")</code></li> <li><i><b>Returns</b></i> <br/>     <code>{@link #getMethod(Class, String, Declared, Object, Class...) getMethod}(<i>[that class]</i>, method_name, declared, xtra_errInfo, param_types)</code></li> </ol></p> */ public static final Method getMethodForClassName(String fq_className, String method_name, Declared declared, Object xtra_errInfo, Class... param_types) { Class <?> cls = getClassForName(fq_className, "fq_className"); return getMethod(cls, method_name, declared, xtra_errInfo, param_types); } /** <p>Get an object's class, or crash if it's {@code null}.</p> * @param fq_className The fully-qualified name of the class. Must be non-{@code null} and valid. * @see #getClass(Object, Object) getClass(o,o) * @exception RTClassNotFoundException If a {@link java.lang.ClassNotFoundException ClassNotFoundException} is thrown. */ public static final Class getClassForName(String fq_className, String fqcn_varName) { try { return Class.forName(fq_className); } catch(ClassNotFoundException cnfx) { throw new RTClassNotFoundException("Unknown class name (is it compiled?): " + fqcn_varName + "=\"" + fq_className + "\""); } catch(Exception x) { CrashIfString.nullEmpty(fq_className, fq_className, null); throw new RuntimeException("Attempting Class.forName(" + fqcn_varName + "), " + fqcn_varName + "=\"" + fq_className + "\"", x); } } /** <p>Get an object's class, or crash if it's {@code null}.</p> * @param object May not be {@code null}. * @see #getClassForName(String, String) getClass(s,s) */ public static final Class getClass(Object object, Object xtra_errInfo) { try { return object.getClass(); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(object, "object", null, rx); } } /** <p>Get a constructor from its object.</p> * @param obj_withConstructor May not be {@code null}. * @return <code>{@link #getConstructor(Class, Declared, Object, Class...) getConstructor}(obj_withConstructor.{@link java.lang.Object#getClass() getClass}(), declared, xtra_errInfo, param_types)</code> */ public static final Constructor getConstructor(Object obj_withConstructor, Declared declared, Object xtra_errInfo, Class<?>... param_types) { try { return getConstructor(obj_withConstructor.getClass(), declared, xtra_errInfo, param_types); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(obj_withConstructor, "obj_withConstructor", null, rx); } } /** <p>Get a constructor from its class.</p> * @param containing_class May not be {@code null} * @return If {@code declared} is<ul> <li>{@link com.github.xbn.lang.reflect.Declared#YES YES}: <code>containing_class.{@link java.lang.Class#getDeclaredConstructor(Class...) getDeclaredConstructor}(param_types)</code></li> <li>{@link com.github.xbn.lang.reflect.Declared#NO NO}: <code>containing_class.{@link java.lang.Class#getConstructor(Class...) getConstructor}(param_types)</code></li> </ul> * @exception RTNoSuchMethodException If a {@link java.lang.NoSuchMethodException} is thrown. * @exception RuntimeException If any error occurs. The original exception is accessible with {@link java.lang.RuntimeException#getCause() getCause}{@code ()}. * @see #getConstructor(Object, Declared, Object, Class...) getConstructor */ public static final Constructor getConstructor(Class<?> containing_class, Declared declared, Object xtra_errInfo, Class<?>... param_types) { try { return ((declared.isNo()) ? containing_class.getConstructor(param_types) : containing_class.getDeclaredConstructor(param_types)); } catch(NoSuchMethodException nsmx) { throw new RTNoSuchMethodException("containing_class=" + containing_class.getName() + ", param_types=(" + getClassNames(null, param_types, null, ", ") + ")", nsmx); } catch(Exception x) { CrashIfObject.nnull(declared, "declared", null); CrashIfString.nullEmpty(containing_class, "containing_class", xtra_errInfo); throw new RuntimeException(getXMsg("Attempting containing_class.get" + (declared.isYes() ? "Declared" : "") + "Constructor(param_types), containing_class=" + containing_class.getName() + ", param_types=(" + getClassNames(null, param_types, null, ", ") + ")", xtra_errInfo), x); } } /** <p>Get a method from its containing object.</p> * @param obj_withMethod May not be {@code null}. * @return <code>{@link #getMethod(Class, String, Declared, Object, Class...) getMethod}(obj_withMethod.{@link java.lang.Object#getClass() getClass}(), method_name, xtra_errInfo, param_types)</code> */ public static final Method getMethod(Object obj_withMethod, String method_name, Declared declared, Object xtra_errInfo, Class<?>... param_types) { try { return getMethod(obj_withMethod.getClass(), method_name, declared, xtra_errInfo, param_types); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(obj_withMethod, "obj_withMethod", null, rx); } } /** <p>Get a method from its containing class.</p> * @param containing_class May not be {@code null} * @return If {@code declared} is<ul> <li>{@link com.github.xbn.lang.reflect.Declared#YES YES}: <code>containing_class.{@link java.lang.Class#getDeclaredMethod(String, Class...) getDeclaredMethod}(method_name, param_types)</code></li> <li>{@link com.github.xbn.lang.reflect.Declared#NO NO}: <code>containing_class.{@link java.lang.Class#getMethod(String, Class...) getMethod}(method_name, param_types)</code></li> </ul> * @exception RTNoSuchMethodException If a {@link java.lang.NoSuchMethodException} is thrown. * @exception RuntimeException If any error occurs. The original exception is accessible with {@link java.lang.RuntimeException#getCause() getCause}{@code ()}. * @see #getMethod(Object, String, Declared, Object, Class...) getMethod(Object, *) * @see #getMethodForClassName(String, String, Declared, Object, Class...) getMethodForClassName */ public static final Method getMethod(Class<?> containing_class, String method_name, Declared declared, Object xtra_errInfo, Class<?>... param_types) { try { return ((declared.isNo()) ? containing_class.getMethod(method_name, param_types) : containing_class.getDeclaredMethod(method_name, param_types)); } catch(NoSuchMethodException nsmx) { throw new RTNoSuchMethodException("containing_class=" + containing_class.getName() + ", param_types=(" + getClassNames(null, param_types, null, ", ") + ")", nsmx); } catch(Exception x) { CrashIfObject.nnull(declared, "declared", null); CrashIfString.nullEmpty(containing_class, "containing_class", xtra_errInfo); throw new RuntimeException(getXMsg("Attempting containing_class.get" + (declared.isYes() ? "Declared" : "") + "Method(method_name, param_types), containing_class=" + containing_class.getName() + ", method_name=\"" + method_name + "\", param_types=(" + getClassNames(null, param_types, null, ", ") + ")", xtra_errInfo), x); } } public static final Field getField(Class<?> containing_class, String field_name, Declared declared, Object xtra_errInfo) { try { return (declared.isYes() ? containing_class.getDeclaredField(field_name) : containing_class.getField(field_name)); } catch(NoSuchFieldException | SecurityException x) { throw new RTNoSuchFieldException(getXMsg("containing_class:" + containing_class.getName() + ", field_name=\"" + field_name + "\"", xtra_errInfo)); } catch(RuntimeException rx) { CrashIfObject.nnull(containing_class, "containing_class", xtra_errInfo); CrashIfObject.nnull(declared, "declared", xtra_errInfo); throw CrashIfObject.nullOrReturnCause(field_name, "field_name", xtra_errInfo, rx); } } /** <p>Get a list of {@code Class}es, for each object in an array, as required when obtaining a method.</p> * @param objects May not be {@code null}, and no element may be {@code null}. * @see #getMethod(Class, String, Declared, Object, Class...) */ public static final List<Class<?>> getClassListFromObjects(Object[] objects) { List<Class<?>> classList = null; try { classList = new ArrayList<Class<?>>(objects.length); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(objects, "objects", null, rx); } for(int i = 0; i < objects.length; i++) { Object o = objects[i]; try { classList.add(o.getClass()); } catch(RuntimeException rx) { throw new NullPointerException("objects[" + i + "]"); } } return classList; } /** <p>Get a list of {@code Class}es, for each object in a list, as required when obtaining a method.</p> * @param objectList May not be {@code null}, and no element may be {@code null}. * @see #getMethod(Class, String, Declared, Object, Class...) */ public static final List<Class<?>> getClassListFromObjects(List<?> objectList) { List<Class<?>> classList = null; try { classList = new ArrayList<Class<?>>(objectList.size()); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(objectList, "objectList", null, rx); } for(int i = 0; i < objectList.size(); i++) { Object o = objectList.get(i); try { classList.add(o.getClass()); } catch(RuntimeException rx) { throw new NullPointerException("objectList[" + i + "]"); } } return classList; } public static final <O> Class<?>[] getClassArrayFromObjects(O[] array) { Class<?>[] classes = null; try { classes = new Class<?>[array.length]; } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(array, "array", null, rx); } for(int i = 0; i < array.length; i++) { classes[i] = array[i].getClass(); } return classes; } public static final String getClassNames(String element_prefix, Class<?>[] classes, String element_postfix, String separator) { return appendClassNames((new StringBuilder()), element_prefix, classes, element_postfix, separator).toString(); } /** <p>YYY</p> * @see java.lang.Class#getName() * @see com.github.aliteralmind.codelet.util.JavaDocUtil#appendClassNameForParams(StringBuilder, Class[], VarArgs) */ public static final StringBuilder appendClassNames(StringBuilder to_appendTo, String element_prefix, Class<?>[] classes, String element_postfix, String separator) { if(classes == null) { return to_appendTo.append("null"); } int lenMinus1 = classes.length - 1; for(int i = 0; i < classes.length; i++) { Class<?> cls = classes[i]; if(element_prefix != null) { to_appendTo.append(element_prefix); } to_appendTo.append(cls.getName()); if(element_postfix != null) { to_appendTo.append(element_postfix); } if(i < lenMinus1) { to_appendTo.append(separator); } } return to_appendTo; } /* public static final Map<Character,String> newClassNameArrayElementEncodingCharMap() { Map<Character,String> map = new HashMap<Character,String>(9); map.put('Z', "boolean"); map.put('B', "byte"); map.put('C', "char"); map.put('L', null); map.put('D', "double"); map.put('F', "float"); map.put('I', "int"); map.put('J', "long"); map.put('S', "short"); return map; } */ private ReflectRtxUtil() { throw new IllegalStateException("Do not instantiate"); } }