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: bommel $)
41   * @author Jakob Korherr (jsf 2.0)
42   * @version $Revision: 1187700 $ $Date: 2011-10-22 07:19:37 -0500 (Sat, 22 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<? extends Object> _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 ? selectItemsParent.getChildren().iterator() : _EMPTY_UICOMPONENT_ITERATOR;
69          _facesContext = facesContext;
70      }
71  
72      @SuppressWarnings("unchecked")
73      public boolean hasNext()
74      {
75          if (_nextItem != null)
76          {
77              return true;
78          }
79          if (_nestedItems != null)
80          {
81              if (_nestedItems.hasNext())
82              {
83                  return true;
84              }
85              _nestedItems = 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)
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                 return true;
139             }
140             else if (child instanceof UISelectItems)
141             {
142                 _currentUISelectItems = ((UISelectItems) child);
143                 Object value = _currentUISelectItems.getValue();
144 
145                 if (value instanceof SelectItem)
146                 {
147                     _nextItem = (SelectItem) value;
148                     return true;
149                 }
150                 else if (value != null && value.getClass().isArray())
151                 {
152                     // value is any kind of array (primitive or non-primitive)
153                     // --> we have to use class Array to get the values
154                     final int length = Array.getLength(value);
155                     Collection<Object> items = new ArrayList<Object>(length);
156                     for (int i = 0; i < length; i++)
157                     {
158                         items.add(Array.get(value, i));
159                     }
160                     _nestedItems = items.iterator();
161                     return hasNext();
162                 }
163                 else if (value instanceof Iterable)
164                 {
165                     // value is Iterable --> Collection, DataModel,...
166                     _nestedItems = ((Iterable<?>) value).iterator();
167                     return hasNext();
168                 }
169                 else if (value instanceof Map)
170                 {
171                     Map<Object, Object> map = ((Map<Object, Object>) value);
172                     Collection<SelectItem> items = new ArrayList<SelectItem>(map.size());
173                     for (Map.Entry<Object, Object> entry : map.entrySet())
174                     {
175                         items.add(new SelectItem(entry.getValue(), entry.getKey().toString()));
176                     }
177                     
178                     _nestedItems = items.iterator();
179                     return hasNext();
180                 }
181                 else
182                 {
183                     Level level = Level.FINE;
184                     if (!_facesContext.isProjectStage(ProjectStage.Production))
185                     {
186                         level = Level.WARNING;
187                     }
188 
189                     if (log.isLoggable(level))
190                     {
191                         ValueExpression expression = _currentUISelectItems.getValueExpression("value");
192                         log.log(level, "ValueExpression {0} of UISelectItems with component-path {1}"
193                                 + " does not reference an Object of type SelectItem,"
194                                 + " array, Iterable or Map, but of type: {2}",
195                                 new Object[] {
196                                     (expression == null ? null : expression.getExpressionString()),
197                                     getPathToComponent(child),
198                                     (value == null ? null : value.getClass().getName()) 
199                                 });
200                     }
201                 }
202             }
203         }
204         return false;
205     }
206 
207     public SelectItem next()
208     {
209         if (!hasNext())
210         {
211             throw new NoSuchElementException();
212         }
213         if (_nextItem != null)
214         {
215             SelectItem value = _nextItem;
216             _nextItem = null;
217             return value;
218         }
219         if (_nestedItems != null)
220         {
221             Object item = _nestedItems.next();
222             
223             if (!(item instanceof SelectItem))
224             {
225                 // check new params of SelectItems (since 2.0): itemValue, itemLabel, itemDescription,...
226                 // Note that according to the spec UISelectItems does not provide Getter and Setter 
227                 // methods for this values, so we have to use the attribute map
228                 Map<String, Object> attributeMap = _currentUISelectItems.getAttributes();
229                 
230                 // write the current item into the request map under the key listed in var, if available
231                 boolean wroteRequestMapVarValue = false;
232                 Object oldRequestMapVarValue = null;
233                 final String var = (String) attributeMap.get(VAR_ATTR);
234                 if(var != null && !"".equals(var))
235                 {
236                     // save the current value of the key listed in var from the request map
237                     oldRequestMapVarValue = _facesContext.getExternalContext().getRequestMap().put(var, item);
238                     wroteRequestMapVarValue = true;
239                 }
240                 
241                 // check the itemValue attribute
242                 Object itemValue = attributeMap.get(ITEM_VALUE_ATTR);
243                 if (itemValue == null)
244                 {
245                     // the itemValue attribute was not provided
246                     // --> use the current item as the itemValue
247                     itemValue = item;
248                 }
249                 
250                 // Spec: When iterating over the select items, toString() 
251                 // must be called on the string rendered attribute values
252                 Object itemLabel = attributeMap.get(ITEM_LABEL_ATTR);
253                 if (itemLabel == null)
254                 {
255                     itemLabel = itemValue.toString();
256                 }
257                 else
258                 {
259                     itemLabel = itemLabel.toString();
260                 }
261                 Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_ATTR);
262                 if (itemDescription != null)
263                 {
264                     itemDescription = itemDescription.toString();
265                 }
266                 Boolean itemDisabled = getBooleanAttribute(_currentUISelectItems, ITEM_DISABLED_ATTR, false);
267                 Boolean itemLabelEscaped = getBooleanAttribute(_currentUISelectItems, ITEM_LABEL_ESCAPED_ATTR, true);
268                 Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_ATTR);
269                 item = new SelectItem(itemValue,
270                         (String) itemLabel,
271                         (String) itemDescription,
272                         itemDisabled,
273                         itemLabelEscaped,
274                         itemValue.equals(noSelectionValue)); 
275                     
276                 // remove the value with the key from var from the request map, if previously written
277                 if(wroteRequestMapVarValue)
278                 {
279                     // If there was a previous value stored with the key from var in the request map, restore it
280                     if (oldRequestMapVarValue != null)
281                     {
282                         _facesContext.getExternalContext()
283                                 .getRequestMap().put(var, oldRequestMapVarValue);
284                     }
285                     else
286                     {
287                         _facesContext.getExternalContext()
288                                 .getRequestMap().remove(var);
289                     }
290                 } 
291             }
292             return (SelectItem) item;
293         }
294         throw new NoSuchElementException();
295     }
296 
297     public void remove()
298     {
299         throw new UnsupportedOperationException();
300     }
301     
302     private boolean getBooleanAttribute(UIComponent component, String attrName, boolean defaultValue)
303     {
304         Object value = component.getAttributes().get(attrName);
305         if (value == null)
306         {
307             return defaultValue;
308         }
309         else if (value instanceof Boolean)
310         {
311             return (Boolean) value;
312         }
313         else
314         {
315             // If the value is a String, parse the boolean.
316             // This makes the following code work: <tag attribute="true" />,
317             // otherwise you would have to write <tag attribute="#{true}" />.
318             return Boolean.valueOf(value.toString());
319         }
320     }
321 
322     private String getPathToComponent(UIComponent component)
323     {
324         StringBuffer buf = new StringBuffer();
325 
326         if (component == null)
327         {
328             buf.append("{Component-Path : ");
329             buf.append("[null]}");
330             return buf.toString();
331         }
332 
333         getPathToComponent(component, buf);
334 
335         buf.insert(0, "{Component-Path : ");
336         buf.append("}");
337 
338         return buf.toString();
339     }
340 
341     private void getPathToComponent(UIComponent component, StringBuffer buf)
342     {
343         if (component == null)
344         {
345             return;
346         }
347 
348         StringBuffer intBuf = new StringBuffer();
349 
350         intBuf.append("[Class: ");
351         intBuf.append(component.getClass().getName());
352         if (component instanceof UIViewRoot)
353         {
354             intBuf.append(",ViewId: ");
355             intBuf.append(((UIViewRoot) component).getViewId());
356         }
357         else
358         {
359             intBuf.append(",Id: ");
360             intBuf.append(component.getId());
361         }
362         intBuf.append("]");
363 
364         buf.insert(0, intBuf);
365 
366         getPathToComponent(component.getParent(), buf);
367     }
368 }