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:
018: package java.beans;
019:
020: import java.util.Collections;
021: import java.util.Map;
022: import java.util.WeakHashMap;
023:
024: /**
025: * The <code>Introspector</code> is a utility for developers to figure out
026: * which properties, events, and methods a JavaBean supports.
027: * <p>
028: * The <code>Introspector</code> class walks over the class/superclass chain
029: * of the target bean class. At each level it checks if there is a matching
030: * <code>BeanInfo</code> class which provides explicit information about the
031: * bean, and if so uses that explicit information. Otherwise it uses the low
032: * level reflection APIs to study the target class and uses design patterns to
033: * analyze its behaviour and then proceeds to continue the introspection with
034: * its baseclass.
035: * </p>
036: * <p>
037: * To look for the explicit information of a bean:
038: * </p>
039: * <ol>
040: * <li>The <code>Introspector</code> appends "BeanInfo" to the qualified name
041: * of the bean class, try to use the new class as the "BeanInfo" class. If the
042: * "BeanInfo" class exsits and returns non-null value when queried for explicit
043: * information, use the explicit information</li>
044: * <li>If the first step fails, the <code>Introspector</code> will extract a
045: * simple class name of the bean class by removing the package name from the
046: * qualified name of the bean class, append "BeanInfo" to it. And look for the
047: * simple class name in the packages defined in the "BeanInfo" search path (The
048: * default "BeanInfo" search path is <code>sun.beans.infos</code>). If it
049: * finds a "BeanInfo" class and the "BeanInfo" class returns non-null value when
050: * queried for explicit information, use the explicit information</li>
051: * </ol>
052: *
053: */
054: //ScrollPane cannot be introspected correctly
055: public class Introspector extends java.lang.Object {
056:
057: // Public fields
058: /**
059: * Constant values to indicate that the <code>Introspector</code> will
060: * ignore all <code>BeanInfo</code> class.
061: */
062: public static final int IGNORE_ALL_BEANINFO = 3;
063:
064: /**
065: * Constant values to indicate that the <code>Introspector</code> will
066: * ignore the <code>BeanInfo</code> class of the current bean class.
067: */
068: public static final int IGNORE_IMMEDIATE_BEANINFO = 2;
069:
070: /**
071: * Constant values to indicate that the <code>Introspector</code> will use
072: * all <code>BeanInfo</code> class which have been found. This is the default one.
073: */
074: public static final int USE_ALL_BEANINFO = 1;
075:
076: // Default search path for BeanInfo classes
077: private static final String DEFAULT_BEANINFO_SEARCHPATH = "sun.beans.infos"; //$NON-NLS-1$
078:
079: // The search path to use to find BeanInfo classes
080: // - an array of package names that are used in turn
081: private static String[] searchPath = { DEFAULT_BEANINFO_SEARCHPATH };
082:
083: // The cache to store Bean Info objects that have been found or created
084: private static final int DEFAULT_CAPACITY = 128;
085:
086: private static Map<Class<?>, StandardBeanInfo> theCache = Collections
087: .synchronizedMap(new WeakHashMap<Class<?>, StandardBeanInfo>(
088: DEFAULT_CAPACITY));
089:
090: private Introspector() {
091: super ();
092: }
093:
094: /**
095: * Decapitalizes a given string according to the rule:
096: * <ul>
097: * <li>If the first or only character is Upper Case, it is made Lower Case
098: * <li>UNLESS the second character is also Upper Case, when the String is
099: * returned unchanged <eul>
100: *
101: * @param name -
102: * the String to decapitalize
103: * @return the decapitalized version of the String
104: */
105: public static String decapitalize(String name) {
106:
107: if (name == null)
108: return null;
109: // The rule for decapitalize is that:
110: // If the first letter of the string is Upper Case, make it lower case
111: // UNLESS the second letter of the string is also Upper Case, in which case no
112: // changes are made.
113: if (name.length() == 0
114: || (name.length() > 1 && Character.isUpperCase(name
115: .charAt(1)))) {
116: return name;
117: }
118:
119: char[] chars = name.toCharArray();
120: chars[0] = Character.toLowerCase(chars[0]);
121: return new String(chars);
122: }
123:
124: /**
125: * Flushes all <code>BeanInfo</code> caches.
126: *
127: */
128: public static void flushCaches() {
129: // Flush the cache by throwing away the cache HashMap and creating a
130: // new empty one
131: theCache.clear();
132: }
133:
134: /**
135: * Flushes the <code>BeanInfo</code> caches of the specified bean class
136: *
137: * @param clazz
138: * the specified bean class
139: */
140: public static void flushFromCaches(Class<?> clazz) {
141: if (clazz == null) {
142: throw new NullPointerException();
143: }
144: theCache.remove(clazz);
145: }
146:
147: /**
148: * Gets the <code>BeanInfo</code> object which contains the information of
149: * the properties, events and methods of the specified bean class.
150: *
151: * <p>
152: * The <code>Introspector</code> will cache the <code>BeanInfo</code>
153: * object. Subsequent calls to this method will be answered with the cached
154: * data.
155: * </p>
156: *
157: * @param beanClass
158: * the specified bean class.
159: * @return the <code>BeanInfo</code> of the bean class.
160: * @throws IntrospectionException
161: */
162: public static BeanInfo getBeanInfo(Class<?> beanClass)
163: throws IntrospectionException {
164: StandardBeanInfo beanInfo = theCache.get(beanClass);
165: if (beanInfo == null) {
166: beanInfo = getBeanInfoImplAndInit(beanClass, null,
167: USE_ALL_BEANINFO);
168: theCache.put(beanClass, beanInfo);
169: }
170: return beanInfo;
171: }
172:
173: /**
174: * Gets the <code>BeanInfo</code> object which contains the information of
175: * the properties, events and methods of the specified bean class. It will
176: * not introspect the "stopclass" and its super class.
177: *
178: * <p>
179: * The <code>Introspector</code> will cache the <code>BeanInfo</code>
180: * object. Subsequent calls to this method will be answered with the cached
181: * data.
182: * </p>
183: *
184: * @param beanClass
185: * the specified beanClass.
186: * @param stopClass
187: * the sopt class which should be super class of the bean class.
188: * May be null.
189: * @return the <code>BeanInfo</code> of the bean class.
190: * @throws IntrospectionException
191: */
192: public static BeanInfo getBeanInfo(Class<?> beanClass,
193: Class<?> stopClass) throws IntrospectionException {
194: if (stopClass == null) {
195: //try to use cache
196: return getBeanInfo(beanClass);
197: }
198: return getBeanInfoImplAndInit(beanClass, stopClass,
199: USE_ALL_BEANINFO);
200: }
201:
202: /**
203: * Gets the <code>BeanInfo</code> object which contains the information of
204: * the properties, events and methods of the specified bean class.
205: * <ol>
206: * <li>If <code>flag==IGNORE_ALL_BEANINFO</code>, the
207: * <code>Introspector</code> will ignore all <code>BeanInfo</code>
208: * class.</li>
209: * <li>If <code>flag==IGNORE_IMMEDIATE_BEANINFO</code>, the
210: * <code>Introspector</code> will ignore the <code>BeanInfo</code> class
211: * of the current bean class.</li>
212: * <li>If <code>flag==USE_ALL_BEANINFO</code>, the
213: * <code>Introspector</code> will use all <code>BeanInfo</code> class
214: * which have been found.</li>
215: * </ol>
216: * <p>
217: * The <code>Introspector</code> will cache the <code>BeanInfo</code>
218: * object. Subsequent calls to this method will be answered with the cached
219: * data.
220: * </p>
221: *
222: * @param beanClass
223: * the specified bean class.
224: * @param flags
225: * the flag to control the usage of the explicit
226: * <code>BeanInfo</code> class.
227: * @return the <code>BeanInfo</code> of the bean class.
228: * @throws IntrospectionException
229: */
230: public static BeanInfo getBeanInfo(Class<?> beanClass, int flags)
231: throws IntrospectionException {
232: if (flags == USE_ALL_BEANINFO) {
233: //try to use cache
234: return getBeanInfo(beanClass);
235: }
236: return getBeanInfoImplAndInit(beanClass, null, flags);
237: }
238:
239: /**
240: * Gets an array of search packages.
241: *
242: * @return an array of search packages.
243: */
244: public static String[] getBeanInfoSearchPath() {
245: String[] path = new String[searchPath.length];
246: System.arraycopy(searchPath, 0, path, 0, searchPath.length);
247: return path;
248: }
249:
250: /**
251: * Sets the search packages.
252: *
253: * @param path the new search packages to be set.
254: */
255: public static void setBeanInfoSearchPath(String[] path) {
256: if (System.getSecurityManager() != null) {
257: System.getSecurityManager().checkPropertiesAccess();
258: }
259: searchPath = path;
260: }
261:
262: private static StandardBeanInfo getBeanInfoImpl(Class<?> beanClass,
263: Class<?> stopClass, int flags)
264: throws IntrospectionException {
265: BeanInfo explicitInfo = null;
266: if (flags == USE_ALL_BEANINFO) {
267: explicitInfo = getExplicitBeanInfo(beanClass);
268: }
269: StandardBeanInfo beanInfo = new StandardBeanInfo(beanClass,
270: explicitInfo, stopClass);
271:
272: if (beanInfo.additionalBeanInfo != null) {
273: for (int i = beanInfo.additionalBeanInfo.length - 1; i >= 0; i--) {
274: BeanInfo info = beanInfo.additionalBeanInfo[i];
275: beanInfo.mergeBeanInfo(info, true);
276: }
277: }
278:
279: // recursive get beaninfo for super classes
280: Class<?> beanSuperClass = beanClass.getSuperclass();
281: if (beanSuperClass != stopClass) {
282: if (beanSuperClass == null)
283: throw new IntrospectionException(
284: "Stop class is not super class of bean class"); //$NON-NLS-1$
285: int super flags = flags == IGNORE_IMMEDIATE_BEANINFO ? USE_ALL_BEANINFO
286: : flags;
287: BeanInfo super BeanInfo = getBeanInfoImpl(beanSuperClass,
288: stopClass, super flags);
289: if (super BeanInfo != null) {
290: beanInfo.mergeBeanInfo(super BeanInfo, false);
291: }
292: }
293: return beanInfo;
294: }
295:
296: private static BeanInfo getExplicitBeanInfo(Class<?> beanClass) {
297: BeanInfo theBeanInfo = null;
298: String beanInfoClassName = beanClass.getName() + "BeanInfo"; //$NON-NLS-1$
299: try {
300: theBeanInfo = loadBeanInfo(beanInfoClassName, beanClass);
301: return theBeanInfo;
302: } catch (Exception e) {
303: //fall through
304: }
305: int index = beanInfoClassName.lastIndexOf('.');
306: String beanInfoName = index >= 0 ? beanInfoClassName
307: .substring(index + 1) : beanInfoClassName;
308: for (int i = 0; i < searchPath.length; i++) {
309: beanInfoClassName = searchPath[i] + "." + beanInfoName; //$NON-NLS-1$
310: try {
311: theBeanInfo = loadBeanInfo(beanInfoClassName, beanClass);
312: break;
313: } catch (Exception e) {
314: //ignore, try next one
315: }
316: }
317: return theBeanInfo;
318: }
319:
320: /*
321: * Method which attempts to instantiate a BeanInfo object of the supplied
322: * classname
323: *
324: * @param theBeanInfoClassName -
325: * the Class Name of the class of which the BeanInfo is an
326: * instance
327: * @param classLoader
328: * @return A BeanInfo object which is an instance of the Class named
329: * theBeanInfoClassName null if the Class does not exist or if there
330: * are problems instantiating the instance
331: */
332: private static BeanInfo loadBeanInfo(String beanInfoClassName,
333: Class<?> beanClass) throws Exception {
334: try {
335: ClassLoader cl = beanClass.getClassLoader();
336: if (cl != null) {
337: return (BeanInfo) Class.forName(beanInfoClassName,
338: true, beanClass.getClassLoader()).newInstance();
339: }
340: } catch (Exception e) {
341: // fall through
342: }
343: try {
344: return (BeanInfo) Class.forName(beanInfoClassName, true,
345: ClassLoader.getSystemClassLoader()).newInstance();
346: } catch (Exception e) {
347: // fall through
348: }
349: return (BeanInfo) Class.forName(beanInfoClassName, true,
350: Thread.currentThread().getContextClassLoader())
351: .newInstance();
352: }
353:
354: private static StandardBeanInfo getBeanInfoImplAndInit(
355: Class<?> beanClass, Class<?> stopClass, int flag)
356: throws IntrospectionException {
357: StandardBeanInfo standardBeanInfo = getBeanInfoImpl(beanClass,
358: stopClass, flag);
359: standardBeanInfo.init();
360: return standardBeanInfo;
361: }
362: }
|