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.jxpath;
018    
019    import java.util.Date;
020    import java.util.Map;
021    import java.util.HashMap;
022    
023    /**
024     * JXPathIntrospector  maintains a registry of {@link JXPathBeanInfo
025     * JXPathBeanInfo} objects for Java classes.
026     *
027     * @author Dmitri Plotnikov
028     * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
029     */
030    public class JXPathIntrospector {
031    
032        private static HashMap byClass = new HashMap();
033        private static HashMap byInterface = new HashMap();
034    
035        static {
036            registerAtomicClass(Class.class);
037            registerAtomicClass(Boolean.TYPE);
038            registerAtomicClass(Boolean.class);
039            registerAtomicClass(Byte.TYPE);
040            registerAtomicClass(Byte.class);
041            registerAtomicClass(Character.TYPE);
042            registerAtomicClass(Character.class);
043            registerAtomicClass(Short.TYPE);
044            registerAtomicClass(Short.class);
045            registerAtomicClass(Integer.TYPE);
046            registerAtomicClass(Integer.class);
047            registerAtomicClass(Long.TYPE);
048            registerAtomicClass(Long.class);
049            registerAtomicClass(Float.TYPE);
050            registerAtomicClass(Float.class);
051            registerAtomicClass(Double.TYPE);
052            registerAtomicClass(Double.class);
053            registerAtomicClass(String.class);
054            registerAtomicClass(Date.class);
055            registerAtomicClass(java.sql.Date.class);
056            registerAtomicClass(java.sql.Time.class);
057            registerAtomicClass(java.sql.Timestamp.class);
058    
059            registerDynamicClass(Map.class, MapDynamicPropertyHandler.class);
060        }
061    
062        /**
063         * Automatically creates and registers a JXPathBeanInfo object
064         * for the specified class. That object returns true to isAtomic().
065         * @param beanClass to register
066         */
067        public static void registerAtomicClass(Class beanClass) {
068            byClass.put(beanClass, new JXPathBasicBeanInfo(beanClass, true));
069        }
070    
071        /**
072         * Automatically creates and registers a {@link JXPathBeanInfo} object
073         * for the specified class. That object returns true to
074         * {@link JXPathBeanInfo#isDynamic()}.
075         *
076         * @param beanClass to register
077         * @param dynamicPropertyHandlerClass to handle beanClass
078         */
079        public static void registerDynamicClass(Class beanClass,
080                Class dynamicPropertyHandlerClass) {
081            JXPathBasicBeanInfo bi =
082                new JXPathBasicBeanInfo(beanClass, dynamicPropertyHandlerClass);
083            if (beanClass.isInterface()) {
084                byInterface.put(beanClass, bi);
085            }
086            else {
087                byClass.put(beanClass, bi);
088            }
089        }
090    
091        /**
092         * Creates and registers a JXPathBeanInfo object for the supplied class. If
093         * the class has already been registered, returns the registered
094         * JXPathBeanInfo object.
095         * <p>
096         * The process of creation of JXPathBeanInfo is as follows:
097         * <ul>
098         * <li>If class named <code>&lt;beanClass&gt;XBeanInfo</code> exists,
099         *     an instance of that class is allocated.
100         * <li>Otherwise, an instance of {@link JXPathBasicBeanInfo
101         *     JXPathBasicBeanInfo}  is allocated.
102         * </ul>
103         * @param beanClass whose info to get
104         * @return JXPathBeanInfo
105         */
106        public static JXPathBeanInfo getBeanInfo(Class beanClass) {
107            JXPathBeanInfo beanInfo = (JXPathBeanInfo) byClass.get(beanClass);
108            if (beanInfo == null) {
109                beanInfo = findDynamicBeanInfo(beanClass);
110                if (beanInfo == null) {
111                    beanInfo = findInformant(beanClass);
112                    if (beanInfo == null) {
113                        beanInfo = new JXPathBasicBeanInfo(beanClass);
114                    }
115                }
116                byClass.put(beanClass, beanInfo);
117            }
118            return beanInfo;
119        }
120    
121        /**
122         * Find a dynamic bean info if available for any superclasses or
123         * interfaces.
124         * @param beanClass to search for
125         * @return JXPathBeanInfo
126         */
127        private static JXPathBeanInfo findDynamicBeanInfo(Class beanClass) {
128            JXPathBeanInfo beanInfo = null;
129            if (beanClass.isInterface()) {
130                beanInfo = (JXPathBeanInfo) byInterface.get(beanClass);
131                if (beanInfo != null && beanInfo.isDynamic()) {
132                    return beanInfo;
133                }
134            }
135    
136            Class[] interfaces = beanClass.getInterfaces();
137            if (interfaces != null) {
138                for (int i = 0; i < interfaces.length; i++) {
139                    beanInfo = findDynamicBeanInfo(interfaces[i]);
140                    if (beanInfo != null && beanInfo.isDynamic()) {
141                        return beanInfo;
142                    }
143                }
144            }
145    
146            Class sup = beanClass.getSuperclass();
147            if (sup != null) {
148                beanInfo = (JXPathBeanInfo) byClass.get(sup);
149                if (beanInfo != null && beanInfo.isDynamic()) {
150                    return beanInfo;
151                }
152                return findDynamicBeanInfo(sup);
153            }
154            return null;
155        }
156    
157        /**
158         * find a JXPathBeanInfo instance for the specified class.
159         * Similar to javax.beans property handler discovery; search for a
160         * class with "XBeanInfo" appended to beanClass.name, then check
161         * whether beanClass implements JXPathBeanInfo for itself.
162         * Invokes the default constructor for any class it finds.
163         * @param beanClass for which to look for an info provider
164         * @return JXPathBeanInfo instance or null if none found
165         */
166        private static synchronized JXPathBeanInfo findInformant(Class beanClass) {
167            String name = beanClass.getName() + "XBeanInfo";
168            try {
169                return (JXPathBeanInfo) instantiate(beanClass, name);
170            }
171            catch (Exception ex) { //NOPMD
172                // Just drop through
173            }
174    
175            // Now try checking if the bean is its own JXPathBeanInfo.
176            try {
177                if (JXPathBeanInfo.class.isAssignableFrom(beanClass)) {
178                    return (JXPathBeanInfo) beanClass.newInstance();
179                }
180            }
181            catch (Exception ex) { //NOPMD
182                // Just drop through
183            }
184    
185            return null;
186        }
187    
188        /**
189         * Try to create an instance of a named class.
190         * First try the classloader of "sibling", then try the system
191         * classloader.
192         * @param sibling Class
193         * @param className to instantiate
194         * @return new Object
195         * @throws Exception if instantiation fails
196         */
197        private static Object instantiate(Class sibling, String className)
198                throws Exception {
199    
200            // First check with sibling's classloader (if any).
201            ClassLoader cl = sibling.getClassLoader();
202            if (cl != null) {
203                try {
204                    Class cls = cl.loadClass(className);
205                    return cls.newInstance();
206                }
207                catch (Exception ex) { //NOPMD
208                    // Just drop through and try the system classloader.
209                }
210            }
211    
212            // Now try the bootstrap classloader.
213            Class cls = Class.forName(className);
214            return cls.newInstance();
215        }
216    }