/** * Copyright 2016 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.lang.internal; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import com.noga.njexl.lang.internal.introspection.MethodKey; import com.noga.njexl.lang.internal.introspection.IntrospectorBase; import com.noga.njexl.lang.internal.logging.Log; /** * Default introspection services. * <p>Finding methods as well as property getters & setters.</p> * @since 1.0 */ public class Introspector { /** The logger to use for all warnings & errors. */ protected final Log rlog; /** The soft reference to the introspector currently in use. */ private volatile SoftReference<IntrospectorBase> ref; /** * Creates an introspector. * @param log the logger to use for warnings. */ protected Introspector(Log log) { rlog = log; ref = new SoftReference<IntrospectorBase>(null); } /** * Coerce an Object to an Integer. * @param arg the Object to coerce * @return an Integer if it can be converted, null otherwise */ protected Integer toInteger(Object arg) { if (arg == null) { return null; } if (arg instanceof Number) { return Integer.valueOf(((Number) arg).intValue()); } try { return Integer.valueOf(arg.toString()); } catch (NumberFormatException xnumber) { return null; } } /** * Coerce an Object to a String. * @param arg the Object to coerce * @return a String if it can be converted, null otherwise */ protected String toString(Object arg) { return arg == null ? null : arg.toString(); } /** * Gets the current introspector base. * <p>If the reference has been collected, this method will recreate the underlying introspector.</p> * @return the introspector */ // CSOFF: DoubleCheckedLocking protected final IntrospectorBase base() { IntrospectorBase intro = ref.get(); if (intro == null) { // double checked locking is ok (fixed by Java 5 memory model). synchronized(this) { intro = ref.get(); if (intro == null) { intro = new IntrospectorBase(rlog); ref = new SoftReference<IntrospectorBase>(intro); } } } return intro; } // CSON: DoubleCheckedLocking /** * Sets the underlying class loader for class solving resolution. * @param loader the loader to use */ public void setClassLoader(ClassLoader loader) { base().setLoader(loader); } /** * Gets a class by name through this introspector class loader. * @param className the class name * @return the class instance or null if it could not be found */ public Class<?> getClassByName(String className) { return base().getClassByName(className); } /** * Gets the field named by <code>key</code> for the class <code>c</code>. * * @param c Class in which the field search is taking place * @param key Name of the field being searched for * @return a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible * */ public final Field getField(Class<?> c, String key) { return base().getField(c, key); } /** * Gets the accessible field names known for a given class. * @param c the class * @return the class field names */ public final String[] getFieldNames(Class<?> c) { return base().getFieldNames(c); } /** * Gets the method defined by <code>name</code> and * <code>params</code> for the Class <code>c</code>. * * @param c Class in which the method search is taking place * @param name Name of the method being searched for * @param params An array of Objects (not Classes) that describe the * the parameters * * @return a {@link java.lang.reflect.Method} * or null if no unambiguous method could be found through introspection. */ public final Method getMethod(Class<?> c, String name, Object[] params) { return base().getMethod(c, new MethodKey(name, params)); } /** * Gets the method defined by <code>key</code> and for the Class <code>c</code>. * * @param c Class in which the method search is taking place * @param key MethodKey of the method being searched for * * @return a {@link java.lang.reflect.Method} * or null if no unambiguous method could be found through introspection. */ public final Method getMethod(Class<?> c, MethodKey key) { return base().getMethod(c, key); } /** * Gets the accessible methods names known for a given class. * @param c the class * @return the class method names */ public final String[] getMethodNames(Class<?> c) { return base().getMethodNames(c); } /** * Gets all the methods with a given name from this map. * @param c the class * @param methodName the seeked methods name * @return the array of methods */ public final Method[] getMethods(Class<?> c, final String methodName) { return base().getMethods(c, methodName); } /** * Returns a general constructor. * @param ctorHandle the object * @param args contructor arguments * @return a {@link java.lang.reflect.Constructor} * or null if no unambiguous contructor could be found through introspection. */ public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) { String className = null; Class<?> clazz = null; if (ctorHandle instanceof Class<?>) { clazz = (Class<?>) ctorHandle; className = clazz.getName(); } else if (ctorHandle != null) { className = ctorHandle.toString(); } else { return null; } return base().getConstructor(clazz, new MethodKey(className, args)); } /** * Returns a general method. * @param obj the object * @param name the method name * @param args method arguments * @return a {@link AbstractExecutor.Method}. */ public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) { AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args); return me.isAlive() ? me : null; } /** * Return a property getter. * @param obj the object to base the property from. * @param identifier property name * @return a {@link AbstractExecutor.Get}. */ public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) { final Class<?> claz = obj.getClass(); final String property = toString(identifier); AbstractExecutor.Get executor; // first try for a getFoo() type of property (also getfoo() ) if (property != null) { executor = new PropertyGetExecutor(this, claz, property); if (executor.isAlive()) { return executor; } //} // look for boolean isFoo() //if (property != null) { executor = new BooleanGetExecutor(this, claz, property); if (executor.isAlive()) { return executor; } } // let's see if we are a map... executor = new MapGetExecutor(this, claz, identifier); if (executor.isAlive()) { return executor; } // let's see if we can convert the identifier to an int, // if obj is an array or a list, we can still do something Integer index = toInteger(identifier); if (index != null) { executor = new ListGetExecutor(this, claz, index); if (executor.isAlive()) { return executor; } } // if that didn't work, look for get("foo") executor = new DuckGetExecutor(this, claz, identifier); if (executor.isAlive()) { return executor; } return null; } /** * Return a property setter. * @param obj the object to base the property from. * @param identifier property name (or identifier) * @param arg value to set * @return a {@link AbstractExecutor.Set}. */ public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) { final Class<?> claz = obj.getClass(); final String property = toString(identifier); AbstractExecutor.Set executor; // first try for a setFoo() type of property (also setfoo() ) if (property != null) { executor = new PropertySetExecutor(this, claz, property, arg); if (executor.isAlive()) { return executor; } } // let's see if we are a map... executor = new MapSetExecutor(this, claz, identifier, arg); if (executor.isAlive()) { return executor; } // let's see if we can convert the identifier to an int, // if obj is an array or a list, we can still do something Integer index = toInteger(identifier); if (index != null) { executor = new ListSetExecutor(this, claz, index, arg); if (executor.isAlive()) { return executor; } } // if that didn't work, look for set(foo) executor = new DuckSetExecutor(this, claz, identifier, arg); if (executor.isAlive()) { return executor; } return null; } }