blob: b616da01d2a414da3d1c3331b05f2cb48b259b81 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package javax.faces.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Represents the data presented by a UIData component, together with
* some state information about the currently selected row within the
* datalist for use by listeners on UIData components. This class allows
* managed bean code to avoid binding directly to UIData components for
* typical uses.
* <p>
* Note that DataModel and its standard subclasses are not serializable,
* as there is no state in a DataModel object itself that needs to be
* preserved between render and restore-view. UIData components therefore
* do not store their DataModel when serialized; they just evaluate their
* "value" EL expression to refetch the object during the
* apply-request-values phase.
* <p>
* Because DataModel is not serializable, any managed bean that needs to
* be serialized and which has a member of type DataModel should therefore
* mark that member transient. If there is a need to preserve the datalist
* contained within the DataModel then ensure a reference to that list is
* stored in a non-transient member, or use a custom serialization method
* that explicitly serializes dataModel.getWrappedData.
*
* See Javadoc of
* <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a> for more.
*/
public abstract class DataModel<E> implements Iterable<E>
{
private final static DataModelListener[] EMPTY_DATA_MODEL_LISTENER = new DataModelListener[]{};
private List<DataModelListener> _listeners;
private DataModelListener[] _cachedListenersArray = null;
public void addDataModelListener(DataModelListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (_listeners == null)
{
_listeners = new ArrayList<>();
}
_listeners.add(listener);
_cachedListenersArray = null;
}
public DataModelListener[] getDataModelListeners()
{
if (_listeners == null)
{
return EMPTY_DATA_MODEL_LISTENER;
}
if (_cachedListenersArray == null)
{
_cachedListenersArray = _listeners.toArray(new DataModelListener[_listeners.size()]);
}
return _cachedListenersArray;
}
/**
* <p>
* Return the number of rows of data available.
* </p>
* <p>
* If the number of rows of data available is not known then -1 is returned.
* This may happen for DataModels that wrap sources of data such as
* java.sql.ResultSet that provide an iterator to access the "next item"
* rather than a fixed-size collection of data.
* </p>
*
* @return the number of rows available.
*/
abstract public int getRowCount();
/**
* Return the object associated with the current row index.
* <p>
* Method isRowAvailable may be called before attempting to access
* this method, to ensure that the data is available.
*
* @return The object associated with the current row index.
* @throws RuntimeException subclass of some kind if the current row index
* is not within the range of the current wrappedData property.
*/
abstract public E getRowData();
/**
* Get the current row index.
* @return The current row index.
*/
abstract public int getRowIndex();
/**
* Get the entire collection of data associated with this component. Note that
* the actual type of the returned object depends upon the concrete
* subclass of DataModel; the object will represent an "ordered sequence
* of components", but may be implemented as an array, java.util.List,
* java.sql.ResultSet or other similar types.
*
* @return the wrapped object.
*/
abstract public Object getWrappedData();
/**
* Returns true if a call to getRowData will return a valid object.
* @return true if a call to getRowData will return a valid object. false otherwise.
*/
abstract public boolean isRowAvailable();
/**
* {@inheritDoc}
*
* @since 2.0
*/
@Override
public Iterator<E> iterator()
{
return new DataModelIterator();
}
public void removeDataModelListener(DataModelListener listener)
{
if (listener == null)
{
throw new NullPointerException("listener");
}
if (_listeners != null)
{
_listeners.remove(listener);
}
_cachedListenersArray = null;
}
/**
* Set the current row index. This affects the behaviour of the
* getRowData method in particular.
*
* @param rowIndex The row index. It may be -1 to indicate "no row",
* or may be a value between 0 and getRowCount()-1.
*/
abstract public void setRowIndex(int rowIndex);
/**
* Set the entire list of data associated with this component. Note that
* the actual type of the provided object must match the expectations
* of the concrete subclass of DataModel. See getWrappedData.
*
* @param data The object to be wrapped.
*/
abstract public void setWrappedData(Object data);
private class DataModelIterator implements Iterator<E>
{
private int nextRowIndex = 0;
public DataModelIterator()
{
setRowIndex(nextRowIndex);
}
@Override
public boolean hasNext()
{
//row count could be unknown, like in ResultSetDataModel
return isRowAvailable();
}
@Override
public E next()
{
if (!hasNext())
{
throw new NoSuchElementException("Couldn't find any element in DataModel at index " + nextRowIndex);
}
E data = (E) getRowData();
nextRowIndex++;
setRowIndex(nextRowIndex);
return data;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
}