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.DataProviderException;
046: import com.sun.data.provider.FieldKey;
047: import com.sun.data.provider.RowKey;
048: import com.sun.data.provider.TableDataProvider;
049: import java.io.Serializable;
050: import java.lang.reflect.Constructor;
051: import java.lang.reflect.Modifier;
052: import java.util.ArrayList;
053: import java.util.HashMap;
054: import java.util.List;
055: import java.util.Map;
056: import java.util.ResourceBundle;
057: import java.util.TreeMap;
058:
059: /**
060: * <p>This {@link TableDataProvider} wraps access to an
061: * array of Java Objects. The {@link FieldKey}s correspond to the JavaBean
062: * properties and optionally the public member fields of the Java Objects.</p>
063: *
064: * <p>Note that this {@link TableDataProvider} determines which fields are
065: * available by examining the underlying component data type of the array.
066: * If you pass in an array that is of type <code>Object[]</code>, then, perhaps
067: * with initialization code like this:</p>
068: * <pre>
069: * Map map = ...;
070: * return new ObjectArrayDataProvider(map.values().toArray());
071: * </pre>
072: * <p>the fields of your actual object type will not be available. If you
073: * know that your data is all of type Foo, do this instead:</p>
074: * <pre>
075: * Map map = ...;
076: * return new ObjectArrayDataProvider
077: * ((Foo[]) map.values().toArray(new Foo[0]));
078: * </pre>
079: *
080: * <p>Since this {@link TableDataProvider} wraps an array, and arrays in Java
081: * are not intrinsically resizeable, this implementation will return
082: * <code>false</code> for any call to <code>canAppendRow()</code>,
083: * <code>canInsertRow()</code>, or <code>canRemoveRow()</code>. It will
084: * throw <code>UnsupportedOperationException</code> if you attempt to call
085: * <code>appendRow()</code>, <code>insertRow()</code>, or
086: * <code>removeRow()</code>.</p>
087: *
088: * <p><strong>WARNING</strong> - Until you call <code>setArray()</code> or
089: * <code>setObjectType()</code> with a non-null parameter, or use a constructor
090: * variant that accepts an non-null array, no information about field keys will
091: * be available. Therefore, any attempt to reference a <code>FieldKey</code>
092: * or field identifier in a method call will throw
093: * <code>IllegalArgumentException</code>.</p>
094: *
095: * <p>NOTE about Serializable: This class wraps access to an array of any Java
096: * Objects. For this class to remain Serializable, the contained Objects must
097: * also be Serializable.</p>
098: *
099: * @author Joe Nuxoll
100: * Winston Prakash (bug fixes)
101: */
102: public class ObjectArrayDataProvider extends AbstractTableDataProvider
103: implements Serializable {
104:
105: // ------------------------------------------------------------ Constructors
106:
107: /**
108: * <p>Construct a new ObjectArrayDataProvider with no known data. The
109: * <code>setArray()</code> method can be used to set the contained array.</p>
110: */
111: public ObjectArrayDataProvider() {
112:
113: setArray(null);
114:
115: }
116:
117: /**
118: * <p>Constructs a new ObjectArraytDataProvider wrapping the specified
119: * array.</p>
120: *
121: * @param array Array to be wrapped
122: */
123: public ObjectArrayDataProvider(Object array[]) {
124:
125: setArray(array);
126:
127: }
128:
129: /**
130: * <p>Constructs a new ObjectArraytDataProvider wrapping the specified
131: * array and value of the <code>includeFields</code> property.</p>
132: *
133: * @param array Array to be wrapped
134: * @param includeFields Desired includeFields property
135: */
136: public ObjectArrayDataProvider(Object array[], boolean includeFields) {
137:
138: setArray(array);
139: setIncludeFields(includeFields);
140:
141: }
142:
143: // ------------------------------------------------------ Instance Variables
144:
145: /**
146: * <p>Storage for the array being wrapped by this data provider.</p>
147: */
148: private Object array[] = null;
149:
150: /**
151: * <p>Resource bundle containing our localized messages.</p>
152: */
153: private transient ResourceBundle bundle = null;
154:
155: /**
156: * <p>Storage for the <code>includeFields</code> property. By default,
157: * this is true.</p>
158: */
159: private boolean includeFields = true;
160:
161: /**
162: * <p>Storage for the object type contained in this data provider.</p>
163: */
164: private Class objectType;
165:
166: // -------------------------------------------------------------- Private Static Variables
167:
168: /**
169: * <p>The maximum number of rows that can be displayed at designtime.</p>
170: */
171: private static final int MAX_DESIGNTIME_ROWCOUNT = 25;
172:
173: /**
174: * <p>When showing fake data, the number of rows to show.</p>
175: */
176: private static final int FAKE_DATA_ROWCOUNT = 3;
177:
178: // -------------------------------------------------------------- Properties
179:
180: /**
181: * <code>Return the array that we are wrapping.</p>
182: */
183: public Object[] getArray() {
184:
185: return this .array;
186:
187: }
188:
189: /**
190: * <p>Replace the array that we are wrapping. In addition,
191: * the <code>objectType</code> property will be reset based on the
192: * class of the underlying element type.</p>
193: *
194: * @param array The new array to be wrapped
195: */
196: public void setArray(Object array[]) {
197: this .array = array;
198: if (array != null) {
199: setObjectType(array.getClass().getComponentType());
200: } else {
201: setObjectType(null);
202: }
203: }
204:
205: /**
206: * <p>Return the object type that this data provider contains. This
207: * determines the list of {@link FieldKey}s that this provider supplies.</p>
208: */
209: public Class getObjectType() {
210:
211: return objectType;
212:
213: }
214:
215: /**
216: * <p>Set the object type contained in this ObjectListDataProvider. This
217: * type determines the list of public properties and fields to expose as
218: * {@link FieldKey}s. If no object type is specified, the first added
219: * object's class will be used as the object type.</p>
220: *
221: * @param objectType The desired Class type to be contained in this
222: * ObjectDataProvider
223: */
224: public void setObjectType(Class objectType) {
225:
226: this .objectType = objectType;
227: this .support = null;
228: fireProviderChanged();
229:
230: }
231:
232: /**
233: * <p>Return the state of the <code>includeFields</code> property.</p>
234: */
235: public boolean isIncludeFields() {
236:
237: return this .includeFields;
238:
239: }
240:
241: /**
242: * <p>Set the <code>includeFields</code> property. This affects the set of
243: * {@link FieldKey}s that this {@link DataProvider} emits. If the
244: * property is set to <code>true</code> (the default), then public fields
245: * will be included in the list of available keys (intermixed with the
246: * public properties). Otherwise, only the public properties will be
247: * available.</p>
248: *
249: * @param includeFields The new include fields value
250: */
251: public void setIncludeFields(boolean includeFields) {
252:
253: this .includeFields = includeFields;
254: this .support = null;
255:
256: }
257:
258: // ---------------------------------------------------------- Public Methods
259:
260: // ---------------------------------------------------- DataProvider Methods
261:
262: /** {@inheritDoc} */
263: public FieldKey getFieldKey(String fieldId)
264: throws DataProviderException {
265: FieldKey fieldKey = null;
266: if (getSupport() != null) {
267: fieldKey = getSupport().getFieldKey(fieldId);
268: }
269: if (fieldKey != null) {
270: return fieldKey;
271: } else {
272: throw new IllegalArgumentException(fieldId);
273: }
274: }
275:
276: /** {@inheritDoc} */
277: public FieldKey[] getFieldKeys() throws DataProviderException {
278:
279: if (getSupport() != null) {
280: return getSupport().getFieldKeys();
281: }
282: return FieldKey.EMPTY_ARRAY;
283:
284: }
285:
286: /** {@inheritDoc} */
287: public Class getType(FieldKey fieldKey)
288: throws DataProviderException {
289: if ((getSupport() == null)
290: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
291: throw new IllegalArgumentException(fieldKey.toString());
292: } else {
293: return getSupport().getType(fieldKey);
294: }
295: }
296:
297: /** {@inheritDoc} */
298: public Object getValue(FieldKey fieldKey)
299: throws DataProviderException {
300:
301: return getValue(fieldKey, getCursorRow());
302:
303: }
304:
305: /** {@inheritDoc} */
306: public void setValue(FieldKey fieldKey, Object value)
307: throws DataProviderException {
308:
309: setValue(fieldKey, getCursorRow(), value);
310:
311: }
312:
313: /** {@inheritDoc} */
314: public boolean isReadOnly(FieldKey fieldKey)
315: throws DataProviderException {
316: if ((getSupport() == null)
317: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
318: throw new IllegalArgumentException(fieldKey.toString());
319: } else {
320: return getSupport().isReadOnly(fieldKey);
321: }
322: }
323:
324: // --------------------------------------- TableDataProvider Methods (Basic)
325:
326: /** {@inheritDoc} */
327: public int getRowCount() throws DataProviderException {
328: //at designtime, if there are no field keys
329: //prevent ELExceptions from being thrown by showing zero rows
330: if (java.beans.Beans.isDesignTime()
331: && getFieldKeys().length < 1) {
332: return 0;
333: }
334:
335: //calculate how many rows currently exist in the wrapped data
336: int currentRowCount = calculateRowCount();
337:
338: if (java.beans.Beans.isDesignTime()) {
339: if (currentRowCount < 1) {
340: //we have no rows to show
341: //so show FAKE_DATA_ROWCOUNT rows of fake data
342: return FAKE_DATA_ROWCOUNT;
343: } else if (currentRowCount > MAX_DESIGNTIME_ROWCOUNT) {
344: //we have too many rows to show
345: //only show the maximum permitted
346: return MAX_DESIGNTIME_ROWCOUNT;
347: } else {
348: return currentRowCount;
349: }
350: } else {
351: return currentRowCount;
352: }
353: }
354:
355: /** {@inheritDoc} */
356: public Object getValue(FieldKey fieldKey, RowKey rowKey)
357: throws DataProviderException {
358: if (java.beans.Beans.isDesignTime()) {
359: //calculate how many rows currently exist in the wrapped data
360: int currentRowCount = calculateRowCount();
361: if (currentRowCount < 1) {
362: //we have no actual rows
363: //so show fake data
364: return AbstractDataProvider
365: .getFakeData(getType(fieldKey));
366: }
367: }
368:
369: if ((getSupport() == null)
370: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
371: throw new IllegalArgumentException(fieldKey.toString());
372: }
373:
374: if (!isRowAvailable(rowKey)) {
375: throw new IndexOutOfBoundsException("" + rowKey);
376: }
377:
378: return getSupport().getValue(fieldKey,
379: array[getRowIndex(rowKey)]);
380: }
381:
382: /** {@inheritDoc} */
383: public void setValue(FieldKey fieldKey, RowKey rowKey, Object value)
384: throws DataProviderException {
385:
386: if ((getSupport() == null)
387: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
388: throw new IllegalArgumentException(fieldKey.toString());
389: }
390: if (getSupport().isReadOnly(fieldKey)) {
391: throw new IllegalStateException(fieldKey.toString() + " "
392: + getBundle().getString("IS_READ_ONLY"));
393: }
394: if (!isRowAvailable(rowKey)) {
395: throw new IndexOutOfBoundsException(rowKey.toString());
396: }
397: Object previous = getSupport().getValue(fieldKey,
398: array[getRowIndex(rowKey)]);
399: getSupport().setValue(fieldKey, array[getRowIndex(rowKey)],
400: value);
401: if (((previous == null) && (value != null))
402: || ((previous != null) && (value == null))
403: || ((previous != null) && (value != null) && !previous
404: .equals(value))) {
405: fireValueChanged(fieldKey, rowKey, previous, value);
406: fireValueChanged(fieldKey, previous, value);
407: }
408: }
409:
410: // -------------------------------------- TableDataProvider Methods (Cursor)
411:
412: // Base class definitions are sufficient
413:
414: // ------------------------ TableDataProvider Methods (Append/Insert/Delete)
415:
416: /**
417: * {@inheritDoc}
418: */
419: public boolean canAppendRow() throws DataProviderException {
420:
421: return false;
422:
423: }
424:
425: /**
426: * {@inheritDoc}
427: */
428: public RowKey appendRow() throws DataProviderException {
429:
430: throw new UnsupportedOperationException();
431:
432: }
433:
434: /**
435: * {@inheritDoc}
436: */
437: public boolean canInsertRow(RowKey beforeRow)
438: throws DataProviderException {
439:
440: return false;
441:
442: }
443:
444: /**
445: * {@inheritDoc}
446: */
447: public RowKey insertRow(RowKey beforeRow)
448: throws DataProviderException {
449:
450: throw new UnsupportedOperationException();
451:
452: }
453:
454: /**
455: * {@inheritDoc}
456: */
457: public boolean canRemoveRow(RowKey row)
458: throws DataProviderException {
459:
460: return false;
461:
462: }
463:
464: /**
465: * <p>Remove the object at the specified row from the list.</p>
466: *
467: * {@inheritDoc}
468: */
469: public void removeRow(RowKey row) throws DataProviderException {
470:
471: throw new UnsupportedOperationException();
472:
473: }
474:
475: // --------------------------------------------------------- Private Methods
476:
477: /**
478: * <p>Return the resource bundle containing our localized messages.</p>
479: */
480: private ResourceBundle getBundle() {
481:
482: if (bundle == null) {
483: bundle = ResourceBundle
484: .getBundle("com/sun/data/provider/impl/Bundle");
485: }
486: return bundle;
487:
488: }
489:
490: /**
491: * <p>Return the row index corresponding to the specified row key.</p>
492: *
493: * @param rowKey Row key for which to extract an index
494: */
495: private int getRowIndex(RowKey rowKey) throws DataProviderException {
496:
497: return ((IndexRowKey) rowKey).getIndex();
498:
499: }
500:
501: /**
502: * <p>Return a suitable {@link RowKey} for the specified row index.</p>
503: */
504: private RowKey getRowKey(int index) throws DataProviderException {
505:
506: return new IndexRowKey(index);
507:
508: }
509:
510: /**
511: * <p>The cached support object for field key manipulation. Must be
512: * transient because its content is not Serializable.</p>
513: */
514: private transient ObjectFieldKeySupport support = null;
515:
516: /**
517: * <p>Return the {@link ObjectFieldKeySupport} instance for the
518: * object class we are wrapping.</p>
519: */
520: private ObjectFieldKeySupport getSupport() {
521:
522: if ((support == null) && (objectType != null)) {
523: support = new ObjectFieldKeySupport(objectType,
524: includeFields);
525: }
526: return support;
527:
528: }
529:
530: /**
531: * <p>Calculate how many rows exist in the wrapped data.</p>
532: */
533: private int calculateRowCount() {
534: int currentRowCount = 0;
535: if (array != null) {
536: currentRowCount = array.length;
537: }
538: return currentRowCount;
539: }
540: }
|