001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.app.componentxml;
031:
032: import java.beans.BeanInfo;
033: import java.beans.IndexedPropertyDescriptor;
034: import java.beans.IntrospectionException;
035: import java.beans.Introspector;
036: import java.beans.PropertyDescriptor;
037: import java.lang.reflect.Field;
038: import java.lang.reflect.Method;
039: import java.lang.reflect.Modifier;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.Map;
043:
044: /**
045: * Provides introspection into Echo components.
046: *
047: * A wrapper for JavaBean APIs to preform introspection on
048: * Echo Components. Provides convenience methods to retrieve
049: * available property names, types, and other bean-related
050: * information.
051: */
052: public class ComponentIntrospector {
053:
054: /**
055: * Modifier requirements for style constant name fields.
056: */
057: private static final int CONSTANT_MODIFERS = Modifier.STATIC
058: | Modifier.PUBLIC | Modifier.FINAL;
059:
060: /**
061: * A map containing references from <code>ClassLoader</code> to
062: * <code>ComponentIntrospector</code> caches.
063: */
064: private static final Map classLoaderCache = new HashMap();
065:
066: /**
067: * Creates a new <code>ComponentIntrospector</code> for a type of
068: * Echo component.
069: *
070: * @param typeName the type name of Echo component
071: * @param classLoader the class loader from which the type class
072: * may be retrieved
073: */
074: public static ComponentIntrospector forName(String typeName,
075: ClassLoader classLoader) throws ClassNotFoundException {
076: // Find or Create Component Introspector Store based on ClassLoader Cache.
077: Map ciStore;
078: synchronized (classLoaderCache) {
079: ciStore = (Map) classLoaderCache.get(classLoader);
080: if (ciStore == null) {
081: ciStore = new HashMap();
082: classLoaderCache.put(classLoader, ciStore);
083: }
084: }
085:
086: // Find or Create Component Introspector from Component Introspector Store.
087: ComponentIntrospector ci;
088: synchronized (ciStore) {
089: ci = (ComponentIntrospector) ciStore.get(typeName);
090: if (ci == null) {
091: ci = new ComponentIntrospector(typeName, classLoader);
092: ciStore.put(typeName, ci);
093: }
094: }
095: return ci;
096: }
097:
098: private Class componentClass;
099: private BeanInfo beanInfo;
100: private Map constants;
101:
102: /**
103: * A mapping between the component's property names and JavaBean
104: * <code>PropertyDescriptor</code>s.
105: */
106: private Map propertyDescriptorMap = new HashMap();
107:
108: /**
109: * Creates a new <code>ComponentIntrospector</code> for the specified
110: * type.
111: *
112: * @param typeName the component type name
113: */
114: private ComponentIntrospector(String typeName,
115: ClassLoader classLoader) throws ClassNotFoundException {
116: super ();
117: componentClass = Class.forName(typeName, true, classLoader);
118: try {
119: beanInfo = Introspector.getBeanInfo(componentClass,
120: Introspector.IGNORE_ALL_BEANINFO);
121: } catch (IntrospectionException ex) {
122: // Should not occur.
123: throw new RuntimeException("Introspection Error", ex);
124: }
125:
126: loadConstants();
127: loadPropertyData();
128: }
129:
130: /**
131: * Retrieves the names of all constants.
132: * A constant is defined to be any public static final variable
133: * declared in the introspected class.
134: *
135: * @return an iterator over the constant names
136: */
137: public Iterator getConstantNames() {
138: return constants.keySet().iterator();
139: }
140:
141: /**
142: * Retrieves the value of the constant with the specified name.
143: *
144: * @param constantName the name of the constant (unqualified)
145: * @return the constant value, or null if no such constant exists
146: * @see #getConstantNames()
147: */
148: public Object getConstantValue(String constantName) {
149: return constants.get(constantName);
150: }
151:
152: /**
153: * Returns the class being introspected.
154: *
155: * @return the introspected class
156: */
157: public Class getObjectClass() {
158: return componentClass;
159: }
160:
161: /**
162: * Returns the <code>Class</code> of a specific property.
163: *
164: * @param propertyName the name of the property
165: * @return the <code>Class</code> of the property
166: */
167: public Class getPropertyClass(String propertyName) {
168: PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyName);
169: if (propertyDescriptor == null) {
170: return null;
171: } else if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
172: return ((IndexedPropertyDescriptor) propertyDescriptor)
173: .getIndexedPropertyType();
174: } else {
175: return propertyDescriptor.getPropertyType();
176: }
177: }
178:
179: /**
180: * Returns the <code>PropertyDescriptor</code> for the specified property.
181: *
182: * @param propertyName the name of the property
183: * @return the <code>PropertyDescriptor</code> associated with the property
184: */
185: public PropertyDescriptor getPropertyDescriptor(String propertyName) {
186: return (PropertyDescriptor) propertyDescriptorMap
187: .get(propertyName);
188: }
189:
190: /**
191: * Returns a write (setter) method for a specific property.
192: *
193: * @param propertyName the name of the property
194: * @return the write method (if available)
195: */
196: public Method getWriteMethod(String propertyName) {
197: PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyName);
198: if (propertyDescriptor == null) {
199: return null;
200: } else {
201: if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
202: return ((IndexedPropertyDescriptor) propertyDescriptor)
203: .getIndexedWriteMethod();
204: } else {
205: return propertyDescriptor.getWriteMethod();
206: }
207: }
208: }
209:
210: /**
211: * Determines whether a property is an indexed property.
212: *
213: * @param propertyName the name of the property to query
214: * @return true if the specified property is indexed
215: */
216: public boolean isIndexedProperty(String propertyName) {
217: return propertyDescriptorMap.get(propertyName) instanceof IndexedPropertyDescriptor;
218: }
219:
220: /**
221: * Initialization method to load data related to style constants.
222: */
223: private void loadConstants() {
224: constants = new HashMap();
225: Field[] fields = componentClass.getFields();
226: for (int index = 0; index < fields.length; ++index) {
227: if ((fields[index].getModifiers() & CONSTANT_MODIFERS) != 0) {
228: String constantName = fields[index].getName();
229: try {
230: Object constantValue = fields[index].get(null);
231: constants.put(constantName, constantValue);
232: } catch (IllegalAccessException ex) {
233: // Should not occur.
234: }
235: }
236: }
237: }
238:
239: /**
240: * Initialization method to load property information.
241: */
242: private void loadPropertyData() {
243: PropertyDescriptor[] propertyDescriptors = beanInfo
244: .getPropertyDescriptors();
245: for (int index = 0; index < propertyDescriptors.length; ++index) {
246: // Limit to mutable properties only.
247:
248: if (propertyDescriptors[index] instanceof IndexedPropertyDescriptor) {
249: if (((IndexedPropertyDescriptor) propertyDescriptors[index])
250: .getIndexedWriteMethod() != null) {
251: String name = propertyDescriptors[index].getName();
252:
253: // Store JavaBean PropertyDescriptor.
254: propertyDescriptorMap.put(name,
255: propertyDescriptors[index]);
256: }
257: } else {
258: if (propertyDescriptors[index].getWriteMethod() != null) {
259: String name = propertyDescriptors[index].getName();
260:
261: // Store JavaBean PropertyDescriptor.
262: propertyDescriptorMap.put(name,
263: propertyDescriptors[index]);
264: }
265: }
266: }
267: }
268: }
|