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 */
017 package org.apache.commons.jxpath;
018
019 import java.util.Date;
020 import java.util.Map;
021 import 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 */
030 public 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><beanClass>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 }