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}