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  
18  package org.apache.commons.jxpath;
19  
20  import java.util.Collections;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import org.apache.commons.jxpath.util.ClassLoaderUtil;
26  
27  /**
28   * JXPathIntrospector maintains a registry of {@link JXPathBeanInfo JXPathBeanInfo} objects for Java classes.
29   */
30  public class JXPathIntrospector {
31  
32      private static Map<Class, JXPathBeanInfo> byClass = Collections.synchronizedMap(new HashMap<>());
33      private static Map<Class, JXPathBeanInfo> byInterface = Collections.synchronizedMap(new HashMap<>());
34      static {
35          registerAtomicClass(Class.class);
36          registerAtomicClass(Boolean.TYPE);
37          registerAtomicClass(Boolean.class);
38          registerAtomicClass(Byte.TYPE);
39          registerAtomicClass(Byte.class);
40          registerAtomicClass(Character.TYPE);
41          registerAtomicClass(Character.class);
42          registerAtomicClass(Short.TYPE);
43          registerAtomicClass(Short.class);
44          registerAtomicClass(Integer.TYPE);
45          registerAtomicClass(Integer.class);
46          registerAtomicClass(Long.TYPE);
47          registerAtomicClass(Long.class);
48          registerAtomicClass(Float.TYPE);
49          registerAtomicClass(Float.class);
50          registerAtomicClass(Double.TYPE);
51          registerAtomicClass(Double.class);
52          registerAtomicClass(String.class);
53          registerAtomicClass(Date.class);
54          registerAtomicClass(java.sql.Date.class);
55          registerAtomicClass(java.sql.Time.class);
56          registerAtomicClass(java.sql.Timestamp.class);
57          registerDynamicClass(Map.class, MapDynamicPropertyHandler.class);
58      }
59  
60      /**
61       * Find a dynamic bean info if available for any superclasses or interfaces.
62       *
63       * @param beanClass to search for
64       * @return JXPathBeanInfo
65       */
66      private static JXPathBeanInfo findDynamicBeanInfo(final Class beanClass) {
67          JXPathBeanInfo beanInfo;
68          if (beanClass.isInterface()) {
69              beanInfo = byInterface.get(beanClass);
70              if (beanInfo != null && beanInfo.isDynamic()) {
71                  return beanInfo;
72              }
73          }
74          final Class[] interfaces = beanClass.getInterfaces();
75          if (interfaces != null) {
76              for (final Class element : interfaces) {
77                  beanInfo = findDynamicBeanInfo(element);
78                  if (beanInfo != null && beanInfo.isDynamic()) {
79                      return beanInfo;
80                  }
81              }
82          }
83          final Class sup = beanClass.getSuperclass();
84          if (sup != null) {
85              beanInfo = byClass.get(sup);
86              if (beanInfo != null && beanInfo.isDynamic()) {
87                  return beanInfo;
88              }
89              return findDynamicBeanInfo(sup);
90          }
91          return null;
92      }
93  
94      /**
95       * find a JXPathBeanInfo instance for the specified class. Similar to javax.beans property handler discovery; search for a class with "XBeanInfo" appended
96       * to beanClass.name, then check whether beanClass implements JXPathBeanInfo for itself. Invokes the default constructor for any class it finds.
97       *
98       * @param beanClass for which to look for an info provider
99       * @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 }