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.el; 018 019import java.beans.BeanInfo; 020import java.beans.EventSetDescriptor; 021import java.beans.IndexedPropertyDescriptor; 022import java.beans.IntrospectionException; 023import java.beans.Introspector; 024import java.beans.PropertyDescriptor; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.util.HashMap; 028import java.util.Map; 029 030import javax.servlet.jsp.el.ELException; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034 035/** 036 * 037 * <p>Manages the BeanInfo for one class - contains the BeanInfo, and 038 * also a mapping from property name to BeanInfoProperty. There are 039 * also static methods for accessing the BeanInfoManager for a class - 040 * those mappings are cached permanently so that once the 041 * BeanInfoManager is calculated, it doesn't have to be calculated 042 * again. 043 * 044 * @author Nathan Abramson - Art Technology Group 045 * @version $Change: 181181 $$DateTime: 2001/06/26 09:55:09 $$Author: bayard $ 046 **/ 047 048public class BeanInfoManager 049{ 050 //------------------------------------- 051 // Constants 052 //------------------------------------- 053 private static Log log = LogFactory.getLog(BeanInfoManager.class); 054 055 //------------------------------------- 056 // Properties 057 //------------------------------------- 058 // property beanClass 059 060 Class mBeanClass; 061 public Class getBeanClass () 062 { return mBeanClass; } 063 064 //------------------------------------- 065 // Member variables 066 //------------------------------------- 067 068 // The BeanInfo 069 BeanInfo mBeanInfo; 070 071 // Mapping from property name to BeanInfoProperty 072 Map mPropertyByName; 073 074 // Mapping from property name to BeanInfoIndexedProperty 075 Map mIndexedPropertyByName; 076 077 // Mapping from event set name to event set descriptor 078 Map mEventSetByName; 079 080 // Flag if this is initialized 081 boolean mInitialized; 082 083 // The global mapping from class to BeanInfoManager 084 static Map mBeanInfoManagerByClass = new HashMap (); 085 086 //------------------------------------- 087 /** 088 * 089 * Constructor 090 **/ 091 BeanInfoManager (Class pBeanClass) 092 { 093 mBeanClass = pBeanClass; 094 } 095 096 //------------------------------------- 097 /** 098 * 099 * Returns the BeanInfoManager for the specified class 100 **/ 101 public static BeanInfoManager getBeanInfoManager (Class pClass) 102 { 103 BeanInfoManager ret = (BeanInfoManager) 104 mBeanInfoManagerByClass.get (pClass); 105 if (ret == null) { 106 ret = createBeanInfoManager (pClass); 107 } 108 return ret; 109 } 110 111 //------------------------------------- 112 /** 113 * 114 * Creates and registers the BeanInfoManager for the given class if 115 * it isn't already registered. 116 **/ 117 static synchronized BeanInfoManager createBeanInfoManager (Class pClass) 118 { 119 // Because this method is synchronized statically, the 120 // BeanInfoManager is not initialized at this time (otherwise it 121 // could end up being a bottleneck for the entire system). It is 122 // put into the map in an uninitialized state. The first time 123 // someone tries to use it, it will be initialized (with proper 124 // synchronizations in place to make sure it is only initialized 125 // once). 126 127 BeanInfoManager ret = (BeanInfoManager) 128 mBeanInfoManagerByClass.get (pClass); 129 if (ret == null) { 130 ret = new BeanInfoManager (pClass); 131 mBeanInfoManagerByClass.put (pClass, ret); 132 } 133 return ret; 134 } 135 136 //------------------------------------- 137 /** 138 * 139 * Returns the BeanInfoProperty for the specified property in the 140 * given class, or null if not found. 141 **/ 142 public static BeanInfoProperty getBeanInfoProperty 143 (Class pClass, 144 String pPropertyName) 145 throws ELException 146 { 147 return getBeanInfoManager (pClass).getProperty (pPropertyName); 148 } 149 150 //------------------------------------- 151 /** 152 * 153 * Returns the BeanInfoIndexedProperty for the specified property in 154 * the given class, or null if not found. 155 **/ 156 public static BeanInfoIndexedProperty getBeanInfoIndexedProperty 157 (Class pClass, 158 String pIndexedPropertyName) 159 throws ELException 160 { 161 return getBeanInfoManager 162 (pClass).getIndexedProperty (pIndexedPropertyName); 163 } 164 165 //------------------------------------- 166 /** 167 * 168 * Makes sure that this class has been initialized, and synchronizes 169 * the initialization if it's required. 170 **/ 171 void checkInitialized () 172 throws ELException 173 { 174 if (!mInitialized) { 175 synchronized (this) { 176 if (!mInitialized) { 177 initialize(); 178 mInitialized = true; 179 } 180 } 181 } 182 } 183 184 //------------------------------------- 185 /** 186 * 187 * Initializes by mapping property names to BeanInfoProperties 188 **/ 189 void initialize () 190 throws ELException 191 { 192 try { 193 mBeanInfo = Introspector.getBeanInfo (mBeanClass); 194 195 mPropertyByName = new HashMap (); 196 mIndexedPropertyByName = new HashMap (); 197 PropertyDescriptor [] pds = mBeanInfo.getPropertyDescriptors (); 198 for (int i = 0; pds != null && i < pds.length; i++) { 199 // Treat as both an indexed property and a normal property 200 PropertyDescriptor pd = pds [i]; 201 if (pd instanceof IndexedPropertyDescriptor) { 202 IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; 203 Method readMethod = getPublicMethod (ipd.getIndexedReadMethod ()); 204 Method writeMethod = getPublicMethod (ipd.getIndexedWriteMethod ()); 205 BeanInfoIndexedProperty property = new BeanInfoIndexedProperty 206 (readMethod, 207 writeMethod, 208 ipd); 209 210 mIndexedPropertyByName.put (ipd.getName (), property); 211 } 212 213 Method readMethod = getPublicMethod (pd.getReadMethod ()); 214 Method writeMethod = getPublicMethod (pd.getWriteMethod ()); 215 BeanInfoProperty property = new BeanInfoProperty 216 (readMethod, 217 writeMethod, 218 pd); 219 220 mPropertyByName.put (pd.getName (), property); 221 } 222 223 mEventSetByName = new HashMap (); 224 EventSetDescriptor [] esds = mBeanInfo.getEventSetDescriptors (); 225 for (int i = 0; esds != null && i < esds.length; i++) { 226 EventSetDescriptor esd = esds [i]; 227 mEventSetByName.put (esd.getName (), esd); 228 } 229 } 230 catch (IntrospectionException exc) { 231 if (log.isWarnEnabled()) { 232 log.warn( 233 MessageUtil.getMessageWithArgs( 234 Constants.EXCEPTION_GETTING_BEANINFO, mBeanClass.getName()), exc); 235 } 236 } 237 } 238 239 //------------------------------------- 240 /** 241 * 242 * Returns the BeanInfo for the class 243 **/ 244 BeanInfo getBeanInfo () 245 throws ELException 246 { 247 checkInitialized(); 248 return mBeanInfo; 249 } 250 251 //------------------------------------- 252 /** 253 * 254 * Returns the BeanInfoProperty for the given property name, or null 255 * if not found. 256 **/ 257 public BeanInfoProperty getProperty (String pPropertyName) 258 throws ELException 259 { 260 checkInitialized(); 261 return (BeanInfoProperty) mPropertyByName.get (pPropertyName); 262 } 263 264 //------------------------------------- 265 /** 266 * 267 * Returns the BeanInfoIndexedProperty for the given property name, 268 * or null if not found. 269 **/ 270 public BeanInfoIndexedProperty getIndexedProperty 271 (String pIndexedPropertyName) 272 throws ELException 273 { 274 checkInitialized(); 275 return (BeanInfoIndexedProperty) 276 mIndexedPropertyByName.get (pIndexedPropertyName); 277 } 278 279 //------------------------------------- 280 /** 281 * 282 * Returns the EventSetDescriptor for the given event set name, or 283 * null if not found. 284 **/ 285 public EventSetDescriptor getEventSet (String pEventSetName) 286 throws ELException 287 { 288 checkInitialized(); 289 return (EventSetDescriptor) mEventSetByName.get (pEventSetName); 290 } 291 292 //------------------------------------- 293 // Finding the public version of a method - if a PropertyDescriptor 294 // is obtained for a non-public class that implements a public 295 // interface, the read/write methods will be for the class, and 296 // therefore inaccessible. To correct this, a version of the same 297 // method must be found in a superclass or interface. 298 //------------------------------------- 299 /** 300 * 301 * Returns a publicly-accessible version of the given method, by 302 * searching for a public declaring class. 303 **/ 304 static Method getPublicMethod (Method pMethod) 305 { 306 if (pMethod == null) { 307 return null; 308 } 309 310 // See if the method is already available from a public class 311 Class cl = pMethod.getDeclaringClass (); 312 if (Modifier.isPublic (cl.getModifiers ())) { 313 return pMethod; 314 } 315 316 // Otherwise, try to find a public class that declares the method 317 Method ret = getPublicMethod (cl, pMethod); 318 if (ret != null) { 319 return ret; 320 } 321 else { 322 return pMethod; 323 } 324 } 325 326 //------------------------------------- 327 /** 328 * 329 * If the given class is public and has a Method that declares the 330 * same name and arguments as the given method, then that method is 331 * returned. Otherwise the superclass and interfaces are searched 332 * recursively. 333 **/ 334 static Method getPublicMethod (Class pClass, 335 Method pMethod) 336 { 337 // See if this is a public class declaring the method 338 if (Modifier.isPublic (pClass.getModifiers ())) { 339 try { 340 Method m; 341 try { 342 m = pClass.getDeclaredMethod (pMethod.getName (), 343 pMethod.getParameterTypes ()); 344 } catch (java.security.AccessControlException ex) { 345 // kludge to accommodate J2EE RI's default settings 346 // TODO: see if we can simply replace 347 // getDeclaredMethod() with getMethod() ...? 348 m = pClass.getMethod(pMethod.getName (), 349 pMethod.getParameterTypes ()); 350 } 351 if (Modifier.isPublic (m.getModifiers ())) { 352 return m; 353 } 354 } 355 catch (NoSuchMethodException exc) {} 356 } 357 358 // Search the interfaces 359 { 360 Class [] interfaces = pClass.getInterfaces (); 361 if (interfaces != null) { 362 for (int i = 0; i < interfaces.length; i++) { 363 Method m = getPublicMethod (interfaces [i], pMethod); 364 if (m != null) { 365 return m; 366 } 367 } 368 } 369 } 370 371 // Search the superclass 372 { 373 Class superclass = pClass.getSuperclass (); 374 if (superclass != null) { 375 Method m = getPublicMethod (superclass, pMethod); 376 if (m != null) { 377 return m; 378 } 379 } 380 } 381 382 return null; 383 } 384 385 //------------------------------------- 386}