/* | |
* 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.html5.renderkit.util; | |
import org.apache.myfaces.html5.component.util.ComponentUtils; | |
import java.lang.reflect.Array; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import java.util.Map; | |
import java.util.NoSuchElementException; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import javax.el.ValueExpression; | |
import javax.faces.application.ProjectStage; | |
import javax.faces.component.UIComponent; | |
import javax.faces.component.UISelectItem; | |
import javax.faces.component.UISelectItems; | |
import javax.faces.context.FacesContext; | |
import javax.faces.model.SelectItem; | |
// ATTENTION | |
// This class is associated with javax.faces.component._SelectItemsIterator. | |
// Changes here should also be applied to this class. | |
/** | |
* @author Mathias Broekelmann (latest modification by $Author$) | |
* @author Jakob Korherr (jsf 2.0) | |
* @version $Revision$ $Date$ | |
*/ | |
public class SelectItemsIterator implements Iterator<SelectItem> | |
{ | |
private static final Logger log = Logger.getLogger(SelectItemsIterator.class.getName()); | |
private static final String VAR_PROP = JsfProperties.VAR_PROP; | |
private static final String ITEM_VALUE_PROP = JsfProperties.ITEM_VALUE_PROP; | |
private static final String ITEM_LABEL_PROP = JsfProperties.ITEM_LABEL_PROP; | |
private static final String ITEM_DESCRIPTION_PROP = JsfProperties.ITEM_DESCRIPTION_PROP; | |
private static final String ITEM_DISABLED_PROP = JsfProperties.ITEM_DISABLED_PROP; | |
private static final String ITEM_LABEL_ESCAPED_PROP = JsfProperties.ITEM_LABEL_ESCAPED_PROP; | |
private static final String NO_SELECTION_VALUE_PROP = JsfProperties.NO_SELECTION_VALUE_PROP; | |
private final Iterator<UIComponent> _children; | |
private Iterator<? extends Object> _nestedItems; | |
private SelectItem _nextItem; | |
private UISelectItems _currentUISelectItems; | |
private FacesContext _facesContext; | |
public SelectItemsIterator(UIComponent selectItemsParent, FacesContext facesContext) | |
{ | |
_children = selectItemsParent.getChildren().iterator(); | |
_facesContext = facesContext; | |
} | |
@SuppressWarnings("unchecked") | |
public boolean hasNext() | |
{ | |
if (_nextItem != null) | |
{ | |
return true; | |
} | |
if (_nestedItems != null) | |
{ | |
if (_nestedItems.hasNext()) | |
{ | |
return true; | |
} | |
_nestedItems = 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) | |
{ | |
UISelectItem uiSelectItem = (UISelectItem) child; | |
Object item = uiSelectItem.getValue(); | |
if (item == null) | |
{ | |
// no value attribute --> create the SelectItem out of the other attributes | |
Object itemValue = uiSelectItem.getItemValue(); | |
String label = uiSelectItem.getItemLabel(); | |
String description = uiSelectItem.getItemDescription(); | |
boolean disabled = uiSelectItem.isItemDisabled(); | |
boolean escape = uiSelectItem.isItemEscaped(); | |
boolean noSelectionOption = uiSelectItem.isNoSelectionOption(); | |
if (label == null) | |
{ | |
label = itemValue.toString(); | |
} | |
item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption); | |
} | |
else if (!(item instanceof SelectItem)) | |
{ | |
ValueExpression expression = uiSelectItem.getValueExpression("value"); | |
throw new IllegalArgumentException("ValueExpression '" | |
+ (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : " | |
+ ComponentUtils.getPathToComponent(child) + " does not reference an Object of type SelectItem"); | |
} | |
_nextItem = (SelectItem) item; | |
return true; | |
} | |
else if (child instanceof UISelectItems) | |
{ | |
_currentUISelectItems = ((UISelectItems) child); | |
Object value = _currentUISelectItems.getValue(); | |
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); | |
Collection<Object> items = new ArrayList<Object>(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) | |
{ | |
Map<Object, Object> map = ((Map<Object, Object>) value); | |
Collection<SelectItem> items = new ArrayList<SelectItem>(map.size()); | |
for (Map.Entry<Object, Object> entry : map.entrySet()) | |
{ | |
items.add(new SelectItem(entry.getValue(), entry.getKey().toString())); | |
} | |
_nestedItems = items.iterator(); | |
return hasNext(); | |
} | |
else | |
{ | |
Level level = Level.FINE; | |
if (!_facesContext.isProjectStage(ProjectStage.Production)) | |
{ | |
level = Level.WARNING; | |
} | |
if (log.isLoggable(level)) | |
{ | |
ValueExpression expression = _currentUISelectItems.getValueExpression("value"); | |
log.log(level, "ValueExpression {0} of UISelectItems with component-path {1}" | |
+ " does not reference an Object of type SelectItem," | |
+ " array, Iterable or Map, but of type: {2}", | |
new Object[] { | |
(expression == null ? null : expression.getExpressionString()), | |
ComponentUtils.getPathToComponent(child), | |
(value == null ? null : value.getClass().getName()) | |
}); | |
} | |
} | |
} | |
} | |
return false; | |
} | |
public SelectItem next() | |
{ | |
if (!hasNext()) | |
{ | |
throw new NoSuchElementException(); | |
} | |
if (_nextItem != null) | |
{ | |
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 | |
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_PROP); | |
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_PROP); | |
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_PROP); | |
if (itemLabel == null) | |
{ | |
itemLabel = itemValue.toString(); | |
} | |
else | |
{ | |
itemLabel = itemLabel.toString(); | |
} | |
Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_PROP); | |
if (itemDescription != null) | |
{ | |
itemDescription = itemDescription.toString(); | |
} | |
Boolean itemDisabled = getBooleanAttribute(_currentUISelectItems, ITEM_DISABLED_PROP, false); | |
Boolean itemLabelEscaped = getBooleanAttribute(_currentUISelectItems, ITEM_LABEL_ESCAPED_PROP, true); | |
Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_PROP); | |
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(); | |
} | |
public void remove() | |
{ | |
throw new UnsupportedOperationException(); | |
} | |
private boolean getBooleanAttribute(UIComponent component, String attrName, boolean defaultValue) | |
{ | |
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()); | |
} | |
} | |
} |