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><beanClass>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 }