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