001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package com.sun.data.provider.impl;
043:
044: import com.sun.data.provider.DataProvider;
045: import com.sun.data.provider.FieldKey;
046: import java.beans.BeanInfo;
047: import java.beans.IntrospectionException;
048: import java.beans.Introspector;
049: import java.beans.PropertyDescriptor;
050: import java.lang.reflect.Field;
051: import java.lang.reflect.Modifier;
052: import java.util.HashMap;
053: import java.util.Iterator;
054: import java.util.Map;
055: import java.util.TreeMap;
056: import com.sun.data.provider.DataProviderException;
057:
058: /**
059: * <p>Support class for {@link DataProvider} implementations that need to
060: * instrospect Java classes to discover properties (and optionally public
061: * fields) and return {@link FieldKey} instances for them.</p>
062: */
063: public class ObjectFieldKeySupport {
064:
065: // ------------------------------------------------------------- Constructor
066:
067: /**
068: * <p>Construct a new support instance wrapping the specified class,
069: * with the specified flag for including public fields.</p>
070: *
071: * <p><strong>WARNING</strong> - Instances of this class will not be
072: * <code>Serializable</code>, so callers should not attempt to save
073: * fields containing such instances.</p>
074: *
075: * @param clazz Class whose properties should be exposed
076: * @param includeFields Flag indicating whether public fields should
077: * also be included
078: */
079: public ObjectFieldKeySupport(Class clazz, boolean includeFields) {
080:
081: this .clazz = clazz;
082: this .includeFields = includeFields;
083: introspect();
084:
085: }
086:
087: // -------------------------------------------------------- Static Variables
088:
089: /**
090: * <p>The empty argument list when calling property getters.</p>
091: */
092: private static final Object[] EMPTY = new Object[0];
093:
094: // ------------------------------------------------------ Instance Variables
095:
096: /**
097: * <p>The class whose properties (and, optionally, fields) we are
098: * exposing.</p>
099: */
100: private Class clazz = null;
101:
102: /**
103: * <p>Map of {@link Field}s for fields, keyed by field name. This
104: * is only populated if <code>includeFields</code> is set to <code>true</code>.</p>
105: */
106: private Map fields = null;
107:
108: /**
109: * <p>Flag indicating whether we should expose public fields as well as
110: * properties as {@link FieldKey}s.</p>
111: */
112: private boolean includeFields = false;
113:
114: /**
115: * <p>Map of all {@link FieldKey}s to be returned.</p>
116: */
117: private Map keys = null;
118:
119: /**
120: * <p>Map of {@link PropertyDescriptor}s for properties, keyed by property name.</p>
121: */
122: private Map props = null;
123:
124: // ---------------------------------------------------------- Public Methods
125:
126: /**
127: * <p>Return the {@link FieldKey} associated with the specified canonical
128: * identifier, if any; otherwise, return <code>null</code>.</p>
129: *
130: * @param fieldId Canonical identifier of the required field
131: */
132: public FieldKey getFieldKey(String fieldId)
133: throws DataProviderException {
134:
135: return (FieldKey) keys.get(fieldId);
136:
137: }
138:
139: /**
140: * <p>Return an array of all supported {@link FieldKey}s.</p>
141: */
142: public FieldKey[] getFieldKeys() throws DataProviderException {
143:
144: return ((FieldKey[]) keys.values().toArray(
145: new FieldKey[keys.size()]));
146:
147: }
148:
149: /**
150: * <p>Return the type of the field associated with the specified
151: * {@link FieldKey}, if it can be determined; otherwise, return
152: * <code>null</code>.</p>
153: *
154: * @param fieldKey {@link FieldKey} to return the type for
155: */
156: public Class getType(FieldKey fieldKey)
157: throws DataProviderException {
158:
159: PropertyDescriptor pd = (PropertyDescriptor) props.get(fieldKey
160: .getFieldId());
161: if (pd != null) {
162: return pd.getPropertyType();
163: }
164: if (includeFields) {
165: Field f = (Field) fields.get(fieldKey.getFieldId());
166: if (f != null) {
167: return f.getType();
168: }
169: }
170: return null;
171:
172: }
173:
174: /**
175: * <p>Return the value for the specified {@link FieldKey}, from the
176: * specified base object.</p>
177: *
178: * @param fieldKey {@link FieldKey} for the requested field
179: * @param base Base object to be used
180: */
181: public Object getValue(FieldKey fieldKey, Object base)
182: throws DataProviderException {
183:
184: if (base == null) {
185: return null;
186: }
187:
188: PropertyDescriptor pd = (PropertyDescriptor) props.get(fieldKey
189: .getFieldId());
190: if (pd != null && pd.getReadMethod() != null) {
191: try {
192: return pd.getReadMethod().invoke(base, EMPTY);
193: } catch (Exception e) {
194: e.printStackTrace();
195: return null;
196: }
197: }
198: if (includeFields) {
199: Field f = (Field) fields.get(fieldKey.getFieldId());
200: if (f != null) {
201: try {
202: return f.get(base);
203: } catch (Exception e) {
204: e.printStackTrace();
205: return null;
206: }
207: }
208: }
209: return null;
210:
211: }
212:
213: /**
214: * <p>Return <code>true</code> if the specified value may be
215: * successfully assigned to the specified field.</p>
216: *
217: * @param fieldKey {@link FieldKey} to check assignability for
218: * @param value Proposed value
219: */
220: public boolean isAssignable(FieldKey fieldKey, Object value)
221: throws DataProviderException {
222:
223: Class type = getType(fieldKey);
224: if (value == null) {
225: return !type.isPrimitive();
226: }
227: Class clazz = value.getClass();
228: if (type.isAssignableFrom(clazz)) {
229: return true;
230: }
231: if ((type.equals(Boolean.TYPE) && clazz.equals(Boolean.class))
232: || (type.equals(Integer.TYPE) && clazz
233: .equals(Integer.class))
234: || (type.equals(Long.TYPE) && clazz.equals(Long.class))
235: || (type.equals(Double.TYPE) && clazz
236: .equals(Double.class))
237: || (type.equals(Character.TYPE) && clazz
238: .equals(Character.class))
239: || (type.equals(Byte.TYPE) && clazz.equals(Byte.class))
240: || (type.equals(Short.TYPE) && clazz
241: .equals(Short.class))
242: || (type.equals(Float.TYPE) && clazz
243: .equals(Float.class))) {
244: return true;
245: } else {
246: return false;
247: }
248:
249: }
250:
251: /**
252: * <p>Return the read only state of the field associated with the
253: * specified {@link FieldKey}, if it can be determined, otherwise,
254: * return <code>true</code>.</p>
255: *
256: * @param fieldKey {@link FieldKey} to return read only state for
257: */
258: public boolean isReadOnly(FieldKey fieldKey)
259: throws DataProviderException {
260:
261: PropertyDescriptor pd = (PropertyDescriptor) props.get(fieldKey
262: .getFieldId());
263: if (pd != null) {
264: return pd.getWriteMethod() == null;
265: }
266: if (includeFields) {
267: Field f = (Field) fields.get(fieldKey.getFieldId());
268: if (f != null) {
269: return false; // All fields are writeable
270: }
271: }
272: return true;
273:
274: }
275:
276: /**
277: * <p>Set the value for the specified {@link FieldKey}, on the
278: * specified base object.</p>
279: *
280: * @param fieldKey {@link FieldKey} for the requested field
281: * @param base Base object to be used
282: * @param value Value to be set
283: *
284: * @exception IllegalArgumentException if a type mismatch occurs
285: * @exception IllegalStateException if setting a read only field
286: * is attempted
287: */
288: public void setValue(FieldKey fieldKey, Object base, Object value)
289: throws DataProviderException {
290:
291: PropertyDescriptor pd = (PropertyDescriptor) props.get(fieldKey
292: .getFieldId());
293: if (pd != null) {
294: if (pd.getWriteMethod() == null) {
295: throw new IllegalStateException("" + fieldKey);
296: }
297: try {
298: pd.getWriteMethod()
299: .invoke(base, new Object[] { value });
300: } catch (IllegalArgumentException e) {
301: throw e;
302: } catch (Exception e) {
303: e.printStackTrace();
304: }
305: }
306: if (includeFields) {
307: Field f = (Field) fields.get(fieldKey.getFieldId());
308: if (f != null) {
309: try {
310: f.set(base, value);
311: } catch (Exception e) {
312: e.printStackTrace();
313: }
314: }
315: }
316:
317: }
318:
319: // --------------------------------------------------------- Private Methods
320:
321: /**
322: * <p>Introspect the public properties (and optionally the public fields)
323: * of the class we are wrapping.</p>
324: */
325: private void introspect() {
326:
327: props = new HashMap();
328: if (includeFields) {
329: fields = new HashMap();
330: }
331:
332: // Introspect the properties and fields of the specified class
333: try {
334: BeanInfo bi = Introspector.getBeanInfo(clazz);
335: PropertyDescriptor[] props = bi.getPropertyDescriptors();
336: for (int i = 0; i < props.length; i++) {
337: this .props.put(props[i].getName(), props[i]);
338: }
339: if (includeFields) {
340: Field[] fields = clazz.getFields();
341: for (int i = 0; i < fields.length; i++) {
342: if (((fields[i].getModifiers() & Modifier.PUBLIC) != 0)
343: && !this .props.containsKey(fields[i]
344: .getName())) {
345: this .fields.put(fields[i].getName(), fields[i]);
346: }
347: }
348: }
349: } catch (IntrospectionException ix) {
350: ix.printStackTrace();
351: }
352:
353: // Accumulate the set of all appropriate FieldKeys
354: keys = new TreeMap();
355: Iterator names = props.keySet().iterator();
356: while (names.hasNext()) {
357: String name = (String) names.next();
358: keys.put(name, new FieldKey(name));
359: }
360: if (includeFields) {
361: names = fields.keySet().iterator();
362: while (names.hasNext()) {
363: String name = (String) names.next();
364: keys.put(name, new FieldKey(name));
365: }
366: }
367:
368: }
369:
370: }
|