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