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    }