View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.renderkit.renderer;
21  
22  import org.apache.myfaces.tobago.internal.component.AbstractUISelectManyBase;
23  import org.apache.myfaces.tobago.internal.util.ArrayUtils;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import javax.el.ValueExpression;
28  import javax.faces.FacesException;
29  import javax.faces.application.ProjectStage;
30  import javax.faces.component.UIComponent;
31  import javax.faces.component.UIInput;
32  import javax.faces.component.UISelectItem;
33  import javax.faces.component.UISelectItems;
34  import javax.faces.component.UISelectMany;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.FacesContext;
37  import javax.faces.convert.Converter;
38  import javax.faces.convert.ConverterException;
39  import javax.faces.model.SelectItem;
40  import javax.faces.model.SelectItemGroup;
41  import java.lang.invoke.MethodHandles;
42  import java.lang.reflect.Array;
43  import java.lang.reflect.Method;
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collection;
47  import java.util.HashSet;
48  import java.util.Iterator;
49  import java.util.LinkedList;
50  import java.util.Map;
51  import java.util.NoSuchElementException;
52  import java.util.Queue;
53  import java.util.Set;
54  import java.util.SortedSet;
55  import java.util.TreeSet;
56  
57  public abstract class SelectManyRendererBase<T extends AbstractUISelectManyBase> extends MessageLayoutRendererBase<T> {
58  
59    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
60  
61    @Override
62    protected boolean isOutputOnly(T component) {
63      return component.isDisabled() || component.isReadonly();
64    }
65  
66    @Override
67    public void decodeInternal(final FacesContext facesContext, final T component) {
68      if (isOutputOnly(component)) {
69        return;
70      }
71  
72      String[] newValues =
73          facesContext.getExternalContext().getRequestParameterValuesMap().get(component.getClientId(facesContext));
74      if (LOG.isDebugEnabled()) {
75        LOG.debug("decode: key='" + component.getClientId(facesContext)
76            + "' value='" + Arrays.toString(newValues) + "'");
77        LOG.debug("size ... '" + (newValues != null ? newValues.length : -1) + "'");
78        if (newValues != null) {
79          for (final String newValue : newValues) {
80            LOG.debug("newValues[i] = '" + newValue + "'");
81          }
82        }
83      }
84  
85      if (newValues == null) {
86        newValues = ArrayUtils.EMPTY_STRING_ARRAY; // because no selection will not submitted by browsers
87      }
88      component.setSubmittedValue(newValues);
89  
90      decodeClientBehaviors(facesContext, component);
91    }
92  
93    public String[] getSubmittedValues(final UIInput input) {
94      return (String[]) input.getSubmittedValue();
95    }
96  
97    @Override
98    public Object getConvertedValueInternal(
99        final FacesContext facesContext, final T component, final Object submittedValue)
100       throws ConverterException {
101 
102     if (submittedValue == null) {
103       return null;
104     } else {
105       if (!(submittedValue instanceof String[])) {
106         throw new ConverterException("Submitted value not of type String[] for component : "
107             + component.getClientId(facesContext) + "expected");
108       }
109     }
110     return getConvertedUISelectManyValue(facesContext, component, (String[]) submittedValue);
111   }
112 
113   // #################################################################################################################
114   // #################################################################################################################
115   // ###  The following methods and classes are copied from myfaces api 2.2.8,
116   // ###  slightly modified to compile in this context.
117   // ###  We copy this to avoid the dependency
118   // #################################################################################################################
119   // #################################################################################################################
120 
121   // #################################################################################################################
122   // ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
123   // ###     api/src/main/java/javax/faces/component/_SharedRendererUtils.java
124   // #################################################################################################################
125   static final String COLLECTION_TYPE_KEY = "collectionType";
126   static final String VALUE_TYPE_KEY = "valueType";
127 
128   static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
129                                               final String[] submittedValue) throws ConverterException {
130     return getConvertedUISelectManyValue(facesContext, component,
131         submittedValue, false);
132   }
133 
134   /**
135    * Gets the converted value of a UISelectMany component.
136    * If the considerValueType is true, this method will also consider the
137    * valueType attribute of Tomahawk UISelectMany components.
138    *
139    * @param facesContext
140    * @param component
141    * @param submittedValue
142    * @param considerValueType
143    * @return
144    * @throws ConverterException
145    */
146   static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
147                                               final String[] submittedValue, final boolean considerValueType)
148       throws ConverterException {
149     // Attention!
150     // This code is duplicated in shared renderkit package (except for considerValueType).
151     // If you change something here please do the same in the other class!
152 
153     if (submittedValue == null) {
154       throw new NullPointerException("submittedValue");
155     }
156 
157     final ValueExpression expression = component.getValueExpression("value");
158     Object targetForConvertedValues = null;
159 
160     // if the component has an attached converter, use it
161     Converter converter = component.getConverter();
162     // at this point the valueType attribute is handled in shared.
163     if (converter == null && considerValueType) {
164       // try to get a converter from the valueType attribute
165       converter = getValueTypeConverter(facesContext, component);
166     }
167 
168     if (expression != null) {
169       final Class<?> modelType = expression
170           .getType(facesContext.getELContext());
171       if (modelType == null) {
172         // FIXME temporal workaround for MYFACES-2552
173         return submittedValue;
174       } else if (modelType.isArray()) {
175         // the target should be an array
176         final Class<?> componentType = modelType.getComponentType();
177         // check for optimization if the target is
178         // a string array --> no conversion needed
179         if (String.class.equals(componentType)) {
180           return submittedValue;
181         }
182         if (converter == null) {
183           // the compononent does not have an attached converter
184           // --> try to get a registered-by-class converter
185           converter = facesContext.getApplication().createConverter(
186               componentType);
187 
188           if (converter == null && !Object.class.equals(componentType)) {
189             // could not obtain a Converter
190             // --> check if we maybe do not really have to convert
191 
192             // target is not an Object array
193             // and not a String array (checked some lines above)
194             // and we do not have a Converter
195             throw new ConverterException(
196                 "Could not obtain a Converter for "
197                     + componentType.getName());
198           }
199         }
200         // instantiate the array
201         targetForConvertedValues = Array.newInstance(componentType,
202             submittedValue.length);
203       } else if (Collection.class.isAssignableFrom(modelType) || Object.class.equals(modelType)) {
204         if (converter == null) {
205           // try to get the by-type-converter from the type of the SelectItems
206           final SelectItemsIterator iterator = new SelectItemsIterator(component, facesContext);
207           converter = getSelectItemsValueConverter(iterator, facesContext);
208         }
209 
210         final Object collectionTypeAttr = component.getAttributes().get(
211             COLLECTION_TYPE_KEY);
212         if (collectionTypeAttr != null) {
213           final Class<?> collectionType = getClassFromAttribute(facesContext, collectionTypeAttr);
214           if (collectionType == null) {
215             throw new FacesException(
216                 "The attribute "
217                     + COLLECTION_TYPE_KEY
218                     + " of component "
219                     + component.getClientId(facesContext)
220                     + " does not evaluate to a "
221                     + "String, a Class object or a ValueExpression pointing "
222                     + "to a String or a Class object.");
223           }
224           // now we have a collectionType --> but is it really some kind of Collection
225           if (!Collection.class.isAssignableFrom(collectionType)) {
226             throw new FacesException("The attribute "
227                 + COLLECTION_TYPE_KEY + " of component "
228                 + component.getClientId(facesContext)
229                 + " does not point to a valid type of Collection.");
230           }
231           // now we have a real collectionType --> try to instantiate it
232           try {
233             targetForConvertedValues = collectionType.newInstance();
234           } catch (final Exception e) {
235             throw new FacesException("The Collection "
236                 + collectionType.getName()
237                 + "can not be instantiated.", e);
238           }
239         } else if (Collection.class.isAssignableFrom(modelType)) {
240           // component.getValue() will implement Collection at this point
241           final Collection<?> componentValue = (Collection<?>) component.getValue();
242           // can we clone the Collection
243           if (componentValue instanceof Cloneable) {
244             // clone method of Object is protected --> use reflection
245             try {
246               final Method cloneMethod = componentValue.getClass()
247                   .getMethod("clone");
248               final Collection<?> clone = (Collection<?>) cloneMethod
249                   .invoke(componentValue);
250               clone.clear();
251               targetForConvertedValues = clone;
252             } catch (final Exception e) {
253               LOG.error("Could not clone " + componentValue.getClass().getName(), e);
254             }
255           }
256 
257           // if clone did not work
258           if (targetForConvertedValues == null) {
259             // try to create the (concrete) collection from modelType
260             // or with the class object of componentValue (if any)
261             try {
262               targetForConvertedValues = (componentValue != null
263                   ? componentValue.getClass()
264                   : modelType).newInstance();
265             } catch (final Exception e) {
266               // this did not work either
267               // use the standard concrete type
268               if (SortedSet.class.isAssignableFrom(modelType)) {
269                 targetForConvertedValues = new TreeSet();
270               } else if (Queue.class.isAssignableFrom(modelType)) {
271                 targetForConvertedValues = new LinkedList();
272               } else if (Set.class.isAssignableFrom(modelType)) {
273                 targetForConvertedValues = new HashSet(
274                     submittedValue.length);
275               } else {
276                 targetForConvertedValues = new ArrayList(
277                     submittedValue.length);
278               }
279             }
280           }
281         } else /* if (Object.class.equals(modelType)) */ {
282           // a modelType of Object is also permitted, in order to support
283           // managed bean properties of type Object
284 
285           // optimization: if we don't have a converter, we can return the submittedValue
286           if (converter == null) {
287             return submittedValue;
288           }
289 
290           targetForConvertedValues = new Object[submittedValue.length];
291         }
292       } else {
293         // the expression does neither point to an array nor to a collection
294         throw new ConverterException(
295             "ValueExpression for UISelectMany must be of type Collection or Array.");
296       }
297     } else {
298       targetForConvertedValues = new Object[submittedValue.length];
299     }
300 
301     // convert the values with the selected converter (if any)
302     // and store them in targetForConvertedValues
303     final boolean isArray = targetForConvertedValues.getClass().isArray();
304     for (int i = 0; i < submittedValue.length; i++) {
305       // get the value
306       final Object value;
307       if (converter != null) {
308         value = converter.getAsObject(facesContext, component,
309             submittedValue[i]);
310       } else {
311         value = submittedValue[i];
312       }
313       // store it in targetForConvertedValues
314       if (isArray) {
315         Array.set(targetForConvertedValues, i, value);
316       } else {
317         ((Collection) targetForConvertedValues).add(value);
318       }
319     }
320 
321     return targetForConvertedValues;
322   }
323 
324   /**
325    * Gets a Class object from a given component attribute. The attribute can
326    * be a ValueExpression (that evaluates to a String or a Class) or a
327    * String (that is a fully qualified Java class name) or a Class object.
328    *
329    * @param facesContext
330    * @param attribute
331    * @return
332    * @throws FacesException if the value is a String and the represented
333    *                        class cannot be found
334    */
335   static Class<?> getClassFromAttribute(final FacesContext facesContext,
336                                         final Object attribute) throws FacesException {
337     // Attention!
338     // This code is duplicated in shared renderkit package.
339     // If you change something here please do the same in the other class!
340 
341     Class<?> type = null;
342 
343     // if there is a value, it must be a ...
344     // ... a ValueExpression that evaluates to a String or a Class
345     final Object attr = attribute instanceof ValueExpression
346         // get the value of the ValueExpression
347         ? ((ValueExpression) attribute).getValue(facesContext.getELContext())
348         : attribute;
349     // ... String that is a fully qualified Java class name
350     if (attr instanceof String) {
351       try {
352         type = Class.forName((String) attr);
353       } catch (final ClassNotFoundException cnfe) {
354         throw new FacesException("Unable to find class " + attr + " on the classpath.", cnfe);
355       }
356     } else if (attr instanceof Class) {
357       // ... a Class object
358       type = (Class<?>) attr;
359     }
360 
361     return type;
362   }
363 
364   /**
365    * Uses the valueType attribute of the given UISelectMany component to
366    * get a by-type converter.
367    *
368    * @param facesContext
369    * @param component
370    * @return
371    */
372   static Converter getValueTypeConverter(final FacesContext facesContext, final UISelectMany component) {
373     Converter converter = null;
374 
375     final Object valueTypeAttr = component.getAttributes().get(VALUE_TYPE_KEY);
376     if (valueTypeAttr != null) {
377       // treat the valueType attribute exactly like the collectionType attribute
378       final Class<?> valueType = getClassFromAttribute(facesContext, valueTypeAttr);
379       if (valueType == null) {
380         throw new FacesException(
381             "The attribute "
382                 + VALUE_TYPE_KEY
383                 + " of component "
384                 + component.getClientId(facesContext)
385                 + " does not evaluate to a "
386                 + "String, a Class object or a ValueExpression pointing "
387                 + "to a String or a Class object.");
388       }
389       // now we have a valid valueType
390       // --> try to get a registered-by-class converter
391       converter = facesContext.getApplication().createConverter(valueType);
392 
393       if (converter == null) {
394         facesContext.getExternalContext().log("Found attribute valueType on component "
395             + getPathToComponent(component)
396             + ", but could not get a by-type converter for type "
397             + valueType.getName());
398       }
399     }
400 
401     return converter;
402   }
403 
404   /**
405    * Iterates through the SelectItems with the given Iterator and tries to obtain
406    * a by-class-converter based on the Class of SelectItem.getValue().
407    *
408    * @param iterator
409    * @param facesContext
410    * @return The first suitable Converter for the given SelectItems or null.
411    */
412   static Converter getSelectItemsValueConverter(final Iterator<SelectItem> iterator, final FacesContext facesContext) {
413     // Attention!
414     // This code is duplicated in jsfapi component package.
415     // If you change something here please do the same in the other class!
416 
417     Converter converter = null;
418     while (converter == null && iterator.hasNext()) {
419       final SelectItem item = iterator.next();
420       if (item instanceof SelectItemGroup) {
421         final Iterator<SelectItem> groupIterator = Arrays.asList(
422             ((SelectItemGroup) item).getSelectItems()).iterator();
423         converter = getSelectItemsValueConverter(groupIterator, facesContext);
424       } else {
425         final Class<?> selectItemsType = item.getValue().getClass();
426 
427         // optimization: no conversion for String values
428         if (String.class.equals(selectItemsType)) {
429           return null;
430         }
431 
432         try {
433           converter = facesContext.getApplication().createConverter(selectItemsType);
434         } catch (final FacesException e) {
435           // nothing - try again
436         }
437       }
438     }
439     return converter;
440   }
441   // #################################################################################################################
442   // ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
443   // ###     api/src/main/java/javax/faces/component/_SharedRendererUtils.java
444   // #################################################################################################################
445 
446   // #################################################################################################################
447   // ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
448   // ###     api/src/main/java/javax/faces/component/_ComponentUtils.java
449   // #################################################################################################################
450   static String getPathToComponent(final UIComponent component) {
451     final StringBuilder builder = new StringBuilder();
452 
453     if (component == null) {
454       builder.append("{Component-Path : ");
455       builder.append("[null]}");
456       return builder.toString();
457     }
458 
459     getPathToComponent(component, builder);
460 
461     builder.insert(0, "{Component-Path : ");
462     builder.append("}");
463 
464     return builder.toString();
465   }
466 
467   private static void getPathToComponent(final UIComponent component, final StringBuilder builder) {
468     if (component == null) {
469       return;
470     }
471 
472     final StringBuilder intBuilder = new StringBuilder();
473 
474     intBuilder.append("[Class: ");
475     intBuilder.append(component.getClass().getName());
476     if (component instanceof UIViewRoot) {
477       intBuilder.append(",ViewId: ");
478       intBuilder.append(((UIViewRoot) component).getViewId());
479     } else {
480       intBuilder.append(",Id: ");
481       intBuilder.append(component.getId());
482     }
483     intBuilder.append("]");
484 
485     builder.insert(0, intBuilder.toString());
486 
487     getPathToComponent(component.getParent(), builder);
488   }
489   // #################################################################################################################
490   // ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
491   // ###     api/src/main/java/javax/faces/component/_ComponentUtils.java
492   // #################################################################################################################
493 
494   // #################################################################################################################
495   // ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
496   // ###     api/src/main/java/javax/faces/component/_SelectItemsIterator.java
497   // #################################################################################################################
498   private static class SelectItemsIterator implements Iterator<SelectItem> {
499 
500     private static final Iterator<UIComponent> EMPTY_UICOMPONENT_ITERATOR = new EmptyIterator<>();
501 
502     // org.apache.myfaces.shared.util.SelectItemsIterator uses JSFAttr
503     private static final String VAR_ATTR = "var";
504     private static final String ITEM_VALUE_ATTR = "itemValue";
505     private static final String ITEM_LABEL_ATTR = "itemLabel";
506     private static final String ITEM_DESCRIPTION_ATTR = "itemDescription";
507     private static final String ITEM_DISABLED_ATTR = "itemDisabled";
508     private static final String ITEM_LABEL_ESCAPED_ATTR = "itemLabelEscaped";
509     private static final String NO_SELECTION_VALUE_ATTR = "noSelectionValue";
510 
511     private final Iterator<UIComponent> children;
512     private Iterator<?> nestedItems;
513     private SelectItem nextItem;
514     private UIComponent currentComponent;
515     private UISelectItems currentUISelectItems;
516     private FacesContext facesContext;
517 
518     SelectItemsIterator(final UIComponent selectItemsParent, final FacesContext facesContext) {
519       children = selectItemsParent.getChildCount() > 0
520           ? selectItemsParent.getChildren().iterator()
521           : EMPTY_UICOMPONENT_ITERATOR;
522       this.facesContext = facesContext;
523     }
524 
525     @Override
526     @SuppressWarnings("unchecked")
527     public boolean hasNext() {
528       if (nextItem != null) {
529         return true;
530       }
531       if (nestedItems != null) {
532         if (nestedItems.hasNext()) {
533           return true;
534         }
535         nestedItems = null;
536         currentComponent = null;
537       }
538       if (children.hasNext()) {
539         UIComponent child = children.next();
540         // When there is other components nested that does
541         // not extends from UISelectItem or UISelectItems
542         // the behavior for this iterator is just skip this
543         // element(s) until an element that extends from these
544         // classes are found. If there is no more elements
545         // that conform this condition, just return false.
546         while (!(child instanceof UISelectItem) && !(child instanceof UISelectItems)) {
547           // Try to skip it
548           if (children.hasNext()) {
549             // Skip and do the same check
550             child = children.next();
551           } else {
552             // End loop, so the final result is return false,
553             // since there are no more components to iterate.
554             return false;
555           }
556         }
557         if (child instanceof UISelectItem) {
558           final UISelectItem uiSelectItem = (UISelectItem) child;
559           Object item = uiSelectItem.getValue();
560           if (item == null) {
561             // no value attribute --> create the SelectItem out of the other attributes
562             final Object itemValue = uiSelectItem.getItemValue();
563             String label = uiSelectItem.getItemLabel();
564             final String description = uiSelectItem.getItemDescription();
565             final boolean disabled = uiSelectItem.isItemDisabled();
566             final boolean escape = uiSelectItem.isItemEscaped();
567             final boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
568             if (label == null) {
569               label = itemValue.toString();
570             }
571             item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption);
572           } else if (!(item instanceof SelectItem)) {
573             final ValueExpression expression = uiSelectItem.getValueExpression("value");
574             throw new IllegalArgumentException("ValueExpression '"
575                 + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
576                 + getPathToComponent(child) + " does not reference an Object of type SelectItem");
577           }
578           nextItem = (SelectItem) item;
579           currentComponent = child;
580           return true;
581         } else if (child instanceof UISelectItems) {
582           currentUISelectItems = (UISelectItems) child;
583           final Object value = currentUISelectItems.getValue();
584           currentComponent = child;
585 
586           if (value instanceof SelectItem) {
587             nextItem = (SelectItem) value;
588             return true;
589           } else if (value != null && value.getClass().isArray()) {
590             // value is any kind of array (primitive or non-primitive)
591             // --> we have to use class Array to get the values
592             final int length = Array.getLength(value);
593             final Collection<Object> items = new ArrayList<>(length);
594             for (int i = 0; i < length; i++) {
595               items.add(Array.get(value, i));
596             }
597             nestedItems = items.iterator();
598             return hasNext();
599           } else if (value instanceof Iterable) {
600             // value is Iterable --> Collection, DataModel,...
601             nestedItems = ((Iterable<?>) value).iterator();
602             return hasNext();
603           } else if (value instanceof Map) {
604             final Map<Object, Object> map = (Map<Object, Object>) value;
605             final Collection<SelectItem> items = new ArrayList<>(map.size());
606             for (final Map.Entry<Object, Object> entry : map.entrySet()) {
607               items.add(new SelectItem(entry.getValue(), entry.getKey().toString()));
608             }
609 
610             nestedItems = items.iterator();
611             return hasNext();
612           } else {
613 
614             if ((facesContext.isProjectStage(ProjectStage.Production) && LOG.isDebugEnabled())
615                 || LOG.isWarnEnabled()) {
616               final ValueExpression expression = currentUISelectItems.getValueExpression("value");
617               final Object[] objects = {
618                   expression == null ? null : expression.getExpressionString(),
619                   getPathToComponent(child),
620                   value == null ? null : value.getClass().getName()
621               };
622               final String message = "ValueExpression {0} of UISelectItems with component-path {1}"
623                   + " does not reference an Object of type SelectItem,"
624                   + " array, Iterable or Map, but of type: {2}";
625               if (facesContext.isProjectStage(ProjectStage.Production)) {
626                 LOG.debug(message, objects);
627               } else {
628                 LOG.warn(message, objects);
629               }
630             }
631           }
632         } else {
633           currentComponent = null;
634         }
635       }
636       return false;
637     }
638 
639     @Override
640     public SelectItem next() {
641       if (!hasNext()) {
642         throw new NoSuchElementException();
643       }
644       if (nextItem != null) {
645         final SelectItem value = nextItem;
646         nextItem = null;
647         return value;
648       }
649       if (nestedItems != null) {
650         Object item = nestedItems.next();
651 
652         if (!(item instanceof SelectItem)) {
653           // check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
654           // Note that according to the spec UISelectItems does not provide Getter and Setter
655           // methods for this values, so we have to use the attribute map
656           final Map<String, Object> attributeMap = currentUISelectItems.getAttributes();
657 
658           // write the current item into the request map under the key listed in var, if available
659           boolean wroteRequestMapVarValue = false;
660           Object oldRequestMapVarValue = null;
661           final String var = (String) attributeMap.get(VAR_ATTR);
662           if (var != null && !"".equals(var)) {
663             // save the current value of the key listed in var from the request map
664             oldRequestMapVarValue = facesContext.getExternalContext().getRequestMap().put(var, item);
665             wroteRequestMapVarValue = true;
666           }
667 
668           // check the itemValue attribute
669           Object itemValue = attributeMap.get(ITEM_VALUE_ATTR);
670           if (itemValue == null) {
671             // the itemValue attribute was not provided
672             // --> use the current item as the itemValue
673             itemValue = item;
674           }
675 
676           // Spec: When iterating over the select items, toString()
677           // must be called on the string rendered attribute values
678           Object itemLabel = attributeMap.get(ITEM_LABEL_ATTR);
679           if (itemLabel == null) {
680             if (itemValue != null) {
681               itemLabel = itemValue.toString();
682             }
683           } else {
684             itemLabel = itemLabel.toString();
685           }
686           Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_ATTR);
687           if (itemDescription != null) {
688             itemDescription = itemDescription.toString();
689           }
690           final Boolean itemDisabled = getBooleanAttribute(currentUISelectItems, ITEM_DISABLED_ATTR, false);
691           final Boolean itemLabelEscaped = getBooleanAttribute(currentUISelectItems, ITEM_LABEL_ESCAPED_ATTR, true);
692           final Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_ATTR);
693           item = new SelectItem(itemValue,
694               (String) itemLabel,
695               (String) itemDescription,
696               itemDisabled,
697               itemLabelEscaped,
698               itemValue.equals(noSelectionValue));
699 
700           // remove the value with the key from var from the request map, if previously written
701           if (wroteRequestMapVarValue) {
702             // If there was a previous value stored with the key from var in the request map, restore it
703             if (oldRequestMapVarValue != null) {
704               facesContext.getExternalContext()
705                   .getRequestMap().put(var, oldRequestMapVarValue);
706             } else {
707               facesContext.getExternalContext()
708                   .getRequestMap().remove(var);
709             }
710           }
711         }
712         return (SelectItem) item;
713       }
714       throw new NoSuchElementException();
715     }
716 
717     @Override
718     public void remove() {
719       throw new UnsupportedOperationException();
720     }
721 
722     public UIComponent getCurrentComponent() {
723       return currentComponent;
724     }
725 
726     private boolean getBooleanAttribute(
727         final UIComponent component, final String attrName, final boolean defaultValue) {
728       final Object value = component.getAttributes().get(attrName);
729       if (value == null) {
730         return defaultValue;
731       } else if (value instanceof Boolean) {
732         return (Boolean) value;
733       } else {
734         // If the value is a String, parse the boolean.
735         // This makes the following code work: <tag attribute="true" />,
736         // otherwise you would have to write <tag attribute="#{true}" />.
737         return Boolean.valueOf(value.toString());
738       }
739     }
740 
741     private String getPathToComponent(final UIComponent component) {
742       final StringBuilder builder = new StringBuilder();
743 
744       if (component == null) {
745         builder.append("{Component-Path : ");
746         builder.append("[null]}");
747         return builder.toString();
748       }
749 
750       getPathToComponent(component, builder);
751 
752       builder.insert(0, "{Component-Path : ");
753       builder.append("}");
754 
755       return builder.toString();
756     }
757 
758     private void getPathToComponent(final UIComponent component, final StringBuilder builder) {
759       if (component == null) {
760         return;
761       }
762 
763       final StringBuilder intBuilder = new StringBuilder();
764 
765       intBuilder.append("[Class: ");
766       intBuilder.append(component.getClass().getName());
767       if (component instanceof UIViewRoot) {
768         intBuilder.append(",ViewId: ");
769         intBuilder.append(((UIViewRoot) component).getViewId());
770       } else {
771         intBuilder.append(",Id: ");
772         intBuilder.append(component.getId());
773       }
774       intBuilder.append("]");
775 
776       builder.insert(0, intBuilder);
777 
778       getPathToComponent(component.getParent(), builder);
779     }
780   }
781   // #################################################################################################################
782   // ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
783   // ###     api/src/main/java/javax/faces/component/_SelectItemsIterator.java
784   // #################################################################################################################
785 
786   // #################################################################################################################
787   // ### BEGIN copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
788   // ###     api/src/main/java/javax/faces/component/_EmptyIterator.java
789   // #################################################################################################################
790   private static class EmptyIterator<T> implements Iterator<T> {
791 
792     @Override
793     public boolean hasNext() {
794       return false;
795     }
796 
797     @Override
798     public T next() {
799       throw new NoSuchElementException();
800     }
801 
802     @Override
803     public void remove() {
804       throw new UnsupportedOperationException();
805     }
806   }
807   // #################################################################################################################
808   // ### END copy out of https://svn.apache.org/repos/asf/myfaces/core/tags/myfaces-core-module-2.2.8/
809   // ###     api/src/main/java/javax/faces/component/_EmptyIterator.java
810   // #################################################################################################################
811 }