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  class _SelectItemsIterator implements Iterator<SelectItem>
40  {
41      
42      private static final Logger log = Logger.getLogger(_SelectItemsIterator.class.getName());
43      
44      private static final Iterator<UIComponent> _EMPTY_UICOMPONENT_ITERATOR = new _EmptyIterator<UIComponent>();
45      
46      // org.apache.myfaces.shared.util.SelectItemsIterator uses JSFAttr
47      private static final String VAR_ATTR = "var";
48      private static final String ITEM_VALUE_ATTR = "itemValue";
49      private static final String ITEM_LABEL_ATTR = "itemLabel";
50      private static final String ITEM_DESCRIPTION_ATTR = "itemDescription";
51      private static final String ITEM_DISABLED_ATTR = "itemDisabled";
52      private static final String ITEM_LABEL_ESCAPED_ATTR = "itemLabelEscaped";
53      private static final String NO_SELECTION_VALUE_ATTR = "noSelectionValue";
54      
55      private final Iterator<UIComponent> _children;
56      private Iterator<?> _nestedItems;
57      private SelectItem _nextItem;
58      private UIComponent _currentComponent;
59      private UISelectItems _currentUISelectItems;
60      private FacesContext _facesContext;
61  
62      public _SelectItemsIterator(UIComponent selectItemsParent, FacesContext facesContext)
63      {
64          _children = selectItemsParent.getChildCount() > 0
65                          ? selectItemsParent.getChildren().iterator()
66                          : _EMPTY_UICOMPONENT_ITERATOR;
67          _facesContext = facesContext;
68      }
69  
70      @Override
71      @SuppressWarnings("unchecked")
72      public boolean hasNext()
73      {
74          if (_nextItem != null)
75          {
76              return true;
77          }
78          if (_nestedItems != null)
79          {
80              if (_nestedItems.hasNext())
81              {
82                  return true;
83              }
84              _nestedItems = null;
85              _currentComponent = null;
86          }
87          if (_children.hasNext())
88          {
89              UIComponent child = _children.next();
90              // When there is other components nested that does
91              // not extends from UISelectItem or UISelectItems
92              // the behavior for this iterator is just skip this
93              // element(s) until an element that extends from these
94              // classes are found. If there is no more elements
95              // that conform this condition, just return false.
96              while (!(child instanceof UISelectItem) && !(child instanceof UISelectItems))
97              {
98                  // Try to skip it
99                  if (_children.hasNext())
100                 {
101                     // Skip and do the same check
102                     child = _children.next();
103                 }
104                 else
105                 {
106                     // End loop, so the final result is return false,
107                     // since there are no more components to iterate.
108                     return false;
109                 }
110             }
111             if (child instanceof UISelectItem)
112             {
113                 UISelectItem uiSelectItem = (UISelectItem) child;
114                 Object item = uiSelectItem.getValue();
115                 if (item == null)
116                 {
117                     // no value attribute --> create the SelectItem out of the other attributes
118                     Object itemValue = uiSelectItem.getItemValue();
119                     String label = uiSelectItem.getItemLabel();
120                     String description = uiSelectItem.getItemDescription();
121                     boolean disabled = uiSelectItem.isItemDisabled();
122                     boolean escape = uiSelectItem.isItemEscaped();
123                     boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
124                     if (label == null && itemValue != null)
125                     {
126                         label = itemValue.toString();
127                     }
128                     item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption);
129                 }
130                 else if (!(item instanceof SelectItem))
131                 {
132                     ValueExpression expression = uiSelectItem.getValueExpression("value");
133                     throw new IllegalArgumentException("ValueExpression '"
134                             + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
135                             + getPathToComponent(child) + " does not reference an Object of type SelectItem");
136                 }
137                 _nextItem = (SelectItem) item;
138                 _currentComponent = child;
139                 return true;
140             }
141             else if (child instanceof UISelectItems)
142             {
143                 _currentUISelectItems = ((UISelectItems) child);
144                 Object value = _currentUISelectItems.getValue();
145                 _currentComponent = child;
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             else
206             {
207                 _currentComponent = null;
208             }
209         }
210         return false;
211     }
212 
213     @Override
214     public SelectItem next()
215     {
216         if (!hasNext())
217         {
218             throw new NoSuchElementException();
219         }
220         if (_nextItem != null)
221         {
222             SelectItem value = _nextItem;
223             _nextItem = null;
224             return value;
225         }
226         if (_nestedItems != null)
227         {
228             Object item = _nestedItems.next();
229             
230             if (!(item instanceof SelectItem))
231             {
232                 // check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
233                 // Note that according to the spec UISelectItems does not provide Getter and Setter 
234                 // methods for this values, so we have to use the attribute map
235                 Map<String, Object> attributeMap = _currentUISelectItems.getAttributes();
236                 
237                 // write the current item into the request map under the key listed in var, if available
238                 boolean wroteRequestMapVarValue = false;
239                 Object oldRequestMapVarValue = null;
240                 String var = (String) attributeMap.get(VAR_ATTR);
241                 if(var != null && !"".equals(var))
242                 {
243                     // save the current value of the key listed in var from the request map
244                     oldRequestMapVarValue = _facesContext.getExternalContext().getRequestMap().put(var, item);
245                     wroteRequestMapVarValue = true;
246                 }
247                 
248                 // check the itemValue attribute
249                 Object itemValue = attributeMap.get(ITEM_VALUE_ATTR);
250                 if (itemValue == null)
251                 {
252                     // the itemValue attribute was not provided
253                     // --> use the current item as the itemValue
254                     itemValue = item;
255                 }
256                 
257                 // Spec: When iterating over the select items, toString() 
258                 // must be called on the string rendered attribute values
259                 Object itemLabel = attributeMap.get(ITEM_LABEL_ATTR);
260                 if (itemLabel == null)
261                 {
262                     if (itemValue != null)
263                     {
264                         itemLabel = itemValue.toString();
265                     }
266                 }
267                 else
268                 {
269                     itemLabel = itemLabel.toString();
270                 }
271                 Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_ATTR);
272                 if (itemDescription != null)
273                 {
274                     itemDescription = itemDescription.toString();
275                 }
276                 Boolean itemDisabled = getBooleanAttribute(_currentUISelectItems, ITEM_DISABLED_ATTR, false);
277                 Boolean itemLabelEscaped = getBooleanAttribute(_currentUISelectItems, ITEM_LABEL_ESCAPED_ATTR, true);
278                 Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_ATTR);
279                 item = new SelectItem(itemValue,
280                         (String) itemLabel,
281                         (String) itemDescription,
282                         itemDisabled,
283                         itemLabelEscaped,
284                         itemValue.equals(noSelectionValue)); 
285                     
286                 // remove the value with the key from var from the request map, if previously written
287                 if(wroteRequestMapVarValue)
288                 {
289                     // If there was a previous value stored with the key from var in the request map, restore it
290                     if (oldRequestMapVarValue != null)
291                     {
292                         _facesContext.getExternalContext()
293                                 .getRequestMap().put(var, oldRequestMapVarValue);
294                     }
295                     else
296                     {
297                         _facesContext.getExternalContext()
298                                 .getRequestMap().remove(var);
299                     }
300                 } 
301             }
302             return (SelectItem) item;
303         }
304         throw new NoSuchElementException();
305     }
306 
307     @Override
308     public void remove()
309     {
310         throw new UnsupportedOperationException();
311     }
312     
313     public UIComponent getCurrentComponent()
314     {
315         return _currentComponent;
316     }
317 
318     private boolean getBooleanAttribute(UIComponent component, String attrName, boolean defaultValue)
319     {
320         Object value = component.getAttributes().get(attrName);
321         if (value == null)
322         {
323             return defaultValue;
324         }
325         else if (value instanceof Boolean)
326         {
327             return (Boolean) value;
328         }
329         else
330         {
331             // If the value is a String, parse the boolean.
332             // This makes the following code work: <tag attribute="true" />,
333             // otherwise you would have to write <tag attribute="#{true}" />.
334             return Boolean.valueOf(value.toString());
335         }
336     }
337 
338     private String getPathToComponent(UIComponent component)
339     {
340         StringBuffer buf = new StringBuffer();
341 
342         if (component == null)
343         {
344             buf.append("{Component-Path : ");
345             buf.append("[null]}");
346             return buf.toString();
347         }
348 
349         getPathToComponent(component, buf);
350 
351         buf.insert(0, "{Component-Path : ");
352         buf.append("}");
353 
354         return buf.toString();
355     }
356 
357     private void getPathToComponent(UIComponent component, StringBuffer buf)
358     {
359         if (component == null)
360         {
361             return;
362         }
363 
364         StringBuffer intBuf = new StringBuffer();
365 
366         intBuf.append("[Class: ");
367         intBuf.append(component.getClass().getName());
368         if (component instanceof UIViewRoot)
369         {
370             intBuf.append(",ViewId: ");
371             intBuf.append(((UIViewRoot) component).getViewId());
372         }
373         else
374         {
375             intBuf.append(",Id: ");
376             intBuf.append(component.getId());
377         }
378         intBuf.append("]");
379 
380         buf.insert(0, intBuf);
381 
382         getPathToComponent(component.getParent(), buf);
383     }
384 }