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