View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.el;
18  
19  import java.beans.BeanInfo;
20  import java.beans.EventSetDescriptor;
21  import java.beans.IndexedPropertyDescriptor;
22  import java.beans.IntrospectionException;
23  import java.beans.Introspector;
24  import java.beans.PropertyDescriptor;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Modifier;
27  import java.util.HashMap;
28  import java.util.Map;
29  
30  import javax.servlet.jsp.el.ELException;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  /**
36   *
37   * <p>Manages the BeanInfo for one class - contains the BeanInfo, and
38   * also a mapping from property name to BeanInfoProperty.  There are
39   * also static methods for accessing the BeanInfoManager for a class -
40   * those mappings are cached permanently so that once the
41   * BeanInfoManager is calculated, it doesn't have to be calculated
42   * again.
43   * 
44   * @author Nathan Abramson - Art Technology Group
45   * @version $Change: 181181 $$DateTime: 2001/06/26 09:55:09 $$Author: bayard $
46   **/
47  
48  public class BeanInfoManager
49  {
50      //-------------------------------------
51      // Constants
52      //-------------------------------------
53      private static Log log = LogFactory.getLog(BeanInfoManager.class);
54      
55    //-------------------------------------
56    // Properties
57    //-------------------------------------
58    // property beanClass
59  
60    Class mBeanClass;
61    public Class getBeanClass ()
62    { return mBeanClass; }
63  
64    //-------------------------------------
65    // Member variables
66    //-------------------------------------
67  
68    // The BeanInfo
69    BeanInfo mBeanInfo;
70  
71    // Mapping from property name to BeanInfoProperty
72    Map mPropertyByName;
73  
74    // Mapping from property name to BeanInfoIndexedProperty
75    Map mIndexedPropertyByName;
76  
77    // Mapping from event set name to event set descriptor
78    Map mEventSetByName;
79  
80    // Flag if this is initialized
81    boolean mInitialized;
82  
83    // The global mapping from class to BeanInfoManager
84    static Map mBeanInfoManagerByClass = new HashMap ();
85  
86    //-------------------------------------
87    /**
88     *
89     * Constructor
90     **/
91    BeanInfoManager (Class pBeanClass)
92    {
93      mBeanClass = pBeanClass;
94    }
95  
96    //-------------------------------------
97    /**
98     *
99     * 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 }