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.Date;
020import java.util.Map;
021import 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 */
030public 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}