blob: 628620817ca72882a3b9e35a9bc5c2820a51d815 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.tobago.internal.renderkit.renderer;
import org.apache.myfaces.tobago.internal.component.AbstractUISelectManyBase;
import org.apache.myfaces.tobago.internal.util.ArrayUtils;
import org.apache.myfaces.tobago.util.ComponentUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.ProjectStage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectMany;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
public abstract class SelectManyRendererBase extends MessageLayoutRendererBase {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public void decode(final FacesContext facesContext, final UIComponent component) {
if (ComponentUtils.isOutputOnly(component)) {
return;
}
final AbstractUISelectManyBase select = (AbstractUISelectManyBase) component;
String[] newValues =
facesContext.getExternalContext().getRequestParameterValuesMap().get(select.getClientId(facesContext));
if (LOG.isDebugEnabled()) {
LOG.debug("decode: key='" + component.getClientId(facesContext)
+ "' value='" + Arrays.toString(newValues) + "'");
LOG.debug("size ... '" + (newValues != null ? newValues.length : -1) + "'");
if (newValues != null) {
for (final String newValue : newValues) {
LOG.debug("newValues[i] = '" + newValue + "'");
}
}
}
if (newValues == null) {
newValues = ArrayUtils.EMPTY_STRING_ARRAY; // because no selection will not submitted by browsers
}
select.setSubmittedValue(newValues);
decodeClientBehaviors(facesContext, select);
}
public String[] getSubmittedValues(final UIInput input) {
return (String[]) input.getSubmittedValue();
}
@Override
public Object getConvertedValue(
final FacesContext facesContext, final UIComponent component, final Object submittedValue)
throws ConverterException {
if (submittedValue == null) {
return null;
} else {
if (!(submittedValue instanceof String[])) {
throw new ConverterException("Submitted value not of type String[] for component : "
+ component.getClientId(facesContext) + "expected");
}
}
return getConvertedUISelectManyValue(facesContext, (UISelectMany) component, (String[]) submittedValue);
}
// #################################################################################################################
// #################################################################################################################
// ### The following methods and classes are copied from myfaces api 2.2.8,
// ### slightly modified to compile in this context.
// ### We copy this to avoid the dependency
// #################################################################################################################
// #################################################################################################################
// #################################################################################################################
// ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_SharedRendererUtils.java
// #################################################################################################################
static final String COLLECTION_TYPE_KEY = "collectionType";
static final String VALUE_TYPE_KEY = "valueType";
static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
final String[] submittedValue) throws ConverterException {
return getConvertedUISelectManyValue(facesContext, component,
submittedValue, false);
}
/**
* Gets the converted value of a UISelectMany component.
* If the considerValueType is true, this method will also consider the
* valueType attribute of Tomahawk UISelectMany components.
*
* @param facesContext
* @param component
* @param submittedValue
* @param considerValueType
* @return
* @throws ConverterException
*/
static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
final String[] submittedValue, final boolean considerValueType)
throws ConverterException {
// Attention!
// This code is duplicated in shared renderkit package (except for considerValueType).
// If you change something here please do the same in the other class!
if (submittedValue == null) {
throw new NullPointerException("submittedValue");
}
final ValueExpression expression = component.getValueExpression("value");
Object targetForConvertedValues = null;
// if the component has an attached converter, use it
Converter converter = component.getConverter();
// at this point the valueType attribute is handled in shared.
if (converter == null && considerValueType) {
// try to get a converter from the valueType attribute
converter = getValueTypeConverter(facesContext, component);
}
if (expression != null) {
final Class<?> modelType = expression
.getType(facesContext.getELContext());
if (modelType == null) {
// FIXME temporal workaround for MYFACES-2552
return submittedValue;
} else if (modelType.isArray()) {
// the target should be an array
final Class<?> componentType = modelType.getComponentType();
// check for optimization if the target is
// a string array --> no conversion needed
if (String.class.equals(componentType)) {
return submittedValue;
}
if (converter == null) {
// the compononent does not have an attached converter
// --> try to get a registered-by-class converter
converter = facesContext.getApplication().createConverter(
componentType);
if (converter == null && !Object.class.equals(componentType)) {
// could not obtain a Converter
// --> check if we maybe do not really have to convert
// target is not an Object array
// and not a String array (checked some lines above)
// and we do not have a Converter
throw new ConverterException(
"Could not obtain a Converter for "
+ componentType.getName());
}
}
// instantiate the array
targetForConvertedValues = Array.newInstance(componentType,
submittedValue.length);
} else if (Collection.class.isAssignableFrom(modelType) || Object.class.equals(modelType)) {
if (converter == null) {
// try to get the by-type-converter from the type of the SelectItems
final SelectItemsIterator iterator = new SelectItemsIterator(component, facesContext);
converter = getSelectItemsValueConverter(iterator, facesContext);
}
final Object collectionTypeAttr = component.getAttributes().get(
COLLECTION_TYPE_KEY);
if (collectionTypeAttr != null) {
final Class<?> collectionType = getClassFromAttribute(facesContext, collectionTypeAttr);
if (collectionType == null) {
throw new FacesException(
"The attribute "
+ COLLECTION_TYPE_KEY
+ " of component "
+ component.getClientId(facesContext)
+ " does not evaluate to a "
+ "String, a Class object or a ValueExpression pointing "
+ "to a String or a Class object.");
}
// now we have a collectionType --> but is it really some kind of Collection
if (!Collection.class.isAssignableFrom(collectionType)) {
throw new FacesException("The attribute "
+ COLLECTION_TYPE_KEY + " of component "
+ component.getClientId(facesContext)
+ " does not point to a valid type of Collection.");
}
// now we have a real collectionType --> try to instantiate it
try {
targetForConvertedValues = collectionType.newInstance();
} catch (final Exception e) {
throw new FacesException("The Collection "
+ collectionType.getName()
+ "can not be instantiated.", e);
}
} else if (Collection.class.isAssignableFrom(modelType)) {
// component.getValue() will implement Collection at this point
final Collection<?> componentValue = (Collection<?>) component
.getValue();
// can we clone the Collection
if (componentValue instanceof Cloneable) {
// clone method of Object is protected --> use reflection
try {
final Method cloneMethod = componentValue.getClass()
.getMethod("clone");
final Collection<?> clone = (Collection<?>) cloneMethod
.invoke(componentValue);
clone.clear();
targetForConvertedValues = clone;
} catch (final Exception e) {
LOG.error("Could not clone " + componentValue.getClass().getName(), e);
}
}
// if clone did not work
if (targetForConvertedValues == null) {
// try to create the (concrete) collection from modelType
// or with the class object of componentValue (if any)
try {
targetForConvertedValues = (componentValue != null
? componentValue.getClass()
: modelType).newInstance();
} catch (final Exception e) {
// this did not work either
// use the standard concrete type
if (SortedSet.class.isAssignableFrom(modelType)) {
targetForConvertedValues = new TreeSet();
} else if (Queue.class.isAssignableFrom(modelType)) {
targetForConvertedValues = new LinkedList();
} else if (Set.class.isAssignableFrom(modelType)) {
targetForConvertedValues = new HashSet(
submittedValue.length);
} else {
targetForConvertedValues = new ArrayList(
submittedValue.length);
}
}
}
} else /* if (Object.class.equals(modelType)) */ {
// a modelType of Object is also permitted, in order to support
// managed bean properties of type Object
// optimization: if we don't have a converter, we can return the submittedValue
if (converter == null) {
return submittedValue;
}
targetForConvertedValues = new Object[submittedValue.length];
}
} else {
// the expression does neither point to an array nor to a collection
throw new ConverterException(
"ValueExpression for UISelectMany must be of type Collection or Array.");
}
} else {
targetForConvertedValues = new Object[submittedValue.length];
}
// convert the values with the selected converter (if any)
// and store them in targetForConvertedValues
final boolean isArray = targetForConvertedValues.getClass().isArray();
for (int i = 0; i < submittedValue.length; i++) {
// get the value
final Object value;
if (converter != null) {
value = converter.getAsObject(facesContext, component,
submittedValue[i]);
} else {
value = submittedValue[i];
}
// store it in targetForConvertedValues
if (isArray) {
Array.set(targetForConvertedValues, i, value);
} else {
((Collection) targetForConvertedValues).add(value);
}
}
return targetForConvertedValues;
}
/**
* Gets a Class object from a given component attribute. The attribute can
* be a ValueExpression (that evaluates to a String or a Class) or a
* String (that is a fully qualified Java class name) or a Class object.
*
* @param facesContext
* @param attribute
* @return
* @throws FacesException if the value is a String and the represented
* class cannot be found
*/
static Class<?> getClassFromAttribute(final FacesContext facesContext,
final Object attribute) throws FacesException {
// Attention!
// This code is duplicated in shared renderkit package.
// If you change something here please do the same in the other class!
Class<?> type = null;
// if there is a value, it must be a ...
// ... a ValueExpression that evaluates to a String or a Class
final Object attr = attribute instanceof ValueExpression
// get the value of the ValueExpression
? ((ValueExpression) attribute).getValue(facesContext.getELContext())
: attribute;
// ... String that is a fully qualified Java class name
if (attr instanceof String) {
try {
type = Class.forName((String) attr);
} catch (final ClassNotFoundException cnfe) {
throw new FacesException("Unable to find class " + attr + " on the classpath.", cnfe);
}
} else if (attr instanceof Class) {
// ... a Class object
type = (Class<?>) attr;
}
return type;
}
/**
* Uses the valueType attribute of the given UISelectMany component to
* get a by-type converter.
*
* @param facesContext
* @param component
* @return
*/
static Converter getValueTypeConverter(final FacesContext facesContext, final UISelectMany component) {
Converter converter = null;
final Object valueTypeAttr = component.getAttributes().get(VALUE_TYPE_KEY);
if (valueTypeAttr != null) {
// treat the valueType attribute exactly like the collectionType attribute
final Class<?> valueType = getClassFromAttribute(facesContext, valueTypeAttr);
if (valueType == null) {
throw new FacesException(
"The attribute "
+ VALUE_TYPE_KEY
+ " of component "
+ component.getClientId(facesContext)
+ " does not evaluate to a "
+ "String, a Class object or a ValueExpression pointing "
+ "to a String or a Class object.");
}
// now we have a valid valueType
// --> try to get a registered-by-class converter
converter = facesContext.getApplication().createConverter(valueType);
if (converter == null) {
facesContext.getExternalContext().log("Found attribute valueType on component "
+ getPathToComponent(component)
+ ", but could not get a by-type converter for type "
+ valueType.getName());
}
}
return converter;
}
/**
* Iterates through the SelectItems with the given Iterator and tries to obtain
* a by-class-converter based on the Class of SelectItem.getValue().
*
* @param iterator
* @param facesContext
* @return The first suitable Converter for the given SelectItems or null.
*/
static Converter getSelectItemsValueConverter(final Iterator<SelectItem> iterator, final FacesContext facesContext) {
// Attention!
// This code is duplicated in jsfapi component package.
// If you change something here please do the same in the other class!
Converter converter = null;
while (converter == null && iterator.hasNext()) {
final SelectItem item = iterator.next();
if (item instanceof SelectItemGroup) {
final Iterator<SelectItem> groupIterator = Arrays.asList(
((SelectItemGroup) item).getSelectItems()).iterator();
converter = getSelectItemsValueConverter(groupIterator, facesContext);
} else {
final Class<?> selectItemsType = item.getValue().getClass();
// optimization: no conversion for String values
if (String.class.equals(selectItemsType)) {
return null;
}
try {
converter = facesContext.getApplication().createConverter(selectItemsType);
} catch (final FacesException e) {
// nothing - try again
}
}
}
return converter;
}
// #################################################################################################################
// ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_SharedRendererUtils.java
// #################################################################################################################
// #################################################################################################################
// ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_ComponentUtils.java
// #################################################################################################################
static String getPathToComponent(final UIComponent component) {
final StringBuilder builder = new StringBuilder();
if (component == null) {
builder.append("{Component-Path : ");
builder.append("[null]}");
return builder.toString();
}
getPathToComponent(component, builder);
builder.insert(0, "{Component-Path : ");
builder.append("}");
return builder.toString();
}
private static void getPathToComponent(final UIComponent component, final StringBuilder builder) {
if (component == null) {
return;
}
final StringBuilder intBuilder = new StringBuilder();
intBuilder.append("[Class: ");
intBuilder.append(component.getClass().getName());
if (component instanceof UIViewRoot) {
intBuilder.append(",ViewId: ");
intBuilder.append(((UIViewRoot) component).getViewId());
} else {
intBuilder.append(",Id: ");
intBuilder.append(component.getId());
}
intBuilder.append("]");
builder.insert(0, intBuilder.toString());
getPathToComponent(component.getParent(), builder);
}
// #################################################################################################################
// ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_ComponentUtils.java
// #################################################################################################################
// #################################################################################################################
// ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_SelectItemsIterator.java
// #################################################################################################################
private static class SelectItemsIterator implements Iterator<SelectItem> {
private static final Iterator<UIComponent> EMPTY_UICOMPONENT_ITERATOR = new EmptyIterator<>();
// org.apache.myfaces.shared.util.SelectItemsIterator uses JSFAttr
private static final String VAR_ATTR = "var";
private static final String ITEM_VALUE_ATTR = "itemValue";
private static final String ITEM_LABEL_ATTR = "itemLabel";
private static final String ITEM_DESCRIPTION_ATTR = "itemDescription";
private static final String ITEM_DISABLED_ATTR = "itemDisabled";
private static final String ITEM_LABEL_ESCAPED_ATTR = "itemLabelEscaped";
private static final String NO_SELECTION_VALUE_ATTR = "noSelectionValue";
private final Iterator<UIComponent> children;
private Iterator<?> nestedItems;
private SelectItem nextItem;
private UIComponent currentComponent;
private UISelectItems currentUISelectItems;
private FacesContext facesContext;
SelectItemsIterator(final UIComponent selectItemsParent, final FacesContext facesContext) {
children = selectItemsParent.getChildCount() > 0
? selectItemsParent.getChildren().iterator()
: EMPTY_UICOMPONENT_ITERATOR;
this.facesContext = facesContext;
}
@Override
@SuppressWarnings("unchecked")
public boolean hasNext() {
if (nextItem != null) {
return true;
}
if (nestedItems != null) {
if (nestedItems.hasNext()) {
return true;
}
nestedItems = null;
currentComponent = null;
}
if (children.hasNext()) {
UIComponent child = children.next();
// When there is other components nested that does
// not extends from UISelectItem or UISelectItems
// the behavior for this iterator is just skip this
// element(s) until an element that extends from these
// classes are found. If there is no more elements
// that conform this condition, just return false.
while (!(child instanceof UISelectItem) && !(child instanceof UISelectItems)) {
// Try to skip it
if (children.hasNext()) {
// Skip and do the same check
child = children.next();
} else {
// End loop, so the final result is return false,
// since there are no more components to iterate.
return false;
}
}
if (child instanceof UISelectItem) {
final UISelectItem uiSelectItem = (UISelectItem) child;
Object item = uiSelectItem.getValue();
if (item == null) {
// no value attribute --> create the SelectItem out of the other attributes
final Object itemValue = uiSelectItem.getItemValue();
String label = uiSelectItem.getItemLabel();
final String description = uiSelectItem.getItemDescription();
final boolean disabled = uiSelectItem.isItemDisabled();
final boolean escape = uiSelectItem.isItemEscaped();
final boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
if (label == null) {
label = itemValue.toString();
}
item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption);
} else if (!(item instanceof SelectItem)) {
final ValueExpression expression = uiSelectItem.getValueExpression("value");
throw new IllegalArgumentException("ValueExpression '"
+ (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
+ getPathToComponent(child) + " does not reference an Object of type SelectItem");
}
nextItem = (SelectItem) item;
currentComponent = child;
return true;
} else if (child instanceof UISelectItems) {
currentUISelectItems = (UISelectItems) child;
final Object value = currentUISelectItems.getValue();
currentComponent = child;
if (value instanceof SelectItem) {
nextItem = (SelectItem) value;
return true;
} else if (value != null && value.getClass().isArray()) {
// value is any kind of array (primitive or non-primitive)
// --> we have to use class Array to get the values
final int length = Array.getLength(value);
final Collection<Object> items = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
items.add(Array.get(value, i));
}
nestedItems = items.iterator();
return hasNext();
} else if (value instanceof Iterable) {
// value is Iterable --> Collection, DataModel,...
nestedItems = ((Iterable<?>) value).iterator();
return hasNext();
} else if (value instanceof Map) {
final Map<Object, Object> map = (Map<Object, Object>) value;
final Collection<SelectItem> items = new ArrayList<>(map.size());
for (final Map.Entry<Object, Object> entry : map.entrySet()) {
items.add(new SelectItem(entry.getValue(), entry.getKey().toString()));
}
nestedItems = items.iterator();
return hasNext();
} else {
if ((facesContext.isProjectStage(ProjectStage.Production) && LOG.isDebugEnabled())
|| LOG.isWarnEnabled()) {
final ValueExpression expression = currentUISelectItems.getValueExpression("value");
final Object[] objects = {
expression == null ? null : expression.getExpressionString(),
getPathToComponent(child),
value == null ? null : value.getClass().getName()
};
final String message = "ValueExpression {0} of UISelectItems with component-path {1}"
+ " does not reference an Object of type SelectItem,"
+ " array, Iterable or Map, but of type: {2}";
if (facesContext.isProjectStage(ProjectStage.Production)) {
LOG.debug(message, objects);
} else {
LOG.warn(message, objects);
}
}
}
} else {
currentComponent = null;
}
}
return false;
}
@Override
public SelectItem next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (nextItem != null) {
final SelectItem value = nextItem;
nextItem = null;
return value;
}
if (nestedItems != null) {
Object item = nestedItems.next();
if (!(item instanceof SelectItem)) {
// check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
// Note that according to the spec UISelectItems does not provide Getter and Setter
// methods for this values, so we have to use the attribute map
final Map<String, Object> attributeMap = currentUISelectItems.getAttributes();
// write the current item into the request map under the key listed in var, if available
boolean wroteRequestMapVarValue = false;
Object oldRequestMapVarValue = null;
final String var = (String) attributeMap.get(VAR_ATTR);
if (var != null && !"".equals(var)) {
// save the current value of the key listed in var from the request map
oldRequestMapVarValue = facesContext.getExternalContext().getRequestMap().put(var, item);
wroteRequestMapVarValue = true;
}
// check the itemValue attribute
Object itemValue = attributeMap.get(ITEM_VALUE_ATTR);
if (itemValue == null) {
// the itemValue attribute was not provided
// --> use the current item as the itemValue
itemValue = item;
}
// Spec: When iterating over the select items, toString()
// must be called on the string rendered attribute values
Object itemLabel = attributeMap.get(ITEM_LABEL_ATTR);
if (itemLabel == null) {
if (itemValue != null) {
itemLabel = itemValue.toString();
}
} else {
itemLabel = itemLabel.toString();
}
Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_ATTR);
if (itemDescription != null) {
itemDescription = itemDescription.toString();
}
final Boolean itemDisabled = getBooleanAttribute(currentUISelectItems, ITEM_DISABLED_ATTR, false);
final Boolean itemLabelEscaped = getBooleanAttribute(currentUISelectItems, ITEM_LABEL_ESCAPED_ATTR, true);
final Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_ATTR);
item = new SelectItem(itemValue,
(String) itemLabel,
(String) itemDescription,
itemDisabled,
itemLabelEscaped,
itemValue.equals(noSelectionValue));
// remove the value with the key from var from the request map, if previously written
if (wroteRequestMapVarValue) {
// If there was a previous value stored with the key from var in the request map, restore it
if (oldRequestMapVarValue != null) {
facesContext.getExternalContext()
.getRequestMap().put(var, oldRequestMapVarValue);
} else {
facesContext.getExternalContext()
.getRequestMap().remove(var);
}
}
}
return (SelectItem) item;
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
public UIComponent getCurrentComponent() {
return currentComponent;
}
private boolean getBooleanAttribute(
final UIComponent component, final String attrName, final boolean defaultValue) {
final Object value = component.getAttributes().get(attrName);
if (value == null) {
return defaultValue;
} else if (value instanceof Boolean) {
return (Boolean) value;
} else {
// If the value is a String, parse the boolean.
// This makes the following code work: <tag attribute="true" />,
// otherwise you would have to write <tag attribute="#{true}" />.
return Boolean.valueOf(value.toString());
}
}
private String getPathToComponent(final UIComponent component) {
final StringBuilder builder = new StringBuilder();
if (component == null) {
builder.append("{Component-Path : ");
builder.append("[null]}");
return builder.toString();
}
getPathToComponent(component, builder);
builder.insert(0, "{Component-Path : ");
builder.append("}");
return builder.toString();
}
private void getPathToComponent(final UIComponent component, final StringBuilder builder) {
if (component == null) {
return;
}
final StringBuilder intBuilder = new StringBuilder();
intBuilder.append("[Class: ");
intBuilder.append(component.getClass().getName());
if (component instanceof UIViewRoot) {
intBuilder.append(",ViewId: ");
intBuilder.append(((UIViewRoot) component).getViewId());
} else {
intBuilder.append(",Id: ");
intBuilder.append(component.getId());
}
intBuilder.append("]");
builder.insert(0, intBuilder);
getPathToComponent(component.getParent(), builder);
}
}
// #################################################################################################################
// ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_SelectItemsIterator.java
// #################################################################################################################
// #################################################################################################################
// ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_EmptyIterator.java
// #################################################################################################################
private static class EmptyIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return false;
}
@Override
public T next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
// #################################################################################################################
// ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
// ### api/src/main/java/javax/faces/component/_EmptyIterator.java
// #################################################################################################################
}