1 /*
2 * $Id: DataModel.java,v 1.20 2007/04/27 22:00:09 ofung Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 package javax.faces.model;
42
43
44 import java.util.ArrayList;
45 import java.util.List;
46 import javax.faces.FacesException;
47 import javax.faces.component.UIData;
48
49
50 /**
51 * <p><strong>DataModel</strong> is an abstraction around arbitrary data
52 * binding technologies that can be used to adapt a variety of data sources
53 * for use by JavaServer Faces components that support per-row processing
54 * for their child components (such as {@link UIData}.</p>
55 *
56 * <p>The data collection underlying a {@link DataModel} instance is
57 * modeled as a collection of row objects that can be accessed by
58 * a zero-relative cursor (row index). The APIs provide mechanisms to
59 * position to a specified zero-relative row index, and to retrieve an
60 * object that represents the data that corresponds to the current
61 * row index.</p>
62 *
63 * <p>A concrete {@link DataModel} instance is attached to a particular
64 * collection of underlying data by calling the <code>setWrappedData()</code>
65 * method. It can be detached from that underlying data collection by
66 * passing a <code>null</code> parameter to this method.</p>
67 *
68 * <p>Concrete {@link DataModel} implementations must provide a public
69 * zero-arguments constructor that calls <code>setWrappedData(null)</code>.
70 * A convenience constructor that takes a wrapped object of the appropriate
71 * type (and passes it on via a call to <code>setWrappedData()</code>,
72 * should also be provided.</p>
73 *
74 * <p>Event listeners may be registered to receive notifications
75 * of when a new row index is selected.</p>
76 */
77
78 public abstract class DataModel {
79
80
81 private static final DataModelListener[] EMPTY_DATA_MODEL_LISTENER =
82 new DataModelListener[0];
83
84 // -------------------------------------------------------------- Properties
85
86
87 /**
88 * <p>Return a flag indicating whether there is <code>rowData</code>
89 * available at the current <code>rowIndex</code>. If no
90 * <code>wrappedData</code> is available, return <code>false</code>.</p>
91 *
92 * @throws FacesException if an error occurs getting the row availability
93 */
94 public abstract boolean isRowAvailable();
95
96
97 /**
98 * <p>Return the number of rows of data objects represented by this
99 * {@link DataModel}. If the number of rows is unknown, or no
100 * <code>wrappedData</code> is available, return -1.</p>
101 *
102 * @throws FacesException if an error occurs getting the row count
103 */
104 public abstract int getRowCount();
105
106
107 /**
108 * <p>Return an object representing the data for the currenty selected
109 * row index. If no <code>wrappedData</code> is available, return
110 * <code>null</code>.</p>
111 *
112 * @throws FacesException if an error occurs getting the row data
113 * @throws IllegalArgumentException if now row data is available
114 * at the currently specified row index
115 */
116 public abstract Object getRowData();
117
118
119 /**
120 * <p>Return the zero-relative index of the currently selected row. If
121 * we are not currently positioned on a row, or no <code>wrappedData</code>
122 * is available, return -1.</p>
123 *
124 * @throws FacesException if an error occurs getting the row index
125 */
126 public abstract int getRowIndex();
127
128
129 /**
130 * <p>Set the zero-relative index of the currently selected row, or -1
131 * to indicate that we are not positioned on a row. It is
132 * possible to set the row index at a value for which the underlying data
133 * collection does not contain any row data. Therefore, callers may
134 * use the <code>isRowAvailable()</code> method to detect whether row data
135 * will be available for use by the <code>getRowData()</code> method.</p>
136 *
137 * <p>If there is no <code>wrappedData</code> available when this method
138 * is called, the specified <code>rowIndex</code> is stored (and may be
139 * retrieved by a subsequent call to <code>getRowData()</code>), but no
140 * event is sent. Otherwise, if the currently selected row index is
141 * changed by this call, a {@link DataModelEvent} will be sent to the
142 * <code>rowSelected()</code> method of all registered
143 * {@link DataModelListener}s.</p>
144 *
145 * @param rowIndex The new zero-relative index (must be non-negative)
146 *
147 * @throws FacesException if an error occurs setting the row index
148 * @throws IllegalArgumentException if <code>rowIndex</code>
149 * is less than -1
150 */
151 public abstract void setRowIndex(int rowIndex);
152
153
154 /**
155 * <p>Return the object representing the data wrapped by this
156 * {@link DataModel}, if any.</p>
157 */
158 public abstract Object getWrappedData();
159
160
161 /**
162 * <p>Set the object representing the data collection wrapped by this
163 * {@link DataModel}. If the specified <code>data</code> is
164 * <code>null</code>, detach this {@link DataModel} from any previously
165 * wrapped data collection instead.</p>
166 *
167 * <p>If <code>data</code> is non-<code>null</code>, the currently selected
168 * row index must be set to zero, and a {@link DataModelEvent} must be sent
169 * to the <code>rowSelected()</code> method of all registered
170 * {@link DataModelListener}s indicating that this row is now selected.</p>
171 *
172 * @param data Data collection to be wrapped, or <code>null</code> to
173 * detach from any previous data collection
174 *
175 * @throws ClassCastException if <code>data</code> is not of the
176 * appropriate type for this {@link DataModel} implementation
177 */
178 public abstract void setWrappedData(Object data);
179
180
181
182 // ------------------------------------------------------ Instance Variables
183
184
185 /**
186 * <p>The list of registered {@link DataModelListener}s for this
187 * {@link DataModel}. This variable will be <code>null</code> unless
188 * there is at least one registered listener.</p>
189 */
190 private List<DataModelListener> listeners = null;
191
192
193 // --------------------------------------------- Event Listener Registration
194
195
196 /**
197 * <p>Add a new {@link DataModelListener} to the set interested in
198 * notifications from this {@link DataModel}.</p>
199 *
200 * @param listener The new {@link DataModelListener} to be registered
201 *
202 * @throws NullPointerException if <code>listener</code>
203 * is <code>null</code>
204 */
205 public void addDataModelListener(DataModelListener listener) {
206
207 if (listener == null) {
208 throw new NullPointerException();
209 }
210 if (listeners == null) {
211 //noinspection CollectionWithoutInitialCapacity
212 listeners = new ArrayList<DataModelListener>();
213 }
214 listeners.add(listener);
215
216 }
217
218
219 /**
220 * <p>Return the set of {@link DataModelListener}s interested in
221 * notifications from this {@link DataModel}. If there are no such
222 * listeners, an empty array is returned.</p>
223 */
224 public DataModelListener[] getDataModelListeners() {
225
226 if (listeners == null) {
227 return EMPTY_DATA_MODEL_LISTENER;
228 } else {
229 return listeners.toArray
230 (new DataModelListener[listeners.size()]);
231 }
232
233 }
234
235
236 /**
237 * <p>Remove an existing {@link DataModelListener} from the set
238 * interested in notifications from this {@link DataModel}.</p>
239 *
240 * @param listener The old {@link DataModelListener} to be deregistered
241 *
242 * @throws NullPointerException if <code>listener</code>
243 * is <code>null</code>
244 */
245 public void removeDataModelListener(DataModelListener listener) {
246
247 if (listener == null) {
248 throw new NullPointerException();
249 }
250 if (listeners != null) {
251 listeners.remove(listener);
252 if (listeners.isEmpty()) {
253 listeners = null;
254 }
255 }
256
257 }
258
259
260 }