View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath;
18  
19  import java.util.Collections;
20  import java.util.Date;
21  import java.util.Map;
22  import java.util.HashMap;
23  
24  import org.apache.commons.jxpath.util.ClassLoaderUtil;
25  
26  /**
27   * JXPathIntrospector  maintains a registry of {@link JXPathBeanInfo
28   * JXPathBeanInfo} objects for Java classes.
29   *
30   * @author Dmitri Plotnikov
31   * @version $Revision: 1293412 $ $Date: 2012-02-24 21:54:12 +0100 (Fr, 24 Feb 2012) $
32   */
33  public class JXPathIntrospector {
34  
35      private static Map byClass = Collections.synchronizedMap(new HashMap());
36      private static Map byInterface = Collections.synchronizedMap(new HashMap());
37  
38      static {
39          registerAtomicClass(Class.class);
40          registerAtomicClass(Boolean.TYPE);
41          registerAtomicClass(Boolean.class);
42          registerAtomicClass(Byte.TYPE);
43          registerAtomicClass(Byte.class);
44          registerAtomicClass(Character.TYPE);
45          registerAtomicClass(Character.class);
46          registerAtomicClass(Short.TYPE);
47          registerAtomicClass(Short.class);
48          registerAtomicClass(Integer.TYPE);
49          registerAtomicClass(Integer.class);
50          registerAtomicClass(Long.TYPE);
51          registerAtomicClass(Long.class);
52          registerAtomicClass(Float.TYPE);
53          registerAtomicClass(Float.class);
54          registerAtomicClass(Double.TYPE);
55          registerAtomicClass(Double.class);
56          registerAtomicClass(String.class);
57          registerAtomicClass(Date.class);
58          registerAtomicClass(java.sql.Date.class);
59          registerAtomicClass(java.sql.Time.class);
60          registerAtomicClass(java.sql.Timestamp.class);
61  
62          registerDynamicClass(Map.class, MapDynamicPropertyHandler.class);
63      }
64  
65      /**
66       * Automatically creates and registers a JXPathBeanInfo object
67       * for the specified class. That object returns true to isAtomic().
68       * @param beanClass to register
69       */
70      public static void registerAtomicClass(Class beanClass) {
71          synchronized (byClass) { 
72              byClass.put(beanClass, new JXPathBasicBeanInfo(beanClass, true));
73          }
74      }
75  
76      /**
77       * Automatically creates and registers a {@link JXPathBeanInfo} object
78       * for the specified class. That object returns true to
79       * {@link JXPathBeanInfo#isDynamic()}.
80       *
81       * @param beanClass to register
82       * @param dynamicPropertyHandlerClass to handle beanClass
83       */
84      public static void registerDynamicClass(Class beanClass,
85              Class dynamicPropertyHandlerClass) {
86          JXPathBasicBeanInfo bi =
87              new JXPathBasicBeanInfo(beanClass, dynamicPropertyHandlerClass);
88          if (beanClass.isInterface()) {
89              synchronized (byInterface) {
90                  byInterface.put(beanClass, bi);
91              }
92          }
93          else {
94              synchronized (byClass) {
95                  byClass.put(beanClass, bi);
96              }
97          }
98      }
99  
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 }