lite.beans.Introspector Maven / Gradle / Ivy
Show all versions of litebeans Show documentation
/*
* 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 lite.beans;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
/**
* The Introspector is a utility for developers to figure out
* which properties, events, and methods a JavaBean supports.
*
* The Introspector class walks over the class/superclass chain
* of the target bean class. At each level it checks if there is a matching
* BeanInfo 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.
*
*
* To look for the explicit information of a bean:
*
*
* The Introspector 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
* If the first step fails, the Introspector 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 sun.beans.infos). If it
* finds a "BeanInfo" class and the "BeanInfo" class returns non-null value when
* queried for explicit information, use the explicit information
*
*
*/
//ScrollPane cannot be introspected correctly
public class Introspector extends java.lang.Object {
// Public fields
/**
* Constant values to indicate that the Introspector will
* ignore all BeanInfo class.
*/
public static final int IGNORE_ALL_BEANINFO = 3;
/**
* Constant values to indicate that the Introspector will
* ignore the BeanInfo class of the current bean class.
*/
public static final int IGNORE_IMMEDIATE_BEANINFO = 2;
/**
* Constant values to indicate that the Introspector will use
* all BeanInfo 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, StandardBeanInfo> theCache = Collections.synchronizedMap(new WeakHashMap, StandardBeanInfo>(DEFAULT_CAPACITY));
private Introspector() {
super();
}
/**
* Decapitalizes a given string according to the rule:
*
* If the first or only character is Upper Case, it is made Lower Case
* UNLESS the second character is also Upper Case, when the String is
* returned unchanged
*
* @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);
}
/**
* Flushes all BeanInfo caches.
*
*/
public static void flushCaches() {
// Flush the cache by throwing away the cache HashMap and creating a
// new empty one
theCache.clear();
}
/**
* Flushes the BeanInfo caches of the specified bean class
*
* @param clazz
* the specified bean class
*/
public static void flushFromCaches(Class> clazz) {
if(clazz == null){
throw new NullPointerException();
}
theCache.remove(clazz);
}
/**
* Gets the BeanInfo object which contains the information of
* the properties, events and methods of the specified bean class.
*
*
* The Introspector will cache the BeanInfo
* object. Subsequent calls to this method will be answered with the cached
* data.
*
*
* @param beanClass
* the specified bean class.
* @return the BeanInfo of the bean class.
* @throws IntrospectionException exception
*/
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;
}
/**
* Gets the BeanInfo object which contains the information of
* the properties, events and methods of the specified bean class. It will
* not introspect the "stopclass" and its super class.
*
*
* The Introspector will cache the BeanInfo
* object. Subsequent calls to this method will be answered with the cached
* data.
*
*
* @param beanClass
* the specified beanClass.
* @param stopClass
* the sopt class which should be super class of the bean class.
* May be null.
* @return the BeanInfo of the bean class.
* @throws IntrospectionException exception
*/
public static BeanInfo getBeanInfo(Class> beanClass, Class> stopClass)
throws IntrospectionException {
if(stopClass == null){
//try to use cache
return getBeanInfo(beanClass);
}
return getBeanInfoImplAndInit(beanClass, stopClass, USE_ALL_BEANINFO);
}
/**
* Gets the BeanInfo object which contains the information of
* the properties, events and methods of the specified bean class.
*
* If flag==IGNORE_ALL_BEANINFO, the
* Introspector will ignore all BeanInfo
* class.
* If flag==IGNORE_IMMEDIATE_BEANINFO, the
* Introspector will ignore the BeanInfo class
* of the current bean class.
* If flag==USE_ALL_BEANINFO, the
* Introspector will use all BeanInfo class
* which have been found.
*
*
* The Introspector will cache the BeanInfo
* object. Subsequent calls to this method will be answered with the cached
* data.
*
*
* @param beanClass
* the specified bean class.
* @param flags
* the flag to control the usage of the explicit
* BeanInfo class.
* @return the BeanInfo of the bean class.
* @throws IntrospectionException exception
*/
public static BeanInfo getBeanInfo(Class> beanClass, int flags)
throws IntrospectionException {
if(flags == USE_ALL_BEANINFO){
//try to use cache
return getBeanInfo(beanClass);
}
return getBeanInfoImplAndInit(beanClass, null, flags);
}
/**
* Gets an array of search packages.
*
* @return an array of search packages.
*/
public static String[] getBeanInfoSearchPath() {
String[] path = new String[searchPath.length];
System.arraycopy(searchPath, 0, path, 0, searchPath.length);
return path;
}
/**
* Sets the search packages.
*
* @param path the new search packages to be set.
*/
public static void setBeanInfoSearchPath(String[] path) {
if (System.getSecurityManager() != null) {
System.getSecurityManager().checkPropertiesAccess();
}
searchPath = path;
}
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;
}
}