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 (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 /** 109 * Gets the field named by <code>key</code> for the class <code>c</code>. 110 * 111 * @param c Class in which the field search is taking place 112 * @param key Name of the field being searched for 113 * @return the desired field or null if it does not exist or is not accessible 114 * */ 115 protected final Field getField(Class<?> c, String key) { 116 return base().getField(c, key); 117 } 118 119 /** 120 * Gets the accessible field names known for a given class. 121 * @param c the class 122 * @return the class field names 123 */ 124 public final String[] getFieldNames(Class<?> c) { 125 return base().getFieldNames(c); 126 } 127 128 /** 129 * Gets the method defined by <code>name</code> and 130 * <code>params</code> for the Class <code>c</code>. 131 * 132 * @param c Class in which the method search is taking place 133 * @param name Name of the method being searched for 134 * @param params An array of Objects (not Classes) that describe the 135 * the parameters 136 * 137 * @return The desired Method object. 138 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection. 139 * CSOFF: RedundantThrows 140 */ 141 protected final Method getMethod(Class<?> c, String name, Object[] params) throws IllegalArgumentException { 142 return base().getMethod(c, new MethodKey(name, params)); 143 } 144 145 /** 146 * Gets the method defined by <code>key</code> and for the Class <code>c</code>. 147 * 148 * @param c Class in which the method search is taking place 149 * @param key MethodKey of the method being searched for 150 * 151 * @return The desired Method object. 152 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection. 153 * CSOFF: RedundantThrows 154 */ 155 protected final Method getMethod(Class<?> c, MethodKey key) throws IllegalArgumentException { 156 return base().getMethod(c, key); 157 } 158 159 160 /** 161 * Gets the accessible methods names known for a given class. 162 * @param c the class 163 * @return the class method names 164 */ 165 public final String[] getMethodNames(Class<?> c) { 166 return base().getMethodNames(c); 167 } 168 169 /** 170 * Returns a general constructor. 171 * @param ctorHandle the object 172 * @param args contrusctor arguments 173 * @return a {@link java.lang.reflect.Constructor} 174 */ 175 public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) { 176 String className = null; 177 Class<?> clazz = null; 178 if (ctorHandle instanceof Class<?>) { 179 clazz = (Class<?>) ctorHandle; 180 className = clazz.getName(); 181 } else if (ctorHandle != null) { 182 className = ctorHandle.toString(); 183 } else { 184 return null; 185 } 186 return base().getConstructor(clazz, new MethodKey(className, args)); 187 } 188 189 /** 190 * Returns a general method. 191 * @param obj the object 192 * @param name the method name 193 * @param args method arguments 194 * @return a {@link AbstractExecutor.Method}. 195 */ 196 public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) { 197 AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args); 198 return me.isAlive() ? me : null; 199 } 200 201 /** 202 * Return a property getter. 203 * @param obj the object to base the property from. 204 * @param identifier property name 205 * @return a {@link AbstractExecutor.Get}. 206 */ 207 public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) { 208 final Class<?> claz = obj.getClass(); 209 final String property = toString(identifier); 210 AbstractExecutor.Get executor; 211 // first try for a getFoo() type of property (also getfoo() ) 212 if (property != null) { 213 executor = new PropertyGetExecutor(this, claz, property); 214 if (executor.isAlive()) { 215 return executor; 216 } 217 } 218 // look for boolean isFoo() 219 if (property != null) { 220 executor = new BooleanGetExecutor(this, claz, property); 221 if (executor.isAlive()) { 222 return executor; 223 } 224 } 225 // let's see if we are a map... 226 executor = new MapGetExecutor(this, claz, identifier); 227 if (executor.isAlive()) { 228 return executor; 229 } 230 // let's see if we can convert the identifier to an int, 231 // if obj is an array or a list, we can still do something 232 Integer index = toInteger(identifier); 233 if (index != null) { 234 executor = new ListGetExecutor(this, claz, index); 235 if (executor.isAlive()) { 236 return executor; 237 } 238 } 239 // if that didn't work, look for set("foo") 240 executor = new DuckGetExecutor(this, claz, identifier); 241 if (executor.isAlive()) { 242 return executor; 243 } 244 return null; 245 } 246 247 /** 248 * Return a property setter. 249 * @param obj the object to base the property from. 250 * @param identifier property name (or identifier) 251 * @param arg value to set 252 * @return a {@link AbstractExecutor.Set}. 253 */ 254 public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) { 255 final Class<?> claz = obj.getClass(); 256 final String property = toString(identifier); 257 AbstractExecutor.Set executor; 258 // first try for a setFoo() type of property (also setfoo() ) 259 if (property != null) { 260 executor = new PropertySetExecutor(this, claz, property, arg); 261 if (executor.isAlive()) { 262 return executor; 263 } 264 } 265 // let's see if we are a map... 266 executor = new MapSetExecutor(this, claz, identifier, arg); 267 if (executor.isAlive()) { 268 return executor; 269 } 270 // let's see if we can convert the identifier to an int, 271 // if obj is an array or a list, we can still do something 272 Integer index = toInteger(identifier); 273 if (index != null) { 274 executor = new ListSetExecutor(this, claz, index, arg); 275 if (executor.isAlive()) { 276 return executor; 277 } 278 } 279 // if that didn't work, look for set("foo") 280 executor = new DuckSetExecutor(this, claz, property, arg); 281 if (executor.isAlive()) { 282 return executor; 283 } 284 return null; 285 } 286 }