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 */ 017package org.apache.commons.jxpath; 018 019import java.util.Collections; 020import java.util.Date; 021import java.util.Map; 022import java.util.HashMap; 023 024import org.apache.commons.jxpath.util.ClassLoaderUtil; 025 026/** 027 * JXPathIntrospector maintains a registry of {@link JXPathBeanInfo 028 * JXPathBeanInfo} objects for Java classes. 029 * 030 * @author Dmitri Plotnikov 031 * @version $Revision: 1293412 $ $Date: 2012-02-24 21:54:12 +0100 (Fr, 24 Feb 2012) $ 032 */ 033public class JXPathIntrospector { 034 035 private static Map byClass = Collections.synchronizedMap(new HashMap()); 036 private static Map byInterface = Collections.synchronizedMap(new HashMap()); 037 038 static { 039 registerAtomicClass(Class.class); 040 registerAtomicClass(Boolean.TYPE); 041 registerAtomicClass(Boolean.class); 042 registerAtomicClass(Byte.TYPE); 043 registerAtomicClass(Byte.class); 044 registerAtomicClass(Character.TYPE); 045 registerAtomicClass(Character.class); 046 registerAtomicClass(Short.TYPE); 047 registerAtomicClass(Short.class); 048 registerAtomicClass(Integer.TYPE); 049 registerAtomicClass(Integer.class); 050 registerAtomicClass(Long.TYPE); 051 registerAtomicClass(Long.class); 052 registerAtomicClass(Float.TYPE); 053 registerAtomicClass(Float.class); 054 registerAtomicClass(Double.TYPE); 055 registerAtomicClass(Double.class); 056 registerAtomicClass(String.class); 057 registerAtomicClass(Date.class); 058 registerAtomicClass(java.sql.Date.class); 059 registerAtomicClass(java.sql.Time.class); 060 registerAtomicClass(java.sql.Timestamp.class); 061 062 registerDynamicClass(Map.class, MapDynamicPropertyHandler.class); 063 } 064 065 /** 066 * Automatically creates and registers a JXPathBeanInfo object 067 * for the specified class. That object returns true to isAtomic(). 068 * @param beanClass to register 069 */ 070 public static void registerAtomicClass(Class beanClass) { 071 synchronized (byClass) { 072 byClass.put(beanClass, new JXPathBasicBeanInfo(beanClass, true)); 073 } 074 } 075 076 /** 077 * Automatically creates and registers a {@link JXPathBeanInfo} object 078 * for the specified class. That object returns true to 079 * {@link JXPathBeanInfo#isDynamic()}. 080 * 081 * @param beanClass to register 082 * @param dynamicPropertyHandlerClass to handle beanClass 083 */ 084 public static void registerDynamicClass(Class beanClass, 085 Class dynamicPropertyHandlerClass) { 086 JXPathBasicBeanInfo bi = 087 new JXPathBasicBeanInfo(beanClass, dynamicPropertyHandlerClass); 088 if (beanClass.isInterface()) { 089 synchronized (byInterface) { 090 byInterface.put(beanClass, bi); 091 } 092 } 093 else { 094 synchronized (byClass) { 095 byClass.put(beanClass, bi); 096 } 097 } 098 } 099 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}