jakarta.faces.model.DataModel Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.faces.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import jakarta.faces.component.UIData;
/**
*
* DataModel is an abstraction around arbitrary data binding
* technologies that can be used to adapt a variety of data sources for use by Jakarta Faces components that
* support per-row processing for their child components (such as {@link UIData}.
*
*
*
* The data collection underlying a {@link DataModel} instance is modeled as a collection of row objects that can be
* accessed by a zero-relative cursor (row index). The APIs provide mechanisms to position to a specified zero-relative
* row index, and to retrieve an object that represents the data that corresponds to the current row index.
*
*
*
* A concrete {@link DataModel} instance is attached to a particular collection of underlying data by calling the
* setWrappedData()
method. It can be detached from that underlying data collection by passing a
* null
parameter to this method.
*
*
*
* Concrete {@link DataModel} implementations must provide a public zero-arguments constructor that calls
* setWrappedData(null)
. A convenience constructor that takes a wrapped object of the appropriate type (and
* passes it on via a call to setWrappedData()
, should also be provided.
*
*
*
* Event listeners may be registered to receive notifications of when a new row index is selected.
*
*/
public abstract class DataModel implements Iterable {
private static final DataModelListener[] EMPTY_DATA_MODEL_LISTENER = new DataModelListener[0];
// -------------------------------------------------------------- Properties
/**
*
* Return a flag indicating whether there is rowData
available at the current rowIndex
. If no
* wrappedData
is available, return false
.
*
*
* @throws jakarta.faces.FacesException if an error occurs getting the row availability
*
* @return true if and only if there is data available at the current index, false otherwise.
*/
public abstract boolean isRowAvailable();
/**
*
* Return the number of rows of data objects represented by this {@link DataModel}. If the number of rows is unknown, or
* no wrappedData
is available, return -1.
*
*
* @throws jakarta.faces.FacesException if an error occurs getting the row count
*
* @return the number of rows of data represented by this {@code DataModel}
*/
public abstract int getRowCount();
/**
*
* Return an object representing the data for the currently selected row index. If no wrappedData
is
* available, return null
.
*
*
* @throws jakarta.faces.FacesException if an error occurs getting the row data
* @throws IllegalArgumentException if now row data is available at the currently specified row index
*
* @return an object representing the data for the currently selected row index
*/
public abstract E getRowData();
/**
*
* Return the zero-relative index of the currently selected row. If we are not currently positioned on a row, or no
* wrappedData
is available, return -1.
*
*
* @throws jakarta.faces.FacesException if an error occurs getting the row index
*
* @return the index of the currently selected row
*/
public abstract int getRowIndex();
/**
*
* Set the zero-relative index of the currently selected row, or -1 to indicate that we are not positioned on a row. It
* is possible to set the row index at a value for which the underlying data collection does not contain any row data.
* Therefore, callers may use the isRowAvailable()
method to detect whether row data will be available for
* use by the getRowData()
method.
*
*
*
* If there is no wrappedData
available when this method is called, the specified rowIndex
is
* stored (and may be retrieved by a subsequent call to getRowData()
), but no event is sent. Otherwise, if
* the currently selected row index is changed by this call, a {@link DataModelEvent} will be sent to the
* rowSelected()
method of all registered {@link DataModelListener}s.
*
*
* @param rowIndex The new zero-relative index (must be non-negative)
*
* @throws jakarta.faces.FacesException if an error occurs setting the row index
* @throws IllegalArgumentException if rowIndex
is less than -1
*/
public abstract void setRowIndex(int rowIndex);
/**
*
* Return the object representing the data wrapped by this {@link DataModel}, if any.
*
*
* @return the {@code Object} that this model wraps.
*/
public abstract Object getWrappedData();
/**
*
* Set the object representing the data collection wrapped by this {@link DataModel}. If the specified data
* is null
, detach this {@link DataModel} from any previously wrapped data collection instead.
*
*
*
* If data
is non-null
, the currently selected row index must be set to zero, and a
* {@link DataModelEvent} must be sent to the rowSelected()
method of all registered
* {@link DataModelListener}s indicating that this row is now selected.
*
*
* @param data Data collection to be wrapped, or null
to detach from any previous data collection
*
* @throws ClassCastException if data
is not of the appropriate type for this {@link DataModel}
* implementation
*/
public abstract void setWrappedData(Object data);
// ------------------------------------------------------ Instance Variables
/**
*
* The list of registered {@link DataModelListener}s for this {@link DataModel}. This variable will be null
* unless there is at least one registered listener.
*
*/
private List listeners = null;
// --------------------------------------------- Event Listener Registration
/**
*
* Add a new {@link DataModelListener} to the set interested in notifications from this {@link DataModel}.
*
*
* @param listener The new {@link DataModelListener} to be registered
*
* @throws NullPointerException if listener
is null
*/
public void addDataModelListener(DataModelListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners == null) {
// noinspection CollectionWithoutInitialCapacity
listeners = new ArrayList<>();
}
listeners.add(listener);
}
/**
*
* Return the set of {@link DataModelListener}s interested in notifications from this {@link DataModel}. If there are no
* such listeners, an empty array is returned.
*
*
* @return the listeners for this instance, or an empty array
*/
public DataModelListener[] getDataModelListeners() {
if (listeners == null) {
return EMPTY_DATA_MODEL_LISTENER;
} else {
return listeners.toArray(new DataModelListener[listeners.size()]);
}
}
/**
*
* Remove an existing {@link DataModelListener} from the set interested in notifications from this {@link DataModel}.
*
*
* @param listener The old {@link DataModelListener} to be deregistered
*
* @throws NullPointerException if listener
is null
*/
public void removeDataModelListener(DataModelListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty()) {
listeners = null;
}
}
}
/**
*
* Return a read-only Iterator
over the row data for this model.
*
*
* @since 2.0
*/
@Override
public Iterator iterator() {
return new DataModelIterator<>(this);
}
// ---------------------------------------------------------- Nested Classes
@SuppressWarnings({ "unchecked" })
private static final class DataModelIterator implements Iterator {
private DataModel model;
private int index;
// -------------------------------------------------------- Constructors
DataModelIterator(DataModel model) {
this.model = model;
this.model.setRowIndex(index);
}
// ----------------------------------------------- Methods from Iterator
/**
* @see java.util.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
return model.isRowAvailable();
}
/**
* @see java.util.Iterator#next()
*/
@Override
public T next() {
if (!model.isRowAvailable()) {
throw new NoSuchElementException();
}
Object o = model.getRowData();
model.setRowIndex(++index);
return (T) o;
}
/**
* Unsupported.
*
* @see java.util.Iterator#remove()
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
} // END DataModelIterator
}