001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.component;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.myfaces.tobago.ajax.api.AjaxComponent;
025    import org.apache.myfaces.tobago.ajax.api.AjaxPhaseListener;
026    import org.apache.myfaces.tobago.ajax.api.AjaxUtils;
027    import org.apache.myfaces.tobago.event.PageActionEvent;
028    import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
029    import org.apache.myfaces.tobago.event.SheetStateChangeListener;
030    import org.apache.myfaces.tobago.event.SheetStateChangeSource;
031    import org.apache.myfaces.tobago.event.SortActionEvent;
032    import org.apache.myfaces.tobago.event.SortActionSource;
033    import org.apache.myfaces.tobago.model.SheetState;
034    import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
035    import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
036    import org.apache.myfaces.tobago.renderkit.SheetRendererWorkaround;
037    import org.apache.myfaces.tobago.util.LayoutInfo;
038    import org.apache.myfaces.tobago.util.LayoutUtil;
039    import org.apache.myfaces.tobago.util.StringUtils;
040    
041    import javax.faces.component.UIColumn;
042    import javax.faces.component.UIComponent;
043    import javax.faces.context.FacesContext;
044    import javax.faces.el.EvaluationException;
045    import javax.faces.el.MethodBinding;
046    import javax.faces.el.ValueBinding;
047    import javax.faces.event.AbortProcessingException;
048    import javax.faces.event.FacesEvent;
049    import javax.faces.event.PhaseId;
050    import javax.servlet.http.HttpServletResponse;
051    import java.io.IOException;
052    import java.util.ArrayList;
053    import java.util.Collections;
054    import java.util.List;
055    import java.util.Map;
056    
057    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_COLUMNS;
058    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DIRECT_LINK_COUNT;
059    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FIRST;
060    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
061    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
062    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ROWS;
063    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTABLE;
064    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTED_LIST_STRING;
065    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_DIRECT_LINKS;
066    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_HEADER;
067    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_PAGE_RANGE;
068    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_ROW_RANGE;
069    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STATE;
070    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH_LIST_STRING;
071    import static org.apache.myfaces.tobago.TobagoConstants.FACET_RELOAD;
072    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
073    
074    public class UIData extends javax.faces.component.UIData
075        implements SheetStateChangeSource, SortActionSource, AjaxComponent {
076    
077      private static final Log LOG = LogFactory.getLog(UIData.class);
078    
079      public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Data";
080    
081      public static final String FACET_SORTER = "sorter";
082      public static final String SORTER_ID = "sorter";
083      public static final String ATTR_SCROLL_POSITION = "attrScrollPosition";
084    
085      public static final String NONE = "none";
086      public static final String SINGLE = "single";
087      public static final String SINGLE_OR_NONE = "singleOrNone";
088      public static final String MULTI = "multi";
089      public static final int DEFAULT_DIRECT_LINK_COUNT = 9;
090      public static final int DEFAULT_ROW_COUNT = 100;
091      private static final String DEFAULT_SELECTABLE = MULTI;
092    
093      private MethodBinding stateChangeListener;
094      private List<Integer> widthList;
095      private MethodBinding sortActionListener;
096      private SheetState sheetState;
097      private Boolean showHeader;
098      private String showRowRange;
099      private String showPageRange;
100      private String showDirectLinks;
101      private String columns;
102      private Integer directLinkCount;
103      private Integer rows;
104    
105      private String selectable;
106    
107      private transient LayoutTokens columnLayout;
108    
109      /**
110       * Remove the (by user) resized column widths. An application may provide a button to access it.
111       * Since 1.0.26.
112       */
113      public void resetColumnWidths() {
114        SheetState state = getSheetState(FacesContext.getCurrentInstance());
115        if (state != null) {
116          state.setColumnWidths(null);
117        }
118        getAttributes().remove(ATTR_WIDTH_LIST_STRING);
119      }
120    
121      public void encodeBegin(FacesContext facesContext) throws IOException {
122        UILayout.prepareDimension(facesContext, this);
123        SheetState state = getSheetState(facesContext);
124        if (state.getFirst() > -1 && state.getFirst() < getRowCount()) {
125          ValueBinding valueBinding = getValueBinding(ATTR_FIRST);
126          if (valueBinding != null) {
127            valueBinding.setValue(facesContext, state.getFirst());
128          } else {
129            setFirst(state.getFirst());
130          }
131        }
132        super.encodeBegin(facesContext);
133      }
134    
135      public void encodeEnd(FacesContext facesContext) throws IOException {
136        setupState(facesContext);
137        prepareDimensions(facesContext);
138        super.encodeEnd(facesContext);
139      }
140    
141      public void processDecodes(FacesContext context) {
142        final String ajaxId = (String) context.getExternalContext()
143            .getRequestParameterMap().get(AjaxPhaseListener.AJAX_COMPONENT_ID);
144        if (ajaxId !=null && ajaxId.equals(getClientId(context))) {
145          if (getFacet(FACET_RELOAD) != null && getFacet(FACET_RELOAD) instanceof UIReload
146              && getFacet(FACET_RELOAD).isRendered()
147              && ((UIReload) getFacet(FACET_RELOAD)).isImmediate()
148              && ajaxId.equals(ComponentUtil.findPage(context, this).getActionId())) {
149            UIReload reload = (UIReload) getFacet(FACET_RELOAD);
150            if (!reload.getUpdate()) {
151              if (context.getExternalContext().getResponse() instanceof HttpServletResponse) {
152                 ((HttpServletResponse) context.getExternalContext().getResponse())
153                     .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
154              }
155              context.responseComplete();
156              return;
157            }
158          }
159        }
160        super.processDecodes(context);
161      }
162    
163      public String getShowRowRange() {
164        if (showRowRange != null) {
165          return showRowRange;
166        }
167        ValueBinding vb = getValueBinding(ATTR_SHOW_ROW_RANGE);
168        if (vb != null) {
169          return (String) vb.getValue(getFacesContext());
170        } else {
171          return NONE;
172        }
173      }
174    
175      public void setShowRowRange(String showRowRange) {
176        this.showRowRange = showRowRange;
177      }
178    
179      public String getShowPageRange() {
180        if (showPageRange != null) {
181          return showPageRange;
182        }
183        ValueBinding vb = getValueBinding(ATTR_SHOW_PAGE_RANGE);
184        if (vb != null) {
185          return (String) vb.getValue(getFacesContext());
186        } else {
187          return NONE;
188        }
189      }
190    
191      public void setShowPageRange(String showPageRange) {
192        this.showPageRange = showPageRange;
193      }
194    
195      public String getColumns() {
196        if (columns != null) {
197          return columns;
198        }
199        ValueBinding vb = getValueBinding(ATTR_COLUMNS);
200        if (vb != null) {
201          return (String) vb.getValue(getFacesContext());
202        } else {
203          return null;
204        }
205      }
206    
207      public void setColumns(String columns) {
208        this.columns = columns;
209      }
210    
211      public String getShowDirectLinks() {
212        if (showDirectLinks != null) {
213          return showDirectLinks;
214        }
215        ValueBinding vb = getValueBinding(ATTR_SHOW_DIRECT_LINKS);
216        if (vb != null) {
217          return (String) vb.getValue(getFacesContext());
218        } else {
219          return NONE;
220        }
221      }
222    
223      public void setShowDirectLinks(String showDirectLinks) {
224        this.showDirectLinks = showDirectLinks;
225      }
226    
227      public String getSelectable() {
228        if (selectable != null) {
229          return selectable;
230        }
231        ValueBinding vb = getValueBinding(ATTR_SELECTABLE);
232        if (vb != null) {
233          return (String) vb.getValue(getFacesContext());
234        } else {
235          return DEFAULT_SELECTABLE;
236        }
237      }
238    
239      public void setSelectable(String selectable) {
240        this.selectable = selectable;
241      }
242    
243      public Integer getDirectLinkCount() {
244        if (directLinkCount != null) {
245          return directLinkCount;
246        }
247        ValueBinding vb = getValueBinding(ATTR_DIRECT_LINK_COUNT);
248        if (vb != null) {
249          return (Integer) vb.getValue(getFacesContext());
250        } else {
251          return DEFAULT_DIRECT_LINK_COUNT;
252        }
253      }
254    
255      public void setDirectLinkCount(Integer directLinkCount) {
256        this.directLinkCount = directLinkCount;
257      }
258    
259      private void setupState(FacesContext facesContext) {
260        SheetState state = getSheetState(facesContext);
261        ensureColumnWidthList(facesContext, state);
262      }
263    
264      public void setState(SheetState state) {
265        this.sheetState = state;
266      }
267    
268      public SheetState getSheetState(FacesContext facesContext) {
269        if (sheetState != null) {
270          return sheetState;
271        } else {
272          ValueBinding stateBinding = getValueBinding(ATTR_STATE);
273          if (stateBinding != null) {
274            SheetState state = (SheetState) stateBinding.getValue(facesContext);
275            if (state == null) {
276              state = new SheetState();
277              stateBinding.setValue(facesContext, state);
278            }
279            return state;
280          } else {
281            sheetState = new SheetState();
282            return sheetState;
283          }
284        }
285      }
286    
287      public LayoutTokens getColumnLayout() {
288        if (columnLayout == null) {
289          String columns = getColumns();
290          if (columns != null) {
291            columnLayout = LayoutTokens.parse(columns);
292          }
293        }
294        return columnLayout;
295      }
296    
297      private void ensureColumnWidthList(FacesContext facesContext, SheetState state) {
298        List<Integer> currentWidthList = null;
299        List<UIColumn> rendererdColumns = getRenderedColumns();
300    
301        final Map attributes = getAttributes();
302        String widthListString = null;
303    
304        if (state != null) {
305          widthListString = state.getColumnWidths();
306        }
307        if (widthListString == null) {
308          widthListString = (String) attributes.get(ATTR_WIDTH_LIST_STRING);
309        }
310    
311        if (widthListString != null) {
312          currentWidthList = StringUtils.parseIntegerList(widthListString);
313        }
314        if (currentWidthList != null && currentWidthList.size() != rendererdColumns.size()) {
315          currentWidthList = null;
316        }
317    
318    
319        if (currentWidthList == null) {
320          LayoutTokens tokens = getColumnLayout();
321          List<UIColumn> allColumns = getAllColumns();
322          LayoutTokens newTokens = new LayoutTokens();
323          if (allColumns.size() > 0) {
324            for (int i = 0; i < allColumns.size(); i++) {
325              UIColumn column = allColumns.get(i);
326              if (column.isRendered()) {
327                if (tokens == null) {
328                  if (column instanceof org.apache.myfaces.tobago.component.UIColumn) {
329                    newTokens.addToken(
330                        LayoutTokens.parseToken(((org.apache.myfaces.tobago.component.UIColumn) column).getWidth()));
331                  } else {
332                    newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
333                  }
334                } else {
335                  if (i < tokens.getSize()) {
336                    newTokens.addToken(tokens.get(i));
337                  } else {
338                    newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
339                  }
340                }
341              }
342            }
343          }
344    
345    
346          int space = LayoutUtil.getInnerSpace(facesContext, this, true);
347          SheetRendererWorkaround renderer
348              = (SheetRendererWorkaround) ComponentUtil.getRenderer(facesContext, this);
349          space -= renderer.getContentBorder(facesContext, this);
350          if (renderer.needVerticalScrollbar(facesContext, this)) {
351            space -= renderer.getScrollbarWidth(facesContext, this);
352          }
353          LayoutInfo layoutInfo = new LayoutInfo(newTokens.getSize(), space, newTokens, getClientId(facesContext), false);
354          parseFixedWidth(facesContext, layoutInfo, rendererdColumns);
355          layoutInfo.parseColumnLayout(space);
356          currentWidthList = layoutInfo.getSpaceList();
357        }
358    
359        if (currentWidthList != null) {
360          if (rendererdColumns.size() != currentWidthList.size()) {
361            LOG.warn("widthList.size() = " + currentWidthList.size()
362                + " != columns.size() = " + rendererdColumns.size() + "  widthList : "
363                + LayoutInfo.listToTokenString(currentWidthList));
364          } else {
365            this.widthList = currentWidthList;
366          }
367        }
368      }
369    
370      private void parseFixedWidth(FacesContext facesContext, LayoutInfo layoutInfo, List<UIColumn> rendereredColumns) {
371        LayoutTokens tokens = layoutInfo.getLayoutTokens();
372        for (int i = 0; i < tokens.getSize(); i++) {
373          LayoutToken token = tokens.get(i);
374          if (token instanceof FixedLayoutToken) {
375            int width = 0;
376            if (!rendereredColumns.isEmpty()) {
377              if (i < rendereredColumns.size()) {
378                UIColumn column = rendereredColumns.get(i);
379                if (column instanceof UIColumnSelector) {
380                  LayoutInformationProvider renderer
381                      = ComponentUtil.getRenderer(facesContext, column);
382                  if (renderer == null) {
383                    LOG.warn("can't find renderer for " + column.getClass().getName());
384                    renderer = ComponentUtil.getRenderer(facesContext, UIPanel.COMPONENT_FAMILY, RENDERER_TYPE_OUT);
385                  }
386                  width = renderer.getFixedWidth(facesContext, column);
387    
388                } else {
389                  for (UIComponent component : (List<UIComponent>) column.getChildren()) {
390                    LayoutInformationProvider renderer
391                        = ComponentUtil.getRenderer(facesContext, component);
392                    width += renderer.getFixedWidth(facesContext, component);
393                  }
394                }
395                layoutInfo.update(width, i);
396              } else {
397                layoutInfo.update(0, i);
398                if (LOG.isWarnEnabled()) {
399                  LOG.warn("More LayoutTokens found than rows! skipping!");
400                }
401              }
402            }
403            if (LOG.isDebugEnabled()) {
404              LOG.debug("set column " + i + " from fixed to with " + width);
405            }
406          }
407        }
408      }
409    
410    
411      private void prepareDimensions(FacesContext facesContext) {
412        // prepare width's in column's children components
413    
414        List<Integer> columnWidths = getWidthList();
415        int i = 0;
416        for (UIColumn column : getRenderedColumns()) {
417          if (i < columnWidths.size()) {
418            Integer width = columnWidths.get(i);
419            if (!(column instanceof UIColumnSelector)) {
420              if (column.getChildCount() == 1) {
421                UIComponent child = (UIComponent) column.getChildren().get(0);
422                int cellPaddingWidth = ((LayoutableRendererBase) getRenderer(facesContext))
423                    .getConfiguredValue(facesContext, this, "cellPaddingWidth");
424                child.getAttributes().put(
425                    ATTR_LAYOUT_WIDTH, width - cellPaddingWidth);
426                child.getAttributes().remove(ATTR_INNER_WIDTH);
427              } else {
428                LOG.warn("More or less than 1 child in column! "
429                    + "Can't set width for column " + i + " to " + width);
430              }
431            }
432          } else {
433            LOG.warn("More columns than columnSizes! "
434                + "Can't set width for column " + i);
435          }
436          i++;
437        }
438      }
439    
440      public int getLast() {
441        int last = getFirst() + getRows();
442        return last < getRowCount() ? last : getRowCount();
443      }
444    
445      public int getPage() {
446        int first = getFirst() + 1;
447        int rows = getRows();
448        if (rows == 0) {
449          // avoid division by zero
450          return 0;
451        }
452        if ((first % rows) > 0) {
453          return (first / rows) + 1;
454        } else {
455          return (first / rows);
456        }
457      }
458    
459      public int getPages() {
460        int rows = getRows();
461        if (rows == 0) {
462          return 0;
463        }
464        return getRowCount() / rows + (getRowCount() % rows == 0 ? 0 : 1);
465      }
466    
467      public List<UIComponent> getRenderedChildrenOf(UIColumn column) {
468        List<UIComponent> children = new ArrayList<UIComponent>();
469        for (Object o : column.getChildren()) {
470          UIComponent kid = (UIComponent) o;
471          if (kid.isRendered()) {
472            children.add(kid);
473          }
474        }
475        return children;
476      }
477    
478      public boolean isAtBeginning() {
479        return getFirst() == 0;
480      }
481    
482      public boolean hasRowCount() {
483        return getRowCount() != -1;
484      }
485    
486      public boolean isAtEnd() {
487        if (!hasRowCount()) {
488          setRowIndex(getFirst() + getRows() + 1);
489          return !isRowAvailable();
490        } else {
491          return getFirst() >= getLastPageIndex();
492        }
493      }
494    
495      public int getLastPageIndex() {
496        int rows = getRows();
497        if (rows == 0) {
498          // avoid division by zero
499          return 0;
500        }
501        int rowCount = getRowCount();
502        int tail = rowCount % rows;
503        return rowCount - (tail != 0 ? tail : rows);
504      }
505    
506      public void processUpdates(FacesContext context) {
507        super.processUpdates(context);
508        updateSheetState(context);
509      }
510    
511      private void updateSheetState(FacesContext facesContext) {
512        SheetState state = getSheetState(facesContext);
513        if (state != null) {
514          // ensure sortActionListener
515    //      getSortActionListener();
516    //      state.setSortedColumn(sortActionListener != null ? sortActionListener.getColumn() : -1);
517    //      state.setAscending(sortActionListener != null && sortActionListener.isAscending());
518          Map attributes = getAttributes();
519          //noinspection unchecked
520          final List<Integer> list = (List<Integer>) attributes.get(ATTR_SELECTED_LIST_STRING);
521          state.setSelectedRows(list != null ? list : Collections.<Integer>emptyList());
522          state.setColumnWidths((String) attributes.get(ATTR_WIDTH_LIST_STRING));
523          state.setScrollPosition((Integer[]) attributes.get(ATTR_SCROLL_POSITION));
524          attributes.remove(ATTR_SELECTED_LIST_STRING);
525          attributes.remove(ATTR_SCROLL_POSITION);
526        }
527      }
528    
529    
530      public Object saveState(FacesContext context) {
531        Object[] saveState = new Object[12];
532        saveState[0] = super.saveState(context);
533        saveState[1] = sheetState;
534        saveState[2] = saveAttachedState(context, sortActionListener);
535        saveState[3] = saveAttachedState(context, stateChangeListener);
536        saveState[4] = showHeader;
537        saveState[5] = showRowRange;
538        saveState[6] = showPageRange;
539        saveState[7] = showDirectLinks;
540        saveState[8] = directLinkCount;
541        saveState[9] = selectable;
542        saveState[10] = columns;
543        saveState[11] = rows;
544        return saveState;
545      }
546    
547      public void restoreState(FacesContext context, Object savedState) {
548        Object[] values = (Object[]) savedState;
549        super.restoreState(context, values[0]);
550        sheetState = (SheetState) values[1];
551        sortActionListener = (MethodBinding) restoreAttachedState(context, values[2]);
552        stateChangeListener = (MethodBinding) restoreAttachedState(context, values[3]);
553        showHeader = (Boolean) values[4];
554        showRowRange = (String) values[5];
555        showPageRange = (String) values[6];
556        showDirectLinks = (String) values[7];
557        directLinkCount = (Integer) values[8];
558        selectable = (String) values[9];
559        columns = (String) values[10];
560        rows = (Integer) values[11];
561      }
562    
563    
564      public List<UIColumn> getAllColumns() {
565        List<UIColumn> columns = new ArrayList<UIColumn>();
566        for (UIComponent kid : (List<UIComponent>) getChildren()) {
567          if (kid instanceof UIColumn && !(kid instanceof UIColumnEvent)) {
568            columns.add((UIColumn) kid);
569          }
570        }
571        return columns;
572      }
573    
574      public List<UIColumn> getRenderedColumns() {
575        List<UIColumn> columns = new ArrayList<UIColumn>();
576        for (UIComponent kid : (List<UIComponent>) getChildren()) {
577          if (kid instanceof UIColumn && kid.isRendered() && !(kid instanceof UIColumnEvent)) {
578            columns.add((UIColumn) kid);
579          }
580        }
581        return columns;
582      }
583    
584      public MethodBinding getSortActionListener() {
585        if (sortActionListener != null) {
586          return sortActionListener;
587        } else {
588          return new Sorter();
589        }
590      }
591    
592      public void setSortActionListener(MethodBinding sortActionListener) {
593        this.sortActionListener = sortActionListener;
594      }
595    
596      public void queueEvent(FacesEvent facesEvent) {
597        UIComponent parent = getParent();
598        if (parent == null) {
599          throw new IllegalStateException(
600              "component is not a descendant of a UIViewRoot");
601        }
602    
603        if (facesEvent.getComponent() == this
604            && (facesEvent instanceof SheetStateChangeEvent
605            || facesEvent instanceof PageActionEvent)) {
606          facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
607          if (LOG.isInfoEnabled()) {
608            LOG.info("queueEvent = \"" + facesEvent + "\"");
609          }
610          parent.queueEvent(facesEvent);
611        } else {
612          UIComponent source = facesEvent.getComponent();
613          UIComponent sourceParent = source.getParent();
614          if (sourceParent.getParent() == this
615              && source.getId() != null && source.getId().endsWith(SORTER_ID)) {
616            facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
617            parent.queueEvent(new SortActionEvent(this, (UIColumn) sourceParent));
618          } else {
619            super.queueEvent(facesEvent);
620          }
621        }
622      }
623    
624      public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
625        super.broadcast(facesEvent);
626        if (facesEvent instanceof SheetStateChangeEvent) {
627          invokeMethodBinding(getStateChangeListener(), facesEvent);
628        } else if (facesEvent instanceof PageActionEvent) {
629          invokeMethodBinding(new Pager(), facesEvent);
630          invokeMethodBinding(getStateChangeListener(), new SheetStateChangeEvent(this));
631        } else if (facesEvent instanceof SortActionEvent) {
632          getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
633          invokeMethodBinding(getSortActionListener(), facesEvent);
634        }
635      }
636    
637      private void invokeMethodBinding(MethodBinding methodBinding, FacesEvent event) {
638        if (methodBinding != null && event != null) {
639          try {
640            Object[] objects = new Object[]{event};
641            methodBinding.invoke(getFacesContext(), objects);
642          } catch (EvaluationException e) {
643            Throwable cause = e.getCause();
644            if (cause instanceof AbortProcessingException) {
645              throw (AbortProcessingException) cause;
646            } else {
647              throw e;
648            }
649          }
650        }
651      }
652    
653      public void addStateChangeListener(SheetStateChangeListener listener) {
654        addFacesListener(listener);
655      }
656    
657      public SheetStateChangeListener[] getStateChangeListeners() {
658        return (SheetStateChangeListener[]) getFacesListeners(SheetStateChangeListener.class);
659      }
660    
661      public void removeStateChangeListener(SheetStateChangeListener listener) {
662        removeFacesListener(listener);
663      }
664    
665      public MethodBinding getStateChangeListener() {
666        return stateChangeListener;
667      }
668    
669      public void setStateChangeListener(MethodBinding stateChangeListener) {
670        this.stateChangeListener = stateChangeListener;
671      }
672    
673      public List<Integer> getWidthList() {
674        return widthList;
675      }
676    
677      public int getRows() {
678        if (rows != null) {
679          return rows;
680        }
681        ValueBinding vb = getValueBinding(ATTR_ROWS);
682        if (vb != null) {
683          return (Integer) vb.getValue(getFacesContext());
684        } else {
685          return DEFAULT_ROW_COUNT;
686        }
687      }
688    
689      public void setRows(int rows) {
690        this.rows = rows;
691      }
692    
693      public boolean isShowHeader() {
694        if (showHeader != null) {
695          return showHeader;
696        }
697        ValueBinding vb = getValueBinding(ATTR_SHOW_HEADER);
698        if (vb != null) {
699          return (!Boolean.FALSE.equals(vb.getValue(getFacesContext())));
700        } else {
701          return true;
702        }
703      }
704    
705      public void setShowHeader(boolean showHeader) {
706        this.showHeader = showHeader;
707      }
708    
709      public void encodeAjax(FacesContext facesContext) throws IOException {
710        setupState(facesContext);
711        prepareDimensions(facesContext);
712        // TODO neets more testing!!!
713        //if (!facesContext.getRenderResponse() && !ComponentUtil.hasErrorMessages(facesContext)) {
714        // in encodeBegin of superclass is some logic which clears the DataModel
715        // this must here also done.
716        // in RI and myfaces this could done via setValue(null)
717        ValueBinding binding = getValueBinding("value");
718        if (binding != null) {
719          setValue(null);
720        } else {
721          setValue(getValue());
722        }
723        //}
724        AjaxUtils.encodeAjaxComponent(facesContext, this);
725      }
726    
727      public void processAjax(FacesContext facesContext) throws IOException {
728        final String ajaxId = (String) facesContext.getExternalContext()
729            .getRequestParameterMap().get(AjaxPhaseListener.AJAX_COMPONENT_ID);
730        if (ajaxId.equals(getClientId(facesContext))) {
731          AjaxUtils.processActiveAjaxComponent(facesContext, this);
732        } else {
733          AjaxUtils.processAjaxOnChildren(facesContext, this);
734        }
735      }
736    
737      public Integer[] getScrollPosition() {
738        Integer[] scrollPosition = (Integer[]) getAttributes().get(ATTR_SCROLL_POSITION);
739        if (scrollPosition == null) {
740          scrollPosition = getSheetState(FacesContext.getCurrentInstance()).getScrollPosition();
741        }
742        return scrollPosition;
743      }
744    
745      public UIComponent findComponent(String searchId) {
746        return super.findComponent(stripRowIndex(searchId));
747      }
748    
749      String stripRowIndex(String searchId) {
750        if (searchId.length() > 0 && Character.isDigit(searchId.charAt(0))) {
751          for (int i = 1; i < searchId.length(); ++i) {
752            char c = searchId.charAt(i);
753            if (c == SEPARATOR_CHAR) {
754              searchId = searchId.substring(i + 1);
755              break;
756            }
757            if (!Character.isDigit(c)) {
758              break;
759            }
760          }
761        }
762        return searchId;
763      }
764    }