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 }