001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.jexl2.internal; 018 019 import java.lang.ref.SoftReference; 020 import java.lang.reflect.Method; 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.Field; 023 024 import org.apache.commons.jexl2.internal.introspection.IntrospectorBase; 025 import org.apache.commons.jexl2.internal.introspection.MethodKey; 026 027 import org.apache.commons.logging.Log; 028 029 /** 030 * Default introspection services. 031 * <p>Finding methods as well as property getters & setters.</p> 032 * @since 1.0 033 */ 034 public class Introspector { 035 /** The logger to use for all warnings & errors. */ 036 protected final Log rlog; 037 /** The soft reference to the introspector currently in use. */ 038 private volatile SoftReference<IntrospectorBase> ref; 039 040 /** 041 * Creates an introspector. 042 * @param log the logger to use for warnings. 043 */ 044 protected Introspector(Log log) { 045 rlog = log; 046 ref = new SoftReference<IntrospectorBase>(null); 047 } 048 049 /** 050 * Coerce an Object to an Integer. 051 * @param arg the Object to coerce 052 * @return an Integer if it can be converted, null otherwise 053 */ 054 protected Integer toInteger(Object arg) { 055 if (arg == null) { 056 return null; 057 } 058 if (arg instanceof Number) { 059 return Integer.valueOf(((Number) arg).intValue()); 060 } 061 try { 062 return Integer.valueOf(arg.toString()); 063 } catch (NumberFormatException xnumber) { 064 return null; 065 } 066 } 067 068 /** 069 * Coerce an Object to a String. 070 * @param arg the Object to coerce 071 * @return a String if it can be converted, null otherwise 072 */ 073 protected String toString(Object arg) { 074 return arg == null ? null : arg.toString(); 075 } 076 077 /** 078 * Gets the current introspector base. 079 * <p>If the reference has been collected, this method will recreate the underlying introspector.</p> 080 * @return the introspector 081 */ 082 // CSOFF: DoubleCheckedLocking 083 protected final IntrospectorBase base() { 084 IntrospectorBase intro = ref.get(); 085 if (intro == null) { 086 // double checked locking is ok (fixed by Java 5 memory model). 087 synchronized(this) { 088 intro = ref.get(); 089 if (intro == null) { 090 intro = new IntrospectorBase(rlog); 091 ref = new SoftReference<IntrospectorBase>(intro); 092 } 093 } 094 } 095 return intro; 096 } 097 // CSON: DoubleCheckedLocking 098 099 /** 100 * Sets the underlying class loader for class solving resolution. 101 * @param loader the loader to use 102 */ 103 public void setClassLoader(ClassLoader loader) { 104 base().setLoader(loader); 105 } 106 107 /** 108 * Gets a class by name through this introspector class loader. 109 * @param className the class name 110 * @return the class instance or null if it could not be found 111 */ 112 public Class<?> getClassByName(String className) { 113 return base().getClassByName(className); 114 } 115 116 /** 117 * Gets the field named by <code>key</code> for the class <code>c</code>. 118 * 119 * @param c Class in which the field search is taking place 120 * @param key Name of the field being searched for 121 * @return a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible 122 * */ 123 public final Field getField(Class<?> c, String key) { 124 return base().getField(c, key); 125 } 126 127 /** 128 * Gets the accessible field names known for a given class. 129 * @param c the class 130 * @return the class field names 131 */ 132 public final String[] getFieldNames(Class<?> c) { 133 return base().getFieldNames(c); 134 } 135 136 /** 137 * Gets the method defined by <code>name</code> and 138 * <code>params</code> for the Class <code>c</code>. 139 * 140 * @param c Class in which the method search is taking place 141 * @param name Name of the method being searched for 142 * @param params An array of Objects (not Classes) that describe the 143 * the parameters 144 * 145 * @return a {@link java.lang.reflect.Method} 146 * or null if no unambiguous method could be found through introspection. 147 */ 148 public final Method getMethod(Class<?> c, String name, Object[] params) { 149 return base().getMethod(c, new MethodKey(name, params)); 150 } 151 152 /** 153 * Gets the method defined by <code>key</code> and for the Class <code>c</code>. 154 * 155 * @param c Class in which the method search is taking place 156 * @param key MethodKey of the method being searched for 157 * 158 * @return a {@link java.lang.reflect.Method} 159 * or null if no unambiguous method could be found through introspection. 160 */ 161 public final Method getMethod(Class<?> c, MethodKey key) { 162 return base().getMethod(c, key); 163 } 164 165 166 /** 167 * Gets the accessible methods names known for a given class. 168 * @param c the class 169 * @return the class method names 170 */ 171 public final String[] getMethodNames(Class<?> c) { 172 return base().getMethodNames(c); 173 } 174 175 /** 176 * Gets all the methods with a given name from this map. 177 * @param c the class 178 * @param methodName the seeked methods name 179 * @return the array of methods 180 */ 181 public final Method[] getMethods(Class<?> c, final String methodName) { 182 return base().getMethods(c, methodName); 183 } 184 185 /** 186 * Returns a general constructor. 187 * @param ctorHandle the object 188 * @param args contructor arguments 189 * @return a {@link java.lang.reflect.Constructor} 190 * or null if no unambiguous contructor could be found through introspection. 191 */ 192 public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) { 193 String className = null; 194 Class<?> clazz = null; 195 if (ctorHandle instanceof Class<?>) { 196 clazz = (Class<?>) ctorHandle; 197 className = clazz.getName(); 198 } else if (ctorHandle != null) { 199 className = ctorHandle.toString(); 200 } else { 201 return null; 202 } 203 return base().getConstructor(clazz, new MethodKey(className, args)); 204 } 205 206 /** 207 * Returns a general method. 208 * @param obj the object 209 * @param name the method name 210 * @param args method arguments 211 * @return a {@link AbstractExecutor.Method}. 212 */ 213 public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) { 214 AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args); 215 return me.isAlive() ? me : null; 216 } 217 218 /** 219 * Return a property getter. 220 * @param obj the object to base the property from. 221 * @param identifier property name 222 * @return a {@link AbstractExecutor.Get}. 223 */ 224 public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) { 225 final Class<?> claz = obj.getClass(); 226 final String property = toString(identifier); 227 AbstractExecutor.Get executor; 228 // first try for a getFoo() type of property (also getfoo() ) 229 if (property != null) { 230 executor = new PropertyGetExecutor(this, claz, property); 231 if (executor.isAlive()) { 232 return executor; 233 } 234 //} 235 // look for boolean isFoo() 236 //if (property != null) { 237 executor = new BooleanGetExecutor(this, claz, property); 238 if (executor.isAlive()) { 239 return executor; 240 } 241 } 242 // let's see if we are a map... 243 executor = new MapGetExecutor(this, claz, identifier); 244 if (executor.isAlive()) { 245 return executor; 246 } 247 // let's see if we can convert the identifier to an int, 248 // if obj is an array or a list, we can still do something 249 Integer index = toInteger(identifier); 250 if (index != null) { 251 executor = new ListGetExecutor(this, claz, index); 252 if (executor.isAlive()) { 253 return executor; 254 } 255 } 256 // if that didn't work, look for set("foo") 257 executor = new DuckGetExecutor(this, claz, identifier); 258 if (executor.isAlive()) { 259 return executor; 260 } 261 // if that didn't work, look for set("foo") 262 executor = new DuckGetExecutor(this, claz, property); 263 if (executor.isAlive()) { 264 return executor; 265 } 266 return null; 267 } 268 269 /** 270 * Return a property setter. 271 * @param obj the object to base the property from. 272 * @param identifier property name (or identifier) 273 * @param arg value to set 274 * @return a {@link AbstractExecutor.Set}. 275 */ 276 public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) { 277 final Class<?> claz = obj.getClass(); 278 final String property = toString(identifier); 279 AbstractExecutor.Set executor; 280 // first try for a setFoo() type of property (also setfoo() ) 281 if (property != null) { 282 executor = new PropertySetExecutor(this, claz, property, arg); 283 if (executor.isAlive()) { 284 return executor; 285 } 286 } 287 // let's see if we are a map... 288 executor = new MapSetExecutor(this, claz, identifier, arg); 289 if (executor.isAlive()) { 290 return executor; 291 } 292 // let's see if we can convert the identifier to an int, 293 // if obj is an array or a list, we can still do something 294 Integer index = toInteger(identifier); 295 if (index != null) { 296 executor = new ListSetExecutor(this, claz, index, arg); 297 if (executor.isAlive()) { 298 return executor; 299 } 300 } 301 // if that didn't work, look for set(foo) 302 executor = new DuckSetExecutor(this, claz, identifier, arg); 303 if (executor.isAlive()) { 304 return executor; 305 } 306 // if that didn't work, look for set("foo") 307 executor = new DuckSetExecutor(this, claz, property, arg); 308 if (executor.isAlive()) { 309 return executor; 310 } 311 return null; 312 } 313 }