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