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}