    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    }