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 018package org.apache.commons.jxpath; 019 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.Map; 024 025import org.apache.commons.jxpath.util.ClassLoaderUtil; 026 027/** 028 * JXPathIntrospector maintains a registry of {@link JXPathBeanInfo JXPathBeanInfo} objects for Java classes. 029 */ 030public class JXPathIntrospector { 031 032 private static Map<Class, JXPathBeanInfo> byClass = Collections.synchronizedMap(new HashMap<>()); 033 private static Map<Class, JXPathBeanInfo> byInterface = Collections.synchronizedMap(new HashMap<>()); 034 static { 035 registerAtomicClass(Class.class); 036 registerAtomicClass(Boolean.TYPE); 037 registerAtomicClass(Boolean.class); 038 registerAtomicClass(Byte.TYPE); 039 registerAtomicClass(Byte.class); 040 registerAtomicClass(Character.TYPE); 041 registerAtomicClass(Character.class); 042 registerAtomicClass(Short.TYPE); 043 registerAtomicClass(Short.class); 044 registerAtomicClass(Integer.TYPE); 045 registerAtomicClass(Integer.class); 046 registerAtomicClass(Long.TYPE); 047 registerAtomicClass(Long.class); 048 registerAtomicClass(Float.TYPE); 049 registerAtomicClass(Float.class); 050 registerAtomicClass(Double.TYPE); 051 registerAtomicClass(Double.class); 052 registerAtomicClass(String.class); 053 registerAtomicClass(Date.class); 054 registerAtomicClass(java.sql.Date.class); 055 registerAtomicClass(java.sql.Time.class); 056 registerAtomicClass(java.sql.Timestamp.class); 057 registerDynamicClass(Map.class, MapDynamicPropertyHandler.class); 058 } 059 060 /** 061 * Find a dynamic bean info if available for any superclasses or interfaces. 062 * 063 * @param beanClass to search for 064 * @return JXPathBeanInfo 065 */ 066 private static JXPathBeanInfo findDynamicBeanInfo(final Class beanClass) { 067 JXPathBeanInfo beanInfo; 068 if (beanClass.isInterface()) { 069 beanInfo = byInterface.get(beanClass); 070 if (beanInfo != null && beanInfo.isDynamic()) { 071 return beanInfo; 072 } 073 } 074 final Class[] interfaces = beanClass.getInterfaces(); 075 if (interfaces != null) { 076 for (final Class element : interfaces) { 077 beanInfo = findDynamicBeanInfo(element); 078 if (beanInfo != null && beanInfo.isDynamic()) { 079 return beanInfo; 080 } 081 } 082 } 083 final Class sup = beanClass.getSuperclass(); 084 if (sup != null) { 085 beanInfo = byClass.get(sup); 086 if (beanInfo != null && beanInfo.isDynamic()) { 087 return beanInfo; 088 } 089 return findDynamicBeanInfo(sup); 090 } 091 return null; 092 } 093 094 /** 095 * find a JXPathBeanInfo instance for the specified class. Similar to javax.beans property handler discovery; search for a class with "XBeanInfo" appended 096 * to beanClass.name, then check whether beanClass implements JXPathBeanInfo for itself. Invokes the default constructor for any class it finds. 097 * 098 * @param beanClass for which to look for an info provider 099 * @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}