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 018 package org.apache.commons.jexl2.internal.introspection; 019 020 import java.lang.reflect.Method; 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.Field; 023 import java.util.Map; 024 import java.util.HashMap; 025 import java.util.List; 026 import java.util.LinkedList; 027 028 import org.apache.commons.logging.Log; 029 030 /** 031 * This basic function of this class is to return a Method object for a 032 * particular class given the name of a method and the parameters to the method 033 * in the form of an Object[] 034 * <p/> 035 * The first time the Introspector sees a class it creates a class method map 036 * for the class in question. Basically the class method map is a Hastable where 037 * Method objects are keyed by a concatenation of the method name and the names 038 * of classes that make up the parameters. 039 * 040 * For example, a method with the following signature: 041 * 042 * public void method(String a, StringBuffer b) 043 * 044 * would be mapped by the key: 045 * 046 * "method" + "java.lang.String" + "java.lang.StringBuffer" 047 * 048 * This mapping is performed for all the methods in a class and stored for 049 * 050 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 051 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a> 052 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a> 053 * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a> 054 * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> 055 * @version $Id: IntrospectorBase.java 896952 2010-01-07 18:21:29Z henrib $ 056 * @since 1.0 057 */ 058 public class IntrospectorBase { 059 /** the logger. */ 060 protected final Log rlog; 061 /** 062 * Holds the method maps for the classes we know about, keyed by Class. 063 */ 064 private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>(); 065 /** 066 * The class loader used to solve constructors if needed. 067 */ 068 private ClassLoader loader; 069 /** 070 * Holds the map of classes ctors we know about as well as unknown ones. 071 */ 072 private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>(); 073 /** 074 * Holds the set of classes we have introspected. 075 */ 076 private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>(); 077 078 /** 079 * Create the introspector. 080 * @param log the logger to use 081 */ 082 public IntrospectorBase(Log log) { 083 this.rlog = log; 084 loader = getClass().getClassLoader(); 085 } 086 087 /** 088 * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>. 089 * 090 * @param c Class in which the method search is taking place 091 * @param key Key of the method being searched for 092 * @return The desired Method object. 093 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection. 094 * 095 */ 096 //CSOFF: RedundantThrows 097 public Method getMethod(Class<?> c, MethodKey key) { 098 try { 099 ClassMap classMap = getMap(c); 100 return classMap.findMethod(key); 101 } catch (MethodKey.AmbiguousException ae) { 102 // whoops. Ambiguous. Make a nice log message and return null... 103 if (rlog != null) { 104 rlog.error("ambiguous method invocation: " 105 + c.getName() + "." 106 + key.debugString()); 107 } 108 } 109 return null; 110 111 } 112 // CSON: RedundantThrows 113 114 115 /** 116 * Gets the field named by <code>key</code> for the class <code>c</code>. 117 * 118 * @param c Class in which the field search is taking place 119 * @param key Name of the field being searched for 120 * @return the desired field or null if it does not exist or is not accessible 121 * */ 122 public Field getField(Class<?> c, String key) { 123 ClassMap classMap = getMap(c); 124 return classMap.findField(c, key); 125 } 126 127 /** 128 * Gets the array of accessible field names known for a given class. 129 * @param c the class 130 * @return the class field names 131 */ 132 public String[] getFieldNames(Class<?> c) { 133 if (c == null) { 134 return new String[0]; 135 } 136 ClassMap classMap = getMap(c); 137 return classMap.getFieldNames(); 138 } 139 140 /** 141 * Gets the array of accessible methods names known for a given class. 142 * @param c the class 143 * @return the class method names 144 */ 145 public String[] getMethodNames(Class<?> c) { 146 if (c == null) { 147 return new String[0]; 148 } 149 ClassMap classMap = getMap(c); 150 return classMap.getMethodNames(); 151 } 152 153 /** 154 * A Constructor get cache-miss. 155 */ 156 private static class CacheMiss { 157 /** The constructor used as cache-miss. */ 158 @SuppressWarnings("unused") 159 public CacheMiss() {} 160 } 161 /** The cache-miss marker for the constructors map. */ 162 private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0]; 163 164 /** 165 * Sets the class loader used to solve constructors. 166 * <p>Also cleans the constructors cache.</p> 167 * @param cloader the class loader; if null, use this instance class loader 168 */ 169 public void setLoader(ClassLoader cloader) { 170 if (cloader == null) { 171 cloader = getClass().getClassLoader(); 172 } 173 if (!cloader.equals(loader)) { 174 synchronized(constructorsMap) { 175 loader = cloader; 176 constructorsMap.clear(); 177 constructibleClasses.clear(); 178 } 179 } 180 } 181 182 /** 183 * Gets the constructor defined by the <code>MethodKey</code>. 184 * 185 * @param key Key of the constructor being searched for 186 * @return The desired Constructor object. 187 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection. 188 */ 189 public Constructor<?> getConstructor(final MethodKey key) { 190 return getConstructor(null, key); 191 } 192 193 /** 194 * Gets the constructor defined by the <code>MethodKey</code>. 195 * @param c the class we want to instantiate 196 * @param key Key of the constructor being searched for 197 * @return The desired Constructor object. 198 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection. 199 */ 200 //CSOFF: RedundantThrows 201 public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) { 202 try { 203 Constructor<?> ctor = null; 204 synchronized(constructorsMap) { 205 ctor = constructorsMap.get(key); 206 // that's a clear miss 207 if (CTOR_MISS.equals(ctor)) { 208 return null; 209 } 210 // let's introspect... 211 if (ctor == null) { 212 final String cname = key.getMethod(); 213 // do we know about this class? 214 Class<?> clazz = constructibleClasses.get(cname); 215 try { 216 // do find the most specific ctor 217 if (clazz == null) { 218 if (c != null && c.getName().equals(key.getMethod())) { 219 clazz = c; 220 } else { 221 clazz = loader.loadClass(cname); 222 } 223 // add it to list of known loaded classes 224 constructibleClasses.put(cname, clazz); 225 } 226 List<Constructor<?>> l = new LinkedList<Constructor<?>>(); 227 for(Constructor<?> ictor : clazz.getConstructors()) { 228 l.add(ictor); 229 } 230 // try to find one 231 ctor = key.getMostSpecificConstructor(l); 232 if (ctor != null) { 233 constructorsMap.put(key, ctor); 234 } else { 235 constructorsMap.put(key, CTOR_MISS); 236 } 237 } catch(ClassNotFoundException xnotfound) { 238 if (rlog.isDebugEnabled()) { 239 rlog.debug("could not load class " + cname, xnotfound); 240 } 241 ctor = null; 242 } catch(MethodKey.AmbiguousException xambiguous) { 243 rlog.warn("ambiguous ctor detected for " + cname, xambiguous); 244 ctor = null; 245 } 246 } 247 } 248 return ctor; 249 } catch (MethodKey.AmbiguousException ae) { 250 // whoops. Ambiguous. Make a nice log message and return null... 251 if (rlog != null) { 252 rlog.error("ambiguous constructor invocation: new " 253 + key.debugString()); 254 } 255 } 256 return null; 257 } 258 // CSON: RedundantThrows 259 260 /** 261 * Gets the ClassMap for a given class. 262 * @param c the class 263 * @return the class map 264 */ 265 private ClassMap getMap(Class<?> c) { 266 synchronized (classMethodMaps) { 267 ClassMap classMap = classMethodMaps.get(c); 268 if (classMap == null) { 269 classMap = new ClassMap(c,rlog); 270 classMethodMaps.put(c, classMap); 271 } 272 return classMap; 273 } 274 } 275 }