1

Why does JSF2/Facelet's ui:repeat not accept java.util.Iterator's for value? One can hide so much implementation and memory conservation behind an Iterator since length need not be known, it would be so useful to have. But instead I need to convert my Iterators to Lists and throw away all my advantages in order to ui:repeat.

There are probably phase or timing or Serializable reasons, but my skimming of the docs that are available do not give those reasons. Do we not yet have the science to make this impossibility possible?

DWoldrich
  • 3,817
  • 1
  • 21
  • 19

2 Answers2

2

<ui:repeat> doesn't support java.util.Iterator.
Have look at UIRepeat.getDataModel() source code:

 private DataModel getDataModel() {
    if (this.model == null) {
        Object val = this.getValue();
        if (val == null) {
            this.model = EMPTY_MODEL;
        } else if (val instanceof DataModel) {
            //noinspection unchecked
            this.model = (DataModel<Object>) val;
        } else if (val instanceof List) {
            //noinspection unchecked
            this.model = new ListDataModel<Object>((List<Object>) val);
        } else if (Object[].class.isAssignableFrom(val.getClass())) {
            this.model = new ArrayDataModel<Object>((Object[]) val);
        } else if (val instanceof ResultSet) {
            this.model = new ResultSetDataModel((ResultSet) val);
        } else {
            this.model = new ScalarDataModel<Object>(val);
        }
    }
    return this.model;
}
landal79
  • 1,520
  • 1
  • 11
  • 26
  • Thank you for pointing this out, I was searching for this code in order to answer my own question. You've answered the what, but I still fail to understand why. Additionally, it appears ResultSetDataModel does not support getRowCount() and always returns -1. Why then couldn't there be an IteratorDataModel that also returns -1 for getRowCount()? There must be a reason. – DWoldrich Apr 14 '14 at 16:12
  • I see from this code that I can craft a custom DataModel object for the value that is used verbatim. I should be able to produce an IteratorDataModel that wraps my Iterator and that ignores its Collection's length like ResultSetDataModel does. I will experiment with writing an IteratorDataModel class and if it works successfully, I will post it here. – DWoldrich Apr 14 '14 at 18:24
2

You can make <ui:repeat> work with an Iterator, but you must wrap the Iterator with an object that extends javax.faces.model.DataModel. Here is my IteratorDataModel class that can make any Iterator accessible to <ui:repeat>. I tested this with JSF 2.1.26:

/**
 * Make a DataModel out of an Iterator that may be used with JSF's ui:repeat tag
 *
 * This DataModel does not support the use of offset or step attributes in the
 * ui:repeat tag, and the size attribute in ui:repeat, when used with this DataModel, 
 * will report an inaccurate value.
 *
 * Copyright (c) 2014 Woldrich, Inc.
 * Licensed under MIT (https://clubcompy.com/wcm/license/mit.txt)
 * 
 * @author Dave Woldrich
 */
public class IteratorDataModel<T> extends DataModel<T> {
    private static final Logger logger = LogManager.getLogger(IteratorDataModel.class);

    private Iterator<?> iterator;
    private int rowIndex; 
    private T item;

    public IteratorDataModel(Iterator<?> iterator) {
        setWrappedData(iterator);      
    }

    @Override
    public int getRowCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public T getRowData() {
        return this.item;
    }

    @Override
    public int getRowIndex() {
        if(this.rowIndex == -1 && this.iterator.hasNext()) {
            this.setRowIndex(0);
        }

        if(logger.isTraceEnabled()) {
            logger.trace("getRowIndex returns " + this.rowIndex);
        }

        return this.rowIndex;
    }

    @Override
    public Object getWrappedData() {
        return this.iterator;
    }

    @Override
    public boolean isRowAvailable() {
        boolean hasNext = this.item != null || this.iterator.hasNext(); 

        if(logger.isTraceEnabled()) {
            logger.trace("isRowAvailable " + hasNext);
        }

        return hasNext;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setRowIndex(int newIndex) {
        if(logger.isTraceEnabled()) {
            logger.trace("setRowIndex (" + newIndex + ")");
        }

        if(newIndex == this.rowIndex+1) {
            this.rowIndex = newIndex;

            this.item = (T) (this.iterator.hasNext() ? this.iterator.next() : null);
        } 
        else if(newIndex > -1 || newIndex != this.rowIndex) {
            if(logger.isTraceEnabled()) {
                logger.trace("setRowIndex not incrementing by 1 as expected, ignored");            
            }
        }
    }

    @Override
    public void setWrappedData(Object data) {
        this.iterator = (Iterator<?>) data;
        this.rowIndex = -1;
        this.item = null;
    }    
}

And in your facelets code, for example, you may use:

<ul>
    <ui:repeat value="#{chatRoom.allComments}" var="aComment" varStatus="status">
        <li><h:outputText value="#{aComment.text}" /></li>
    </ui:repeat>
</ul>

where chatRoom.getAllComments() would return a IteratorDataModel<Comment> that is constructed passing an Iterator<Comment>.

DWoldrich
  • 3,817
  • 1
  • 21
  • 19