0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package com.sun.data.provider.impl;
0043:
0044: import java.io.Serializable;
0045: import java.lang.reflect.Constructor;
0046: import java.lang.reflect.Modifier;
0047: import java.util.ArrayList;
0048: import java.util.HashMap;
0049: import java.util.Iterator;
0050: import java.util.List;
0051: import java.util.Map;
0052: import java.util.ResourceBundle;
0053: import java.util.Set;
0054: import java.util.TreeSet;
0055: import com.sun.data.provider.DataProviderException;
0056: import com.sun.data.provider.FieldKey;
0057: import com.sun.data.provider.RowKey;
0058: import com.sun.data.provider.TransactionalDataListener;
0059: import com.sun.data.provider.TransactionalDataProvider;
0060:
0061: /**
0062: * <p>This {@link com.sun.data.provider.TableDataProvider} wraps access to a
0063: * list of Java Objects. The {@link FieldKey}s correspond to the JavaBean
0064: * properties and optionally the public member fields of the Java Object.</p>
0065: *
0066: * <p>This class implements {@link TransactionalDataProvider} semantics,
0067: * meaning that all updates to existing fields, as well as inserted and
0068: * deleted rows, are cached until <code>commitChanges()</code> is called.
0069: * Once that call is made, any <code>RowKey</code> you have retrieved from
0070: * this instance is invalid, and must be reacquired.</p>
0071: *
0072: * <p><strong>WARNING</strong> - Until you call <code>setList()</code> or
0073: * <code>setObjectType()</code> with a non-null parameter, or use a constructor
0074: * variant that accepts an non-null non-empty list, no information about field keys will
0075: * be available. Therefore, any attempt to reference a <code>FieldKey</code>
0076: * or field identifier in a method call will throw
0077: * <code>IllegalArgumentException</code>.</p>
0078: *
0079: * <p>NOTE about Serializable: This class wraps access to a list of any Java
0080: * Objects. The Objects can be swapped out using the <code>setObject(Object)</code>
0081: * method. For this class to remain Serializable, the contained Objects must
0082: * also be Serializable.</p>
0083: *
0084: * @author Joe Nuxoll
0085: * Winston Prakash (bug fixes)
0086: */
0087: public class ObjectListDataProvider extends AbstractTableDataProvider
0088: implements Serializable, TransactionalDataProvider {
0089:
0090: // ------------------------------------------------------------ Constructors
0091:
0092: /**
0093: * <p>Construct a new ObjectListDataProvider with no known object type. The
0094: * <code>setObjectType()</code> method can be used to set the object type.
0095: * If not set, the first added object will automatically define the object
0096: * type.</p>
0097: */
0098: public ObjectListDataProvider() {
0099:
0100: setObjectType(null);
0101:
0102: }
0103:
0104: /**
0105: * <p>Constructs a new ObjectListDataProvider wrapping the specified list.
0106: * </p>
0107: *
0108: * @param list List to be wrapped
0109: */
0110: public ObjectListDataProvider(List list) {
0111:
0112: setList(list);
0113:
0114: }
0115:
0116: /**
0117: * <p>Constructs a new ObjectListDataProvider wrapping the specified list
0118: * with the specified include fields flag.</p>
0119: *
0120: * @param list List to be wrapped
0121: * @param includeFields Desired include fields property setting
0122: */
0123: public ObjectListDataProvider(List list, boolean includeFields) {
0124:
0125: setList(list);
0126: setIncludeFields(includeFields);
0127:
0128: }
0129:
0130: /**
0131: * <p>Constructs a new ObjectListDataProvider for the specified object type.
0132: * </p>
0133: *
0134: * @param objectType Desired object type Class
0135: */
0136: public ObjectListDataProvider(Class objectType) {
0137:
0138: setObjectType(objectType);
0139:
0140: }
0141:
0142: /**
0143: * <p>Constructs a new ObjectListDataProvider for the specified object type
0144: * and <code>includeFields</code> property value.</p>
0145: *
0146: * @param objectType Desired object type Class
0147: * @param includeFields Desired include fields property setting
0148: */
0149: public ObjectListDataProvider(Class objectType,
0150: boolean includeFields) {
0151:
0152: setObjectType(objectType);
0153: setIncludeFields(includeFields);
0154:
0155: }
0156:
0157: // ------------------------------------------------------ Instance Variables
0158:
0159: /**
0160: * <p>List of object instances to be appended to the underlying
0161: * list when changes are committed. The {@link RowKey} values
0162: * that correspond to these rows will have row index values starting
0163: * with the number of rows in the underlying list. That is, if there
0164: * are five rows in the list already, the first appended row will have
0165: * an index of five, the second will have a row index of six, and
0166: * so on.</p>
0167: */
0168: private List appends = new ArrayList();
0169:
0170: /**
0171: * <p>Resource bundle containing our localized messages.</p>
0172: */
0173: private transient ResourceBundle bundle = null;
0174:
0175: /**
0176: * <p>Set of {@link RowKey}s of rows that have been marked to be
0177: * deleted. An <code>Iterator</code> over this set will return
0178: * the corresponding {@link RowKey}s in ascending order.</p>
0179: */
0180: private Set deletes = new TreeSet();
0181:
0182: /**
0183: * <p>Storage for the <code>includeFields</code> property. By default,
0184: * this is true.</p>
0185: */
0186: private boolean includeFields = true;
0187:
0188: /**
0189: * <p>Storage for the internal list of objects wrapped by this
0190: * data provider.</p>
0191: */
0192: private List list = new ArrayList();
0193:
0194: /**
0195: * <p>Storage for the object type contained in this data provider.</p>
0196: */
0197: private Class objectType;
0198:
0199: /**
0200: * <p>Map keyed by {@link RowKey}, whose elements are themselves
0201: * Maps keyed by {@link FieldKey} of each field for which an
0202: * updated value has been cached.</p>
0203: */
0204: private Map updates = new HashMap();
0205:
0206: /**
0207: * <p>Storage for the userResizable property (default value is
0208: * <code>true</true>).</p>
0209: */
0210: private boolean userResizable = true;
0211:
0212: // -------------------------------------------------------------- Private Static Variables
0213:
0214: /**
0215: * <p>The maximum number of rows that can be displayed at designtime.</p>
0216: */
0217: private static final int MAX_DESIGNTIME_ROWCOUNT = 25;
0218:
0219: /**
0220: * <p>When showing fake data, the number of rows to show.</p>
0221: */
0222: private static final int FAKE_DATA_ROWCOUNT = 3;
0223:
0224: // -------------------------------------------------------------- Properties
0225:
0226: /**
0227: * <p>Return the state of the <code>includeFields</code> property.</p>
0228: */
0229: public boolean isIncludeFields() {
0230:
0231: return this .includeFields;
0232:
0233: }
0234:
0235: /**
0236: * <p>Set the <code>includeFields</code> property. This affects the set of
0237: * {@link FieldKey}s that this {@link com.sun.data.provider.DataProvider}
0238: * emits. If the property is set to <code>true</code> (the default), then
0239: * public fields will be included in the list of available keys (intermixed
0240: * with the public properties). Otherwise, only the public properties will
0241: * be available.</p>
0242: *
0243: * @param includeFields The new include fields value
0244: */
0245: public void setIncludeFields(boolean includeFields) {
0246:
0247: this .includeFields = includeFields;
0248: this .support = null;
0249:
0250: }
0251:
0252: /**
0253: * <code>Return the <code>List</code> that we are wrapping.</p>
0254: */
0255: public List getList() {
0256:
0257: return this .list;
0258:
0259: }
0260:
0261: /**
0262: * <p>Replace the <code>List</code> that we are wrapping. In addition,
0263: * the <code>objectType</code> property will be reset based on the
0264: * class of the first element in the list (if any). If the list is
0265: * empty, <code>objectType</code> will be set to <code>null</code>.
0266: *
0267: * @param list The new list to be wrapped
0268: */
0269: public void setList(List list) {
0270: this .list = list;
0271: if (list != null && list.size() > 0) {
0272: setObjectType(list.get(0).getClass());
0273: } else {
0274: setObjectType(null);
0275: }
0276: }
0277:
0278: /**
0279: * <p>Return the object type that this data provider contains. This
0280: * determines the list of {@link FieldKey}s that this provider supplies.</p>
0281: */
0282: public Class getObjectType() {
0283:
0284: return objectType;
0285:
0286: }
0287:
0288: /**
0289: * <p>Set the object type contained in this ObjectListDataProvider. This
0290: * type determines the list of public properties and fields to expose as
0291: * {@link FieldKey}s. If no object type is specified, the first added
0292: * object's class will be used as the object type.</p>
0293: *
0294: * @param objectType The desired Class type to be contained in this
0295: * ObjectDataProvider
0296: */
0297: public void setObjectType(Class objectType) {
0298:
0299: this .objectType = objectType;
0300: this .objectTypeConstructor = null;
0301: this .support = null;
0302: fireProviderChanged();
0303:
0304: }
0305:
0306: /**
0307: * <p>Return the current state of the userResizable property. Note that
0308: * the wrapped list will not be actually resizable unless there is a
0309: * public no-args constructor on the <code>objectType</code> class.</p>
0310: */
0311: public boolean isUserResizable() {
0312:
0313: return this .userResizable;
0314:
0315: }
0316:
0317: /**
0318: * <p>Set the user resizable property. If set to <code>true</code> (the
0319: * default), the resizability of this ObjectListDataProvider is based on
0320: * wether or not a public default constructor exists in the object type.
0321: * If the userResizable propert is set to <code>false</code>, then this
0322: * ObjectListDataProvider will not be resizable, regardless of the existence
0323: * of a public default constructor on the object type.</p>
0324: *
0325: * @param resizable <code>true</code> to make this ObjectListDataProvider
0326: * resizable, pending the existence of a public default constructor
0327: * on the contained object type, or <code>false</code> to make it
0328: * non-resizable.
0329: * @see com.sun.data.provider.TableDataProvider#canInsertRow(RowKey beforeRow)
0330: */
0331: public void setUserResizable(boolean resizable) {
0332:
0333: this .userResizable = resizable;
0334:
0335: }
0336:
0337: // ---------------------------------------------------------- Public Methods
0338:
0339: /**
0340: * <p>Append the specified object to the list of contained objects.</p>
0341: *
0342: * @param object The Object to store in the list
0343: */
0344: public void addObject(Object object) {
0345:
0346: if (objectType == null) {
0347: setObjectType(object.getClass());
0348: }
0349: appendRow(object);
0350:
0351: }
0352:
0353: /**
0354: * <p>Add the specified object to the list of contained objects
0355: * at the specified row.</p>
0356: *
0357: * @param row The desired index for the new object
0358: * @param object The Object to store in the list
0359: */
0360: /* FIXME - inserts not currently supported
0361: public void addObject(RowKey row, Object object) {
0362:
0363: if (objectType == null) {
0364: setObjectType(object.getClass());
0365: }
0366: int index = ((IndexRowKey) row).getIndex();
0367: list.add(index, object);
0368: fireRowAdded(row);
0369: if (getCursorIndex() == index) {
0370: fireValueChanged(null, null, object);
0371: }
0372:
0373: }
0374: */
0375:
0376: /**
0377: * <p>Clear the list of contained objects.</p>
0378: */
0379: // FIXME - probably remove as being redundant
0380: public void clearObjectList() {
0381:
0382: list.clear();
0383: fireProviderChanged();
0384:
0385: }
0386:
0387: /**
0388: * <p>Returns the object stored at the specified row.</p>
0389: *
0390: * @param row The desired row to retrieve the contained object from
0391: */
0392: public Object getObject(RowKey row) {
0393:
0394: return list.get(getRowIndex(row));
0395:
0396: }
0397:
0398: /**
0399: * <p>Return the contained objects as an array.</p>
0400: */
0401: public Object[] getObjects() {
0402:
0403: return list.toArray(new Object[list.size()]);
0404:
0405: }
0406:
0407: /**
0408: * <p>Return <code>true</code> if the specified row has been
0409: * marked for removal on the next call to <code>commitChanges()</code>.</p>
0410: *
0411: * @param row The {@link RowKey} of the row to check
0412: */
0413: public boolean isRemoved(RowKey row) {
0414:
0415: return deletes.contains(row);
0416:
0417: }
0418:
0419: /**
0420: * <p>Remove the specified object from the list of contained objects.</p>
0421: *
0422: * @param object The Object to remove from the list
0423: */
0424: public void removeObject(Object object) {
0425:
0426: int index = list.indexOf(object);
0427: if (index > -1) {
0428: removeObject(getRowKey(index));
0429: }
0430:
0431: }
0432:
0433: /**
0434: * <p>Remove the object at the specified row from the list
0435: * of contained objects.</p>
0436: *
0437: * @param row The desired Object row to remove from the list
0438: */
0439: public void removeObject(RowKey row) {
0440:
0441: removeRow(row);
0442:
0443: }
0444:
0445: /**
0446: * <p>Replace the object at the specified row.</p>
0447: *
0448: * @param row The desired row to set the contained object
0449: * @param object The new object to set at the specified row
0450: */
0451: public void setObject(RowKey row, Object object) {
0452:
0453: Object previous = getObject(row);
0454: list.set(getRowIndex(row), object);
0455: fireValueChanged(null, row, previous, object);
0456: if (getCursorRow() == row) {
0457: fireValueChanged(null, previous, object);
0458: }
0459:
0460: }
0461:
0462: // ---------------------------------------------------- DataProvider Methods
0463:
0464: /** {@inheritDoc} */
0465: public FieldKey getFieldKey(String fieldId)
0466: throws DataProviderException {
0467: FieldKey fieldKey = null;
0468: if (getSupport() != null) {
0469: fieldKey = getSupport().getFieldKey(fieldId);
0470: }
0471: if (fieldKey != null) {
0472: return fieldKey;
0473: } else {
0474: throw new IllegalArgumentException(fieldId);
0475: }
0476: }
0477:
0478: /** {@inheritDoc} */
0479: public FieldKey[] getFieldKeys() throws DataProviderException {
0480:
0481: if (getSupport() != null) {
0482: return getSupport().getFieldKeys();
0483: }
0484: return FieldKey.EMPTY_ARRAY;
0485:
0486: }
0487:
0488: /** {@inheritDoc} */
0489: public Class getType(FieldKey fieldKey)
0490: throws DataProviderException {
0491: if ((getSupport() == null)
0492: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
0493: throw new IllegalArgumentException(fieldKey.toString());
0494: } else {
0495: return getSupport().getType(fieldKey);
0496: }
0497: }
0498:
0499: /** {@inheritDoc} */
0500: public Object getValue(FieldKey fieldKey)
0501: throws DataProviderException {
0502:
0503: return getValue(fieldKey, getCursorRow());
0504:
0505: }
0506:
0507: /** {@inheritDoc} */
0508: public void setValue(FieldKey fieldKey, Object value)
0509: throws DataProviderException {
0510:
0511: setValue(fieldKey, getCursorRow(), value);
0512:
0513: }
0514:
0515: /** {@inheritDoc} */
0516: public boolean isReadOnly(FieldKey fieldKey)
0517: throws DataProviderException {
0518: if ((getSupport() == null)
0519: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
0520: throw new IllegalArgumentException(fieldKey.toString());
0521: } else {
0522: return getSupport().isReadOnly(fieldKey);
0523: }
0524: }
0525:
0526: // --------------------------------------- TableDataProvider Methods (Basic)
0527:
0528: /** {@inheritDoc} */
0529: public int getRowCount() throws DataProviderException {
0530: //at designtime, if there are no field keys
0531: //prevent ELExceptions from being thrown by showing zero rows
0532: if (java.beans.Beans.isDesignTime()
0533: && getFieldKeys().length < 1) {
0534: return 0;
0535: }
0536:
0537: //calculate how many rows currently exist in the wrapped data
0538: int currentRowCount = calculateRowCount();
0539:
0540: if (java.beans.Beans.isDesignTime()) {
0541: if (currentRowCount < 1) {
0542: //we have no rows to show
0543: //so show FAKE_DATA_ROWCOUNT rows of fake data
0544: return FAKE_DATA_ROWCOUNT;
0545: } else if (currentRowCount > MAX_DESIGNTIME_ROWCOUNT) {
0546: //we have too many rows to show
0547: //only show the maximum permitted
0548: return MAX_DESIGNTIME_ROWCOUNT;
0549: } else {
0550: return currentRowCount;
0551: }
0552: } else {
0553: return currentRowCount;
0554: }
0555: }
0556:
0557: /** {@inheritDoc} */
0558: public Object getValue(FieldKey fieldKey, RowKey rowKey)
0559: throws DataProviderException {
0560: if (java.beans.Beans.isDesignTime()) {
0561: //calculate how many rows currently exist in the wrapped data
0562: int currentRowCount = calculateRowCount();
0563: if (currentRowCount < 1) {
0564: //we have no actual rows
0565: //so show fake data
0566: return AbstractDataProvider
0567: .getFakeData(getType(fieldKey));
0568: }
0569: }
0570:
0571: if ((getSupport() == null)
0572: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
0573: throw new IllegalArgumentException(fieldKey.toString());
0574: }
0575:
0576: // Return pending update value (if any)
0577: Map fieldUpdates = (Map) updates.get(rowKey);
0578: if ((fieldUpdates != null)
0579: && (fieldUpdates.containsKey(fieldKey))) {
0580: return fieldUpdates.get(fieldKey);
0581: }
0582:
0583: // Otherwise, return the value from the underlying list
0584: if (!isRowAvailable(rowKey) && !deletes.contains(rowKey)) {
0585: throw new IndexOutOfBoundsException("" + rowKey);
0586: }
0587:
0588: int index = getRowIndex(rowKey);
0589: // getRowCount() returns list.size()-deletes.size()
0590: // So index could be list.size()-1 (last index) - Winston
0591: //if (index < getRowCount()) {
0592: if (index < list.size()) {
0593: return getSupport().getValue(fieldKey, list.get(index));
0594: } else {
0595: return getSupport().getValue(fieldKey,
0596: appends.get(index - getRowCount()));
0597: }
0598: }
0599:
0600: /**
0601: * <p>Return <code>true</code> if the specified {@link RowKey} represents
0602: * a row in the original list, or a row that has been appended. FIXME -
0603: * deal with {@link RowKey}s for inserted rows too, when inserts are
0604: * supported.</p>
0605: *
0606: * @param row {@link RowKey} to test for availability
0607: */
0608: public boolean isRowAvailable(RowKey row)
0609: throws DataProviderException {
0610:
0611: if (deletes.contains(row)) {
0612: return false;
0613: }
0614: if (row instanceof IndexRowKey) {
0615: //return (getRowCount() + appends.size()) > ((IndexRowKey) row).getIndex();
0616: // Bug Fix: 6348255 - Delete two rows including the last row, exception thrown
0617: return (list.size() + appends.size()) > ((IndexRowKey) row)
0618: .getIndex();
0619: }
0620: return false;
0621: }
0622:
0623: /** {@inheritDoc} */
0624: public void setValue(FieldKey fieldKey, RowKey rowKey, Object value)
0625: throws DataProviderException {
0626:
0627: if ((getSupport() == null)
0628: || (getSupport().getFieldKey(fieldKey.getFieldId()) == null)) {
0629: throw new IllegalArgumentException(fieldKey.toString());
0630: }
0631: if (getSupport().isReadOnly(fieldKey)) {
0632: throw new IllegalStateException(fieldKey.toString() + " "
0633: + getBundle().getString("IS_READ_ONLY"));
0634: }
0635: if (!isRowAvailable(rowKey) && !deletes.contains(rowKey)) {
0636: throw new IndexOutOfBoundsException(rowKey.toString());
0637: }
0638:
0639: // Retrieve the previous value and determine if it has changed
0640: Object previous = getValue(fieldKey, rowKey);
0641: if (((previous == null) && (value == null))
0642: || ((previous != null) && (value != null) && previous
0643: .equals(value))) {
0644: return; // No change
0645: }
0646:
0647: // Verify type compatibility of the proposed new value
0648: if (!getSupport().isAssignable(fieldKey, value)) {
0649: throw new IllegalArgumentException(fieldKey + " = " + value); // NOI18N
0650: }
0651:
0652: // Record a pending change for this row and field
0653: Map fieldUpdates = (Map) updates.get(rowKey);
0654: if (fieldUpdates == null) {
0655: fieldUpdates = new HashMap();
0656: updates.put(rowKey, fieldUpdates);
0657: }
0658: fieldUpdates.put(fieldKey, value);
0659: fireValueChanged(fieldKey, rowKey, previous, value);
0660: fireValueChanged(fieldKey, previous, value);
0661:
0662: }
0663:
0664: // -------------------------------------- TableDataProvider Methods (Cursor)
0665:
0666: // Base class definitions are sufficient
0667:
0668: // ------------------------ TableDataProvider Methods (Append/Insert/Delete)
0669:
0670: /**
0671: * <p>Return true if the <code>userResizable</code> property is set to
0672: * <code>true</code>, and there is a public zero-args constructor for the
0673: * class specified by the <code>objectType</code> property.</p>
0674: *
0675: * {@inheritDoc}
0676: */
0677: public boolean canAppendRow() throws DataProviderException {
0678:
0679: if (!userResizable) {
0680: return false;
0681: }
0682: if (objectType != null) {
0683: return getObjectTypeConstructor() != null;
0684: }
0685: return false;
0686:
0687: }
0688:
0689: /**
0690: * <p>Construct a new instance of the specified object type and append it
0691: * to the end of the list.</p>
0692: *
0693: * {@inheritDoc}
0694: */
0695: public RowKey appendRow() throws DataProviderException {
0696:
0697: if (!canAppendRow()) {
0698: throw new IllegalStateException(getBundle().getString(
0699: "OLDP_NOT_RESIZABLE"));
0700: }
0701: try {
0702: Constructor con = getObjectTypeConstructor();
0703: appends.add(con.newInstance(new Object[0]));
0704: RowKey rowKey = getRowKey(list.size() + appends.size() - 1);
0705: fireRowAdded(rowKey);
0706: return rowKey;
0707: } catch (Exception x) {
0708: throw new IllegalStateException(getBundle().getString(
0709: "OLDP_NOT_RESIZABLE")
0710: + ":" + x.getMessage());
0711: }
0712:
0713: }
0714:
0715: /**
0716: * <p>Append the specified object to the end of the list.</p>
0717: *
0718: * @param object Object to be appended
0719: */
0720: public RowKey appendRow(Object object) throws DataProviderException {
0721:
0722: if (!userResizable) {
0723: throw new IllegalStateException(getBundle().getString(
0724: "OLDP_NOT_RESIZABLE"));
0725: }
0726: appends.add(object);
0727: RowKey rowKey = getRowKey(list.size() + appends.size() - 1);
0728: fireRowAdded(rowKey);
0729: return rowKey;
0730:
0731: }
0732:
0733: /**
0734: * <p>Return true if the <code>userResizable</code> property is set to
0735: * <code>true</code>, and there is a public zero-args constructor for the
0736: * class specified by the <code>objectType</code> property.</p>
0737: *
0738: * {@inheritDoc}
0739: */
0740: public boolean canInsertRow(RowKey beforeRow)
0741: throws DataProviderException {
0742:
0743: // FIXME - inserts are not currently supported
0744: return false;
0745:
0746: }
0747:
0748: /**
0749: * <p>Construct a new instance of the specified object type and insert it
0750: * at the specified position in the list.</p>
0751: *
0752: * @param beforeRow Row before which to insert the new row
0753: *
0754: * {@inheritDoc}
0755: */
0756: public RowKey insertRow(RowKey beforeRow)
0757: throws DataProviderException {
0758:
0759: throw new UnsupportedOperationException();
0760:
0761: /*
0762: * FIXME - inserts are not currently supported, and when they
0763: * are supported will need to be cached until commit or revert.
0764: if (!canInsertRow(beforeRow)) {
0765: throw new IllegalStateException("This ObjectListDataProvider is not resizable.");
0766: }
0767: try {
0768: Constructor con = getObjectTypeConstructor();
0769: if (con != null) {
0770: Object o = con.newInstance(new Object[0]);
0771: if (o != null) {
0772: addObject(beforeRow, o);
0773: return beforeRow;
0774: }
0775: }
0776: } catch (Exception x) {
0777: throw new IllegalStateException("This ObjectListDataProvider is not resizable: " + x.getMessage());
0778: }
0779: return null;
0780: */
0781:
0782: }
0783:
0784: /**
0785: * <p>Return <code>true</code> if the <code>userResizable</code>
0786: * property is set to <code>true</code>.</p>
0787: *
0788: * {@inheritDoc}
0789: */
0790: public boolean canRemoveRow(RowKey row)
0791: throws DataProviderException {
0792:
0793: return userResizable;
0794:
0795: }
0796:
0797: /**
0798: * <p>Remove the object at the specified row from the list.</p>
0799: *
0800: * {@inheritDoc}
0801: */
0802: public void removeRow(RowKey row) throws DataProviderException {
0803:
0804: // Verify that we can actually remove this row
0805: if (!canRemoveRow(row)) {
0806: throw new IllegalStateException(getBundle().getString(
0807: "OLDP_NOT_RESIZABLE")); // NOI18N
0808: }
0809: if (!isRowAvailable(row)) {
0810: throw new IllegalArgumentException(getBundle().getString(
0811: "CAN_NOT_DELETE_ROW_KEY")
0812: + row); // NOI18N
0813: }
0814:
0815: // Record the fact that we are going to delete this row
0816: deletes.add(row);
0817:
0818: // Fire appropriate events regarding this deletion
0819: fireRowRemoved(row);
0820: if (getCursorRow() == row) {
0821: fireValueChanged(null, list.get(getRowIndex(row)), null);
0822: }
0823:
0824: }
0825:
0826: // --------------------------------------- TransactionalDataProvider Methods
0827:
0828: /**
0829: * <p>Cause any cached updates to existing field values, as well as
0830: * inserted and deleted rows, to be flowed through to the underlying
0831: * <code>List</code> wrapped by this
0832: * {@link com.sun.data.provider.DataProvider}.</p>
0833: */
0834: public void commitChanges() throws DataProviderException {
0835:
0836: // Commit all pending updates to the underlying list
0837: Iterator rowUpdates = updates.entrySet().iterator();
0838: while (rowUpdates.hasNext()) {
0839: Map.Entry rowUpdate = (Map.Entry) rowUpdates.next();
0840: RowKey rowKey = (RowKey) rowUpdate.getKey();
0841: int index = getRowIndex(rowKey);
0842: Object row = null;
0843: if (index < list.size()) {
0844: row = list.get(index);
0845: } else {
0846: row = appends.get(index - list.size());
0847: }
0848: Iterator fieldUpdates = ((Map) rowUpdate.getValue())
0849: .entrySet().iterator();
0850: while (fieldUpdates.hasNext()) {
0851: Map.Entry fieldUpdate = (Map.Entry) fieldUpdates.next();
0852: getSupport().setValue((FieldKey) fieldUpdate.getKey(),
0853: row, fieldUpdate.getValue());
0854: }
0855: }
0856: updates.clear();
0857:
0858: // Commit all pending deletes to the underlying list
0859: RowKey deletes[] = (RowKey[]) this .deletes
0860: .toArray(new RowKey[this .deletes.size()]);
0861: for (int i = (deletes.length - 1); i >= 0; i--) {
0862: list.remove(getRowIndex(deletes[i]));
0863: }
0864: this .deletes.clear();
0865:
0866: // FIXME - inserts will need to be interwoven to avoid indexing errors
0867:
0868: // Commit all pending appends to the underlying list
0869: Iterator appendInstances = appends.iterator();
0870: while (appendInstances.hasNext()) {
0871: list.add(appendInstances.next());
0872: }
0873: appends.clear();
0874:
0875: // Notify interested listeners that we have committed
0876: fireChangesCommitted();
0877:
0878: }
0879:
0880: /** {@inheritDoc} */
0881: public void revertChanges() throws DataProviderException {
0882:
0883: // Erase any cached information about pending changes
0884: updates.clear();
0885: deletes.clear();
0886: appends.clear();
0887:
0888: // Notify interested listeners that we are reverting
0889: fireChangesReverted();
0890:
0891: }
0892:
0893: /** {@inheritDoc} */
0894: public void addTransactionalDataListener(
0895: TransactionalDataListener listener) {
0896:
0897: super .addDataListener(listener);
0898:
0899: }
0900:
0901: /** {@inheritDoc} */
0902: public TransactionalDataListener[] getTransactionalDataListeners() {
0903:
0904: if (dpListeners == null) {
0905: return new TransactionalDataListener[0];
0906: } else {
0907: ArrayList tdpList = new ArrayList();
0908: for (int i = 0; i < dpListeners.length; i++) {
0909: if (dpListeners[i] instanceof TransactionalDataListener) {
0910: tdpList.add(dpListeners[i]);
0911: }
0912: }
0913: return (TransactionalDataListener[]) tdpList
0914: .toArray(new TransactionalDataListener[tdpList
0915: .size()]);
0916: }
0917:
0918: }
0919:
0920: /** {@inheritDoc} */
0921: public void removeTransactionalDataListener(
0922: TransactionalDataListener listener) {
0923:
0924: super .removeDataListener(listener);
0925:
0926: }
0927:
0928: // --------------------------------------------------------- Private Methods
0929:
0930: /**
0931: * <p>Fire a <code>changesCommitted</code> method to all registered
0932: * listeners.</p>
0933: */
0934: private void fireChangesCommitted() {
0935:
0936: TransactionalDataListener listeners[] = getTransactionalDataListeners();
0937: for (int i = 0; i < listeners.length; i++) {
0938: listeners[i].changesCommitted(this );
0939: }
0940:
0941: }
0942:
0943: /**
0944: * <p>Fire a <code>changesReverted</code> method to all registered
0945: * listeners.</p>
0946: */
0947: private void fireChangesReverted() {
0948:
0949: TransactionalDataListener listeners[] = getTransactionalDataListeners();
0950: for (int i = 0; i < listeners.length; i++) {
0951: listeners[i].changesReverted(this );
0952: }
0953:
0954: }
0955:
0956: /**
0957: * <p>Return the resource bundle containing our localized messages.</p>
0958: */
0959: private ResourceBundle getBundle() {
0960:
0961: if (bundle == null) {
0962: bundle = ResourceBundle
0963: .getBundle("com/sun/data/provider/impl/Bundle");
0964: }
0965: return bundle;
0966:
0967: }
0968:
0969: /**
0970: * <p>The zero-args public constructor for the <code>objectType</code>
0971: * class (lazily instantiated by getObjectTypeConstructor(), marked
0972: * as transient because Constructor instances are not Serializable).</p>
0973: */
0974: private transient Constructor objectTypeConstructor = null;
0975:
0976: /**
0977: * <p>Return the zero-arguments public constructor for the class
0978: * specified by the <code>objectType</code> property, if there is one.</p>
0979: */
0980: private Constructor getObjectTypeConstructor() {
0981:
0982: if (objectTypeConstructor != null) {
0983: return objectTypeConstructor;
0984: }
0985: try {
0986: Constructor con = objectType.getConstructor(new Class[0]);
0987: if ((con != null) && Modifier.isPublic(con.getModifiers())) {
0988: objectTypeConstructor = con;
0989: }
0990: } catch (NoSuchMethodException e) {
0991: objectTypeConstructor = null;
0992: }
0993: return objectTypeConstructor;
0994:
0995: }
0996:
0997: /**
0998: * <p>Return the row index corresponding to the specified row key.</p>
0999: *
1000: * @param rowKey Row key for which to extract an index
1001: */
1002: private int getRowIndex(RowKey rowKey) {
1003:
1004: return ((IndexRowKey) rowKey).getIndex();
1005:
1006: }
1007:
1008: /**
1009: * <p>Return a suitable {@link RowKey} for the specified row index.</p>
1010: */
1011: private RowKey getRowKey(int index) {
1012:
1013: return new IndexRowKey(index);
1014:
1015: }
1016:
1017: /**
1018: * <p>The cached support object for field key manipulation. Must be
1019: * transient because its content is not Serializable.</p>
1020: */
1021: private transient ObjectFieldKeySupport support = null;
1022:
1023: /**
1024: * <p>Return the {@link ObjectFieldKeySupport} instance for the
1025: * object class we are wrapping.</p>
1026: */
1027: private ObjectFieldKeySupport getSupport() {
1028:
1029: if ((support == null) && (objectType != null)) {
1030: support = new ObjectFieldKeySupport(objectType,
1031: includeFields);
1032: }
1033: return support;
1034:
1035: }
1036:
1037: /**
1038: * <p>Calculate how many rows exist in the wrapped data.</p>
1039: */
1040: private int calculateRowCount() {
1041: int currentRowCount = 0;
1042: if (list != null) {
1043: currentRowCount = list.size() - deletes.size();
1044: }
1045: return currentRowCount;
1046: }
1047: }
|