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.util;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Visual;
24  import org.apache.myfaces.tobago.context.Markup;
25  import org.apache.myfaces.tobago.util.ComponentUtils;
26  
27  import javax.el.ValueExpression;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.UISelectItem;
30  import javax.faces.component.UISelectItems;
31  import javax.faces.context.FacesContext;
32  import javax.faces.model.SelectItem;
33  import java.lang.reflect.Array;
34  import java.util.ArrayList;
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.NoSuchElementException;
41  
42  /**
43   * Based on code from MyFaces core.
44   */
45  public class SelectItemUtils {
46  
47    /**
48     * Creates a list of SelectItems to use for rendering.
49     */
50    public static Iterable<SelectItem> getItemIterator(final FacesContext facesContext, final UIComponent selector) {
51      if (selector.getChildCount() == 0) {
52        return Collections.emptyList();
53      } else {
54        return new Iterable<SelectItem>() {
55  
56          private SelectItemsIterator iterator;
57  
58          @Override
59          public Iterator<SelectItem> iterator() {
60            if (iterator == null) {
61              iterator = new SelectItemsIterator(facesContext, selector);
62            }
63            return iterator;
64          }
65        };
66      }
67    }
68  
69    /**
70     * Creates a list of SelectItems to use for rendering.
71     * You should only use this method (which returns a list), when you need a list.
72     * Otherwise please use {@link #getItemIterator(javax.faces.context.FacesContext, javax.faces.component.UIComponent)}
73     */
74    public static List<SelectItem> getItemList(final FacesContext facesContext, final UIComponent selector) {
75      if (selector.getChildCount() == 0) {
76        return Collections.emptyList();
77      } else {
78        final Iterable<SelectItem> iterator = getItemIterator(facesContext, selector);
79        final List<SelectItem> result = new ArrayList<SelectItem>();
80        for (SelectItem selectItem : iterator) {
81          result.add(selectItem);
82        }
83        return result;
84      }
85    }
86  
87    private static class SelectItemsIterator implements Iterator<SelectItem> {
88  
89      private final FacesContext facesContext;
90      private final Iterator<UIComponent> children;
91      private Iterator<?> nestedItems;
92      private SelectItem nextItem;
93      private UISelectItems currentUISelectItems;
94  
95      private SelectItemsIterator(final FacesContext facesContext, final UIComponent selector) {
96        this.children = selector.getChildren().iterator();
97        this.facesContext = facesContext;
98      }
99  
100     @Override
101     @SuppressWarnings("unchecked")
102     public boolean hasNext() {
103       if (nextItem != null) {
104         return true;
105       }
106       if (nestedItems != null) {
107         if (nestedItems.hasNext()) {
108           return true;
109         }
110         nestedItems = null;
111       }
112 
113       UIComponent child = null;
114       while (children.hasNext()) {
115         final UIComponent c = children.next();
116         // When there is other components nested that does
117         // not extends from UISelectItem or UISelectItems
118         // the behavior for this iterator is just skip this
119         // element(s) until an element that extends from these
120         // classes are found. If there is no more elements
121         // that conform this condition, just return false.
122         if (c instanceof UISelectItem || c instanceof UISelectItems) {
123           child = c;
124           break;
125         }
126       }
127       if (child == null) {
128         return false;
129       }
130 
131       if (child instanceof UISelectItem) {
132         final UISelectItem uiSelectItem = (UISelectItem) child;
133         Object item = uiSelectItem.getValue();
134         if (item == null) {
135           // no value attribute --> create the SelectItem out of the other attributes
136           final Object itemValue = uiSelectItem.getItemValue();
137           String label = uiSelectItem.getItemLabel();
138           final String description = uiSelectItem.getItemDescription();
139           final boolean disabled = uiSelectItem.isItemDisabled();
140 //          boolean escape = uiSelectItem.isItemEscaped();
141 //          boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
142           if (label == null) {
143             label = itemValue.toString();
144           }
145           String image = null;
146           Markup markup = null;
147           if (uiSelectItem instanceof org.apache.myfaces.tobago.component.UISelectItem) {
148             org.apache.myfaces.tobago.component.UISelectItem tobagoSelectItem
149                 = (org.apache.myfaces.tobago.component.UISelectItem) uiSelectItem;
150             image = tobagoSelectItem.getItemImage();
151             markup = tobagoSelectItem.getMarkup();
152           }
153           item = new org.apache.myfaces.tobago.model.SelectItem(itemValue, label, description, disabled, image, markup);
154         } else if (!(item instanceof SelectItem)) {
155           ValueExpression expression = uiSelectItem.getValueExpression("value");
156           throw new IllegalArgumentException("ValueExpression '"
157               + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
158               + child + " does not reference an Object of type SelectItem");
159         }
160         nextItem = (SelectItem) item;
161         return true;
162       } else { // UISelectItems
163         currentUISelectItems = ((UISelectItems) child);
164         final Object value = currentUISelectItems.getValue();
165 
166         if (value instanceof SelectItem) {
167           nextItem = (SelectItem) value;
168           return true;
169         } else if (value != null && value.getClass().isArray()) {
170           // value is any kind of array (primitive or non-primitive)
171           // --> we have to use class Array to get the values
172           final int length = Array.getLength(value);
173           final Collection<Object> items = new ArrayList<Object>(length);
174           for (int i = 0; i < length; i++) {
175             items.add(Array.get(value, i));
176           }
177           nestedItems = items.iterator();
178           return hasNext();
179         } else if (value instanceof Iterable) {
180           // value is Iterable --> Collection, DataModel,...
181           nestedItems = ((Iterable<?>) value).iterator();
182           return hasNext();
183         } else if (value instanceof Map) {
184           final Map<Object, Object> map = ((Map<Object, Object>) value);
185           final Collection<SelectItem> items = new ArrayList<SelectItem>(map.size());
186           for (Map.Entry<Object, Object> entry : map.entrySet()) {
187             items.add(new org.apache.myfaces.tobago.model.SelectItem(entry.getValue(), entry.getKey().toString()));
188           }
189           nestedItems = items.iterator();
190           return hasNext();
191         }
192       }
193       return false;
194     }
195 
196     @Override
197     public SelectItem next() {
198       if (!hasNext()) {
199         throw new NoSuchElementException();
200       }
201       if (nextItem != null) {
202         final SelectItem value = nextItem;
203         nextItem = null;
204         return value;
205       }
206       if (nestedItems != null) {
207         Object item = nestedItems.next();
208 
209         if (!(item instanceof SelectItem)) {
210           // check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
211           // Note that according to the spec UISelectItems does not provide Getter and Setter
212           // methods for this values, so we have to use the attribute map
213           final Map<String, Object> attributeMap = currentUISelectItems.getAttributes();
214 
215           // write the current item into the request map under the key listed in var, if available
216           boolean wroteRequestMapVarValue = false;
217           Object oldRequestMapVarValue = null;
218           final String var = ComponentUtils.getStringAttribute(currentUISelectItems, Attributes.var);
219           if (var != null && !"".equals(var)) {
220             // save the current value of the key listed in var from the request map
221             oldRequestMapVarValue = facesContext.getExternalContext().getRequestMap().put(var, item);
222             wroteRequestMapVarValue = true;
223           }
224 
225           // check the itemValue attribute
226           Object itemValue = ComponentUtils.getAttribute(currentUISelectItems, Attributes.itemValue);
227           if (itemValue == null) {
228             // the itemValue attribute was not provided
229             // --> use the current item as the itemValue
230             itemValue = item;
231           }
232 
233           // Spec: When iterating over the select items, toString()
234           // must be called on the string rendered attribute values
235           Object itemLabel = ComponentUtils.getAttribute(currentUISelectItems, Attributes.itemLabel);
236           if (itemLabel == null) {
237             itemLabel = itemValue.toString();
238           } else {
239             itemLabel = itemLabel.toString();
240           }
241           Object itemDescription = ComponentUtils.getAttribute(currentUISelectItems, Attributes.itemDescription);
242           if (itemDescription != null) {
243             itemDescription = itemDescription.toString();
244           }
245           final Boolean itemDisabled
246               = ComponentUtils.getBooleanAttribute(currentUISelectItems, Attributes.itemDisabled, false);
247           final String itemImage = ComponentUtils.getStringAttribute(currentUISelectItems, Attributes.itemImage);
248           final Markup markup;
249           if (currentUISelectItems instanceof Visual) {
250             markup = ((Visual) currentUISelectItems).getMarkup();
251           } else {
252             markup = Markup.NULL;
253           }
254 // TBD: should this be possible?
255 //        Boolean itemLabelEscaped = getBooleanAttribute(currentUISelectItems, ITEM_LABEL_ESCAPED_PROP, true);
256 // TBD ?
257 //        Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_PROP);
258           item = new org.apache.myfaces.tobago.model.SelectItem(
259               itemValue, (String) itemLabel, (String) itemDescription, itemDisabled, itemImage, markup);
260 
261           // remove the value with the key from var from the request map, if previously written
262           if (wroteRequestMapVarValue) {
263             // If there was a previous value stored with the key from var in the request map, restore it
264             if (oldRequestMapVarValue != null) {
265               facesContext.getExternalContext().getRequestMap().put(var, oldRequestMapVarValue);
266             } else {
267               facesContext.getExternalContext().getRequestMap().remove(var);
268             }
269           }
270         }
271         return (SelectItem) item;
272       }
273       throw new NoSuchElementException();
274     }
275 
276     @Override
277     public void remove() {
278       throw new UnsupportedOperationException();
279     }
280   }
281 
282 }