1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can obtain
10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12 * language governing permissions and limitations under the License.
13 *
14 * When distributing the software, include this License Header Notice in each
15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16 * Sun designates this particular file as subject to the "Classpath" exception
17 * as provided by Sun in the GPL Version 2 section of the License file that
18 * accompanied this code. If applicable, add the following below the License
19 * Header, with the fields enclosed by brackets [] replaced by your own
20 * identifying information: "Portions Copyrighted [year]
21 * [name of copyright owner]"
22 *
23 * Contributor(s):
24 *
25 * If you wish your version of this file to be governed by only the CDDL or
26 * only the GPL Version 2, indicate your decision by adding "[Contributor]
27 * elects to include this software in this distribution under the [CDDL or GPL
28 * Version 2] license." If you don't indicate a single choice of license, a
29 * recipient has the option to distribute your version of this file under
30 * either the CDDL, the GPL Version 2 or to extend the choice of license to
31 * its licensees as provided above. However, if you add GPL Version 2 code
32 * and therefore, elected the GPL Version 2 license, then the option applies
33 * only if the new code is made subject to such option by the copyright
34 * holder.
35 */
36
37 package javax.faces.component;
38
39 import javax.el.ELException;
40 import javax.el.ValueExpression;
41 import javax.faces.FacesException;
42 import javax.faces.application.FacesMessage;
43 import javax.faces.context.FacesContext;
44 import javax.faces.el.ValueBinding;
45 import javax.faces.event.AbortProcessingException;
46 import javax.faces.event.FacesEvent;
47 import javax.faces.event.FacesListener;
48 import javax.faces.event.PhaseId;
49 import javax.faces.model.ArrayDataModel;
50 import javax.faces.model.DataModel;
51 import javax.faces.model.ListDataModel;
52 import javax.faces.model.ResultDataModel;
53 import javax.faces.model.ResultSetDataModel;
54 import javax.faces.model.ScalarDataModel;
55 import javax.servlet.jsp.jstl.sql.Result;
56
57 import java.io.IOException;
58 import java.io.Serializable;
59 import java.sql.ResultSet;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Iterator;
65
66
67 /**
68 * <p><strong>UIData</strong> is a {@link UIComponent} that supports data
69 * binding to a collection of data objects represented by a {@link DataModel}
70 * instance, which is the current value of this component itself (typically
71 * established via a {@link ValueExpression}). During iterative processing over
72 * the rows of data in the data model, the object for the current row is exposed
73 * as a request attribute under the key specified by the <code>var</code>
74 * property.</p>
75 * <p/>
76 * <p>Only children of type {@link UIColumn} should be processed by renderers
77 * associated with this component.</p>
78 * <p/>
79 * <p>By default, the <code>rendererType</code> property is set to
80 * <code>javax.faces.Table</code>. This value can be changed by calling the
81 * <code>setRendererType()</code> method.</p>
82 */
83
84 public class UIData extends UIComponentBase
85 implements NamingContainer {
86
87 // ------------------------------------------------------ Manifest Constants
88
89
90 /**
91 * <p>The standard component type for this component.</p>
92 */
93 public static final String COMPONENT_TYPE = "javax.faces.Data";
94
95
96 /**
97 * <p>The standard component family for this component.</p>
98 */
99 public static final String COMPONENT_FAMILY = "javax.faces.Data";
100
101 // ------------------------------------------------------------ Constructors
102
103
104 /**
105 * <p>Create a new {@link UIData} instance with default property
106 * values.</p>
107 */
108 public UIData() {
109
110 super();
111 setRendererType("javax.faces.Table");
112
113 }
114
115 // ------------------------------------------------------ Instance Variables
116
117
118 /**
119 * <p>The first row number (zero-relative) to be displayed.</p>
120 */
121 private Integer first;
122
123
124 /**
125 * <p>The {@link DataModel} associated with this component, lazily
126 * instantiated if requested. This object is not part of the saved and
127 * restored state of the component.</p>
128 */
129 private DataModel model = null;
130
131
132 /**
133 * <p> During iteration through the rows of this table, This ivar is used to
134 * store the previous "var" value for this instance. When the row iteration
135 * is complete, this value is restored to the request map.
136 */
137 private Object oldVar;
138
139
140 /**
141 * <p>The zero-relative index of the current row number, or -1 for no
142 * current row association.</p>
143 */
144 private int rowIndex = -1;
145
146
147 /**
148 * <p>The number of rows to display, or zero for all remaining rows in the
149 * table.</p>
150 */
151 private Integer rows;
152
153
154 /**
155 * <p>This map contains <code>SavedState</code> instances for each
156 * descendant component, keyed by the client identifier of the descendant.
157 * Because descendant client identifiers will contain the
158 * <code>rowIndex</code> value of the parent, per-row state information is
159 * actually preserved.</p>
160 */
161 @SuppressWarnings({"CollectionWithoutInitialCapacity"})
162 private Map<String, SavedState> saved = new HashMap<String, SavedState>();
163
164
165 /**
166 * <p>The local value of this {@link UIComponent}.</p>
167 */
168 private Object value = null;
169
170
171 /**
172 * <p>The request scope attribute under which the data object for the
173 * current row will be exposed when iterating.</p>
174 */
175 private String var = null;
176
177
178 /**
179 * <p>Holds the base client ID that will be used to generate per-row
180 * client IDs (this will be null if this UIData is nested within another).</p>
181 *
182 * <p>This is not part of the component state.</p>
183 */
184 private String baseClientId = null;
185
186
187 /**
188 * <p> Length of the cached <code>baseClientId</code> plus one for the
189 * NamingContainer.SEPARATOR_CHAR. </p>
190 *
191 * <p>This is not part of the component state.</p>
192 */
193 private int baseClientIdLength;
194
195
196 /**
197 * <p>StringBuilder used to build per-row client IDs.</p>
198 *
199 * <p>This is not part of the component state.</p>
200 */
201 private StringBuilder clientIdBuilder = null;
202
203
204 /**
205 * <p>Flag indicating whether or not this UIData instance is nested
206 * within another UIData instance</p>
207 *
208 * <p>This is not part of the component state.</p>
209 */
210 private Boolean isNested = null;
211
212
213 // -------------------------------------------------------------- Properties
214
215
216 public String getFamily() {
217
218 return (COMPONENT_FAMILY);
219
220 }
221
222
223 /**
224 * <p>Return the zero-relative row number of the first row to be
225 * displayed.</p>
226 */
227 public int getFirst() {
228
229 if (this.first != null) {
230 return (this.first);
231 }
232 ValueExpression ve = getValueExpression("first");
233 if (ve != null) {
234 Integer value;
235 try {
236 value = (Integer) ve.getValue(getFacesContext().getELContext());
237 }
238 catch (ELException e) {
239 throw new FacesException(e);
240 }
241 if (null == value) {
242 return first;
243 }
244 return (value.intValue());
245 } else {
246 return (0);
247 }
248
249 }
250
251
252 /**
253 * <p>Set the zero-relative row number of the first row to be
254 * displayed.</p>
255 *
256 * @param first New first row number
257 *
258 * @throws IllegalArgumentException if <code>first</code> is negative
259 */
260 public void setFirst(int first) {
261
262 if (first < 0) {
263 throw new IllegalArgumentException(String.valueOf(first));
264 }
265 this.first = first;
266
267 }
268
269
270 /**
271 * <p>Return the footer facet of this component (if any). A convenience
272 * method for <code>getFacet("footer")</code>.</p>
273 */
274 public UIComponent getFooter() {
275
276 return getFacet("footer");
277
278 }
279
280
281 /**
282 * <p>Set the footer facet of this component. A convenience method for
283 * <code>getFacets().put("footer", footer)</code>.</p>
284 *
285 * @param footer the new footer facet
286 *
287 * @throws NullPointerException if <code>footer</code> is <code>null</code>
288 */
289 public void setFooter(UIComponent footer) {
290
291 getFacets().put("footer", footer);
292
293 }
294
295
296 /**
297 * <p>Return the header facet of this component (if any). A convenience
298 * method for <code>getFacet("header")</code>.</p>
299 */
300 public UIComponent getHeader() {
301
302 return getFacet("header");
303
304 }
305
306
307 /**
308 * <p>Set the header facet of this component. A convenience method for
309 * <code>getFacets().put("header", header)</code>.</p>
310 *
311 * @param header the new header facet
312 *
313 * @throws NullPointerException if <code>header</code> is <code>null</code>
314 */
315 public void setHeader(UIComponent header) {
316
317 getFacets().put("header", header);
318
319 }
320
321
322 /**
323 * <p>Return a flag indicating whether there is <code>rowData</code>
324 * available at the current <code>rowIndex</code>. If no
325 * <code>wrappedData</code> is available, return <code>false</code>.</p>
326 *
327 * @throws FacesException if an error occurs getting the row availability
328 */
329 public boolean isRowAvailable() {
330
331 return (getDataModel().isRowAvailable());
332
333 }
334
335
336 /**
337 * <p>Return the number of rows in the underlying data model. If the number
338 * of available rows is unknown, return -1.</p>
339 *
340 * @throws FacesException if an error occurs getting the row count
341 */
342 public int getRowCount() {
343
344 return (getDataModel().getRowCount());
345
346 }
347
348
349 /**
350 * <p>Return the data object representing the data for the currently
351 * selected row index, if any.</p>
352 *
353 * @throws FacesException if an error occurs getting the row data
354 * @throws IllegalArgumentException if now row data is available at the
355 * currently specified row index
356 */
357 public Object getRowData() {
358
359 return (getDataModel().getRowData());
360
361 }
362
363
364 /**
365 * <p>Return the zero-relative index of the currently selected row. If we
366 * are not currently positioned on a row, return -1. This property is
367 * <strong>not</strong> enabled for value binding expressions.</p>
368 *
369 * @throws FacesException if an error occurs getting the row index
370 */
371 public int getRowIndex() {
372
373 return (this.rowIndex);
374
375 }
376
377
378 /**
379 * <p>Set the zero relative index of the current row, or -1 to indicate that
380 * no row is currently selected, by implementing the following algorithm.
381 * It is possible to set the row index at a value for which the underlying
382 * data collection does not contain any row data. Therefore, callers may
383 * use the <code>isRowAvailable()</code> method to detect whether row data
384 * will be available for use by the <code>getRowData()</code> method.</p>
385 *</p>
386 * <ul>
387 * <li>Save current state information for all descendant components (as
388 * described below).
389 * <li>Store the new row index, and pass it on to the {@link DataModel}
390 * associated with this {@link UIData} instance.</li>
391 * <li>If the new <code>rowIndex</code> value is -1:
392 * <ul>
393 * <li>If the <code>var</code> property is not null,
394 * remove the corresponding request scope attribute (if any).</li>
395 * <li>Reset the state information for all descendant components
396 * (as described below).</li>
397 * </ul></li>
398 * <li>If the new <code>rowIndex</code> value is not -1:
399 * <ul>
400 * <li>If the <code>var</code> property is not null, call
401 * <code>getRowData()</code> and expose the resulting data object
402 * as a request scope attribute whose key is the <code>var</code>
403 * property value.</li>
404 * <li>Reset the state information for all descendant components
405 * (as described below).
406 * </ul></li>
407 * </ul>
408 *
409 * <p>To save current state information for all descendant components,
410 * {@link UIData} must maintain per-row information for each descendant
411 * as follows:<p>
412 * <ul>
413 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, save
414 * the state of its <code>localValue</code> property.</li>
415 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
416 * save the state of the <code>localValueSet</code> property.</li>
417 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, save
418 * the state of the <code>valid</code> property.</li>
419 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
420 * save the state of the <code>submittedValue</code> property.</li>
421 * </ul>
422 *
423 * <p>To restore current state information for all descendant components,
424 * {@link UIData} must reference its previously stored information for the
425 * current <code>rowIndex</code> and call setters for each descendant
426 * as follows:</p>
427 * <ul>
428 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
429 * restore the <code>value</code> property.</li>
430 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
431 * restore the state of the <code>localValueSet</code> property.</li>
432 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
433 * restore the state of the <code>valid</code> property.</li>
434 * <li>If the descendant is an instance of <code>EditableValueHolder</code>,
435 * restore the state of the <code>submittedValue</code> property.</li>
436 * </ul>
437 *
438 * @param rowIndex The new row index value, or -1 for no associated row
439 *
440 * @throws FacesException if an error occurs setting the row index
441 * @throws IllegalArgumentException if <code>rowIndex</code>
442 * is less than -1
443 */
444 public void setRowIndex(int rowIndex) {
445
446 // Save current state for the previous row index
447 saveDescendantState();
448
449 // Update to the new row index
450 this.rowIndex = rowIndex;
451 DataModel localModel = getDataModel();
452 localModel.setRowIndex(rowIndex);
453
454 // Clear or expose the current row data as a request scope attribute
455 if (var != null) {
456 Map<String, Object> requestMap =
457 getFacesContext().getExternalContext().getRequestMap();
458 if (rowIndex == -1) {
459 oldVar = requestMap.remove(var);
460 } else if (isRowAvailable()) {
461 requestMap.put(var, getRowData());
462 } else {
463 requestMap.remove(var);
464 if (null != oldVar) {
465 requestMap.put(var, oldVar);
466 oldVar = null;
467 }
468 }
469 }
470
471 // Reset current state information for the new row index
472 restoreDescendantState();
473
474 }
475
476
477 /**
478 * <p>Return the number of rows to be displayed, or zero for all remaining
479 * rows in the table. The default value of this property is zero.</p>
480 */
481 public int getRows() {
482
483 if (this.rows != null) {
484 return (this.rows);
485 }
486 ValueExpression ve = getValueExpression("rows");
487 if (ve != null) {
488 Integer value;
489 try {
490 value = (Integer) ve.getValue(getFacesContext().getELContext());
491 }
492 catch (ELException e) {
493 throw new FacesException(e);
494 }
495
496 if (null == value) {
497 return rows;
498 }
499 return (value.intValue());
500 } else {
501 return (0);
502 }
503
504 }
505
506
507 /**
508 * <p>Set the number of rows to be displayed, or zero for all remaining rows
509 * in the table.</p>
510 *
511 * @param rows New number of rows
512 *
513 * @throws IllegalArgumentException if <code>rows</code> is negative
514 */
515 public void setRows(int rows) {
516
517 if (rows < 0) {
518 throw new IllegalArgumentException(String.valueOf(rows));
519 }
520 this.rows = rows;
521
522 }
523
524
525 /**
526 * <p>Return the request-scope attribute under which the data object for the
527 * current row will be exposed when iterating. This property is
528 * <strong>not</strong> enabled for value binding expressions.</p>
529 */
530 public String getVar() {
531
532 return (this.var);
533
534 }
535
536
537 /**
538 * <p>Set the request-scope attribute under which the data object for the
539 * current row wil be exposed when iterating.</p>
540 *
541 * @param var The new request-scope attribute name
542 */
543 public void setVar(String var) {
544
545 this.var = var;
546
547 }
548
549 // ----------------------------------------------------- StateHolder Methods
550
551
552 private Object[] values;
553
554 public Object saveState(FacesContext context) {
555
556 if (values == null) {
557 values = new Object[7];
558 }
559
560 values[0] = super.saveState(context);
561 values[1] = first;
562 values[2] = rowIndex;
563 values[3] = rows;
564 values[4] = saved;
565 values[5] = value;
566 values[6] = var;
567 return (values);
568
569 }
570
571
572 public void restoreState(FacesContext context, Object state) {
573
574 values = (Object[]) state;
575 super.restoreState(context, values[0]);
576 first = (Integer) values[1];
577 rowIndex = (Integer) values[2];
578 rows = (Integer) values[3];
579 saved = TypedCollections
580 .dynamicallyCastMap((Map) values[4], String.class, SavedState.class);
581 value = values[5];
582 var = (String) values[6];
583
584 }
585
586
587 /**
588 * <p>Return the value of the UIData. This value must either be
589 * be of type {@link DataModel}, or a type that can be adapted
590 * into a {@link DataModel}. <code>UIData</code> will automatically
591 * adapt the following types:</p>
592 * <ul>
593 * <li>Arrays</li>
594 * <li><code>java.util.List</code></li>
595 * <li><code>java.sql.ResultSet</code></li>
596 * <li><code>javax.servlet.jsp.jstl.sql.Result</code></li>
597 * </ul>
598 * <p>All other types will be adapted using the {@link ScalarDataModel}
599 * class, which will treat the object as a single row of data.</p>
600 */
601 public Object getValue() {
602
603 if (this.value != null) {
604 return (this.value);
605 }
606 ValueExpression ve = getValueExpression("value");
607 if (ve != null) {
608 try {
609 return (ve.getValue(getFacesContext().getELContext()));
610 }
611 catch (ELException e) {
612 throw new FacesException(e);
613 }
614
615 } else {
616 return (null);
617 }
618
619 }
620
621
622 /**
623 * <p>Set the value of the <code>UIData</code>. This value must either be
624 * be of type {@link DataModel}, or a type that can be adapted into a {@link
625 * DataModel}.</p>
626 *
627 * @param value the new value
628 */
629 public void setValue(Object value) {
630 setDataModel(null);
631 this.value = value;
632
633 }
634
635 // ----------------------------------------------------- UIComponent Methods
636
637
638 /**
639 * <p>If "name" is something other than "value", "var", or "rowIndex", rely
640 * on the superclass conversion from <code>ValueBinding</code> to
641 * <code>ValueExpression</code>.</p>
642 *
643 * @param name Name of the attribute or property for which to set a
644 * {@link ValueBinding}
645 * @param binding The {@link ValueBinding} to set, or <code>null</code> to
646 * remove any currently set {@link ValueBinding}
647 *
648 * @throws IllegalArgumentException if <code>name</code> is one of
649 * <code>id</code>, <code>parent</code>,
650 * <code>var</code>, or <code>rowIndex</code>
651 * @throws NullPointerException if <code>name</code> is <code>null</code>
652 * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String,
653 *javax.el.ValueExpression)}.
654 */
655 public void setValueBinding(String name, ValueBinding binding) {
656
657 if ("value".equals(name)) {
658 setDataModel(null);
659 } else if ("var".equals(name) || "rowIndex".equals(name)) {
660 throw new IllegalArgumentException();
661 }
662 super.setValueBinding(name, binding);
663
664 }
665
666 /**
667 * <p>Set the {@link ValueExpression} used to calculate the value for the
668 * specified attribute or property name, if any. In addition, if a {@link
669 * ValueExpression} is set for the <code>value</code> property, remove any
670 * synthesized {@link DataModel} for the data previously bound to this
671 * component.</p>
672 *
673 * @param name Name of the attribute or property for which to set a
674 * {@link ValueExpression}
675 * @param binding The {@link ValueExpression} to set, or <code>null</code>
676 * to remove any currently set {@link ValueExpression}
677 *
678 * @throws IllegalArgumentException if <code>name</code> is one of
679 * <code>id</code>, <code>parent</code>,
680 * <code>var</code>, or <code>rowIndex</code>
681 * @throws NullPointerException if <code>name</code> is <code>null</code>
682 * @since 1.2
683 */
684 public void setValueExpression(String name, ValueExpression binding) {
685
686 if ("value".equals(name)) {
687 this.model = null;
688 } else if ("var".equals(name) || "rowIndex".equals(name)) {
689 throw new IllegalArgumentException();
690 }
691 super.setValueExpression(name, binding);
692
693 }
694
695 /**
696 * <p>Return a client identifier for this component that includes the
697 * current value of the <code>rowIndex</code> property, if it is not set to
698 * -1. This implies that multiple calls to <code>getClientId()</code> may
699 * return different results, but ensures that child components can
700 * themselves generate row-specific client identifiers (since {@link UIData}
701 * is a {@link NamingContainer}).</p>
702 *
703 * @throws NullPointerException if <code>context</code> is <code>null</code>
704 */
705 public String getClientId(FacesContext context) {
706
707 if (context == null) {
708 throw new NullPointerException();
709 }
710
711 // If baseClientId and clientIdBuilder are both null, this is the
712 // first time that getClientId() has been called.
713 // If we're not nested within another UIData, then:
714 // - create a new StringBuilder assigned to clientIdBuilder containing
715 // our client ID.
716 // - toString() the builder - this result will be our baseClientId
717 // for the duration of the component
718 // - append SEPARATOR_CHAR to the builder
719 // If we are nested within another UIData, then:
720 // - create an empty StringBuilder that will be used to build
721 // this instance's ID
722 if (baseClientId == null && clientIdBuilder == null) {
723 if (!isNestedWithinUIData()) {
724 clientIdBuilder = new StringBuilder(super.getClientId(context));
725 baseClientId = clientIdBuilder.toString();
726 baseClientIdLength = (baseClientId.length() + 1);
727 clientIdBuilder.append(NamingContainer.SEPARATOR_CHAR);
728 clientIdBuilder.setLength(baseClientIdLength);
729 } else {
730 clientIdBuilder = new StringBuilder();
731 }
732 }
733 if (rowIndex >= 0) {
734 String cid;
735 if (!isNestedWithinUIData()) {
736 // we're not nested, so the clientIdBuilder is already
737 // primed with clientID + SEPARATOR_CHAR. Append
738 // the current rowIndex, and toString() the builder.
739 // reset the builder to it's primed state.
740 cid = clientIdBuilder.append(rowIndex).toString();
741 clientIdBuilder.setLength(baseClientIdLength);
742 } else {
743 // we're nested, so we have to build the ID from scratch
744 // each time. Reuse the same clientIdBuilder instance
745 // for each call by resetting the length to 0 after
746 // the ID has been computed.
747 cid = clientIdBuilder.append(super.getClientId(context))
748 .append(NamingContainer.SEPARATOR_CHAR).append(rowIndex)
749 .toString();
750 clientIdBuilder.setLength(0);
751 }
752 return (cid);
753 } else {
754 if (!isNestedWithinUIData()) {
755 // Not nested and no row available, so just return our baseClientId
756 return (baseClientId);
757 } else {
758 // nested and no row available, return the result of getClientId().
759 // this is necessary as the client ID will reflect the row that
760 // this table represents
761 return super.getClientId(context);
762 }
763 }
764
765 }
766
767 /**
768 * <p>Override behavior from {@link UIComponentBase#invokeOnComponent} to
769 * provide special care for positioning the data properly before finding the
770 * component and invoking the callback on it. If the argument
771 * <code>clientId</code> is equal to <code>this.getClientId()</code> simply
772 * invoke the <code>contextCallback</code>, passing the <code>context</code>
773 * argument and <b>this</b> as arguments, and return <code>true.</code>
774 * Otherwise, attempt to extract a rowIndex from the <code>clientId</code>.
775 * For example, if the argument <code>clientId</code> was
776 * <code>form:data:3:customerHeader</code> the rowIndex would be
777 * <code>3</code>. Let this value be called <code>newIndex</code>. The
778 * current rowIndex of this instance must be saved aside and restored before
779 * returning in all cases, regardless of the outcome of the search or if any
780 * exceptions are thrown in the process.</p>
781 *
782 * <p>The implementation of this method must never return <code>true</code>
783 * if setting the rowIndex of this instance to be equal to
784 * <code>newIndex</code> causes this instance to return <code>false</code>
785 * from {@link #isRowAvailable}.</p>
786 *
787 * @throws NullPointerException {@inheritDoc}
788 * @throws FacesException {@inheritDoc} Also throws <code>FacesException</code>
789 * if any exception is thrown when deriving the
790 * rowIndex from the argument <code>clientId</code>.
791 * @since 1.2
792 */
793 public boolean invokeOnComponent(FacesContext context, String clientId,
794 ContextCallback callback)
795 throws FacesException {
796 if (null == context || null == clientId || null == callback) {
797 throw new NullPointerException();
798 }
799 String myId = super.getClientId(context);
800 boolean found = false;
801 if (clientId.equals(myId)) {
802 try {
803 callback.invokeContextCallback(context, this);
804 return true;
805 }
806 catch (Exception e) {
807 throw new FacesException(e);
808 }
809 }
810
811 // check the facets, if any, of UIData
812 if (this.getFacetCount() > 0) {
813 for (Iterator<UIComponent> i = this.getFacets().values().iterator(); i.hasNext(); ) {
814 UIComponent c = i.next();
815 if (clientId.equals(c.getClientId(context))) {
816 callback.invokeContextCallback(context, c);
817 return true;
818 }
819 }
820 }
821
822 int lastSep, newRow, savedRowIndex = this.getRowIndex();
823 try {
824 // If we need to strip out the rowIndex from our id
825 // PENDING(edburns): is this safe with respect to I18N?
826 if (myId.endsWith(NamingContainer.SEPARATOR_CHAR + Integer.toString(savedRowIndex, 10))) {
827 lastSep = myId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
828 assert (-1 != lastSep);
829 myId = myId.substring(0, lastSep);
830 }
831
832 // myId will be something like form:outerData for a non-nested table,
833 // and form:outerData:3:data for a nested table.
834 // clientId will be something like form:outerData:3:outerColumn
835 // for a non-nested table. clientId will be something like
836 // outerData:3:data:3:input for a nested table.
837 if (clientId.startsWith(myId)) {
838 int preRowIndexSep, postRowIndexSep;
839
840 if (-1 != (preRowIndexSep =
841 clientId.indexOf(NamingContainer.SEPARATOR_CHAR,
842 myId.length()))) {
843 // Check the length
844 if (++preRowIndexSep < clientId.length()) {
845 if (-1 != (postRowIndexSep =
846 clientId.indexOf(NamingContainer.SEPARATOR_CHAR,
847 preRowIndexSep + 1))) {
848 try {
849 newRow = Integer
850 .valueOf(clientId.substring(preRowIndexSep,
851 postRowIndexSep))
852 .intValue();
853 } catch (NumberFormatException ex) {
854 // PENDING(edburns): I18N
855 String message =
856 "Trying to extract rowIndex from clientId \'"
857 +
858 clientId
859 + "\' "
860 + ex.getMessage();
861 throw new NumberFormatException(message);
862 }
863 this.setRowIndex(newRow);
864 if (this.isRowAvailable()) {
865 found = super.invokeOnComponent(context,
866 clientId,
867 callback);
868 }
869 }
870 }
871 }
872 }
873 }
874 catch (FacesException fe) {
875 throw fe;
876 }
877 catch (Exception e) {
878 throw new FacesException(e);
879 }
880 finally {
881 this.setRowIndex(savedRowIndex);
882 }
883 return found;
884 }
885
886
887 /**
888 * <p>Override the default {@link UIComponentBase#queueEvent} processing to
889 * wrap any queued events in a wrapper so that we can reset the current row
890 * index in <code>broadcast()</code>.</p>
891 *
892 * @param event {@link FacesEvent} to be queued
893 *
894 * @throws IllegalStateException if this component is not a descendant of a
895 * {@link UIViewRoot}
896 * @throws NullPointerException if <code>event</code> is <code>null</code>
897 */
898 public void queueEvent(FacesEvent event) {
899
900 super.queueEvent(new WrapperEvent(this, event, getRowIndex()));
901
902 }
903
904
905 /**
906 * <p>Override the default {@link UIComponentBase#broadcast} processing to
907 * unwrap any wrapped {@link FacesEvent} and reset the current row index,
908 * before the event is actually broadcast. For events that we did not wrap
909 * (in <code>queueEvent()</code>), default processing will occur.</p>
910 *
911 * @param event The {@link FacesEvent} to be broadcast
912 *
913 * @throws AbortProcessingException Signal the JavaServer Faces
914 * implementation that no further
915 * processing on the current event should
916 * be performed
917 * @throws IllegalArgumentException if the implementation class of this
918 * {@link FacesEvent} is not supported by
919 * this component
920 * @throws NullPointerException if <code>event</code> is <code>null</code>
921 */
922 public void broadcast(FacesEvent event)
923 throws AbortProcessingException {
924
925 if (!(event instanceof WrapperEvent)) {
926 super.broadcast(event);
927 return;
928 }
929
930 // Set up the correct context and fire our wrapped event
931 WrapperEvent revent = (WrapperEvent) event;
932 if (isNestedWithinUIData()) {
933 setDataModel(null);
934 }
935 int oldRowIndex = getRowIndex();
936 setRowIndex(revent.getRowIndex());
937 FacesEvent rowEvent = revent.getFacesEvent();
938 rowEvent.getComponent().broadcast(rowEvent);
939 setRowIndex(oldRowIndex);
940
941 }
942
943
944 /**
945 * <p>In addition to the default behavior, ensure that any saved per-row
946 * state for our child input components is discarded unless it is needed to
947 * rerender the current page with errors.
948 *
949 * @param context FacesContext for the current request
950 *
951 * @throws IOException if an input/output error occurs while
952 * rendering
953 * @throws NullPointerException if <code>context</code> is <code>null</code>
954 */
955 public void encodeBegin(FacesContext context) throws IOException {
956
957 setDataModel(null); // re-evaluate even with server-side state saving
958 if (!keepSaved(context)) {
959 //noinspection CollectionWithoutInitialCapacity
960 saved = new HashMap<String, SavedState>();
961 }
962 super.encodeBegin(context);
963
964 }
965
966
967 /**
968 * <p>Override the default {@link UIComponentBase#processDecodes} processing
969 * to perform the following steps.</p> <ul> <li>If the <code>rendered</code>
970 * property of this {@link UIComponent} is <code>false</code>, skip further
971 * processing.</li> <li>Set the current <code>rowIndex</code> to -1.</li>
972 * <li>Call the <code>processDecodes()</code> method of all facets of this
973 * {@link UIData}, in the order determined by a call to
974 * <code>getFacets().keySet().iterator()</code>.</li> <li>Call the
975 * <code>processDecodes()</code> method of all facets of the {@link
976 * UIColumn} children of this {@link UIData}.</li> <li>Iterate over the set
977 * of rows that were included when this component was rendered (i.e. those
978 * defined by the <code>first</code> and <code>rows</code> properties),
979 * performing the following processing for each row: <ul> <li>Set the
980 * current <code>rowIndex</code> to the appropriate value for this row.</li>
981 * <li>If <code>isRowAvailable()</code> returns <code>true</code>, iterate
982 * over the children components of each {@link UIColumn} child of this
983 * {@link UIData} component, calling the <code>processDecodes()</code>
984 * method for each such child.</li> </ul></li> <li>Set the current
985 * <code>rowIndex</code> to -1.</li> <li>Call the <code>decode()</code>
986 * method of this component.</li> <li>If a <code>RuntimeException</code> is
987 * thrown during decode processing, call {@link FacesContext#renderResponse}
988 * and re-throw the exception.</li> </ul>
989 *
990 * @param context {@link FacesContext} for the current request
991 *
992 * @throws NullPointerException if <code>context</code> is <code>null</code>
993 */
994 public void processDecodes(FacesContext context) {
995
996 if (context == null) {
997 throw new NullPointerException();
998 }
999 if (!isRendered()) {
1000 return;
1001 }
1002
1003 setDataModel(null); // Re-evaluate even with server-side state saving
1004 if (null == saved || !keepSaved(context)) {
1005 //noinspection CollectionWithoutInitialCapacity
1006 saved = new HashMap<String, SavedState>(); // We don't need saved state here
1007 }
1008
1009 iterate(context, PhaseId.APPLY_REQUEST_VALUES);
1010 decode(context);
1011
1012 }
1013
1014
1015 /**
1016 * <p>Override the default {@link UIComponentBase#processValidators}
1017 * processing to perform the following steps.</p> <ul> <li>If the
1018 * <code>rendered</code> property of this {@link UIComponent} is
1019 * <code>false</code>, skip further processing.</li> <li>Set the current
1020 * <code>rowIndex</code> to -1.</li> <li>Call the <code>processValidators()</code>
1021 * method of all facets of this {@link UIData}, in the order determined by a
1022 * call to <code>getFacets().keySet().iterator()</code>.</li> <li>Call the
1023 * <code>processValidators()</code> method of all facets of the {@link
1024 * UIColumn} children of this {@link UIData}.</li> <li>Iterate over the set
1025 * of rows that were included when this component was rendered (i.e. those
1026 * defined by the <code>first</code> and <code>rows</code> properties),
1027 * performing the following processing for each row: <ul> <li>Set the
1028 * current <code>rowIndex</code> to the appropriate value for this row.</li>
1029 * <li>If <code>isRowAvailable()</code> returns <code>true</code>, iterate
1030 * over the children components of each {@link UIColumn} child of this
1031 * {@link UIData} component, calling the <code>processValidators()</code>
1032 * method for each such child.</li> </ul></li> <li>Set the current
1033 * <code>rowIndex</code> to -1.</li> </ul>
1034 *
1035 * @param context {@link FacesContext} for the current request
1036 *
1037 * @throws NullPointerException if <code>context</code> is <code>null</code>
1038 */
1039 public void processValidators(FacesContext context) {
1040
1041 if (context == null) {
1042 throw new NullPointerException();
1043 }
1044 if (!isRendered()) {
1045 return;
1046 }
1047 if (isNestedWithinUIData()) {
1048 setDataModel(null);
1049 }
1050 iterate(context, PhaseId.PROCESS_VALIDATIONS);
1051 // This is not a EditableValueHolder, so no further processing is required
1052
1053 }
1054
1055
1056 /**
1057 * <p>Override the default {@link UIComponentBase#processUpdates}
1058 * processing to perform the following steps.</p>
1059 * <ul>
1060 * <li>If the <code>rendered</code> property of this {@link UIComponent}
1061 * is <code>false</code>, skip further processing.</li>
1062 * <li>Set the current <code>rowIndex</code> to -1.</li>
1063 * <li>Call the <code>processUpdates()</code> method of all facets
1064 * of this {@link UIData}, in the order determined
1065 * by a call to <code>getFacets().keySet().iterator()</code>.</li>
1066 * <li>Call the <code>processUpdates()</code> method of all facets
1067 * of the {@link UIColumn} children of this {@link UIData}.</li>
1068 * <li>Iterate over the set of rows that were included when this
1069 * component was rendered (i.e. those defined by the <code>first</code>
1070 * and <code>rows</code> properties), performing the following
1071 * processing for each row:
1072 * <ul>
1073 * <li>Set the current <code>rowIndex</code> to the appropriate
1074 * value for this row.</li>
1075 * <li>If <code>isRowAvailable()</code> returns <code>true</code>,
1076 * iterate over the children components of each {@link UIColumn}
1077 * child of this {@link UIData} component, calling the
1078 * <code>processUpdates()</code> method for each such child.</li>
1079 * </ul></li>
1080 * <li>Set the current <code>rowIndex</code> to -1.</li>
1081 * </ul>
1082 *
1083 * @param context {@link FacesContext} for the current request
1084 *
1085 * @throws NullPointerException if <code>context</code> is <code>null</code>
1086 */
1087 public void processUpdates(FacesContext context) {
1088
1089 if (context == null) {
1090 throw new NullPointerException();
1091 }
1092 if (!isRendered()) {
1093 return;
1094 }
1095 if (isNestedWithinUIData()) {
1096 setDataModel(null);
1097 }
1098 iterate(context, PhaseId.UPDATE_MODEL_VALUES);
1099 // This is not a EditableValueHolder, so no further processing is required
1100
1101 }
1102
1103 // --------------------------------------------------------- Protected Methods
1104
1105
1106 /**
1107 * <p>Return the internal {@link DataModel} object representing the data
1108 * objects that we will iterate over in this component's rendering.</p>
1109 * <p/>
1110 * <p>If the model has been cached by a previous call to {@link
1111 * #setDataModel}, return it. Otherwise call {@link #getValue}. If the
1112 * result is null, create an empty {@link ListDataModel} and return it. If
1113 * the result is an instance of {@link DataModel}, return it. Otherwise,
1114 * adapt the result as described in {@link #getValue} and return it.</p>
1115 */
1116 protected DataModel getDataModel() {
1117
1118 // Return any previously cached DataModel instance
1119 if (this.model != null) {
1120 return (model);
1121 }
1122
1123 // Synthesize a DataModel around our current value if possible
1124 Object current = getValue();
1125 if (current == null) {
1126 setDataModel(new ListDataModel(Collections.EMPTY_LIST));
1127 } else if (current instanceof DataModel) {
1128 setDataModel((DataModel) current);
1129 } else if (current instanceof List) {
1130 setDataModel(new ListDataModel((List) current));
1131 } else if (Object[].class.isAssignableFrom(current.getClass())) {
1132 setDataModel(new ArrayDataModel((Object[]) current));
1133 } else if (current instanceof ResultSet) {
1134 setDataModel(new ResultSetDataModel((ResultSet) current));
1135 } else if (current instanceof Result) {
1136 setDataModel(new ResultDataModel((Result) current));
1137 } else {
1138 setDataModel(new ScalarDataModel(current));
1139 }
1140 return (model);
1141
1142 }
1143
1144 /**
1145 * <p>Set the internal DataModel. This <code>UIData</code> instance must
1146 * use the given {@link DataModel} as its internal value representation from
1147 * now until the next call to <code>setDataModel</code>. If the given
1148 * <code>DataModel</code> is <code>null</code>, the internal
1149 * <code>DataModel</code> must be reset in a manner so that the next call to
1150 * {@link #getDataModel} causes lazy instantion of a newly refreshed
1151 * <code>DataModel</code>.</p>
1152 * <p/>
1153 * <p>Subclasses might call this method if they either want to restore the
1154 * internal <code>DataModel</code> during the <em>Restore View</em> phase or
1155 * if they want to explicitly refresh the current <code>DataModel</code> for
1156 * the <em>Render Response</em> phase.</p>
1157 *
1158 * @param dataModel the new <code>DataModel</code> or <code>null</code> to
1159 * cause the model to be refreshed.
1160 */
1161
1162 protected void setDataModel(DataModel dataModel) {
1163 this.model = dataModel;
1164 }
1165
1166 // ---------------------------------------------------- Private Methods
1167
1168
1169 /**
1170 * <p>Perform the appropriate phase-specific processing and per-row
1171 * iteration for the specified phase, as follows:
1172 * <ul>
1173 * <li>Set the <code>rowIndex</code> property to -1, and process the facets
1174 * of this {@link UIData} component exactly once.</li>
1175 * <li>Set the <code>rowIndex</code> property to -1, and process the facets
1176 * of the {@link UIColumn} children of this {@link UIData} component
1177 * exactly once.</li>
1178 * <li>Iterate over the relevant rows, based on the <code>first</code>
1179 * and <code>row</code> properties, and process the children
1180 * of the {@link UIColumn} children of this {@link UIData} component
1181 * once per row.</li>
1182 * </ul>
1183 *
1184 * @param context {@link FacesContext} for the current request
1185 * @param phaseId {@link PhaseId} of the phase we are currently running
1186 */
1187 private void iterate(FacesContext context, PhaseId phaseId) {
1188
1189 // Process each facet of this component exactly once
1190 setRowIndex(-1);
1191 if (getFacetCount() > 0) {
1192 for (UIComponent facet : getFacets().values()) {
1193 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
1194 facet.processDecodes(context);
1195 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
1196 facet.processValidators(context);
1197 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
1198 facet.processUpdates(context);
1199 } else {
1200 throw new IllegalArgumentException();
1201 }
1202 }
1203 }
1204
1205 // Process each facet of our child UIColumn components exactly once
1206 setRowIndex(-1);
1207 if (getChildCount() > 0) {
1208 for (UIComponent column : getChildren()) {
1209 if (!(column instanceof UIColumn) || !column.isRendered()) {
1210 continue;
1211 }
1212 if (column.getFacetCount() > 0) {
1213 for (UIComponent columnFacet : column.getFacets().values()) {
1214 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
1215 columnFacet.processDecodes(context);
1216 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
1217 columnFacet.processValidators(context);
1218 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
1219 columnFacet.processUpdates(context);
1220 } else {
1221 throw new IllegalArgumentException();
1222 }
1223 }
1224 }
1225 }
1226 }
1227
1228 // Iterate over our UIColumn children, once per row
1229 int processed = 0;
1230 int rowIndex = getFirst() - 1;
1231 int rows = getRows();
1232
1233 while (true) {
1234
1235 // Have we processed the requested number of rows?
1236 if ((rows > 0) && (++processed > rows)) {
1237 break;
1238 }
1239
1240 // Expose the current row in the specified request attribute
1241 setRowIndex(++rowIndex);
1242 if (!isRowAvailable()) {
1243 break; // Scrolled past the last row
1244 }
1245
1246 // Perform phase-specific processing as required
1247 // on the *children* of the UIColumn (facets have
1248 // been done a single time with rowIndex=-1 already)
1249 if (getChildCount() > 0) {
1250 for (UIComponent kid : getChildren()) {
1251 if (!(kid instanceof UIColumn) || !kid.isRendered()) {
1252 continue;
1253 }
1254 if (kid.getChildCount() > 0) {
1255 for (UIComponent grandkid : kid.getChildren()) {
1256 if (!grandkid.isRendered()) {
1257 continue;
1258 }
1259 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
1260 grandkid.processDecodes(context);
1261 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
1262 grandkid.processValidators(context);
1263 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
1264 grandkid.processUpdates(context);
1265 } else {
1266 throw new IllegalArgumentException();
1267 }
1268 }
1269 }
1270 }
1271 }
1272
1273 }
1274
1275 // Clean up after ourselves
1276 setRowIndex(-1);
1277
1278 }
1279
1280
1281 /**
1282 * <p>Return <code>true</code> if we need to keep the saved
1283 * per-child state information. This will be the case if any of the
1284 * following are true:</p>
1285 *
1286 * <ul>
1287 *
1288 * <li>there are messages queued with severity ERROR or FATAL.</li>
1289 *
1290 * <li>this <code>UIData</code> instance is nested inside of another
1291 * <code>UIData</code> instance</li>
1292 *
1293 * </ul>
1294 *
1295 * @param context {@link FacesContext} for the current request
1296 */
1297 private boolean keepSaved(FacesContext context) {
1298
1299 return (contextHasErrorMessages(context) || isNestedWithinUIData());
1300
1301 }
1302
1303
1304 private Boolean isNestedWithinUIData() {
1305 if (isNested == null) {
1306 UIComponent parent = this;
1307 while (null != (parent = parent.getParent())) {
1308 if (parent instanceof UIData) {
1309 isNested = Boolean.TRUE;
1310 break;
1311 }
1312 }
1313 if (isNested == null) {
1314 isNested = Boolean.FALSE;
1315 }
1316 return isNested;
1317 } else {
1318 return isNested;
1319 }
1320 }
1321
1322
1323 private boolean contextHasErrorMessages(FacesContext context) {
1324
1325 FacesMessage.Severity sev = context.getMaximumSeverity();
1326 return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0));
1327
1328 }
1329
1330
1331 /**
1332 * <p>Restore state information for all descendant components, as described
1333 * for <code>setRowIndex()</code>.</p>
1334 */
1335 private void restoreDescendantState() {
1336
1337 FacesContext context = getFacesContext();
1338 if (getChildCount() > 0) {
1339 for (UIComponent kid : getChildren()) {
1340 if (kid instanceof UIColumn) {
1341 restoreDescendantState(kid, context);
1342 }
1343 }
1344 }
1345
1346 }
1347
1348
1349 /**
1350 * <p>Restore state information for the specified component and its
1351 * descendants.</p>
1352 *
1353 * @param component Component for which to restore state information
1354 * @param context {@link FacesContext} for the current request
1355 */
1356 private void restoreDescendantState(UIComponent component,
1357 FacesContext context) {
1358
1359 // Reset the client identifier for this component
1360 String id = component.getId();
1361 component.setId(id); // Forces client id to be reset
1362
1363 // Restore state for this component (if it is a EditableValueHolder)
1364 if (component instanceof EditableValueHolder) {
1365 EditableValueHolder input = (EditableValueHolder) component;
1366 String clientId = component.getClientId(context);
1367 SavedState state = saved.get(clientId);
1368 if (state == null) {
1369 state = new SavedState();
1370 }
1371 input.setValue(state.getValue());
1372 input.setValid(state.isValid());
1373 input.setSubmittedValue(state.getSubmittedValue());
1374 // This *must* be set after the call to setValue(), since
1375 // calling setValue() always resets "localValueSet" to true.
1376 input.setLocalValueSet(state.isLocalValueSet());
1377 }
1378
1379 // Restore state for children of this component
1380 if (component.getChildCount() > 0) {
1381 for (UIComponent kid : component.getChildren()) {
1382 restoreDescendantState(kid, context);
1383 }
1384 }
1385
1386 // Restore state for facets of this component
1387 if (component.getFacetCount() > 0) {
1388 for (UIComponent facet : component.getFacets().values()) {
1389 restoreDescendantState(facet, context);
1390 }
1391 }
1392
1393 }
1394
1395
1396 /**
1397 * <p>Save state information for all descendant components, as described for
1398 * <code>setRowIndex()</code>.</p>
1399 */
1400 private void saveDescendantState() {
1401
1402 FacesContext context = getFacesContext();
1403 if (getChildCount() > 0) {
1404 for (UIComponent kid : getChildren()) {
1405 if (kid instanceof UIColumn) {
1406 saveDescendantState(kid, context);
1407 }
1408 }
1409 }
1410
1411 }
1412
1413
1414 /**
1415 * <p>Save state information for the specified component and its
1416 * descendants.</p>
1417 *
1418 * @param component Component for which to save state information
1419 * @param context {@link FacesContext} for the current request
1420 */
1421 private void saveDescendantState(UIComponent component,
1422 FacesContext context) {
1423
1424 // Save state for this component (if it is a EditableValueHolder)
1425 if (component instanceof EditableValueHolder) {
1426 EditableValueHolder input = (EditableValueHolder) component;
1427 String clientId = component.getClientId(context);
1428 SavedState state = saved.get(clientId);
1429 if (state == null) {
1430 state = new SavedState();
1431 saved.put(clientId, state);
1432 }
1433 state.setValue(input.getLocalValue());
1434 state.setValid(input.isValid());
1435 state.setSubmittedValue(input.getSubmittedValue());
1436 state.setLocalValueSet(input.isLocalValueSet());
1437 }
1438
1439 // Save state for children of this component
1440 if (component.getChildCount() > 0) {
1441 for (UIComponent uiComponent : component.getChildren()) {
1442 saveDescendantState(uiComponent, context);
1443 }
1444 }
1445
1446 // Save state for facets of this component
1447 if (component.getFacetCount() > 0) {
1448 for (UIComponent facet : component.getFacets().values()) {
1449 saveDescendantState(facet, context);
1450 }
1451 }
1452
1453 }
1454
1455 }
1456
1457 // ------------------------------------------------------------- Private Classes
1458
1459
1460 // Private class to represent saved state information
1461 @SuppressWarnings({"SerializableHasSerializationMethods",
1462 "NonSerializableFieldInSerializableClass"})
1463 class SavedState implements Serializable {
1464
1465 private static final long serialVersionUID = 2920252657338389849L;
1466 private Object submittedValue;
1467
1468 Object getSubmittedValue() {
1469 return (this.submittedValue);
1470 }
1471
1472 void setSubmittedValue(Object submittedValue) {
1473 this.submittedValue = submittedValue;
1474 }
1475
1476 private boolean valid = true;
1477
1478 boolean isValid() {
1479 return (this.valid);
1480 }
1481
1482 void setValid(boolean valid) {
1483 this.valid = valid;
1484 }
1485
1486 private Object value;
1487
1488 Object getValue() {
1489 return (this.value);
1490 }
1491
1492 public void setValue(Object value) {
1493 this.value = value;
1494 }
1495
1496 private boolean localValueSet;
1497
1498 boolean isLocalValueSet() {
1499 return (this.localValueSet);
1500 }
1501
1502 public void setLocalValueSet(boolean localValueSet) {
1503 this.localValueSet = localValueSet;
1504 }
1505
1506 public String toString() {
1507 return ("submittedValue: " + submittedValue +
1508 " value: " + value +
1509 " localValueSet: " + localValueSet);
1510 }
1511
1512 }
1513
1514
1515 // Private class to wrap an event with a row index
1516 class WrapperEvent extends FacesEvent {
1517
1518
1519 public WrapperEvent(UIComponent component, FacesEvent event, int rowIndex) {
1520 super(component);
1521 this.event = event;
1522 this.rowIndex = rowIndex;
1523 }
1524
1525 private FacesEvent event = null;
1526 private int rowIndex = -1;
1527
1528 public FacesEvent getFacesEvent() {
1529 return (this.event);
1530 }
1531
1532 public int getRowIndex() {
1533 return (this.rowIndex);
1534 }
1535
1536 public PhaseId getPhaseId() {
1537 return (this.event.getPhaseId());
1538 }
1539
1540 public void setPhaseId(PhaseId phaseId) {
1541 this.event.setPhaseId(phaseId);
1542 }
1543
1544 public boolean isAppropriateListener(FacesListener listener) {
1545 return (false);
1546 }
1547
1548 public void processListener(FacesListener listener) {
1549 throw new IllegalStateException();
1550 }
1551
1552
1553 }