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  package org.apache.myfaces.shared.util;
20  
21  import java.lang.reflect.Array;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.NoSuchElementException;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import javax.el.ValueExpression;
31  import javax.faces.application.ProjectStage;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UISelectItem;
34  import javax.faces.component.UISelectItems;
35  import javax.faces.context.FacesContext;
36  import javax.faces.model.SelectItem;
37  
38  import org.apache.myfaces.shared.util.renderkit.JsfProperties;
39  
40  // ATTENTION
41  // This class is associated with javax.faces.component._SelectItemsIterator.
42  // Changes here should also be applied to this class.
43  
44  public class SelectItemsIterator implements Iterator<SelectItem>
45  {
46      private static final Logger log = Logger.getLogger(SelectItemsIterator.class.getName());
47      
48      private static final String VAR_PROP = JsfProperties.VAR_PROP;
49      private static final String ITEM_VALUE_PROP = JsfProperties.ITEM_VALUE_PROP;
50      private static final String ITEM_LABEL_PROP = JsfProperties.ITEM_LABEL_PROP;
51      private static final String ITEM_DESCRIPTION_PROP = JsfProperties.ITEM_DESCRIPTION_PROP;
52      private static final String ITEM_DISABLED_PROP = JsfProperties.ITEM_DISABLED_PROP;
53      private static final String ITEM_LABEL_ESCAPED_PROP = JsfProperties.ITEM_LABEL_ESCAPED_PROP;
54      private static final String NO_SELECTION_VALUE_PROP = JsfProperties.NO_SELECTION_VALUE_PROP;
55      
56      private static final Iterator<UIComponent> _EMPTY_UICOMPONENT_ITERATOR = new _EmptyIterator<UIComponent>();
57      
58      private final Iterator<UIComponent> _children;
59      private Iterator<? extends Object> _nestedItems;
60      private SelectItem _nextItem;
61      private UIComponent _currentComponent;
62      private UISelectItems _currentUISelectItems;
63      private Object _currentValue;
64      private FacesContext _facesContext;
65  
66      public SelectItemsIterator(UIComponent selectItemsParent, FacesContext facesContext)
67      {
68          _children = selectItemsParent.getChildCount() > 0
69                  ? selectItemsParent.getChildren().iterator()
70                  : _EMPTY_UICOMPONENT_ITERATOR;
71          _facesContext = facesContext;
72      }
73  
74      @SuppressWarnings("unchecked")
75      public boolean hasNext()
76      {
77          if (_nextItem != null)
78          {
79              return true;
80          }
81          if (_nestedItems != null)
82          {
83              if (_nestedItems.hasNext())
84              {
85                  return true;
86              }
87              _nestedItems = null;
88              _currentComponent = null;
89              _currentValue = null;
90          }
91          if (_children.hasNext())
92          {
93              UIComponent child = _children.next();
94              // When there is other components nested that does
95              // not extends from UISelectItem or UISelectItems
96              // the behavior for this iterator is just skip this
97              // element(s) until an element that extends from these
98              // classes are found. If there is no more elements
99              // that conform this condition, just return false.
100             while (!(child instanceof UISelectItem) && !(child instanceof UISelectItems))
101             {
102                 // Try to skip it
103                 if (_children.hasNext())
104                 {
105                     // Skip and do the same check
106                     child = _children.next();
107                 }
108                 else
109                 {
110                     // End loop, so the final result is return false,
111                     // since there are no more components to iterate.
112                     return false;
113                 }
114             }
115             if (child instanceof UISelectItem)
116             {
117                 UISelectItem uiSelectItem = (UISelectItem) child;
118                 Object item = uiSelectItem.getValue();
119                 if (item == null)
120                 {
121                     // no value attribute --> create the SelectItem out of the other attributes
122                     Object itemValue = uiSelectItem.getItemValue();
123                     String label = uiSelectItem.getItemLabel();
124                     String description = uiSelectItem.getItemDescription();
125                     boolean disabled = uiSelectItem.isItemDisabled();
126                     boolean escape = uiSelectItem.isItemEscaped();
127                     boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
128                     if (label == null && itemValue != null)
129                     {
130                         label = itemValue.toString();
131                     }
132                     item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption);
133                 }
134                 else if (!(item instanceof SelectItem))
135                 {
136                     ValueExpression expression = uiSelectItem.getValueExpression("value");
137                     throw new IllegalArgumentException("ValueExpression '"
138                             + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
139                             + DebugUtils.getPathToComponent(child)
140                             + " does not reference an Object of type SelectItem");
141                 }
142                 _nextItem = (SelectItem) item;
143                 _currentComponent = child;
144                 _currentValue = item;
145                 return true;
146             }
147             else if (child instanceof UISelectItems)
148             {
149                 _currentUISelectItems = ((UISelectItems) child);
150                 Object value = _currentUISelectItems.getValue();
151                 _currentComponent = child;
152 
153                 if (value instanceof SelectItem)
154                 {
155                     _nextItem = (SelectItem) value;
156                     return true;
157                 }
158                 else if (value != null && value.getClass().isArray())
159                 {
160                     // value is any kind of array (primitive or non-primitive)
161                     // --> we have to use class Array to get the values
162                     final int length = Array.getLength(value);
163                     Collection<Object> items = new ArrayList<Object>(length);
164                     for (int i = 0; i < length; i++)
165                     {
166                         items.add(Array.get(value, i));
167                     }
168                     _nestedItems = items.iterator();
169                     return hasNext();
170                 }
171                 else if (value instanceof Iterable)
172                 {
173                     // value is Iterable --> Collection, DataModel,...
174                     _nestedItems = ((Iterable<?>) value).iterator();
175                     return hasNext();
176                 }
177                 else if (value instanceof Map)
178                 {
179                     Map<Object, Object> map = ((Map<Object, Object>) value);
180                     Collection<SelectItem> items = new ArrayList<SelectItem>(map.size());
181                     for (Map.Entry<Object, Object> entry : map.entrySet())
182                     {
183                         items.add(new SelectItem(entry.getValue(), entry.getKey().toString()));
184                     }
185                     
186                     _nestedItems = items.iterator();
187                     return hasNext();
188                 }
189                 else
190                 {
191                     Level level = Level.FINE;
192                     if (!_facesContext.isProjectStage(ProjectStage.Production))
193                     {
194                         level = Level.WARNING;
195                     }
196 
197                     if (log.isLoggable(level))
198                     {
199                         ValueExpression expression = _currentUISelectItems.getValueExpression("value");
200                         log.log(level, "ValueExpression {0} of UISelectItems with component-path {1}"
201                                 + " does not reference an Object of type SelectItem,"
202                                 + " array, Iterable or Map, but of type: {2}",
203                                 new Object[] {
204                                     (expression == null ? null : expression.getExpressionString()),
205                                     DebugUtils.getPathToComponent(child),
206                                     (value == null ? null : value.getClass().getName()) 
207                                 });
208                     }
209                 }
210             }
211             else
212             {
213                 _currentComponent = null;
214                 _currentValue = null;
215             }
216         }
217         return false;
218     }
219 
220     public SelectItem next()
221     {
222         if (!hasNext())
223         {
224             throw new NoSuchElementException();
225         }
226         if (_nextItem != null)
227         {
228             SelectItem value = _nextItem;
229             _nextItem = null;
230             return value;
231         }
232         if (_nestedItems != null)
233         {
234             Object item = _nestedItems.next();
235             
236             if (!(item instanceof SelectItem))
237             {
238                 // check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
239                 // Note that according to the spec UISelectItems does not provide Getter and Setter 
240                 // methods for this values, so we have to use the attribute map
241                 Map<String, Object> attributeMap = _currentUISelectItems.getAttributes();
242                 _currentValue = item;
243                 
244                 // write the current item into the request map under the key listed in var, if available
245                 boolean wroteRequestMapVarValue = false;
246                 Object oldRequestMapVarValue = null;
247                 final String var = (String) attributeMap.get(VAR_PROP);
248                 if(var != null && !"".equals(var))
249                 {
250                     // save the current value of the key listed in var from the request map
251                     oldRequestMapVarValue = _facesContext.getExternalContext().getRequestMap().put(var, item);
252                     wroteRequestMapVarValue = true;
253                 }
254                 
255                 // check the itemValue attribute
256                 Object itemValue = attributeMap.get(ITEM_VALUE_PROP);
257                 if (itemValue == null)
258                 {
259                     // the itemValue attribute was not provided
260                     // --> use the current item as the itemValue
261                     itemValue = item;
262                 }
263                 
264                 // Spec: When iterating over the select items, toString() 
265                 // must be called on the string rendered attribute values
266                 Object itemLabel = attributeMap.get(ITEM_LABEL_PROP);
267                 if (itemLabel == null)
268                 {
269                     if (itemValue != null)
270                     {
271                         itemLabel = itemValue.toString();
272                     }
273                 }
274                 else
275                 {
276                     itemLabel = itemLabel.toString();
277                 }
278                 Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_PROP);
279                 if (itemDescription != null)
280                 {
281                     itemDescription = itemDescription.toString();
282                 }
283                 Boolean itemDisabled = getBooleanAttribute(_currentUISelectItems, ITEM_DISABLED_PROP, false);
284                 Boolean itemLabelEscaped = getBooleanAttribute(_currentUISelectItems, ITEM_LABEL_ESCAPED_PROP, true);
285                 Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_PROP);
286                 item = new SelectItem(itemValue,
287                         (String) itemLabel,
288                         (String) itemDescription,
289                         itemDisabled,
290                         itemLabelEscaped,
291                         itemValue.equals(noSelectionValue)); 
292                     
293                 // remove the value with the key from var from the request map, if previously written
294                 if(wroteRequestMapVarValue)
295                 {
296                     // If there was a previous value stored with the key from var in the request map, restore it
297                     if (oldRequestMapVarValue != null)
298                     {
299                         _facesContext.getExternalContext()
300                                 .getRequestMap().put(var, oldRequestMapVarValue);
301                     }
302                     else
303                     {
304                         _facesContext.getExternalContext()
305                                 .getRequestMap().remove(var);
306                     }
307                 } 
308             }
309             return (SelectItem) item;
310         }
311         throw new NoSuchElementException();
312     }
313 
314     public void remove()
315     {
316         throw new UnsupportedOperationException();
317     }
318     
319     public UIComponent getCurrentComponent()
320     {
321         return _currentComponent;
322     }
323     
324     public Object getCurrentValue()
325     {
326         return _currentValue;
327     }
328 
329     private boolean getBooleanAttribute(UIComponent component, String attrName, boolean defaultValue)
330     {
331         Object value = component.getAttributes().get(attrName);
332         if (value == null)
333         {
334             return defaultValue;
335         }
336         else if (value instanceof Boolean)
337         {
338             return (Boolean) value;
339         }
340         else
341         {
342             // If the value is a String, parse the boolean.
343             // This makes the following code work: <tag attribute="true" />,
344             // otherwise you would have to write <tag attribute="#{true}" />.
345             return Boolean.valueOf(value.toString());
346         }
347     }
348 
349 }