com.sun.jsftemplating.component.dataprovider.MultipleListDataProvider Maven / Gradle / Ivy
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* https://glassfish.dev.java.net/public/CDDLv1.0.html or
* glassfish/bootstrap/legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at glassfish/bootstrap/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.jsftemplating.component.dataprovider;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import com.sun.data.provider.DataProviderException;
import com.sun.data.provider.FieldKey;
import com.sun.data.provider.RowKey;
import com.sun.data.provider.TransactionalDataListener;
import com.sun.data.provider.impl.IndexRowKey;
import com.sun.data.provider.impl.ObjectFieldKeySupport;
import com.sun.data.provider.impl.ObjectListDataProvider;
/**
* This implementation allows for multiple List
objects to
* be used to represent table rows. If mulitiple List
s are
* used, they must be parallel lists (i.e. have the same # and order of
* information).
*/
public class MultipleListDataProvider extends ObjectListDataProvider {
private static final long serialVersionUID = 1L;
/**
* Default Constructor.
*/
public MultipleListDataProvider() {
setIncludeFields(false);
}
/**
* Constructor that creates initializes the DataProvider with a
* single List
. Fields are not included by
* default.
*
* @param list Primary list containing row information.
*/
public MultipleListDataProvider(List> list) {
this(list, false);
}
/**
* Constructor that creates initializes the DataProvider with a
* single List
. Fields are included if
* includeFields
is true.
*
* @param lists List<List<Object>> to be
* wrapped.
* @param includeFields Desired include fields property setting
*/
public MultipleListDataProvider(List> lists, boolean includeFields) {
// Set the lists
setLists(lists);
// Do not include fields
setIncludeFields(false);
}
/**
* Constructor for an empty DataProvider
with a known
* type. This constructor is only useful when there is a single
* List
. Fields are not included.
*
* @param objectTypes Desired object type Classes.
*/
public MultipleListDataProvider(Class [] objectTypes) {
this(objectTypes, false);
}
/**
* Constructor for an empty DataProvider
with a known
* type. This constructor is only useful when there is a single
* List
. Fields are included if
* includeFields
is true.
*
* @param objTypes Desired object type of Classes
* @param includeFields Desired include fields property setting
*/
public MultipleListDataProvider(Class [] objTypes, boolean includeFields) {
setObjectTypes(objTypes);
setIncludeFields(includeFields);
}
// FIXME: Provide apis to manage multiple lists:
// FIXME: addList(String, List)
// FIXME: removeList(String)??
/**
* This method returns an array of Object
* (Object [])
for the given row
. Each
* array element cooresponds to an Object
in one of the
* List
s represented by this instance of
* MultipleListDataProvider
.
*
* @throws IndexOutOfBoundsException If row
is invalid.
*/
public Object getObject(RowKey row) {
if (!isRowAvailable(row)) {
throw new IndexOutOfBoundsException("" + row);
}
int rowIdx = getRowIndex(row);
List> lists = getLists();
Object [] result = new Object[lists.size()];
int idx = 0;
for (List list : lists) {
result[idx++] = list.get(rowIdx);
}
return result;
}
/**
* This method returns an array of array ofObject
s
* (Object [][])
. The first element of the array is the
* row, the second cooresponds to the List
:
* Object[row #][list #]
. If there is only one
* List
represented by this instance of
* MultipleListDataProvider
, then the list # will be 0;
* if there are 2 List
s, then the list # will be 0 or 1,
* etc.
*/
public Object [] getObjects() {
// Get the array demensions
List> lists = getLists();
int numLists = lists.size();
if (numLists == 0) {
// This isn't likely, but just in case...
return new Object[0][0];
}
int numRows = lists.get(0).size();
Object [][] result = new Object[numRows][numLists];
// Fill the array
int listNum = 0;
for (int rowNum = 0; rowNum < numRows; rowNum++) {
listNum = 0;
for (List list : lists) {
result[rowNum][listNum++] = list.get(rowNum);
}
}
// Return the result (Object[rows][lists])
return result;
}
/**
* This sets the Object type contained in the
* List
that this MulitpleObjectDataProvider
* represents. This method should only be used when there is one
* List
held by this
* MultipleListDataProvider
. In cases where you have
* more than one list (and likely you do because you are using this
* DataProvider
), you should use
* {@link #setObjectTypes(Class [])}.
*/
public void setObjectType(Class objectType) {
setObjectTypes(new Class [] {objectType});
}
/**
* This method sets the Object types for each List
that
* is represented by this instance of this class. By doing this, you
* allow the FieldKeys to be generated. You only need to specify this
* information if you have no rows. If there is some data, that data
* will be used to determine the type.
*
* @param objectTypes The Class
types of the row data.
*/
public void setObjectTypes(Class [] objectTypes) {
_types = objectTypes;
}
/**
* Not supported. This method does not appear to have great value.
*/
public void removeObject(Object object) {
throw new UnsupportedOperationException();
}
/**
* Not yet supported.
*
* @param row The RowKey
of the row to check.
*/
public boolean isRemoved(RowKey row) {
return _deletes.contains(row);
}
/**
* Replace the object at the specified row.
*
* @param row The desired row to set the contained object
* @param object The new object to set at the specified row
*/
public void setObject(RowKey row, Object object) {
throw new UnsupportedOperationException(this.getClass().getName()
+ " does not support the setObject(RowKey, Object) method. "
+ "Instead use setObjects(RowKey, Object []).");
}
/**
* This method allows the given row
to be replaced with
* the given objects
.
*
* @param row The row to replace.
* @param objects The array of Objects
to use.
*/
public void setObjects(RowKey row, Object [] objects) {
Object [] previous = (Object []) getObject(row);
int rowNum = getRowIndex(row);
int cnt = 0;
for (List list : getLists()) {
list.set(rowNum, objects[cnt++]);
}
fireValueChanged(null, row, previous, objects);
if (getCursorRow() == row) {
fireValueChanged(null, previous, objects);
}
}
/**
* Not yet supported...
*/
public void addObject(Object object) {
// FIXME: Add api to add the Object*s* (see addObject(object), must have 1 for each List
throw new UnsupportedOperationException();
}
// ---------------------------------------------------- DataProvider Methods
/**
*
*/
public FieldKey getFieldKey(String fieldId) throws DataProviderException {
return getSupport().getFieldKey(fieldId);
}
/**
*
*/
public FieldKey [] getFieldKeys() throws DataProviderException {
return getSupport().getFieldKeys();
}
/**
*
*/
public Class getType(FieldKey fieldKey) throws DataProviderException {
return getSupport().getType(fieldKey);
}
/**
*
*/
public boolean isReadOnly(FieldKey fieldKey) throws DataProviderException {
return getSupport().isReadOnly(fieldKey);
}
// FIXME: The following method is very difficult to implement correctly b/c
// FIXME: the super class uses its own private "getSupport()" method which will
// FIXME: return the wrong "support" class. However, the superclass is also
// FIXME: managing the "updates" so we must call it... Ask Creator group to
// FIXME: properly expose "support" via an interface and get/setSupport()
// FIXME: methods. Or ask them to provide access to the updates/deletes/etc.
// FIXME: properties.
//
// FIXME: OK... it appears that they ObjectListDataProvider does not utilize the FieldKey methods in its superclass and instead maintains a private list via its support class. This means I cannot do anything to make this method work with updates!!!
/**
*
*/
public Object getValue(FieldKey fieldKey, RowKey rowKey) throws DataProviderException {
Object val = null;
try {
// This will try to get the value, but is unlikely to succeed...
val = super.getValue(fieldKey, rowKey);
} catch(Exception ex) {
// Now check the "right" support class...
if (getSupport().getFieldKey(fieldKey.getFieldId()) == null) {
throw new IllegalArgumentException("" + fieldKey);
}
// Make sure it's a valid row key...
if (!isRowAvailable(rowKey)) {
throw new IndexOutOfBoundsException("" + rowKey);
}
int index = getRowIndex(rowKey);
if (index < getRowCount()) {
val = getSupport().getValue(
fieldKey, getListForFieldKey(fieldKey).get(index));
} else {
// FIXME: Need to manually implement appends, updates, and deletes!! :(
// return getSupport().getValue(fieldKey, appends.get(index - getRowCount()));
}
}
return val;
}
/**
*
*/
public void setValue(FieldKey fieldKey, RowKey rowKey, Object value) throws DataProviderException {
if (getSupport().getFieldKey(fieldKey.getFieldId()) == null) {
throw new IllegalArgumentException("" + fieldKey);
}
if (getSupport().isReadOnly(fieldKey)) {
throw new IllegalStateException("" + fieldKey);
}
if (!isRowAvailable(rowKey)) {
throw new IndexOutOfBoundsException("" + rowKey);
}
// Retrieve the previous value and determine if it has changed
Object previous = getValue(fieldKey, rowKey);
if (((previous == null) && (value == null)) ||
((previous != null) && (value != null) && previous.equals(value))) {
return; // No change
}
// Verify type compatibility of the proposed new value
if (!getSupport().isAssignable(fieldKey, value)) {
throw new IllegalArgumentException(fieldKey + " = " + value); // NOI18N
}
// FIXME: Need to explicitly support updates (can't get via polymorphism due to bad design)! :(
/*
// Record a pending change for this row and field
Map fieldUpdates = (Map) updates.get(rowKey);
if (fieldUpdates == null) {
fieldUpdates = new HashMap();
updates.put(rowKey, fieldUpdates);
}
fieldUpdates.put(fieldKey, value);
*/
//Remove the following set line and defer until commit() once updates/deletes/etc. is worked out
int index = getRowIndex(rowKey);
getSupport().setValue(
fieldKey, getListForFieldKey(fieldKey).get(index), value);
fireValueChanged(fieldKey, rowKey, previous, value);
fireValueChanged(fieldKey, previous, value);
}
/**
* Construct new instances for each List
represented by
* this class and appended them to each List
.
*/
public RowKey appendRow() throws DataProviderException {
// FIXME: Support this!
throw new UnsupportedOperationException();
}
/**
*
This method returns true
if rows may be appended.
*/
public boolean canAppendRow() throws DataProviderException {
return (isUserResizable() && (getObjectTypes() != null));
}
/**
* This method is not supported.
*
* @param object Object to be appended.
*/
public RowKey appendRow(Object object) throws DataProviderException {
// FIXME: Support this!
throw new UnsupportedOperationException();
}
/**
* Remove the object at the specified row from the list.
*
* {@inheritDoc}
*/
public void removeRow(RowKey rowKey) throws DataProviderException {
// Verify we can actually remove this row
if (!canRemoveRow(rowKey)) {
// FIXME: I18N
throw new IllegalStateException(
"This ObjectListDataProvider is not resizable.");
}
if (!isRowAvailable(rowKey)) {
// FIXME: I18N
throw new IllegalArgumentException(
"Cannot delete row for row key " + rowKey);
}
// Record the fact that we are going to delete this row
_deletes.add(rowKey);
// Fire appropriate events regarding this deletion
fireRowRemoved(rowKey);
if (getCursorRow() == rowKey) {
fireValueChanged(null, getObject(rowKey), null);
}
}
/**
* This method returns the # of rows represented by this
* MultipleListDataProvider
.
*/
public int getRowCount() throws DataProviderException {
List> lists = getLists();
int count = 0;
if ((lists != null) && (lists.size() != 0)) {
count = lists.get(0).size();
}
return count;
}
/**
* Return true
if the specified RowKey
* represents a row in the original list, or a row that has been
* appended.
*
* @param row RowKey
to test for availability.
*/
public boolean isRowAvailable(RowKey row) throws DataProviderException {
int idx = getRowIndex(row);
if (idx < 0) {
return false;
}
if (idx < (getRowCount() /* + appendCount */)) {
return true;
}
return false;
}
// --------------------------------------- TransactionalDataProvider Methods
/**
* Cause any cached updates to existing field values, as well as
* inserted and deleted rows, to be flowed through to the underlying
* List
s wrapped by this DataProvider
.
*/
public void commitChanges() throws DataProviderException {
// FIXME: Do updates here... (see super())
// Commit pending deletes
// Iterate backwards so that we correctly modify List
RowKey deletes[] = (RowKey[])
_deletes.toArray(new RowKey[_deletes.size()]);
int rowIdx = -1;
for (int idx = (deletes.length - 1); idx >= 0; idx--) {
rowIdx = getRowIndex(deletes[idx]);
for (List list : getLists()) {
list.remove(rowIdx);
}
}
_deletes.clear();
// FIXME: Deal w/ appends (see super())
// Notify interested listeners that we have committed
fireChangesCommitted();
}
/**
* Fire a changesCommitted
method to all registered
* listeners.
*/
protected void fireChangesCommitted() {
// FIXME: This method overrides a method by the same name in the superclass that is private! The super() should make this protected.
TransactionalDataListener listeners[] = getTransactionalDataListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].changesCommitted(this);
}
}
/**
* Fire a changesReverted
method to all registered
* listeners.
*/
private void fireChangesReverted() {
// FIXME: This method overrides a method by the same name in the superclass that is private! The super() should make this protected.
TransactionalDataListener listeners[] = getTransactionalDataListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].changesReverted(this);
}
}
/**
*
*/
public void revertChanges() throws DataProviderException {
// FIXME: Do updates here... (see super())
//updates.clear();
_deletes.clear();
// FIXME: Deal w/ appends
//appends.clear();
// Notify interested listeners that we are reverting
fireChangesReverted();
}
/**
* Accessor for the List
of List
s.
*/
public List> getLists() {
return _lists;
}
/**
* Setter for the List
of List
s.
*/
public void setLists(List> lists) {
_lists = lists;
}
/**
* This method returns
*/
public List getListForFieldKey(FieldKey key) {
if ((key != null) && !(key instanceof IndexFieldKey)) {
key = support.getFieldKey(key.getFieldId());
}
if (key == null) {
throw new IllegalArgumentException("Invalid FieldKey: " + key);
}
return getLists().get(((IndexFieldKey) key).getIndex());
}
/**
*
The cached support object for field key manipulation. Must be
* transient because its content is not Serializable.
*/
private transient MultipleObjectFieldKeySupport support = null;
/**
* Return the {@link ObjectFieldKeySupport} instance for the object
* class we are wrapping.
*/
private MultipleObjectFieldKeySupport getSupport() {
if (support == null) {
// Try to get first element of the list to help find FieldKeys
Object [] objs = (Object []) getObject(getRowKey(0));
if ((objs != null) && (objs.length > 0)) {
support = new MultipleObjectFieldKeySupport(
objs, isIncludeFields());
}
// else {
// FIXME: Add ability to use other meta information (i.e. _types and/or special class to describe info (i.e. for Maps)) to do this.
// }
}
return support;
}
/**
* This method converts the given rowKey
to an
* int
.
*/
protected int getRowIndex(RowKey rowKey) {
// FIXME: This method overrides a private method by the same name... that method shouldn't be private!
if (rowKey instanceof IndexRowKey) {
return ((IndexRowKey) rowKey).getIndex();
}
return -1;
}
/**
* This method converts the given int
to a
* RowKey
.
*/
protected RowKey getRowKey(int index) {
return new IndexRowKey(index);
}
////////////////////////////////////////////////////////////////////////
// Unsupported Methods
////////////////////////////////////////////////////////////////////////
/**
* This method is not supported.
*/
public Class getObjectType() {
throw new UnsupportedOperationException(this.getClass().getName()
+ " does not support the getObjectType() method because it "
+ "must return multiple types. Please use \"Class [] "
+ "getObjectTypes()\" instead.");
}
/**
* This method returns an array of Class []
representing
* the Object
types represented by this instance of this
* class.
*/
public Class [] getObjectTypes() {
return _types;
}
/**
* Storage for our List of Lists.
*/
private List> _lists = new ArrayList>();
/**
* Type information for the LIsts.
*/
private Class [] _types = null;
/**
* Set of {@link RowKey}s marked to be deleted. An
* Iterator
over this set will return the corresponding
* {@link RowKey}s in ascending order.
*/
protected Set _deletes = new TreeSet();
}