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