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    }