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.el;
018
019 import java.beans.BeanInfo;
020 import java.beans.EventSetDescriptor;
021 import java.beans.IndexedPropertyDescriptor;
022 import java.beans.IntrospectionException;
023 import java.beans.Introspector;
024 import java.beans.PropertyDescriptor;
025 import java.lang.reflect.Method;
026 import java.lang.reflect.Modifier;
027 import java.util.HashMap;
028 import java.util.Map;
029
030 import javax.servlet.jsp.el.ELException;
031
032 import org.apache.commons.logging.Log;
033 import 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
048 public 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 }