/* * NOTICE: THE FILE HAS BEEN MODIFIED TO SUIT THE NEEDS OF THE PROJECT. * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.robobinding.internal.java_beans; /** * The <code>Introspector</code> is a utility for developers to figure out which * properties, events, and methods a JavaBean supports. * <p> * The <code>Introspector</code> class walks over the class/superclass chain of * the target bean class. At each level it checks if there is a matching * <code>BeanInfo</code> class which provides explicit information about the * bean, and if so uses that explicit information. Otherwise it uses the low * level reflection APIs to study the target class and uses design patterns to * analyze its behaviour and then proceeds to continue the introspection with * its baseclass. * </p> * <p> * To look for the explicit information of a bean: * </p> * <ol> * <li>The <code>Introspector</code> appends "BeanInfo" to the qualified name of * the bean class, try to use the new class as the "BeanInfo" class. If the * "BeanInfo" class exsits and returns non-null value when queried for explicit * information, use the explicit information</li> * <li>If the first step fails, the <code>Introspector</code> will extract a * simple class name of the bean class by removing the package name from the * qualified name of the bean class, append "BeanInfo" to it. And look for the * simple class name in the packages defined in the "BeanInfo" search path (The * default "BeanInfo" search path is <code>sun.beans.infos</code>). If it finds * a "BeanInfo" class and the "BeanInfo" class returns non-null value when * queried for explicit information, use the explicit information</li> * </ol> * */ // ScrollPane cannot be introspected correctly public class Introspector extends java.lang.Object { // Public fields /** * Constant values to indicate that the <code>Introspector</code> will * ignore all <code>BeanInfo</code> class. */ public static final int IGNORE_ALL_BEANINFO = 3; /** * Constant values to indicate that the <code>Introspector</code> will * ignore the <code>BeanInfo</code> class of the current bean class. */ public static final int IGNORE_IMMEDIATE_BEANINFO = 2; /** * Constant values to indicate that the <code>Introspector</code> will use * all <code>BeanInfo</code> class which have been found. This is the * default one. */ public static final int USE_ALL_BEANINFO = 1; // Default search path for BeanInfo classes private static final String DEFAULT_BEANINFO_SEARCHPATH = "sun.beans.infos"; //$NON-NLS-1$ // The search path to use to find BeanInfo classes // - an array of package names that are used in turn private static String[] searchPath = { DEFAULT_BEANINFO_SEARCHPATH }; // The cache to store Bean Info objects that have been found or created //private static final int DEFAULT_CAPACITY = 128; //private static Map<Class<?>, StandardBeanInfo> theCache = Collections.synchronizedMap(new WeakHashMap<Class<?>, StandardBeanInfo>(DEFAULT_CAPACITY)); private static WeakCache<Class<?>, StandardBeanInfo> theCache = new WeakCache<Class<?>, StandardBeanInfo>(); private Introspector() { super(); } /** * Gets the <code>BeanInfo</code> object which contains the information of * the properties, events and methods of the specified bean class. * * <p> * The <code>Introspector</code> will cache the <code>BeanInfo</code> * object. Subsequent calls to this method will be answered with the cached * data. * </p> * * @param beanClass * the specified bean class. * @return the <code>BeanInfo</code> of the bean class. * @throws IntrospectionException */ public static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException { StandardBeanInfo beanInfo = theCache.get(beanClass); if (beanInfo == null) { beanInfo = getBeanInfoImplAndInit(beanClass, null, USE_ALL_BEANINFO); theCache.put(beanClass, beanInfo); } return beanInfo; } private static StandardBeanInfo getBeanInfoImpl(Class<?> beanClass, Class<?> stopClass, int flags) throws IntrospectionException { BeanInfo explicitInfo = null; if (flags == USE_ALL_BEANINFO) { explicitInfo = getExplicitBeanInfo(beanClass); } StandardBeanInfo beanInfo = new StandardBeanInfo(beanClass, explicitInfo, stopClass); if (beanInfo.additionalBeanInfo != null) { for (int i = beanInfo.additionalBeanInfo.length - 1; i >= 0; i--) { BeanInfo info = beanInfo.additionalBeanInfo[i]; beanInfo.mergeBeanInfo(info, true); } } // recursive get beaninfo for super classes Class<?> beanSuperClass = beanClass.getSuperclass(); if (beanSuperClass != stopClass) { if (beanSuperClass == null) throw new IntrospectionException("Stop class is not super class of bean class"); //$NON-NLS-1$ int superflags = flags == IGNORE_IMMEDIATE_BEANINFO ? USE_ALL_BEANINFO : flags; BeanInfo superBeanInfo = getBeanInfoImpl(beanSuperClass, stopClass, superflags); if (superBeanInfo != null) { beanInfo.mergeBeanInfo(superBeanInfo, false); } } return beanInfo; } private static BeanInfo getExplicitBeanInfo(Class<?> beanClass) { String beanInfoClassName = beanClass.getName() + "BeanInfo"; //$NON-NLS-1$ try { return loadBeanInfo(beanInfoClassName, beanClass); } catch (Exception e) { // fall through } int index = beanInfoClassName.lastIndexOf('.'); String beanInfoName = index >= 0 ? beanInfoClassName.substring(index + 1) : beanInfoClassName; BeanInfo theBeanInfo = null; BeanDescriptor beanDescriptor = null; for (int i = 0; i < searchPath.length; i++) { beanInfoClassName = searchPath[i] + "." + beanInfoName; //$NON-NLS-1$ try { theBeanInfo = loadBeanInfo(beanInfoClassName, beanClass); } catch (Exception e) { // ignore, try next one continue; } beanDescriptor = theBeanInfo.getBeanDescriptor(); if (beanDescriptor != null && beanClass == beanDescriptor.getBeanClass()) { return theBeanInfo; } } if (BeanInfo.class.isAssignableFrom(beanClass)) { try { return loadBeanInfo(beanClass.getName(), beanClass); } catch (Exception e) { // fall through } } return null; } /* * Method which attempts to instantiate a BeanInfo object of the supplied * classname * * @param theBeanInfoClassName - the Class Name of the class of which the * BeanInfo is an instance * * @param classLoader * * @return A BeanInfo object which is an instance of the Class named * theBeanInfoClassName null if the Class does not exist or if there are * problems instantiating the instance */ private static BeanInfo loadBeanInfo(String beanInfoClassName, Class<?> beanClass) throws Exception { try { ClassLoader cl = beanClass.getClassLoader(); if (cl != null) { return (BeanInfo) Class.forName(beanInfoClassName, true, beanClass.getClassLoader()).newInstance(); } } catch (Exception e) { // fall through } try { return (BeanInfo) Class.forName(beanInfoClassName, true, ClassLoader.getSystemClassLoader()).newInstance(); } catch (Exception e) { // fall through } return (BeanInfo) Class.forName(beanInfoClassName, true, Thread.currentThread().getContextClassLoader()).newInstance(); } private static StandardBeanInfo getBeanInfoImplAndInit(Class<?> beanClass, Class<?> stopClass, int flag) throws IntrospectionException { StandardBeanInfo standardBeanInfo = getBeanInfoImpl(beanClass, stopClass, flag); standardBeanInfo.init(); return standardBeanInfo; } /** * Decapitalizes a given string according to the rule: * <ul> * <li>If the first or only character is Upper Case, it is made Lower Case * <li>UNLESS the second character is also Upper Case, when the String is * returned unchanged. * </ul> * * @param name * - the String to decapitalize * @return the decapitalized version of the String */ public static String decapitalize(String name) { if (name == null) return null; // The rule for decapitalize is that: // If the first letter of the string is Upper Case, make it lower case // UNLESS the second letter of the string is also Upper Case, in which // case no // changes are made. if (name.length() == 0 || (name.length() > 1 && Character.isUpperCase(name.charAt(1)))) { return name; } char[] chars = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); } }